Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/handlers/project/invoke/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ export function createProjectInvokeHandler(core: Core, io: AppIO) {
description: "invoke a Runtime, harness, or Gateway",
flags: invokeFlags,
middlewares: [withProject({ projectManager: core.projectManager, optional: true })],
examples: [
{ description: "Choose a project resource to invoke", command: "agentcore invoke" },
{
description: "Send a payload to a project Runtime",
command: `agentcore invoke --runtime checkout --payload '{"prompt":"Hello"}'`,
},
{
description: "Send a prompt to a harness by ID",
command: `agentcore invoke --harness support-AbCdEf1234 --prompt "Hello"`,
},
{
description: "List the tools on a Gateway by ARN",
command:
"agentcore invoke --gateway arn:aws:bedrock-agentcore:us-west-2:111122223333:gateway/tools-AbCdEf1234 " +
`--path /mcp --payload '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'`,
},
{
description: "Invoke a Runtime on the local development server",
command: `agentcore invoke --runtime checkout --local --payload '{"prompt":"Hello"}'`,
},
],
handle: async (ctx, flags) => {
const project = ctx.value(ProjectKey);
const headless = ctx.require(JsonKey) || Object.values(flags).some(isSet);
Expand Down
8 changes: 7 additions & 1 deletion src/router/flags.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Option } from "commander";
import { InputValidationError } from "../errors";
import type { Context } from "./context";
import type { Flag, GlobalFlag } from "./handler";
import type { Flag, GlobalFlag, HelpExample } from "./handler";
import { coerce, formatZodError, inspect } from "./schema";

// toOption builds a Commander Option from a flag's schema. A boolean that defaults
Expand Down Expand Up @@ -54,6 +54,12 @@ export function formatParameterDetails(flags: Flag[]): string | undefined {
return `\nParameter details:\n\n${sections.join("\n\n")}\n`;
}

export function formatExamples(examples: HelpExample[]): string | undefined {
if (examples.length === 0) return undefined;
const sections = examples.map(({ description, command }) => ` ${description}\n $ ${command}`);
return `\nExamples:\n\n${sections.join("\n\n")}\n`;
}

// attributeName mirrors how Commander camelCases an option name into the key it
// stores on the parsed options object (e.g. "harness-id" -> "harnessId").
export function attributeName(name: string): string {
Expand Down
20 changes: 20 additions & 0 deletions src/router/handler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,19 @@ export interface Handler {
children(): Handler[];
}

export interface HelpExample {
description: string;
command: string;
}

export interface ExamplesProvider {
examples(): HelpExample[];
}

export function isExamplesProvider(h: Handler): h is Handler & ExamplesProvider {
return typeof (h as Partial<ExamplesProvider>).examples === "function";
}

type CreateHandlerInput<
F extends readonly Flag<string, any>[],
A extends readonly Argument<string, any>[],
Expand All @@ -112,6 +125,7 @@ type CreateHandlerInput<
handle?: HandleFn<F, A>;
children?: Handler[];
middlewares?: Middleware[];
examples?: HelpExample[];
};

const noOpHandler = async (_ctx: Context, _flags: any, _args: any): Promise<void> => {};
Expand All @@ -124,6 +138,7 @@ class BaseHandler implements Handler {
_handle: HandleFn<any, any>;
_children: Handler[];
_middlewares: Middleware[];
_examples: HelpExample[];

constructor(
input: CreateHandlerInput<readonly Flag<string, any>[], readonly Argument<string, any>[]>,
Expand All @@ -135,6 +150,7 @@ class BaseHandler implements Handler {
this._handle = (input.handle ?? noOpHandler) as HandleFn<any, any>;
this._children = input.children ?? [];
this._middlewares = input.middlewares ?? [];
this._examples = input.examples ?? [];
}

name(): string {
Expand Down Expand Up @@ -168,6 +184,10 @@ class BaseHandler implements Handler {
middlewares(): Middleware[] {
return this._middlewares;
}

examples(): HelpExample[] {
return this._examples;
}
}

// createHandler infers the flags tuple from `flags` (the `const` type parameter
Expand Down
1 change: 1 addition & 0 deletions src/router/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
type GlobalFlag,
type Argument,
type FlagsOf,
type HelpExample,
createHandler,
flag,
globalFlag,
Expand Down
21 changes: 21 additions & 0 deletions src/router/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,27 @@ test("commands without long-form flag help have no Parameter details section", a

expect(out).toContain("--id");
expect(out).not.toContain("Parameter details:");
expect(out).not.toContain("Examples:");
});

test("handlers with examples render an Examples section", async () => {
const get = createHandler({
name: "get",
description: "",
examples: [
{ description: "Get a thing", command: "app get --id a" },
{ description: "Get it as JSON", command: "app get --id a --json" },
],
handle: async () => {},
});
const root = new Router("app");
root.handler(get);

const out = await helpOutput(root, ["app", "get", "--help"]);

expect(out).toContain(
"Examples:\n\n Get a thing\n $ app get --id a\n\n Get it as JSON\n $ app get --id a --json\n",
);
});

// --- telemetry: command path recording -------------------------------------
Expand Down
14 changes: 13 additions & 1 deletion src/router/router.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import type { Argument, Flag, GlobalFlag, Handler } from "./handler";
import {
isExamplesProvider,
type Argument,
type Flag,
type GlobalFlag,
type Handler,
} from "./handler";
import { type Middleware, type MiddlewareProvider, isMiddlewareProvider } from "./middleware";
import { type Context, type ContextKey, ValueContext, contextKey } from "./context";
import {
applyGlobalFlags,
attributeName,
formatExamples,
formatParameterDetails,
parseFlags,
toOption,
Expand Down Expand Up @@ -227,6 +234,11 @@ export function compile(
);
}

const examples = formatExamples(isExamplesProvider(node) ? node.examples() : []);
if (examples) {
c.addHelpText("after", examples);
}

// Flags with long-form documentation get a "Parameter details" section after
// the option list in `--help` output.
const parameterDetails = formatParameterDetails(ownFlags);
Expand Down
Loading