diff --git a/README.md b/README.md index 41a92ba40..c4438845f 100644 --- a/README.md +++ b/README.md @@ -185,9 +185,8 @@ one resource of the requested type, `--name` may be omitted. ```bash # Create a project. The default is a harness project: a managed agent -# configured by spec, no model-loop code to maintain. --defaults says so -# explicitly; harness flags (--model-id, --max-iterations, --timeout, …) -# tune it. +# configured by spec, no model-loop code to maintain. Harness flags +# (--model-id, --max-iterations, --timeout, …) tune it. agentcore project create --name MyAssistant cd MyAssistant && agentcore project deploy # … or run `agentcore project create` bare in a terminal for the guided diff --git a/src/handlers/project/create/create.screen.test.tsx b/src/handlers/project/create/create.screen.test.tsx index 5d939f28a..0e89f3698 100644 --- a/src/handlers/project/create/create.screen.test.tsx +++ b/src/handlers/project/create/create.screen.test.tsx @@ -99,7 +99,7 @@ describe("project create wizard", () => { await r.press("return"); // Success: next steps point at the new directory and deploy. - await waitForText(r.lastFrame, "project created in ./DemoApp", 5000); + await waitForText(r.lastFrame, "✔ project created in ./DemoApp", 5000); expect(r.lastFrame()).toContain("cd DemoApp"); expect(r.lastFrame()).toContain("agentcore project deploy"); @@ -144,7 +144,7 @@ describe("project create wizard", () => { await r.press("return"); await waitForText(r.lastFrame, "this project will be created"); await r.press("return"); - await waitForText(r.lastFrame, "project created in ./TunedApp", 5000); + await waitForText(r.lastFrame, "✔ project created in ./TunedApp", 5000); expect(inputs[0]).toEqual({ name: "TunedApp", @@ -192,7 +192,7 @@ describe("project create wizard", () => { expect(review).toContain("api key arn"); expect(review.replace(/\s/g, "")).toContain(apiKeyArn); await r.press("return"); - await waitForText(r.lastFrame, "project created in ./OpenAIApp", 5000); + await waitForText(r.lastFrame, "✔ project created in ./OpenAIApp", 5000); expect(inputs[0]).toEqual({ name: "OpenAIApp", @@ -309,7 +309,7 @@ describe("project create wizard", () => { expect(r.lastFrame()).toContain("agent-python-strands"); expect(r.lastFrame()).toContain("long and short-term"); await r.press("return"); - await waitForText(r.lastFrame, "project created in ./StrandsApp", 5000); + await waitForText(r.lastFrame, "✔ project created in ./StrandsApp", 5000); // Identical to the flag-driven `--template agent-python-strands` input. expect(inputs).toEqual([ @@ -351,7 +351,7 @@ describe("project create wizard", () => { await r.press("return"); await waitForText(r.lastFrame, "this project will be created"); await r.press("return"); - await waitForText(r.lastFrame, "project created in ./BareStrands", 5000); + await waitForText(r.lastFrame, "✔ project created in ./BareStrands", 5000); expect(inputs[0]).toEqual({ name: "BareStrands", @@ -385,7 +385,7 @@ describe("project create wizard", () => { await waitForText(r.lastFrame, "this project will be created"); expect(r.lastFrame()).not.toContain("memory"); await r.press("return"); - await waitForText(r.lastFrame, "project created in ./HelloApp", 5000); + await waitForText(r.lastFrame, "✔ project created in ./HelloApp", 5000); expect(inputs[0]).toEqual({ name: "HelloApp", @@ -470,12 +470,17 @@ describe("project create wizard", () => { test("streamed progress renders as the CLI's step list", async () => { const core = new TestCoreClient(); + let releaseFirstStep!: () => void; + const beforeFirstStep = new Promise((resolve) => { + releaseFirstStep = resolve; + }); let release!: () => void; const held = new Promise((resolve) => { release = resolve; }); core.projectManager.create = () => { return (async function* () { + await beforeFirstStep; yield { type: "step", message: "syncing dependencies" }; await held; throw new Error("stopped"); @@ -494,6 +499,9 @@ describe("project create wizard", () => { await waitForText(r.lastFrame, "this project will be created"); await r.press("return"); + await waitForText(r.lastFrame, "creating DemoApp…"); + releaseFirstStep(); + // The running step is the spinner row itself, as on the command line; the // generic "creating…" spinner shows only until the first step arrives. await waitForText(r.lastFrame, "syncing dependencies"); @@ -650,7 +658,7 @@ describe("project create dispatch", () => { const root = buildRoot(streams.io); const error: unknown = await root - .route(["node", "agentcore", "project", "create", "--defaults"]) + .route(["node", "agentcore", "project", "create", "--skip-git"]) .then(() => undefined) .catch((caught: unknown) => caught); diff --git a/src/handlers/project/create/index.ts b/src/handlers/project/create/index.ts index 4b23a9bca..a816b32b7 100644 --- a/src/handlers/project/create/index.ts +++ b/src/handlers/project/create/index.ts @@ -86,11 +86,6 @@ export const createCreateProjectHandler = (config: CreateProjectHandlerConfig) = // interactive `project create` reaches the TUI wizard middleware instead // of dying on Commander's mandatory-option check. flag("name", "name of the project to create", ProjectNameSchema.optional()), - flag( - "defaults", - "create a harness project with default settings (this is the default)", - z.boolean().default(false), - ), flag( "template", "a preset of flags for scaffolding the runtime; compatible flags override preset values", @@ -212,8 +207,7 @@ export const createCreateProjectHandler = (config: CreateProjectHandlerConfig) = ); if (flags["harness-memory"] === false) presentHarnessFlags.push("no-harness-memory"); - // Mirrors the original CLI's dispatch: mixing the two paths is an error, - // while --defaults on the runtime path is simply ignored. + // Mirrors the original CLI's dispatch: mixing the two paths is an error. if (presentRuntimeFlags.length > 0 && presentHarnessFlags.length > 0) { throw new InputValidationError( `Cannot mix runtime scaffolding flags (${formatFlagList(presentRuntimeFlags)}) ` + @@ -297,7 +291,7 @@ export const createCreateProjectHandler = (config: CreateProjectHandlerConfig) = scaffoldHarnessInput: resolveScaffoldHarnessInput({ ...flags, name }), }; - if (!isRuntimePath && !flags["defaults"] && presentHarnessFlags.length === 0) { + if (!isRuntimePath && presentHarnessFlags.length === 0) { config.io.stderr.write( "Creating a harness project (pass --framework or --template to scaffold agent code instead).\n", ); diff --git a/src/handlers/project/project.screen.test.tsx b/src/handlers/project/project.screen.test.tsx index 90fcf55ee..e7abc7d5b 100644 --- a/src/handlers/project/project.screen.test.tsx +++ b/src/handlers/project/project.screen.test.tsx @@ -28,8 +28,7 @@ function projectSubcommands(): string[] { ValueContext.EmptyContext(), ); const project = root.commands.find((command) => command.name() === "project")!; - // `help` is Commander's own, not one of ours. - return project.commands.map((command) => command.name()).filter((name) => name !== "help"); + return project.commands.map((command) => command.name()); } describe("project menu", () => { diff --git a/src/handlers/project/project.test.ts b/src/handlers/project/project.test.ts index 4bf91b179..f0749a862 100644 --- a/src/handlers/project/project.test.ts +++ b/src/handlers/project/project.test.ts @@ -100,13 +100,11 @@ describe("project create", () => { expect(io.stderr()).toContain("Creating a harness project"); }); - test("--defaults selects the harness path explicitly, without the implicit-default notice", async () => { - const directory = await inTempDirectory(); - const { io } = await run(["create", "--name", "MyAgent", "--defaults"]); - - const spec = await Bun.file(join(directory, "MyAgent", "agentcore", "agentcore.json")).json(); - expect(spec.harnesses).toHaveLength(1); - expect(io.stderr()).not.toContain("Creating a harness project"); + test("rejects the removed --defaults flag", async () => { + await inTempDirectory(); + await expect(run(["create", "--name", "MyAgent", "--defaults"])).rejects.toThrow( + /unknown option '--defaults'/, + ); }); test("harness-only flags flow into the harness spec", async () => { @@ -254,15 +252,6 @@ describe("project create", () => { ).rejects.toThrow(/harness-only flags \(--no-harness-memory\)/); }); - test("--defaults is ignored when a runtime path flag routes to scaffolding", async () => { - const directory = await inTempDirectory(); - await run(["create", "--name", "MyAgent", "--defaults", "--template", "agent-python"]); - - const spec = await Bun.file(join(directory, "MyAgent", "agentcore", "agentcore.json")).json(); - expect(spec.runtimes[0]).toMatchObject({ name: "agent_python" }); - expect(spec.harnesses).toBeUndefined(); - }); - test("a harness create installs CDK dependencies and git only (no uv sync)", async () => { const directory = await inTempDirectory(); const { core } = await run(["create", "--name", "MyAgent"]); diff --git a/src/router/router.test.ts b/src/router/router.test.ts index 6a8eb899f..ced99b7d3 100644 --- a/src/router/router.test.ts +++ b/src/router/router.test.ts @@ -687,6 +687,25 @@ async function helpOutput(root: Router, argv: string[]): Promise { return out; } +test("command groups omit Commander's generated help subcommand", async () => { + const nested = new Router("nested").handler(leaf("deep", () => {})); + const root = new Router("app").handler(nested); + + const command = compile(root, ValueContext.EmptyContext()); + const nestedCommand = command.commands.find((child) => child.name() === "nested")!; + + // Help generation used to materialize a synthetic `help [command]` child. + command.helpInformation(); + nestedCommand.helpInformation(); + expect(command.commands.map((child) => child.name())).not.toContain("help"); + expect(nestedCommand.commands.map((child) => child.name())).not.toContain("help"); + + const out = await helpOutput(root, ["app", "nested", "--help"]); + expect(out).toContain("deep"); + expect(out).toContain("--help"); + expect(out).not.toContain("help [command]"); +}); + test("flags with long-form help render a Parameter details section", async () => { const create = createHandler({ name: "create", diff --git a/src/router/router.tsx b/src/router/router.tsx index 4a823cbf2..5b7f41e86 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -165,6 +165,7 @@ export function compile( const effectiveTuiSupport = tuiSupported && node.doesSupportTui(); const compiledNode = withEffectiveTuiSupport(node, effectiveTuiSupport); const c = new RoutedCommand(compiledNode); + c.addHelpCommand(false); c.description(node.description()); const ownFlags = node.flags();