argsbarg 5.1.8 → 5.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -1
- package/docs/cli-program.md +3 -1
- package/docs/config-schema.md +5 -3
- package/docs/configure.md +13 -7
- package/docs/distribution-homebrew.md +3 -2
- package/examples/full-example/justfile +2 -5
- package/index.d.ts +10 -1
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +1 -3
- package/src/builtins/completion-bash.ts +6 -27
- package/src/builtins/completion-group.ts +1 -2
- package/src/builtins/completion-simulate-shared.ts +2 -10
- package/src/builtins/completion-zsh.ts +7 -33
- package/src/builtins/config.test.ts +28 -0
- package/src/builtins/config.ts +40 -10
- package/src/builtins/configure-copy.ts +3 -6
- package/src/builtins/dispatch.ts +2 -10
- package/src/builtins/export.ts +1 -3
- package/src/builtins/presentation.ts +2 -7
- package/src/builtins/registry.ts +1 -5
- package/src/cli-tool/create.test.ts +3 -6
- package/src/cli-tool/create.ts +5 -22
- package/src/cli-tool/post-create.ts +1 -3
- package/src/cli-tool/program.ts +1 -2
- package/src/cli-tool/run-create.ts +2 -8
- package/src/cli.ts +6 -27
- package/src/config/bindings.test.ts +72 -0
- package/src/config/bindings.ts +115 -0
- package/src/config/bootstrap.ts +80 -65
- package/src/config/context.test.ts +45 -0
- package/src/config/context.ts +74 -14
- package/src/config/entry.ts +1 -3
- package/src/config/file.test.ts +46 -3
- package/src/config/file.ts +59 -19
- package/src/config/resolve.ts +1 -4
- package/src/config/schema.ts +1 -3
- package/src/config/validate.test.ts +3 -11
- package/src/config/validate.ts +30 -30
- package/src/config.integration.test.ts +1 -5
- package/src/configure/configure.test.ts +19 -5
- package/src/configure/index.ts +17 -15
- package/src/docs/api-guide.ts +1 -6
- package/src/docs/builtin.ts +1 -5
- package/src/docs/docs.test.ts +2 -6
- package/src/docs/mcp-guide.ts +3 -13
- package/src/docs/mcp-resources.test.ts +1 -3
- package/src/docs/mcp-resources.ts +1 -6
- package/src/docs/save.ts +2 -10
- package/src/formats.test.ts +1 -7
- package/src/formats.ts +1 -5
- package/src/headless.test.ts +5 -17
- package/src/help.ts +9 -51
- package/src/hidden-mcpb.test.ts +3 -12
- package/src/hidden.ts +1 -3
- package/src/install/binary-placement.test.ts +2 -6
- package/src/install/binary-placement.ts +1 -4
- package/src/install/gh-release-update.ts +5 -22
- package/src/install/mcp-codex.ts +1 -2
- package/src/install/mcp-config.ts +2 -12
- package/src/install/mcp-opencode.test.ts +1 -3
- package/src/install/mcp-opencode.ts +3 -15
- package/src/install/plan.ts +2 -10
- package/src/install/status.ts +3 -5
- package/src/install/target-base.ts +2 -11
- package/src/install/target-detect.ts +1 -5
- package/src/install/target-effective.ts +3 -13
- package/src/install/target-mcp-cli.ts +4 -26
- package/src/install/target-mcp-json.ts +3 -14
- package/src/install/target-plan-build.ts +1 -5
- package/src/install/target-registry.ts +13 -21
- package/src/install/target-scope.ts +8 -25
- package/src/install/target-types.ts +1 -6
- package/src/install/targets/app.ts +2 -2
- package/src/install/targets/opencode-mcp.ts +1 -6
- package/src/install/targets.test.ts +3 -10
- package/src/install/uninstall.ts +2 -9
- package/src/invoke.test.ts +1 -3
- package/src/mcp/bundle.ts +4 -19
- package/src/mcp/claude.test.ts +3 -3
- package/src/mcp/claude.ts +4 -17
- package/src/mcp/env.ts +1 -3
- package/src/mcp/server.ts +3 -12
- package/src/mcp/tools.ts +2 -11
- package/src/mcp.integration.test.ts +4 -10
- package/src/parse.test.ts +9 -34
- package/src/parse.ts +10 -44
- package/src/schema.ts +2 -10
- package/src/skill/hint.ts +1 -5
- package/src/skill/install.ts +1 -5
- package/src/test-fixtures.ts +1 -3
- package/src/validate.ts +29 -90
package/src/parse.test.ts
CHANGED
|
@@ -12,12 +12,7 @@ import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
|
12
12
|
import { cliHelpRender } from "./help.ts";
|
|
13
13
|
import { Cli, CliFallbackMode, CliOptionKind, type CliProgram } from "./index.ts";
|
|
14
14
|
import { applyShellEnv } from "./mcp/env.ts";
|
|
15
|
-
import {
|
|
16
|
-
allMcpResources,
|
|
17
|
-
collectMcpTools,
|
|
18
|
-
mcpToolCallToArgv,
|
|
19
|
-
resolveMcpSchemaUri,
|
|
20
|
-
} from "./mcp/tools.ts";
|
|
15
|
+
import { allMcpResources, collectMcpTools, mcpToolCallToArgv, resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
21
16
|
import { ParseKind, parse, postParseValidate } from "./parse.ts";
|
|
22
17
|
import { cliSchemaJson } from "./schema.ts";
|
|
23
18
|
import { generatePluginSkillBundle, generateSkillBundle } from "./skill/generate.ts";
|
|
@@ -338,10 +333,7 @@ test("trailing options include parent-scoped flags", () => {
|
|
|
338
333
|
],
|
|
339
334
|
});
|
|
340
335
|
cliValidateProgram(root);
|
|
341
|
-
const pr = postParseValidate(
|
|
342
|
-
root,
|
|
343
|
-
parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]),
|
|
344
|
-
);
|
|
336
|
+
const pr = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]));
|
|
345
337
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
346
338
|
expect(pr.path).toEqual(["group", "leaf"]);
|
|
347
339
|
expect(pr.args).toEqual(["./file"]);
|
|
@@ -415,10 +407,7 @@ test("stops parsing options at --", () => {
|
|
|
415
407
|
],
|
|
416
408
|
});
|
|
417
409
|
cliValidateProgram(root);
|
|
418
|
-
const pr = postParseValidate(
|
|
419
|
-
root,
|
|
420
|
-
parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]),
|
|
421
|
-
);
|
|
410
|
+
const pr = postParseValidate(root, parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]));
|
|
422
411
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
423
412
|
expect(pr.opts.name).toBe("pat");
|
|
424
413
|
expect(pr.args).toEqual(["--name", "bob", "-x"]);
|
|
@@ -505,9 +494,7 @@ test("presence option cannot be required", () => {
|
|
|
505
494
|
test("leaf completion help prints correctly", async () => {
|
|
506
495
|
// Test the fix where `completion zsh -h` on a leaf root was incorrectly ignored.
|
|
507
496
|
// We run this as a subprocess so we don't accidentally exit the test runner.
|
|
508
|
-
const { stdout, stderr, exitCode } = await $`bun run examples/minimal.ts completion zsh -h
|
|
509
|
-
.nothrow()
|
|
510
|
-
.quiet();
|
|
497
|
+
const { stdout, stderr, exitCode } = await $`bun run examples/minimal.ts completion zsh -h`.nothrow().quiet();
|
|
511
498
|
const out = stdout.toString();
|
|
512
499
|
expect(exitCode).toBe(0);
|
|
513
500
|
expect(out).toContain("Show help for this command.");
|
|
@@ -517,9 +504,7 @@ test("leaf completion help prints correctly", async () => {
|
|
|
517
504
|
|
|
518
505
|
/** Docs schema exports JSON for nested CLIs. */
|
|
519
506
|
test("docs schema exports JSON for nested CLIs", async () => {
|
|
520
|
-
const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs schema
|
|
521
|
-
.nothrow()
|
|
522
|
-
.quiet();
|
|
507
|
+
const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs schema`.nothrow().quiet();
|
|
523
508
|
expect(exitCode).toBe(0);
|
|
524
509
|
expect(stderr.toString()).toBe("");
|
|
525
510
|
|
|
@@ -543,11 +528,7 @@ test("docs schema exports JSON for leaf roots", async () => {
|
|
|
543
528
|
expect(schema.key).toBe("minimal.ts");
|
|
544
529
|
expect(schema.positionals[0].name).toBe("name");
|
|
545
530
|
expect(schema.options[0].name).toBe("verbose");
|
|
546
|
-
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual([
|
|
547
|
-
"version",
|
|
548
|
-
"configure",
|
|
549
|
-
"docs",
|
|
550
|
-
]);
|
|
531
|
+
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["version", "configure", "docs"]);
|
|
551
532
|
});
|
|
552
533
|
|
|
553
534
|
test("version builtin prints program version", async () => {
|
|
@@ -956,9 +937,7 @@ test("Enum completions list choices in bash script", () => {
|
|
|
956
937
|
{
|
|
957
938
|
key: "run",
|
|
958
939
|
description: "",
|
|
959
|
-
options: [
|
|
960
|
-
{ name: "mode", description: "m", kind: CliOptionKind.Enum, choices: ["dev", "prod"] },
|
|
961
|
-
],
|
|
940
|
+
options: [{ name: "mode", description: "m", kind: CliOptionKind.Enum, choices: ["dev", "prod"] }],
|
|
962
941
|
handler: () => {},
|
|
963
942
|
},
|
|
964
943
|
],
|
|
@@ -1047,9 +1026,7 @@ test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
|
|
|
1047
1026
|
},
|
|
1048
1027
|
],
|
|
1049
1028
|
});
|
|
1050
|
-
expect(() => cliValidateProgram(root)).toThrow(
|
|
1051
|
-
/fallbackCommand 'missing' is not a child of 'docs'/,
|
|
1052
|
-
);
|
|
1029
|
+
expect(() => cliValidateProgram(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/);
|
|
1053
1030
|
});
|
|
1054
1031
|
|
|
1055
1032
|
test("cliValidateProgram accepts nested fallbackCommand when child exists", () => {
|
|
@@ -1279,9 +1256,7 @@ test("cliSkillInstall claude target uses .claude/skills", () => {
|
|
|
1279
1256
|
try {
|
|
1280
1257
|
const files = cliSkillInstall(nestedMcpFixture, "claude", { rimraf: true });
|
|
1281
1258
|
expect(files.some((f) => f.includes(".claude/skills/nested_ts/"))).toBe(true);
|
|
1282
|
-
expect(readFileSync(join(cwd, ".claude", "skills", "nested_ts", "SKILL.md"), "utf8")).toContain(
|
|
1283
|
-
"Claude Code",
|
|
1284
|
-
);
|
|
1259
|
+
expect(readFileSync(join(cwd, ".claude", "skills", "nested_ts", "SKILL.md"), "utf8")).toContain("Claude Code");
|
|
1285
1260
|
} finally {
|
|
1286
1261
|
process.chdir(prev);
|
|
1287
1262
|
rmSync(cwd, { recursive: true, force: true });
|
package/src/parse.ts
CHANGED
|
@@ -204,8 +204,7 @@ function consumeOptions(
|
|
|
204
204
|
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
205
205
|
nextIndex: idx,
|
|
206
206
|
};
|
|
207
|
-
if (err)
|
|
208
|
-
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
207
|
+
if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
209
208
|
} else {
|
|
210
209
|
const err = consumeShort(tok);
|
|
211
210
|
if (err === "")
|
|
@@ -213,8 +212,7 @@ function consumeOptions(
|
|
|
213
212
|
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
214
213
|
nextIndex: idx,
|
|
215
214
|
};
|
|
216
|
-
if (err)
|
|
217
|
-
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
215
|
+
if (err) return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
218
216
|
}
|
|
219
217
|
}
|
|
220
218
|
|
|
@@ -317,11 +315,7 @@ function finishLeaf(
|
|
|
317
315
|
}
|
|
318
316
|
}
|
|
319
317
|
if (count < argMin) {
|
|
320
|
-
return errorResult(
|
|
321
|
-
`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`,
|
|
322
|
-
path,
|
|
323
|
-
[],
|
|
324
|
-
);
|
|
318
|
+
return errorResult(`Expected at least ${argMin} argument(s) for ${p.name}, got ${count}`, path, []);
|
|
325
319
|
}
|
|
326
320
|
}
|
|
327
321
|
|
|
@@ -360,11 +354,7 @@ function finishLeaf(
|
|
|
360
354
|
// ── Main Parser ───────────────────────────────────────────────────────────────
|
|
361
355
|
|
|
362
356
|
/** Builds a user-error parse result; `path` defaults to `errorHelpPath`. */
|
|
363
|
-
function errorResult(
|
|
364
|
-
errorMsg: string,
|
|
365
|
-
errorHelpPath: string[] = [],
|
|
366
|
-
path: string[] = errorHelpPath,
|
|
367
|
-
): ParseResult {
|
|
357
|
+
function errorResult(errorMsg: string, errorHelpPath: string[] = [], path: string[] = errorHelpPath): ParseResult {
|
|
368
358
|
return {
|
|
369
359
|
kind: ParseKind.Error,
|
|
370
360
|
path,
|
|
@@ -466,9 +456,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
466
456
|
node = findChild(root.commands, cmdName);
|
|
467
457
|
if (!node) {
|
|
468
458
|
return errorResult(
|
|
469
|
-
forcePositionals
|
|
470
|
-
? `Expected subcommand but got positional: ${cmdName}`
|
|
471
|
-
: `Unknown command: ${cmdName}`,
|
|
459
|
+
forcePositionals ? `Expected subcommand but got positional: ${cmdName}` : `Unknown command: ${cmdName}`,
|
|
472
460
|
path,
|
|
473
461
|
[],
|
|
474
462
|
);
|
|
@@ -504,10 +492,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
504
492
|
if (isCliRouter(current) && current.commands.length > 0) {
|
|
505
493
|
const fb = current.fallbackCommand;
|
|
506
494
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
507
|
-
if (
|
|
508
|
-
fb !== undefined &&
|
|
509
|
-
(fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)
|
|
510
|
-
) {
|
|
495
|
+
if (fb !== undefined && (fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)) {
|
|
511
496
|
const fbNode = findChild(current.commands, fb);
|
|
512
497
|
if (fbNode) {
|
|
513
498
|
path.push(fb);
|
|
@@ -520,15 +505,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
520
505
|
if (!isCliLeaf(current)) {
|
|
521
506
|
return helpResult(path, false);
|
|
522
507
|
}
|
|
523
|
-
return finishLeaf(
|
|
524
|
-
current,
|
|
525
|
-
i,
|
|
526
|
-
argv,
|
|
527
|
-
path,
|
|
528
|
-
opts,
|
|
529
|
-
collectOptionDefs(root, path),
|
|
530
|
-
forcePositionals,
|
|
531
|
-
);
|
|
508
|
+
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
532
509
|
}
|
|
533
510
|
|
|
534
511
|
const tok = argv[i];
|
|
@@ -550,8 +527,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
550
527
|
const fb = current.fallbackCommand;
|
|
551
528
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
552
529
|
const canRouteUnknown =
|
|
553
|
-
fb !== undefined &&
|
|
554
|
-
(fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
530
|
+
fb !== undefined && (fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
555
531
|
|
|
556
532
|
if (canRouteUnknown && fb !== undefined) {
|
|
557
533
|
const fbNode = findChild(current.commands, fb);
|
|
@@ -563,9 +539,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
563
539
|
}
|
|
564
540
|
|
|
565
541
|
return errorResult(
|
|
566
|
-
forcePositionals
|
|
567
|
-
? `Expected subcommand but got positional: ${tok}`
|
|
568
|
-
: `Unknown subcommand: ${tok}`,
|
|
542
|
+
forcePositionals ? `Expected subcommand but got positional: ${tok}` : `Unknown subcommand: ${tok}`,
|
|
569
543
|
path,
|
|
570
544
|
);
|
|
571
545
|
}
|
|
@@ -573,15 +547,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
573
547
|
if (!isCliLeaf(current)) {
|
|
574
548
|
return helpResult(path, false);
|
|
575
549
|
}
|
|
576
|
-
return finishLeaf(
|
|
577
|
-
current,
|
|
578
|
-
i,
|
|
579
|
-
argv,
|
|
580
|
-
path,
|
|
581
|
-
opts,
|
|
582
|
-
collectOptionDefs(root, path),
|
|
583
|
-
forcePositionals,
|
|
584
|
-
);
|
|
550
|
+
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
585
551
|
}
|
|
586
552
|
}
|
|
587
553
|
|
package/src/schema.ts
CHANGED
|
@@ -5,13 +5,7 @@ This module serializes the CLI schema tree to JSON for machine-readable introspe
|
|
|
5
5
|
import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
|
|
6
6
|
import { cliResolveNotes } from "./help.ts";
|
|
7
7
|
import { visibleOptions } from "./hidden.ts";
|
|
8
|
-
import {
|
|
9
|
-
type CliNode,
|
|
10
|
-
type CliProgram,
|
|
11
|
-
isCliLeaf,
|
|
12
|
-
isCliRouter,
|
|
13
|
-
leafOutputSchema,
|
|
14
|
-
} from "./types.ts";
|
|
8
|
+
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter, leafOutputSchema } from "./types.ts";
|
|
15
9
|
|
|
16
10
|
const RESERVED = new Set(["completion", "configure", "docs", "mcp", "version"]);
|
|
17
11
|
|
|
@@ -55,9 +49,7 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
|
|
|
55
49
|
|
|
56
50
|
const children = isCliRouter(cmd) ? cmd.commands.filter((ch) => !RESERVED.has(ch.key)) : [];
|
|
57
51
|
if (children.length > 0) {
|
|
58
|
-
out.commands = children
|
|
59
|
-
.map((ch) => exportCommand(ch, root))
|
|
60
|
-
.filter((ch): ch is CliSchemaExport => ch !== null);
|
|
52
|
+
out.commands = children.map((ch) => exportCommand(ch, root)).filter((ch): ch is CliSchemaExport => ch !== null);
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
return out;
|
package/src/skill/hint.ts
CHANGED
|
@@ -9,11 +9,7 @@ export function generatedFileHtmlComment(source: string): string {
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/** Prepends a hint, or inserts after frontmatter when requested. */
|
|
12
|
-
export function insertGeneratedHint(
|
|
13
|
-
content: string,
|
|
14
|
-
hint: string,
|
|
15
|
-
options?: { afterFrontmatter?: boolean },
|
|
16
|
-
): string {
|
|
12
|
+
export function insertGeneratedHint(content: string, hint: string, options?: { afterFrontmatter?: boolean }): string {
|
|
17
13
|
if (options?.afterFrontmatter) {
|
|
18
14
|
const match = content.match(MARKDOWN_FRONTMATTER_RE);
|
|
19
15
|
if (match) {
|
package/src/skill/install.ts
CHANGED
|
@@ -30,11 +30,7 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
|
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
33
|
-
export function cliSkillInstall(
|
|
34
|
-
root: CliProgram,
|
|
35
|
-
target: SkillTarget,
|
|
36
|
-
opts: SkillInstallOpts,
|
|
37
|
-
): string[] {
|
|
33
|
+
export function cliSkillInstall(root: CliProgram, target: SkillTarget, opts: SkillInstallOpts): string[] {
|
|
38
34
|
const bundle = generateSkillBundle(root, target);
|
|
39
35
|
const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
|
|
40
36
|
const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
|
package/src/test-fixtures.ts
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import type { CliProgram } from "./types.ts";
|
|
2
2
|
import { CliFallbackMode, CliOptionKind } from "./types.ts";
|
|
3
3
|
|
|
4
|
-
export function testProgram(
|
|
5
|
-
prog: Record<string, unknown> & { key: string; description: string },
|
|
6
|
-
): CliProgram {
|
|
4
|
+
export function testProgram(prog: Record<string, unknown> & { key: string; description: string }): CliProgram {
|
|
7
5
|
return { version: "0.0.0", ...prog } as CliProgram;
|
|
8
6
|
}
|
|
9
7
|
|
package/src/validate.ts
CHANGED
|
@@ -29,15 +29,11 @@ function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
|
|
|
29
29
|
}
|
|
30
30
|
for (const reserved of DOCS_BUILTIN_TOPIC_KEYS) {
|
|
31
31
|
if (reserved in docs.topics) {
|
|
32
|
-
throw new CliSchemaValidationError(
|
|
33
|
-
`docs.topics key '${reserved}' is reserved for the docs built-in`,
|
|
34
|
-
);
|
|
32
|
+
throw new CliSchemaValidationError(`docs.topics key '${reserved}' is reserved for the docs built-in`);
|
|
35
33
|
}
|
|
36
34
|
}
|
|
37
35
|
if (docs.defaultTopic !== undefined && !(docs.defaultTopic in docs.topics)) {
|
|
38
|
-
throw new CliSchemaValidationError(
|
|
39
|
-
`docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`,
|
|
40
|
-
);
|
|
36
|
+
throw new CliSchemaValidationError(`docs.defaultTopic '${docs.defaultTopic}' is not a key in docs.topics`);
|
|
41
37
|
}
|
|
42
38
|
for (const key of keys) {
|
|
43
39
|
const text = docs.topics[key]?.text;
|
|
@@ -57,18 +53,14 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
|
|
|
57
53
|
const envNames = new Set<string>();
|
|
58
54
|
for (const [key, entry] of Object.entries(entries)) {
|
|
59
55
|
if (key.length === 0) {
|
|
60
|
-
throw new CliSchemaValidationError(
|
|
61
|
-
"program.appConfig.entries keys must be non-empty strings",
|
|
62
|
-
);
|
|
56
|
+
throw new CliSchemaValidationError("program.appConfig.entries keys must be non-empty strings");
|
|
63
57
|
}
|
|
64
58
|
if (entry === undefined || typeof entry !== "object") {
|
|
65
59
|
throw new CliSchemaValidationError(`program.appConfig.entries['${key}'] must be an object`);
|
|
66
60
|
}
|
|
67
61
|
const description = entry.description;
|
|
68
62
|
if (typeof description !== "string" || description.trim().length === 0) {
|
|
69
|
-
throw new CliSchemaValidationError(
|
|
70
|
-
`program.appConfig.entries['${key}'].description must be a non-empty string`,
|
|
71
|
-
);
|
|
63
|
+
throw new CliSchemaValidationError(`program.appConfig.entries['${key}'].description must be a non-empty string`);
|
|
72
64
|
}
|
|
73
65
|
if (entry.env !== undefined) {
|
|
74
66
|
if (typeof entry.env !== "string" || entry.env.length === 0) {
|
|
@@ -82,9 +74,7 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
|
|
|
82
74
|
envNames.add(entry.env);
|
|
83
75
|
}
|
|
84
76
|
if (entry.resolve !== undefined && typeof entry.resolve !== "function") {
|
|
85
|
-
throw new CliSchemaValidationError(
|
|
86
|
-
`program.appConfig.entries['${key}'].resolve must be a function when set`,
|
|
87
|
-
);
|
|
77
|
+
throw new CliSchemaValidationError(`program.appConfig.entries['${key}'].resolve must be a function when set`);
|
|
88
78
|
}
|
|
89
79
|
}
|
|
90
80
|
|
|
@@ -98,9 +88,7 @@ function validateConfigBlock(appConfigBlock: import("./types.ts").CliAppConfig):
|
|
|
98
88
|
const properties = jsonSchema.properties;
|
|
99
89
|
if (properties !== undefined) {
|
|
100
90
|
if (typeof properties !== "object" || properties === null || Array.isArray(properties)) {
|
|
101
|
-
throw new CliSchemaValidationError(
|
|
102
|
-
"program.appConfig.jsonSchema.properties must be an object when set",
|
|
103
|
-
);
|
|
91
|
+
throw new CliSchemaValidationError("program.appConfig.jsonSchema.properties must be an object when set");
|
|
104
92
|
}
|
|
105
93
|
for (const key of Object.keys(entries)) {
|
|
106
94
|
if (!(key in properties)) {
|
|
@@ -133,9 +121,7 @@ function validateConfigureConfig(program: CliProgram): void {
|
|
|
133
121
|
if (!configure) return;
|
|
134
122
|
|
|
135
123
|
if ("prefix" in configure) {
|
|
136
|
-
throw new CliSchemaValidationError(
|
|
137
|
-
"configure.prefix removed; app binary installs via Homebrew",
|
|
138
|
-
);
|
|
124
|
+
throw new CliSchemaValidationError("configure.prefix removed; app binary installs via Homebrew");
|
|
139
125
|
}
|
|
140
126
|
|
|
141
127
|
if (!configure.targets) return;
|
|
@@ -178,10 +164,7 @@ function validateConfigureConfig(program: CliProgram): void {
|
|
|
178
164
|
}
|
|
179
165
|
|
|
180
166
|
for (const mcpKey of MCP_KEYS) {
|
|
181
|
-
if (
|
|
182
|
-
!mcpServerRequiredForArtifact(mcpKey, mcpEnabled) &&
|
|
183
|
-
installTargetExplicitTruthy(targets[mcpKey])
|
|
184
|
-
) {
|
|
167
|
+
if (!mcpServerRequiredForArtifact(mcpKey, mcpEnabled) && installTargetExplicitTruthy(targets[mcpKey])) {
|
|
185
168
|
throw new CliSchemaValidationError(`configure.targets.${mcpKey} requires mcpServer.enabled`);
|
|
186
169
|
}
|
|
187
170
|
}
|
|
@@ -194,15 +177,11 @@ export function cliValidateProgram(program: CliProgram): void {
|
|
|
194
177
|
}
|
|
195
178
|
|
|
196
179
|
if (program.mcpServer !== undefined && program.mcpServer.enabled !== true) {
|
|
197
|
-
throw new CliSchemaValidationError(
|
|
198
|
-
"mcpServer requires enabled: true; omit mcpServer to disable MCP",
|
|
199
|
-
);
|
|
180
|
+
throw new CliSchemaValidationError("mcpServer requires enabled: true; omit mcpServer to disable MCP");
|
|
200
181
|
}
|
|
201
182
|
|
|
202
183
|
if (program.docs !== undefined && program.docs.enabled !== true) {
|
|
203
|
-
throw new CliSchemaValidationError(
|
|
204
|
-
"docs requires enabled: true; omit docs to disable bundled documentation",
|
|
205
|
-
);
|
|
184
|
+
throw new CliSchemaValidationError("docs requires enabled: true; omit docs to disable bundled documentation");
|
|
206
185
|
}
|
|
207
186
|
|
|
208
187
|
if (program.docs?.enabled === true) {
|
|
@@ -235,24 +214,16 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
235
214
|
if (!isRoot) {
|
|
236
215
|
const rogue = node as CliProgram;
|
|
237
216
|
if (rogue.mcpServer !== undefined) {
|
|
238
|
-
throw new CliSchemaValidationError(
|
|
239
|
-
`mcpServer is only supported on the program root (not on ${node.key})`,
|
|
240
|
-
);
|
|
217
|
+
throw new CliSchemaValidationError(`mcpServer is only supported on the program root (not on ${node.key})`);
|
|
241
218
|
}
|
|
242
219
|
if (rogue.configure !== undefined) {
|
|
243
|
-
throw new CliSchemaValidationError(
|
|
244
|
-
`configure is only supported on the program root (not on ${node.key})`,
|
|
245
|
-
);
|
|
220
|
+
throw new CliSchemaValidationError(`configure is only supported on the program root (not on ${node.key})`);
|
|
246
221
|
}
|
|
247
222
|
if (rogue.docs !== undefined) {
|
|
248
|
-
throw new CliSchemaValidationError(
|
|
249
|
-
`docs is only supported on the program root (not on ${node.key})`,
|
|
250
|
-
);
|
|
223
|
+
throw new CliSchemaValidationError(`docs is only supported on the program root (not on ${node.key})`);
|
|
251
224
|
}
|
|
252
225
|
if (rogue.appConfig !== undefined) {
|
|
253
|
-
throw new CliSchemaValidationError(
|
|
254
|
-
`appConfig is only supported on the program root (not on ${node.key})`,
|
|
255
|
-
);
|
|
226
|
+
throw new CliSchemaValidationError(`appConfig is only supported on the program root (not on ${node.key})`);
|
|
256
227
|
}
|
|
257
228
|
}
|
|
258
229
|
|
|
@@ -266,20 +237,13 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
266
237
|
throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
|
|
267
238
|
}
|
|
268
239
|
const resolved = outputSchema ?? legacyOutputSchema;
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
(typeof resolved !== "object" || resolved === null || Array.isArray(resolved))
|
|
272
|
-
) {
|
|
273
|
-
throw new CliSchemaValidationError(
|
|
274
|
-
"outputSchema must be a JSON Schema object (not null or an array)",
|
|
275
|
-
);
|
|
240
|
+
if (resolved !== undefined && (typeof resolved !== "object" || resolved === null || Array.isArray(resolved))) {
|
|
241
|
+
throw new CliSchemaValidationError("outputSchema must be a JSON Schema object (not null or an array)");
|
|
276
242
|
}
|
|
277
243
|
} else {
|
|
278
244
|
const rogue = node as unknown as CliLeaf;
|
|
279
245
|
if (rogue.mcpTool !== undefined) {
|
|
280
|
-
throw new CliSchemaValidationError(
|
|
281
|
-
`mcpTool is only supported on leaf commands (not on ${node.key})`,
|
|
282
|
-
);
|
|
246
|
+
throw new CliSchemaValidationError(`mcpTool is only supported on leaf commands (not on ${node.key})`);
|
|
283
247
|
}
|
|
284
248
|
}
|
|
285
249
|
|
|
@@ -290,9 +254,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
290
254
|
for (const uri of uris) {
|
|
291
255
|
if (reserved.has(uri)) {
|
|
292
256
|
const kind = uri === schemaUri ? "built-in schema resource" : "auto docs topic resource";
|
|
293
|
-
throw new CliSchemaValidationError(
|
|
294
|
-
`mcpServer.resources URI '${uri}' conflicts with ${kind}`,
|
|
295
|
-
);
|
|
257
|
+
throw new CliSchemaValidationError(`mcpServer.resources URI '${uri}' conflicts with ${kind}`);
|
|
296
258
|
}
|
|
297
259
|
}
|
|
298
260
|
if (new Set(uris).size !== uris.length) {
|
|
@@ -316,9 +278,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
316
278
|
if (node.fallbackCommand !== undefined) {
|
|
317
279
|
const valid = node.commands.find((c) => c.key === node.fallbackCommand);
|
|
318
280
|
if (!valid) {
|
|
319
|
-
throw new CliSchemaValidationError(
|
|
320
|
-
`fallbackCommand '${node.fallbackCommand}' is not a child of '${node.key}'`,
|
|
321
|
-
);
|
|
281
|
+
throw new CliSchemaValidationError(`fallbackCommand '${node.fallbackCommand}' is not a child of '${node.key}'`);
|
|
322
282
|
}
|
|
323
283
|
}
|
|
324
284
|
|
|
@@ -336,21 +296,15 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
336
296
|
const seenShorts = new Set<string>();
|
|
337
297
|
for (const opt of options) {
|
|
338
298
|
if (opt.required && opt.kind === CliOptionKind.Presence) {
|
|
339
|
-
throw new CliSchemaValidationError(
|
|
340
|
-
`Presence option cannot be required: ${scopeKey}/${opt.name}`,
|
|
341
|
-
);
|
|
299
|
+
throw new CliSchemaValidationError(`Presence option cannot be required: ${scopeKey}/${opt.name}`);
|
|
342
300
|
}
|
|
343
301
|
|
|
344
302
|
if (opt.shortName !== undefined) {
|
|
345
303
|
if (opt.shortName === "h") {
|
|
346
|
-
throw new CliSchemaValidationError(
|
|
347
|
-
`Short alias -h is reserved for help: ${scopeKey}/${opt.name}`,
|
|
348
|
-
);
|
|
304
|
+
throw new CliSchemaValidationError(`Short alias -h is reserved for help: ${scopeKey}/${opt.name}`);
|
|
349
305
|
}
|
|
350
306
|
if (seenShorts.has(opt.shortName)) {
|
|
351
|
-
throw new CliSchemaValidationError(
|
|
352
|
-
`Duplicate short alias -${opt.shortName} in scope ${scopeKey}`,
|
|
353
|
-
);
|
|
307
|
+
throw new CliSchemaValidationError(`Duplicate short alias -${opt.shortName} in scope ${scopeKey}`);
|
|
354
308
|
}
|
|
355
309
|
seenShorts.add(opt.shortName);
|
|
356
310
|
}
|
|
@@ -362,9 +316,7 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
362
316
|
);
|
|
363
317
|
}
|
|
364
318
|
if (new Set(opt.choices).size !== opt.choices.length) {
|
|
365
|
-
throw new CliSchemaValidationError(
|
|
366
|
-
`Option '${opt.name}' on '${scopeKey}': Enum choices must be distinct`,
|
|
367
|
-
);
|
|
319
|
+
throw new CliSchemaValidationError(`Option '${opt.name}' on '${scopeKey}': Enum choices must be distinct`);
|
|
368
320
|
}
|
|
369
321
|
for (const choice of opt.choices) {
|
|
370
322
|
if (choice.length === 0) {
|
|
@@ -374,9 +326,7 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
374
326
|
}
|
|
375
327
|
}
|
|
376
328
|
} else if (opt.choices !== undefined) {
|
|
377
|
-
throw new CliSchemaValidationError(
|
|
378
|
-
`Option '${opt.name}' on '${scopeKey}': choices is only valid for Enum kind`,
|
|
379
|
-
);
|
|
329
|
+
throw new CliSchemaValidationError(`Option '${opt.name}' on '${scopeKey}': choices is only valid for Enum kind`);
|
|
380
330
|
}
|
|
381
331
|
|
|
382
332
|
if (opt.format !== undefined || opt.pattern !== undefined || opt.default !== undefined) {
|
|
@@ -398,9 +348,7 @@ function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts")
|
|
|
398
348
|
}
|
|
399
349
|
|
|
400
350
|
if (opt.format !== undefined && opt.pattern !== undefined) {
|
|
401
|
-
throw new CliSchemaValidationError(
|
|
402
|
-
`Option ${label}: format and pattern are mutually exclusive`,
|
|
403
|
-
);
|
|
351
|
+
throw new CliSchemaValidationError(`Option ${label}: format and pattern are mutually exclusive`);
|
|
404
352
|
}
|
|
405
353
|
|
|
406
354
|
if (opt.format !== undefined) {
|
|
@@ -433,15 +381,10 @@ function validateOptionValueMetadata(scopeKey: string, opt: import("./types.ts")
|
|
|
433
381
|
}
|
|
434
382
|
}
|
|
435
383
|
|
|
436
|
-
function validatePositionals(
|
|
437
|
-
scopeKey: string,
|
|
438
|
-
positionals: import("./types.ts").CliPositional[],
|
|
439
|
-
): void {
|
|
384
|
+
function validatePositionals(scopeKey: string, positionals: import("./types.ts").CliPositional[]): void {
|
|
440
385
|
for (const p of positionals) {
|
|
441
386
|
if (p.argMin !== undefined && p.argMin < 0) {
|
|
442
|
-
throw new CliSchemaValidationError(
|
|
443
|
-
`argMin must be >= 0 for positional ${scopeKey}/${p.name}`,
|
|
444
|
-
);
|
|
387
|
+
throw new CliSchemaValidationError(`argMin must be >= 0 for positional ${scopeKey}/${p.name}`);
|
|
445
388
|
}
|
|
446
389
|
if (p.argMax !== undefined && p.argMax < 0) {
|
|
447
390
|
throw new CliSchemaValidationError(
|
|
@@ -450,9 +393,7 @@ function validatePositionals(
|
|
|
450
393
|
}
|
|
451
394
|
const { argMin = 1, argMax = 1 } = p;
|
|
452
395
|
if (argMax > 0 && argMin > argMax) {
|
|
453
|
-
throw new CliSchemaValidationError(
|
|
454
|
-
`argMin must not exceed argMax for positional ${scopeKey}/${p.name}`,
|
|
455
|
-
);
|
|
396
|
+
throw new CliSchemaValidationError(`argMin must not exceed argMax for positional ${scopeKey}/${p.name}`);
|
|
456
397
|
}
|
|
457
398
|
}
|
|
458
399
|
|
|
@@ -473,9 +414,7 @@ function validatePositionals(
|
|
|
473
414
|
}
|
|
474
415
|
const { argMax = 1 } = positional;
|
|
475
416
|
if (argMax === 0 && idx + 1 < positionals.length) {
|
|
476
|
-
throw new CliSchemaValidationError(
|
|
477
|
-
`Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,
|
|
478
|
-
);
|
|
417
|
+
throw new CliSchemaValidationError(`Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`);
|
|
479
418
|
}
|
|
480
419
|
}
|
|
481
420
|
}
|