/* Domain-specific regression tests (split from index.test.ts). */ import { expect, test } from "bun:test"; import { $ } from "bun"; import { completionBashScript, completionZshScript } from "../builtins/index.ts"; import { cliPresentationRoot } from "../builtins/presentation.ts"; import { cliHelpRender } from "../help.ts"; import { Cli, CliFallbackMode, CliOptionKind, type CliProgram } from "../index.ts"; import { applyShellEnv } from "../mcp/env.ts"; import { allMcpResources, collectMcpTools, mcpToolCallToArgv, resolveMcpSchemaUri } from "../mcp/tools.ts"; import { generatePluginSkillBundle } from "../skill/generate.ts"; import { enumMcpFixture, nestedDocsFallbackFixture, nestedMcpFixture, requireMcpTool, testProgram, varargsReadFixture, } from "../test/fixtures.ts"; import { ParseKind, parse, postParseValidate } from "./parse.ts"; import { cliSchemaJson } from "./schema.ts"; import { cliValidateProgram } from "./validate.ts"; /** Tests that bundled short presence flags. */ test("bundled short presence flags", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "a", description: "", kind: CliOptionKind.Presence, shortName: "a", }, { name: "b", description: "", kind: CliOptionKind.Presence, shortName: "b", }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "-ab"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.opts.a).toBe("1"); expect(pr.opts.b).toBe("1"); }); /** Tests that long option equals. */ test("long option equals", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "name", description: "", kind: CliOptionKind.String, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "--name=pat"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.opts.name).toBe("pat"); }); /** Tests that fallback missing or unknown root flags. */ test("fallback missing or unknown root flags", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "hello", description: "Say hi.", options: [ { name: "name", description: "", kind: CliOptionKind.String, }, ], handler: () => {}, }, ], fallbackCommand: "hello", fallbackMode: CliFallbackMode.MissingOrUnknown, }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["--name", "bob"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.path).toEqual(["hello"]); expect(pr.opts.name).toBe("bob"); }); test("param router descent captures pathParams", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "workspaces", description: "Workspaces.", commands: [ { key: ":id", description: "One workspace.", commands: [ { key: "get", description: "Get workspace.", handler: () => {}, }, ], }, ], }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["workspaces", "qa2", "get"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.path).toEqual(["workspaces", ":id", "get"]); expect(pr.pathParams).toEqual({ id: "qa2" }); }); test("cli.enabled cascade blocks disabled router", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "workspaces", description: "Disabled.", cli: { enabled: false }, commands: [ { key: "list", description: "List.", handler: () => {}, }, ], }, ], }); cliValidateProgram(root); const pr = parse(root, ["workspaces", "list"]); expect(pr.kind).toBe(ParseKind.Error); expect(pr.errorMsg).toContain("Unknown command"); }); test("completion match child emits param router fallback", () => { const root = testProgram({ key: "app", description: "Test", commands: [ { key: "workspaces", description: "Workspaces.", commands: [ { key: ":id", description: "One workspace.", commands: [{ key: "get", description: "Get.", handler: () => {} }], }, ], }, ], }); cliValidateProgram(root); const bash = completionBashScript(cliPresentationRoot(root)); expect(bash).toContain("*) echo"); }); test("unknown command", () => { const root = testProgram({ key: "app", description: "", commands: [{ key: "hello", description: "", handler: () => {} }], }); cliValidateProgram(root); const pr = parse(root, ["nope"]); expect(pr.kind).toBe(ParseKind.Error); expect(pr.errorMsg).toContain("Unknown command"); }); test("implicit help empty", () => { const root = testProgram({ key: "app", description: "", commands: [{ key: "x", description: "", handler: () => {} }], }); cliValidateProgram(root); const pr = parse(root, []); expect(pr.kind).toBe(ParseKind.Help); expect(pr.helpExplicit).toBe(false); }); /** Invalid number post validate. */ test("invalid number post validate", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "", options: [ { name: "n", description: "", kind: CliOptionKind.Number, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); let pr = parse(root, ["x", "--n", "notnum"]); pr = postParseValidate(root, pr); expect(pr.kind).toBe(ParseKind.Error); expect(pr.errorMsg).toContain("Invalid number"); }); /** Supports scientific notation in numbers. */ test("supports scientific notation in numbers", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "", options: [ { name: "n", description: "", kind: CliOptionKind.Number, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); let pr = parse(root, ["x", "--n", "1.23e4"]); pr = postParseValidate(root, pr); expect(pr.kind).toBe(ParseKind.Ok); expect(Number(pr.opts.n)).toBe(12300); }); /** Completion scripts contain app name. */ test("completion scripts contain app name", () => { const root = testProgram({ key: "myapp", description: "Test", commands: [{ key: "hello", description: "Say hello.", handler: () => {} }], }); cliValidateProgram(root); const bash = completionBashScript(cliPresentationRoot(root)); expect(bash).toContain("bash completion for myapp"); expect(bash).toContain("complete -F _myapp myapp"); const zsh = completionZshScript(cliPresentationRoot(root)); expect(zsh).toContain("#compdef myapp"); expect(zsh).toContain("compdef _myapp myapp"); expect(zsh).toContain("hello:Say hello."); }); /** Completion scripts do not emit invalid bash substitutions. */ test("completion scripts do not emit invalid bash substitutions", () => { const root = testProgram({ key: "app", description: "Test", commands: [{ key: "hello", description: "Say hello.", handler: () => {} }], }); cliValidateProgram(root); const bash = completionBashScript(cliPresentationRoot(root)); expect(bash).not.toContain("${${"); }); /** Completion scripts escape shell-sensitive command text in zsh. */ test("completion scripts escape shell-sensitive command text in zsh", () => { const root = testProgram({ key: "app", description: "Test", commands: [ { key: "quote'cmd", description: "Say 'hello' and keep going.", handler: () => {}, }, ], }); cliValidateProgram(root); const zsh = completionZshScript(cliPresentationRoot(root)); expect(zsh).toContain("quote'\\''cmd:Say '\\''hello'\\'' and keep going."); }); /** Completion scripts keep dotted app names in registration names. */ test("completion scripts keep dotted app names in registration names", () => { const root = testProgram({ key: "minimal.ts", description: "Test", commands: [{ key: "hello", description: "Say hello.", handler: () => {} }], }); cliValidateProgram(root); const bash = completionBashScript(cliPresentationRoot(root)); expect(bash).toContain("complete -F _minimal_ts minimal.ts"); const zsh = completionZshScript(cliPresentationRoot(root)); expect(zsh).toContain("compdef _minimal_ts minimal.ts"); }); /** Tests that trailing options after bounded positionals. */ test("trailing options after bounded positionals", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "verbose", description: "", kind: CliOptionKind.Presence, }, ], positionals: [ { name: "path", description: "", kind: CliOptionKind.String, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "./file", "--verbose"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["./file"]); expect(pr.opts.verbose).toBe("1"); }); /** Tests that options can be interleaved between bounded positionals. */ test("options interleaved between bounded positionals", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "copy", description: "copy", options: [ { name: "force", description: "", kind: CliOptionKind.Presence, shortName: "f", }, { name: "mode", description: "", kind: CliOptionKind.String, }, ], positionals: [ { name: "src", description: "", kind: CliOptionKind.String, }, { name: "dest", description: "", kind: CliOptionKind.String, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); // Presence flag interleaved between positionals const prPresence = postParseValidate(root, parse(root, ["copy", "file1", "--force", "file2"])); expect(prPresence.kind).toBe(ParseKind.Ok); expect(prPresence.args).toEqual(["file1", "file2"]); expect(prPresence.opts.force).toBe("1"); // String option with value interleaved between positionals const prString = postParseValidate(root, parse(root, ["copy", "file1", "--mode", "fast", "file2"])); expect(prString.kind).toBe(ParseKind.Ok); expect(prString.args).toEqual(["file1", "file2"]); expect(prString.opts.mode).toBe("fast"); // Multiple flags interleaved between positionals const prMulti = postParseValidate(root, parse(root, ["copy", "file1", "--mode", "fast", "-f", "file2"])); expect(prMulti.kind).toBe(ParseKind.Ok); expect(prMulti.args).toEqual(["file1", "file2"]); expect(prMulti.opts.mode).toBe("fast"); expect(prMulti.opts.force).toBe("1"); // Unknown option interleaved between positionals returns error const prUnknown = postParseValidate(root, parse(root, ["copy", "file1", "--unknown", "file2"])); expect(prUnknown.kind).toBe(ParseKind.Error); expect(prUnknown.errorMsg).toContain("Unknown option: --unknown"); // Interleaved help request triggers contextual help const prHelp = parse(root, ["copy", "file1", "-h"]); expect(prHelp.kind).toBe(ParseKind.Help); expect(prHelp.helpPath).toEqual(["copy"]); // Double dash between positionals disables option consumption const prDoubleDash = postParseValidate(root, parse(root, ["copy", "file1", "--", "--force"])); expect(prDoubleDash.kind).toBe(ParseKind.Ok); expect(prDoubleDash.args).toEqual(["file1", "--force"]); expect(prDoubleDash.opts.force).toBeUndefined(); }); /** Tests that options can be interleaved with optional positionals. */ test("options interleaved with optional positionals", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "deploy", description: "deploy", options: [ { name: "force", description: "", kind: CliOptionKind.Presence, }, ], positionals: [ { name: "env", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 1, }, { name: "target", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 1, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); // Interleaved between two optional positionals const prBoth = postParseValidate(root, parse(root, ["deploy", "prod", "--force", "us-east"])); expect(prBoth.kind).toBe(ParseKind.Ok); expect(prBoth.args).toEqual(["prod", "us-east"]); expect(prBoth.opts.force).toBe("1"); // Option after first optional positional when second is omitted const prOne = postParseValidate(root, parse(root, ["deploy", "prod", "--force"])); expect(prOne.kind).toBe(ParseKind.Ok); expect(prOne.args).toEqual(["prod"]); expect(prOne.opts.force).toBe("1"); // Option before optional positionals when all are omitted const prNone = postParseValidate(root, parse(root, ["deploy", "--force"])); expect(prNone.kind).toBe(ParseKind.Ok); expect(prNone.args).toEqual([]); expect(prNone.opts.force).toBe("1"); }); /** Tests that options can be interleaved between bounded positional and varargs tail. */ test("options interleaved between bounded positional and varargs tail", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "upload", description: "upload", options: [ { name: "json", description: "", kind: CliOptionKind.Presence, }, ], positionals: [ { name: "target", description: "", kind: CliOptionKind.String, argMin: 1, argMax: 1, }, { name: "files", description: "", kind: CliOptionKind.String, argMin: 1, argMax: 0, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); // Flag between target and files does not get captured as first file const pr = postParseValidate(root, parse(root, ["upload", "s3", "--json", "a.txt", "b.txt"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["s3", "a.txt", "b.txt"]); expect(pr.opts.json).toBe("1"); }); /** Tests that options on routing groups are rejected at schema validation. */ test("rejects options on routing groups", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "group", description: "group", options: [ { name: "json", description: "", kind: CliOptionKind.Presence, }, ], commands: [ { key: "leaf", description: "leaf", handler: () => {}, }, ], }, ], }); expect(() => cliValidateProgram(root)).toThrow(/routing group/); }); /** Tests that leaf flags are only accepted on the leaf command segment. */ test("leaf flags are only accepted on the leaf command segment", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "group", description: "group", commands: [ { key: "leaf", description: "leaf", options: [ { name: "json", description: "", kind: CliOptionKind.Presence, }, { name: "user", description: "", kind: CliOptionKind.String, shortName: "u", }, ], positionals: [ { name: "path", description: "", kind: CliOptionKind.String, }, ], handler: () => {}, }, ], }, ], }); cliValidateProgram(root); const ok = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"])); expect(ok.kind).toBe(ParseKind.Ok); expect(ok.opts.json).toBe("1"); const bad = parse(root, ["group", "--json", "leaf", "-u", "alice", "./file"]); expect(bad.kind).toBe(ParseKind.Error); }); /** Varargs tail parses trailing options. */ test("varargs tail parses trailing options", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "json", description: "", kind: CliOptionKind.Presence, }, ], positionals: [ { name: "files", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 0, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "./file", "--json"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["./file"]); expect(pr.opts.json).toBe("1"); }); /** Stops parsing options at --. */ test("stops parsing options at --", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "name", description: "", kind: CliOptionKind.String, }, ], positionals: [ { name: "files", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 0, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.opts.name).toBe("pat"); expect(pr.args).toEqual(["--name", "bob", "-x"]); }); /** Missing required option returns error. */ test("missing required option returns error", () => { const root = testProgram({ key: "app", description: "", options: [ { name: "req", description: "", kind: CliOptionKind.String, required: true, }, ], commands: [ { key: "x", description: "cmd", handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x"])); expect(pr.kind).toBe(ParseKind.Error); expect(pr.errorMsg).toContain("Missing required option: --req"); }); /** Provided required option parses ok. */ test("provided required option parses ok", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "x", description: "cmd", options: [ { name: "req", description: "", kind: CliOptionKind.String, required: true, }, ], handler: () => {}, }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["x", "--req", "val"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.opts.req).toBe("val"); }); /** Tests that presence option cannot be required. */ test("presence option cannot be required", () => { const root = testProgram({ key: "app", description: "", options: [ { name: "flag", description: "", kind: CliOptionKind.Presence, required: true, }, ], commands: [ { key: "x", description: "cmd", handler: () => {}, }, ], }); expect(() => cliValidateProgram(root)).toThrow(/Presence option cannot be required/); }); test("leaf completion help prints correctly", async () => { // Test the fix where `completion zsh -h` on a leaf root was incorrectly ignored. // We run this as a subprocess so we don't accidentally exit the test runner. const { stdout, stderr, exitCode } = await $`bun run examples/minimal.ts completion zsh -h`.nothrow().quiet(); const out = stdout.toString(); expect(exitCode).toBe(0); expect(out).toContain("Show help for this command."); expect(out).toContain("Homebrew"); expect(stderr.toString()).toBe(""); }); /** Docs schema exports JSON for nested CLIs. */ test("docs cli-schema exports JSON for nested CLIs", async () => { const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs cli-schema`.nothrow().quiet(); expect(exitCode).toBe(0); expect(stderr.toString()).toBe(""); const schema = JSON.parse(stdout.toString()); expect(schema.key).toBe("nested.ts"); expect(schema.fallbackCommand).toBe("read"); expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["stat", "read"]); expect(schema.commands).not.toContainEqual(expect.objectContaining({ key: "completion" })); const lookup = schema.commands[0].commands[0].commands[0]; expect(lookup.key).toBe("lookup"); expect(lookup.positionals[0].name).toBe("path"); }); /** Docs schema exports JSON for leaf roots. */ test("docs cli-schema exports JSON for leaf roots", async () => { const { stdout, exitCode } = await $`bun run examples/minimal.ts docs cli-schema`.nothrow().quiet(); expect(exitCode).toBe(0); const schema = JSON.parse(stdout.toString()); expect(schema.key).toBe("minimal.ts"); expect(schema.positionals[0].name).toBe("name"); expect(schema.options[0].name).toBe("verbose"); expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["version", "configure", "docs"]); }); test("version builtin prints program version", async () => { const { stdout, exitCode } = await $`bun run examples/nested.ts version`.nothrow().quiet(); expect(exitCode).toBe(0); expect(stdout.toString().trim()).toMatch(/^\d+\.\d+\.\d+/); }); test("leaf root help omits hidden completion built-in", async () => { const { stdout, exitCode } = await $`bun run examples/minimal.ts -h`.nothrow().quiet(); expect(exitCode).toBe(0); expect(stdout.toString()).not.toContain("completion"); expect(stdout.toString()).toContain("configure"); }); /** Root --schema is no longer a flag. */ test("root --schema is no longer a flag", () => { const root = testProgram({ key: "app", version: "1.0.0", description: "demo", docs: { topics: { readme: { text: "# readme\n" } }, }, commands: [ { key: "x", description: "cmd", handler: () => {}, }, ], }); cliValidateProgram(root); const pr = parse(cliPresentationRoot(root), ["--schema"]); expect(pr.kind).not.toBe(ParseKind.Ok); }); /** CliSchemaJson omits handlers and completion built-ins. */ test("cliSchemaJson omits handlers and completion built-ins", () => { const root = testProgram({ key: "app", description: "demo", commands: [ { key: "x", description: "cmd", handler: () => {}, }, { key: "completion", description: "should not appear", commands: [ { key: "bash", description: "", handler: () => {}, }, ], }, ], }); const schema = JSON.parse(cliSchemaJson(root)); expect(schema.commands).toHaveLength(1); expect(schema.commands[0].key).toBe("x"); expect(schema).not.toHaveProperty("handler"); }); /** CliSchemaExport configure notes reference README for install. */ test("cliSchemaExport configure notes reference README for install", () => { const root = testProgram({ key: "myapp", version: "1.0.0", description: "demo", commands: [ { key: "run", description: "run", handler: () => {}, }, ], }); const json = cliSchemaJson(root); expect(json).not.toContain("{argsbarg:program}"); expect(json).toContain("README"); expect(json).not.toContain("brew install "); }); /** CliSchemaExport resolves {argsbarg:program} in consumer notes. */ test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => { const root = testProgram({ key: "myapp", version: "1.0.0", description: "demo", commands: [ { key: "run", description: "run", notes: "Run `{argsbarg:program} run` to start.", handler: () => {}, }, ], }); const schema = JSON.parse(cliSchemaJson(root)); expect(schema.commands[0].notes).toBe("Run `myapp run` to start."); }); test("Enum option inputSchema includes enum array", () => { const tools = collectMcpTools(enumMcpFixture); const run = requireMcpTool(tools, "run"); const schema = run.inputSchema as { properties: { mode: { enum?: string[] } } }; expect(schema.properties.mode.enum).toEqual(["dev", "prod"]); }); test("cliValidateProgram rejects Enum with no choices", () => { const root = testProgram({ key: "app", description: "", handler: () => {}, options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: [] }], }); expect(() => cliValidateProgram(root)).toThrow(/requires non-empty choices/); }); test("cliValidateProgram rejects Enum with duplicate choices", () => { const root = testProgram({ key: "app", description: "", handler: () => {}, options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: ["a", "a"] }], }); expect(() => cliValidateProgram(root)).toThrow(/choices must be distinct/); }); /** McpTool.description override wins without env suffix. */ test("mcpTool.description override wins without env suffix", () => { const root = testProgram({ key: "app", description: "", mcpServer: { enabled: true }, commands: [ { key: "x", description: "Leaf desc.", mcpTool: { description: "custom" }, handler: () => {}, }, ], }); const tools = collectMcpTools(root); expect(tools[0]?.description).toBe("custom"); }); test("cliValidateProgram requires program.appConfig description", () => { const root = testProgram({ key: "app", description: "", appConfig: { entries: { token: {} as { description: string } } }, handler: () => {}, }); expect(() => cliValidateProgram(root)).toThrow(/description must be a non-empty string/); }); /** CliValidateProgram rejects duplicate mcpResources URIs. */ test("cliValidateProgram rejects duplicate mcpResources URIs", () => { const root = testProgram({ key: "app", description: "", mcpServer: { enabled: true, resources: [ { uri: "a://1", name: "a", load: () => "a" }, { uri: "a://1", name: "b", load: () => "b" }, ], }, commands: [{ key: "x", description: "", handler: () => {} }], }); expect(() => cliValidateProgram(root)).toThrow(/URIs must be unique/); }); test("cliValidateProgram rejects empty mcpServer", () => { const root = testProgram({ key: "app", description: "", mcpServer: {} as { enabled: boolean }, handler: () => {}, }); expect(() => cliValidateProgram(root)).toThrow(/mcpServer requires enabled: true/); }); test("cliValidateProgram rejects empty httpServer", () => { const root = testProgram({ key: "app", description: "", httpServer: {} as { enabled: boolean }, handler: () => {}, }); expect(() => cliValidateProgram(root)).toThrow(/httpServer requires enabled: true/); }); test("resolveMcpSchemaUri uses sanitized root key", () => { const root = testProgram({ key: "nested.ts", description: "", mcpServer: { enabled: true }, handler: () => {}, }); expect(resolveMcpSchemaUri(root)).toBe("nested_ts://schema"); }); test("resolveMcpSchemaUri uses plain key when alphanumeric", () => { const root = testProgram({ key: "qa", description: "", mcpServer: { enabled: true }, handler: () => {}, }); expect(resolveMcpSchemaUri(root)).toBe("qa://schema"); }); test("cliValidateProgram rejects resource URI matching default schema URI", () => { const root = testProgram({ key: "app", description: "", mcpServer: { enabled: true, resources: [{ uri: "app://schema", name: "dup", load: () => "" }], }, commands: [{ key: "x", description: "", handler: () => {} }], }); expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/); }); /** CliValidateProgram rejects resource URI matching schemaResourceUri. */ test("cliValidateProgram rejects resource URI matching schemaResourceUri", () => { const root = testProgram({ key: "app", description: "", mcpServer: { enabled: true, schemaResourceUri: "custom://schema", resources: [{ uri: "custom://schema", name: "dup", load: () => "" }], }, commands: [{ key: "x", description: "", handler: () => {} }], }); expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/); }); /** CliValidateProgram rejects resource URI matching auto docs topic. */ test("cliValidateProgram rejects resource URI matching auto docs topic", () => { const root = testProgram({ key: "app", description: "", docs: { topics: { readme: { text: "# r\n" } } }, mcpServer: { enabled: true, resources: [{ uri: "app://docs/readme", name: "dup", load: () => "" }], }, commands: [{ key: "x", description: "", handler: () => {} }], }); expect(() => cliValidateProgram(root)).toThrow(/conflicts with auto docs topic resource/); }); /** AllMcpResources includes custom resources. */ test("allMcpResources includes custom resources", () => { const root = testProgram({ key: "app", description: "", mcpServer: { enabled: true, resources: [{ uri: "test://x", name: "x", load: () => "body" }], }, commands: [{ key: "leaf", description: "", handler: () => {} }], }); const resources = allMcpResources(root); expect(resources.map((r) => r.uri)).toContain("app://schema"); expect(resources.map((r) => r.uri)).toContain("test://x"); }); /** AllMcpResources includes docs topic resources. */ test("allMcpResources includes docs topic resources", () => { const root = testProgram({ key: "app", description: "", docs: { topics: { readme: { text: "# hi\n" } } }, mcpServer: { enabled: true }, commands: [{ key: "leaf", description: "", handler: () => {} }], }); const resources = allMcpResources(root); expect(resources.map((r) => r.uri)).toContain("app://docs/readme"); const readme = resources.find((r) => r.uri === "app://docs/readme"); expect(readme?.load()).toBe("# hi\n"); }); /** ApplyShellEnv merges PATH and preserves host vars. */ test("applyShellEnv merges PATH and preserves host vars", () => { const origPath = process.env.PATH ?? ""; const origHome = process.env.HOME; process.env.PATH = "/host/bin"; process.env.HOME = "host-home"; applyShellEnv({ PATH: "/shell/bin:/host/bin", HOME: "shell-home", NEWVAR: "yes" }); expect(process.env.PATH?.startsWith("/shell/bin:")).toBe(true); expect(process.env.PATH).toContain("/host/bin"); expect(process.env.HOME).toBe("host-home"); expect(process.env.NEWVAR).toBe("yes"); process.env.PATH = origPath; if (origHome === undefined) { delete process.env.HOME; } else { process.env.HOME = origHome; } delete process.env.NEWVAR; }); /** Enum completions list choices in bash script. */ test("Enum completions list choices in bash script", () => { const root = testProgram({ key: "app", description: "", commands: [ { key: "run", description: "", options: [{ name: "mode", description: "m", kind: CliOptionKind.Enum, choices: ["dev", "prod"] }], handler: () => {}, }, ], }); const bash = completionBashScript(cliPresentationRoot(root)); expect(bash).toContain("--mode) COMPREPLY="); expect(bash).toContain("dev"); expect(bash).toContain("prod"); }); test("nested fallback routes to default when argv exhausted at router", () => { const root = nestedDocsFallbackFixture(); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["docs"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.path).toEqual(["docs", "guide"]); }); /** Nested fallback MissingOrUnknown routes unknown token to default. */ test("nested fallback MissingOrUnknown routes unknown token to default", () => { const root = testProgram({ key: "app", description: "", docs: { enabled: false }, commands: [ { key: "docs", description: "Documentation commands.", fallbackCommand: "guide", fallbackMode: CliFallbackMode.MissingOrUnknown, commands: [ { key: "guide", description: "User guide.", positionals: [ { name: "topic", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 0, }, ], handler: () => {}, }, { key: "api", description: "API reference.", handler: () => {}, }, ], }, ], }); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["docs", "extra-topic"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.path).toEqual(["docs", "guide"]); expect(pr.args).toEqual(["extra-topic"]); }); test("nested fallback MissingOnly errors on unknown subcommand", () => { const root = nestedDocsFallbackFixture(); cliValidateProgram(root); const pr = parse(root, ["docs", "nope"]); expect(pr.kind).toBe(ParseKind.Error); expect(pr.errorMsg).toContain("Unknown subcommand"); }); /** CliValidateProgram rejects invalid nested fallbackCommand. */ test("cliValidateProgram rejects invalid nested fallbackCommand", () => { const root = testProgram({ key: "app", description: "", docs: { enabled: false }, commands: [ { key: "docs", description: "", fallbackCommand: "missing", commands: [ { key: "guide", description: "", handler: () => {}, }, ], }, ], }); expect(() => cliValidateProgram(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/); }); test("cliValidateProgram accepts nested fallbackCommand when child exists", () => { const root = nestedDocsFallbackFixture(); expect(() => cliValidateProgram(root)).not.toThrow(); }); test("nested router scoped help does not route to fallback", () => { const root = nestedDocsFallbackFixture(); cliValidateProgram(root); const pr = parse(root, ["docs", "--help"]); expect(pr.kind).toBe(ParseKind.Help); expect(pr.helpPath).toEqual(["docs"]); expect(pr.helpExplicit).toBe(true); const help = cliHelpRender(cliPresentationRoot(root), pr.helpPath, false); expect(help).toContain("api"); expect(help).toContain("guide"); }); test("varargs trailing option after positionals via Cli.invoke", async () => { const root = varargsReadFixture(); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["read", "file.txt", "--json"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["file.txt"]); expect(pr.opts.json).toBe("1"); }); test("varargs option before positionals", () => { const root = varargsReadFixture(); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["read", "--json", "file.txt"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["file.txt"]); expect(pr.opts.json).toBe("1"); }); test("varargs multiple files then trailing option", () => { const root = varargsReadFixture(); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["read", "a.txt", "b.txt", "--json"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["a.txt", "b.txt"]); expect(pr.opts.json).toBe("1"); }); test("varargs double dash forces positional", () => { const root = varargsReadFixture(); cliValidateProgram(root); const pr = postParseValidate(root, parse(root, ["read", "file.txt", "--", "--json"])); expect(pr.kind).toBe(ParseKind.Ok); expect(pr.args).toEqual(["file.txt", "--json"]); expect(pr.opts.json).toBeUndefined(); }); test("varargs unknown flag errors", async () => { const root = varargsReadFixture(); cliValidateProgram(root); const result = await new Cli(root).invoke(["read", "--unknown"]); expect(result.kind).toBe("error"); expect(result.stderr).toContain("--unknown"); }); test("mcpToolCallToArgv rejects comma-separated string for varargs", () => { const tools = collectMcpTools(nestedMcpFixture); const read = requireMcpTool(tools, "read"); const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: "a,b" }); expect(argv).toEqual({ error: expect.stringContaining("JSON array") }); }); test("mcpToolCallToArgv rejects bare string for varargs", () => { const tools = collectMcpTools(nestedMcpFixture); const read = requireMcpTool(tools, "read"); const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: "a" }); expect(argv).toEqual({ error: expect.stringContaining("JSON array") }); }); test("mcpToolCallToArgv array varargs unchanged", () => { const tools = collectMcpTools(nestedMcpFixture); const read = requireMcpTool(tools, "read"); const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: ["a", "b"] }); expect(argv).toEqual(["read", "a", "b"]); }); test("mcpToolCallToArgv empty array varargs errors when required", () => { const tools = collectMcpTools(nestedMcpFixture); const read = requireMcpTool(tools, "read"); const argv = mcpToolCallToArgv(nestedMcpFixture, read, { files: [] }); expect(argv).toEqual({ error: "Missing argument: files" }); }); // ── Skills ──────────────────────────────────────────────────────────────────── /** Configure config on non-root node is rejected. */ test("configure config on non-root node is rejected", () => { const root = { key: "app", version: "0.0.0", description: "", commands: [ { key: "x", description: "", configure: { enabled: false }, handler: () => {}, }, ], } as unknown as CliProgram; expect(() => cliValidateProgram(root)).toThrow(/configure is only supported on the program root/); }); test("configure.prefix is rejected", () => { const root = { key: "app", version: "0.0.0", description: "", configure: { prefix: "/opt/bin" }, handler: () => {}, } as unknown as CliProgram; expect(() => cliValidateProgram(root)).toThrow(/configure\.prefix removed/); }); /** Tests that generatePluginSkillBundle is MCP routing stub without shell catalog. */ test("generatePluginSkillBundle is MCP routing stub without shell catalog", () => { const bundle = generatePluginSkillBundle(nestedMcpFixture); expect(bundle.dirName).toBe("nested_ts"); expect(bundle.skillMd).toMatch(/^---\nname: nested_ts\n/); expect(bundle.skillMd).toContain("MCP toolset"); expect(bundle.skillMd).toContain("Server id: `nested_ts`"); expect(bundle.skillMd).toContain("nested_ts://schema"); expect(bundle.skillMd).toContain("tools/list"); expect(bundle.skillMd).not.toContain("Invoke via shell"); expect(bundle.skillMd).not.toContain("reference.md"); expect(bundle.skillMd).not.toContain("`nested.ts stat owner lookup `"); expect(bundle.skillMd).not.toContain("## Commands"); });