Skip to content
Merged
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
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
22 changes: 15 additions & 7 deletions src/handlers/project/create/create.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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<void>((resolve) => {
releaseFirstStep = resolve;
});
let release!: () => void;
const held = new Promise<void>((resolve) => {
release = resolve;
});
core.projectManager.create = () => {
return (async function* () {
await beforeFirstStep;
yield { type: "step", message: "syncing dependencies" };
await held;
throw new Error("stopped");
Expand All @@ -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");
Expand Down Expand Up @@ -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);

Expand Down
10 changes: 2 additions & 8 deletions src/handlers/project/create/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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)}) ` +
Expand Down Expand Up @@ -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",
);
Expand Down
3 changes: 1 addition & 2 deletions src/handlers/project/project.screen.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down
21 changes: 5 additions & 16 deletions src/handlers/project/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 () => {
Expand Down Expand Up @@ -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"]);
Expand Down
19 changes: 19 additions & 0 deletions src/router/router.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -687,6 +687,25 @@ async function helpOutput(root: Router, argv: string[]): Promise<string> {
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",
Expand Down
1 change: 1 addition & 0 deletions src/router/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Loading