From 5ae99e0ff3c88f28ab68d7cc05d88fb2d89e0209 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 21:43:21 +0000 Subject: [PATCH 1/6] fix: point to the fix when a slice zone is missing, and tell agents how init picks a repository `slice connect` now names `type edit-tab --with-slice-zone` when the type has no slice zone, or lists the slice zones the type has. `init --help` says that init without --repo creates a repository, and that agents pick without asking. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- src/commands/init.ts | 4 ++++ src/commands/slice-connect.ts | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index acfbc0ce..7d15a9ab 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -54,6 +54,10 @@ const config = { slicemachine.config.json exists, its repository and settings will be migrated. + Without --repo, init creates a new repository, even if the project used + another one before. Use --repo only for a repository the user names. Do + not ask the user which to use. + Run \`prismic docs view cli#set-up-a-type-builder-project\` for details. `, options: { diff --git a/src/commands/slice-connect.ts b/src/commands/slice-connect.ts index dfc335b2..f63b7449 100644 --- a/src/commands/slice-connect.ts +++ b/src/commands/slice-connect.ts @@ -46,7 +46,13 @@ export default createCommand(config, async ({ positionals, values }) => { const sliceZoneField = allFields[sliceZone]; if (!sliceZoneField || sliceZoneField.type !== "Slices") { - throw new CommandError(`Slice zone "${sliceZone}" not found in "${to}".`); + const sliceZoneIds = Object.keys(allFields).filter((key) => allFields[key].type === "Slices"); + const [firstTab = "Main"] = Object.keys(customType.json); + const hint = + sliceZoneIds.length > 0 + ? `Use --slice-zone with one of: ${sliceZoneIds.join(", ")}.` + : `"${to}" has no slice zone. Add one with \`prismic type edit-tab "${firstTab}" --from-type ${to} --with-slice-zone\`, then run this command again.`; + throw new CommandError(`Slice zone "${sliceZone}" not found in "${to}". ${hint}`); } sliceZoneField.config ??= {}; From 80fc1e30ea31f740033fd16d7b992d612800ee3b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 22:09:34 +0000 Subject: [PATCH 2/6] fix: name the fix when slice disconnect cannot find a slice zone Make slice disconnect list the other slice zones and point to --slice-zone, or say that the type has no slice zone. Make slice connect say only that the type has no slice zone when it has none. Rewrite the init help so that it states how init picks a repository, with no instructions for agents. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- src/commands/init.ts | 8 +++----- src/commands/slice-connect.ts | 15 +++++++++------ src/commands/slice-disconnect.ts | 8 +++++++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 7d15a9ab..4670d9e5 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -52,11 +52,9 @@ const config = { Use --repo to connect to an existing repository instead. If a slicemachine.config.json exists, its repository and settings will be - migrated. - - Without --repo, init creates a new repository, even if the project used - another one before. Use --repo only for a repository the user names. Do - not ask the user which to use. + migrated. Otherwise, init creates a new repository, even if the project + used another one before. If a prismic.config.json exists, init requires + --repo. Run \`prismic docs view cli#set-up-a-type-builder-project\` for details. `, diff --git a/src/commands/slice-connect.ts b/src/commands/slice-connect.ts index f63b7449..f4ea5395 100644 --- a/src/commands/slice-connect.ts +++ b/src/commands/slice-connect.ts @@ -47,12 +47,15 @@ export default createCommand(config, async ({ positionals, values }) => { const sliceZoneField = allFields[sliceZone]; if (!sliceZoneField || sliceZoneField.type !== "Slices") { const sliceZoneIds = Object.keys(allFields).filter((key) => allFields[key].type === "Slices"); - const [firstTab = "Main"] = Object.keys(customType.json); - const hint = - sliceZoneIds.length > 0 - ? `Use --slice-zone with one of: ${sliceZoneIds.join(", ")}.` - : `"${to}" has no slice zone. Add one with \`prismic type edit-tab "${firstTab}" --from-type ${to} --with-slice-zone\`, then run this command again.`; - throw new CommandError(`Slice zone "${sliceZone}" not found in "${to}". ${hint}`); + if (sliceZoneIds.length === 0) { + const [firstTab = "Main"] = Object.keys(customType.json); + throw new CommandError( + `"${to}" has no slice zone. Add one with \`prismic type edit-tab "${firstTab}" --from-type ${to} --with-slice-zone\`, then run this command again.`, + ); + } + throw new CommandError( + `Slice zone "${sliceZone}" not found in "${to}". Use --slice-zone with one of: ${sliceZoneIds.join(", ")}.`, + ); } sliceZoneField.config ??= {}; diff --git a/src/commands/slice-disconnect.ts b/src/commands/slice-disconnect.ts index e48c0122..d5ba931c 100644 --- a/src/commands/slice-disconnect.ts +++ b/src/commands/slice-disconnect.ts @@ -37,7 +37,13 @@ export default createCommand(config, async ({ positionals, values }) => { const sliceZoneField = allFields[sliceZone]; if (!sliceZoneField || sliceZoneField.type !== "Slices") { - throw new CommandError(`Slice zone "${sliceZone}" not found in "${from}".`); + const sliceZoneIds = Object.keys(allFields).filter((key) => allFields[key].type === "Slices"); + if (sliceZoneIds.length === 0) { + throw new CommandError(`"${from}" has no slice zone, so no slice is connected to it.`); + } + throw new CommandError( + `Slice zone "${sliceZone}" not found in "${from}". Use --slice-zone with one of: ${sliceZoneIds.join(", ")}.`, + ); } if (!sliceZoneField.config?.choices || !(slice.id in sliceZoneField.config.choices)) { From 7766946d38d749297ec17600e0b324ffa2c868e9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 22:27:37 +0000 Subject: [PATCH 3/6] feat: let commands declare help text for agents only Add an optional `agents` field to the command config. When the CLI detects an agent, `--help` shows it as an AGENTS section before USAGE, with the same format as the router AGENTS section. Humans do not see it. Use it in `init` to tell agents not to ask the user whether to create a new repository or connect an existing one. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- src/commands/init.ts | 4 ++++ src/lib/command.ts | 11 +++++++++-- test/init.test.ts | 15 +++++++++++++++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/commands/init.ts b/src/commands/init.ts index 4670d9e5..9b115adf 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -58,6 +58,10 @@ const config = { Run \`prismic docs view cli#set-up-a-type-builder-project\` for details. `, + agents: ` + Do not ask the user whether to create a new repository or connect an + existing one. Use --repo only for a repository the user names, and run init. + `, options: { repo: { type: "string", diff --git a/src/lib/command.ts b/src/lib/command.ts index 21e733a2..2014d0b1 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -7,6 +7,8 @@ import { dedent, formatTable } from "./string"; export type CommandConfig = { name: string; description: string; + /** Help shown only to detected agents, as an AGENTS section. */ + agents?: string; sections?: Record; positionals?: Record; options?: Record< @@ -86,6 +88,7 @@ export function createCommand( if (result.values.help) { const names = Object.keys(positionals); const blocks: Record = { + ...(isAgent && config.agents && { AGENTS: agentsBlock(config.agents) }), USAGE: ` ${[config.name, ...names.map((name) => `<${name}>`), "[options]"].join(" ")}`, }; if (names.length > 0) { @@ -151,7 +154,7 @@ export function createCommandRouter(config: { const blocks = { ...(isAgent && { - AGENTS: dedent` + AGENTS: agentsBlock(dedent` Every command except --help, read-only ones included, needs the same --task-id and the same --user-intent, the user's request in one sentence, for as long as you work on that request. Get the id first: run \`${config.name.split(" ")[0]} task-id\` @@ -159,7 +162,7 @@ export function createCommandRouter(config: { commands without the id fail. All the steps of one request share the id, and the next request gets a new one. The id and intent are used for analytics only. - `.replace(/^/gm, " "), + `), }), USAGE: ` ${config.name} [options]`, COMMANDS: formatTable( @@ -173,6 +176,10 @@ export function createCommandRouter(config: { }; } +function agentsBlock(text: string): string { + return dedent(text).replace(/^/gm, " "); +} + function optionRows(options: NonNullable): string[][] { const all: typeof options = isAgent ? { ...options, ...AGENT_OPTIONS } : options; const rows: string[][] = []; diff --git a/test/init.test.ts b/test/init.test.ts index ba769d8f..568ec8f4 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -35,6 +35,21 @@ it("supports --help", async ({ expect, prismic }) => { expect(stdout).toContain("prismic docs view cli#set-up-a-type-builder-project"); }); +it("shows the AGENTS section in help only when an agent is detected", async ({ + expect, + prismic, +}) => { + const agentHelp = ( + await prismic("init", ["--help"], { nodeOptions: { env: { AI_AGENT: "test-agent" } } }) + ).stdout; + const humanHelp = (await prismic("init", ["--help"])).stdout; + expect(agentHelp).toContain("AGENTS"); + expect(agentHelp).toContain("Do not ask the user"); + expect(agentHelp.indexOf("AGENTS")).toBeLessThan(agentHelp.indexOf("USAGE")); + expect(humanHelp).not.toContain("AGENTS"); + expect(humanHelp).not.toContain("Do not ask the user"); +}); + it("fails if prismic.config.json already exists without --repo", async ({ expect, prismic, From c7317f461c990f2ef6d98d55b1dd72df156ae6a9 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 23:00:30 +0000 Subject: [PATCH 4/6] feat: show the shared AGENTS text on every command's help Agents now see the shared AGENTS paragraph on the help of all commands, not only on routers. When a leaf command sets `agents`, its text goes in the same AGENTS section, after the shared paragraph and a blank line. Routers and leaf commands use one rendering function. Router output does not change. Humans see no AGENTS section. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- src/lib/command.ts | 31 ++++++++++++++++--------------- test/index.test.ts | 9 +++++++-- test/init.test.ts | 6 +++--- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/lib/command.ts b/src/lib/command.ts index 2014d0b1..59cb27ac 100644 --- a/src/lib/command.ts +++ b/src/lib/command.ts @@ -7,7 +7,7 @@ import { dedent, formatTable } from "./string"; export type CommandConfig = { name: string; description: string; - /** Help shown only to detected agents, as an AGENTS section. */ + /** Help shown only to detected agents, after the shared text in the AGENTS section. */ agents?: string; sections?: Record; positionals?: Record; @@ -88,7 +88,7 @@ export function createCommand( if (result.values.help) { const names = Object.keys(positionals); const blocks: Record = { - ...(isAgent && config.agents && { AGENTS: agentsBlock(config.agents) }), + ...agentsBlock(config.name, config.agents), USAGE: ` ${[config.name, ...names.map((name) => `<${name}>`), "[options]"].join(" ")}`, }; if (names.length > 0) { @@ -153,17 +153,7 @@ export function createCommandRouter(config: { } const blocks = { - ...(isAgent && { - AGENTS: agentsBlock(dedent` - Every command except --help, read-only ones included, needs the same - --task-id and the same --user-intent, the user's request in one sentence, for - as long as you work on that request. Get the id first: run \`${config.name.split(" ")[0]} task-id\` - on its own and wait for its output before you run any other command, as - commands without the id fail. All the steps of one request share the id, and - the next request gets a new one. The id and intent are used for analytics - only. - `), - }), + ...agentsBlock(config.name), USAGE: ` ${config.name} [options]`, COMMANDS: formatTable( Object.entries(config.commands) @@ -176,8 +166,19 @@ export function createCommandRouter(config: { }; } -function agentsBlock(text: string): string { - return dedent(text).replace(/^/gm, " "); +function agentsBlock(name: string, extra?: string): { AGENTS?: string } { + if (!isAgent) return {}; + const shared = dedent` + Every command except --help, read-only ones included, needs the same + --task-id and the same --user-intent, the user's request in one sentence, for + as long as you work on that request. Get the id first: run \`${name.split(" ")[0]} task-id\` + on its own and wait for its output before you run any other command, as + commands without the id fail. All the steps of one request share the id, and + the next request gets a new one. The id and intent are used for analytics + only. + `; + const text = extra ? `${shared}\n\n${dedent(extra)}` : shared; + return { AGENTS: text.replace(/^(?=.)/gm, " ") }; } function optionRows(options: NonNullable): string[][] { diff --git a/test/index.test.ts b/test/index.test.ts index ac649394..00077ca3 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -114,8 +114,13 @@ it("shows the agent options and `task-id` in help only when an agent is detected for (const [root, ...rest] of [[""], ["repo"], ["repo", "view"]]) { const args = [...rest, "--help"]; - expect((await prismic(root, args, agent)).stdout).toContain("--task-id"); - expect((await prismic(root, args, human)).stdout).not.toContain("--task-id"); + const agentStdout = (await prismic(root, args, agent)).stdout; + const humanStdout = (await prismic(root, args, human)).stdout; + expect(agentStdout).toContain("--task-id"); + expect(agentStdout.match(/^AGENTS$/gm)).toHaveLength(1); + expect(agentStdout.indexOf("AGENTS")).toBeLessThan(agentStdout.indexOf("USAGE")); + expect(humanStdout).not.toContain("--task-id"); + expect(humanStdout).not.toContain("AGENTS"); } const agentHelp = (await prismic("", ["--help"], agent)).stdout; diff --git a/test/init.test.ts b/test/init.test.ts index 568ec8f4..2957e093 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -43,9 +43,9 @@ it("shows the AGENTS section in help only when an agent is detected", async ({ await prismic("init", ["--help"], { nodeOptions: { env: { AI_AGENT: "test-agent" } } }) ).stdout; const humanHelp = (await prismic("init", ["--help"])).stdout; - expect(agentHelp).toContain("AGENTS"); - expect(agentHelp).toContain("Do not ask the user"); - expect(agentHelp.indexOf("AGENTS")).toBeLessThan(agentHelp.indexOf("USAGE")); + expect(agentHelp.match(/^AGENTS$/gm)).toHaveLength(1); + expect(agentHelp).toContain("used for analytics\n only.\n\n Do not ask the user"); + expect(agentHelp.indexOf("Do not ask the user")).toBeLessThan(agentHelp.indexOf("USAGE")); expect(humanHelp).not.toContain("AGENTS"); expect(humanHelp).not.toContain("Do not ask the user"); }); From ac77b7369e14c84f490aedd84fe7f74bce3b934e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 23:25:22 +0000 Subject: [PATCH 5/6] test: cover the slice zone errors of slice connect and disconnect Add tests for a type with no slice zone and for a --slice-zone that matches no slice zone, in slice connect and slice disconnect. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- test/slice-connect.test.ts | 30 +++++++++++++++++++++++++++ test/slice-disconnect.test.ts | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/test/slice-connect.test.ts b/test/slice-connect.test.ts index 46033429..df3092fb 100644 --- a/test/slice-connect.test.ts +++ b/test/slice-connect.test.ts @@ -46,3 +46,33 @@ it("connects a slice to a type", async ({ expect, prismic, project }) => { const choices = (updated.json.Main.slices as DynamicSlicesModel).config!.choices!; expect(choices[slice.id]).toEqual({ type: "SharedSlice" }); }); + +it("says how to add a slice zone when the type has none", async ({ expect, prismic, project }) => { + const slice = buildSlice(); + const customType = buildCustomType(); + + await writeLocalSlice(project, slice); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("slice", ["connect", slice.id, "--to", customType.id]); + expect(exitCode).toBe(1); + expect(stderr).toContain( + `"${customType.id}" has no slice zone. Add one with \`prismic type edit-tab "Main" --from-type ${customType.id} --with-slice-zone\`, then run this command again.`, + ); +}); + +it("lists the slice zones when --slice-zone matches none", async ({ expect, prismic, project }) => { + const slice = buildSlice(); + const customType = buildCustomType({ + json: { Main: { body: { type: "Slices", config: { choices: {} } } } }, + }); + + await writeLocalSlice(project, slice); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("slice", ["connect", slice.id, "--to", customType.id]); + expect(exitCode).toBe(1); + expect(stderr).toContain( + `Slice zone "slices" not found in "${customType.id}". Use --slice-zone with one of: body.`, + ); +}); diff --git a/test/slice-disconnect.test.ts b/test/slice-disconnect.test.ts index e058c6de..fb01c98a 100644 --- a/test/slice-disconnect.test.ts +++ b/test/slice-disconnect.test.ts @@ -46,3 +46,41 @@ it("disconnects a slice from a type", async ({ expect, prismic, project }) => { const choices = (updated.json.Main.slices as DynamicSlicesModel).config!.choices!; expect(choices[slice.id]).toBeUndefined(); }); + +it("fails when the type has no slice zone", async ({ expect, prismic, project }) => { + const slice = buildSlice(); + const customType = buildCustomType(); + + await writeLocalSlice(project, slice); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("slice", [ + "disconnect", + slice.id, + "--from", + customType.id, + ]); + expect(exitCode).toBe(1); + expect(stderr).toContain(`"${customType.id}" has no slice zone, so no slice is connected to it.`); +}); + +it("lists the slice zones when --slice-zone matches none", async ({ expect, prismic, project }) => { + const slice = buildSlice(); + const customType = buildCustomType({ + json: { Main: { body: { type: "Slices", config: { choices: {} } } } }, + }); + + await writeLocalSlice(project, slice); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("slice", [ + "disconnect", + slice.id, + "--from", + customType.id, + ]); + expect(exitCode).toBe(1); + expect(stderr).toContain( + `Slice zone "slices" not found in "${customType.id}". Use --slice-zone with one of: body.`, + ); +}); From e0fd1214d5ecf2c167803daccb798736b5f61bb7 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 13 Nov 2026 23:25:22 +0000 Subject: [PATCH 6/6] test: remove the duplicate root help checks from the index test The loop already checks the AGENTS section on the root help. Move the --analytics-task-id check into the loop, so it also runs on the router and leaf help. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01L1dGD553PK5SyWPuV2R36u --- test/index.test.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/test/index.test.ts b/test/index.test.ts index 00077ca3..b4b27c5f 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -117,16 +117,10 @@ it("shows the agent options and `task-id` in help only when an agent is detected const agentStdout = (await prismic(root, args, agent)).stdout; const humanStdout = (await prismic(root, args, human)).stdout; expect(agentStdout).toContain("--task-id"); + expect(agentStdout).not.toContain("--analytics-task-id"); expect(agentStdout.match(/^AGENTS$/gm)).toHaveLength(1); expect(agentStdout.indexOf("AGENTS")).toBeLessThan(agentStdout.indexOf("USAGE")); expect(humanStdout).not.toContain("--task-id"); expect(humanStdout).not.toContain("AGENTS"); } - - const agentHelp = (await prismic("", ["--help"], agent)).stdout; - const humanHelp = (await prismic("", ["--help"], human)).stdout; - expect(agentHelp).toContain("AGENTS"); - expect(agentHelp.indexOf("AGENTS")).toBeLessThan(agentHelp.indexOf("USAGE")); - expect(humanHelp).not.toContain("AGENTS"); - expect(agentHelp).not.toContain("--analytics-task-id"); });