argsbarg 4.1.1 → 5.0.2
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 +34 -1
- package/README.md +5 -5
- package/docs/README.md +3 -3
- package/docs/ai-skills.md +9 -9
- package/docs/bundled-docs.md +4 -4
- package/docs/cli-program.md +8 -8
- package/docs/config-schema.md +2 -2
- package/docs/configure.md +177 -0
- package/docs/developing.md +1 -1
- package/docs/distribution-homebrew.md +12 -9
- package/docs/mcp.md +9 -9
- package/examples/full-example/README.md +3 -3
- package/examples/full-example/justfile +6 -6
- package/examples/full-example/scripts/formula-shared.ts +3 -2
- package/examples/full-example/src/program.ts +3 -3
- package/examples/nested.ts +1 -1
- package/index.d.ts +23 -16
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +105 -61
- package/src/builtins/completion-group.ts +4 -6
- package/src/builtins/config.test.ts +8 -0
- package/src/builtins/configure-copy.ts +86 -0
- package/src/builtins/configure.ts +70 -0
- package/src/builtins/dispatch.ts +13 -33
- package/src/builtins/index.ts +1 -1
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/registry.ts +6 -6
- package/src/capabilities.ts +22 -13
- package/src/cli-tool/cli-smoke.test.ts +21 -3
- package/src/cli-tool/create.test.ts +36 -1
- package/src/cli-tool/create.ts +26 -4
- package/src/cli-tool/full-example-capabilities.test.ts +10 -2
- package/src/cli-tool/main.ts +0 -0
- package/src/cli-tool/program.ts +20 -5
- package/src/cli-tool/run-create.ts +8 -19
- package/src/config/bootstrap.ts +11 -9
- package/src/config/context.test.ts +8 -0
- package/src/config/file.test.ts +13 -1
- package/src/config/resolve.test.ts +18 -0
- package/src/config/resolve.ts +2 -2
- package/src/config/validate.test.ts +11 -0
- package/src/config.integration.test.ts +5 -0
- package/src/configure/configure.test.ts +170 -0
- package/src/configure/index.ts +298 -0
- package/src/configure/prompt.ts +47 -0
- package/src/docs/api-guide.test.ts +10 -0
- package/src/docs/builtin.ts +3 -5
- package/src/docs/docs.test.ts +32 -5
- package/src/docs/mcp-guide.ts +4 -4
- package/src/docs/mcp-resources.test.ts +10 -0
- package/src/formats.test.ts +9 -0
- package/src/headless.test.ts +10 -0
- package/src/hidden-mcpb.test.ts +22 -0
- package/src/index.ts +2 -2
- package/src/install/binary-placement.test.ts +14 -0
- package/src/install/gh-release-update.test.ts +9 -0
- package/src/install/install-validate.test.ts +15 -5
- package/src/install/mcp-codex.test.ts +7 -0
- package/src/install/mcp-openclaw.test.ts +6 -0
- package/src/install/mcp-opencode.test.ts +10 -0
- package/src/install/opts.ts +17 -0
- package/src/install/status.test.ts +9 -0
- package/src/install/target-effective.ts +8 -8
- package/src/install/target-scope.ts +11 -8
- package/src/install/targets/configure.ts +1 -1
- package/src/install/targets.test.ts +23 -4
- package/src/invoke.test.ts +15 -1
- package/src/mcp/claude.test.ts +10 -0
- package/src/mcp/env.test.ts +12 -0
- package/src/mcp/tools.ts +1 -1
- package/src/mcp/zip.test.ts +5 -0
- package/src/mcp.integration.test.ts +43 -4
- package/src/parse.test.ts +82 -13
- package/src/schema.ts +1 -9
- package/src/skill/hint.ts +2 -2
- package/src/types.ts +22 -14
- package/src/validate.ts +17 -17
- package/docs/install.md +0 -206
- package/src/builtins/install.ts +0 -106
- package/src/builtins/uninstall.ts +0 -80
- package/src/install/index.ts +0 -409
- package/src/install/install.test.ts +0 -317
package/src/parse.test.ts
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "./test-fixtures.ts";
|
|
32
32
|
import { cliValidateProgram } from "./validate.ts";
|
|
33
33
|
|
|
34
|
+
/** Tests that bundled short presence flags. */
|
|
34
35
|
test("bundled short presence flags", () => {
|
|
35
36
|
const root = testProgram({
|
|
36
37
|
key: "app",
|
|
@@ -64,6 +65,7 @@ test("bundled short presence flags", () => {
|
|
|
64
65
|
expect(pr.opts.b).toBe("1");
|
|
65
66
|
});
|
|
66
67
|
|
|
68
|
+
/** Tests that long option equals. */
|
|
67
69
|
test("long option equals", () => {
|
|
68
70
|
const root = testProgram({
|
|
69
71
|
key: "app",
|
|
@@ -89,6 +91,7 @@ test("long option equals", () => {
|
|
|
89
91
|
expect(pr.opts.name).toBe("pat");
|
|
90
92
|
});
|
|
91
93
|
|
|
94
|
+
/** Tests that fallback missing or unknown root flags. */
|
|
92
95
|
test("fallback missing or unknown root flags", () => {
|
|
93
96
|
const root = testProgram({
|
|
94
97
|
key: "app",
|
|
@@ -117,6 +120,7 @@ test("fallback missing or unknown root flags", () => {
|
|
|
117
120
|
expect(pr.opts.name).toBe("bob");
|
|
118
121
|
});
|
|
119
122
|
|
|
123
|
+
/** Tests that unknown command. */
|
|
120
124
|
test("unknown command", () => {
|
|
121
125
|
const root = testProgram({
|
|
122
126
|
key: "app",
|
|
@@ -129,6 +133,7 @@ test("unknown command", () => {
|
|
|
129
133
|
expect(pr.errorMsg).toContain("Unknown command");
|
|
130
134
|
});
|
|
131
135
|
|
|
136
|
+
/** Tests that implicit help empty. */
|
|
132
137
|
test("implicit help empty", () => {
|
|
133
138
|
const root = testProgram({
|
|
134
139
|
key: "app",
|
|
@@ -141,6 +146,7 @@ test("implicit help empty", () => {
|
|
|
141
146
|
expect(pr.helpExplicit).toBe(false);
|
|
142
147
|
});
|
|
143
148
|
|
|
149
|
+
/** Invalid number post validate. */
|
|
144
150
|
test("invalid number post validate", () => {
|
|
145
151
|
const root = testProgram({
|
|
146
152
|
key: "app",
|
|
@@ -167,6 +173,7 @@ test("invalid number post validate", () => {
|
|
|
167
173
|
expect(pr.errorMsg).toContain("Invalid number");
|
|
168
174
|
});
|
|
169
175
|
|
|
176
|
+
/** Supports scientific notation in numbers. */
|
|
170
177
|
test("supports scientific notation in numbers", () => {
|
|
171
178
|
const root = testProgram({
|
|
172
179
|
key: "app",
|
|
@@ -193,6 +200,7 @@ test("supports scientific notation in numbers", () => {
|
|
|
193
200
|
expect(Number(pr.opts.n)).toBe(12300);
|
|
194
201
|
});
|
|
195
202
|
|
|
203
|
+
/** Completion scripts contain app name. */
|
|
196
204
|
test("completion scripts contain app name", () => {
|
|
197
205
|
const root = testProgram({
|
|
198
206
|
key: "myapp",
|
|
@@ -210,6 +218,7 @@ test("completion scripts contain app name", () => {
|
|
|
210
218
|
expect(zsh).toContain("hello:Say hello.");
|
|
211
219
|
});
|
|
212
220
|
|
|
221
|
+
/** Completion scripts do not emit invalid bash substitutions. */
|
|
213
222
|
test("completion scripts do not emit invalid bash substitutions", () => {
|
|
214
223
|
const root = testProgram({
|
|
215
224
|
key: "app",
|
|
@@ -221,6 +230,7 @@ test("completion scripts do not emit invalid bash substitutions", () => {
|
|
|
221
230
|
expect(bash).not.toContain("${${");
|
|
222
231
|
});
|
|
223
232
|
|
|
233
|
+
/** Completion scripts escape shell-sensitive command text in zsh. */
|
|
224
234
|
test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
225
235
|
const root = testProgram({
|
|
226
236
|
key: "app",
|
|
@@ -238,6 +248,7 @@ test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
|
238
248
|
expect(zsh).toContain("quote'\\''cmd:Say '\\''hello'\\'' and keep going.");
|
|
239
249
|
});
|
|
240
250
|
|
|
251
|
+
/** Completion scripts keep dotted app names in registration names. */
|
|
241
252
|
test("completion scripts keep dotted app names in registration names", () => {
|
|
242
253
|
const root = testProgram({
|
|
243
254
|
key: "minimal.ts",
|
|
@@ -253,6 +264,7 @@ test("completion scripts keep dotted app names in registration names", () => {
|
|
|
253
264
|
expect(zsh).toContain("compdef _minimal_ts minimal.ts");
|
|
254
265
|
});
|
|
255
266
|
|
|
267
|
+
/** Tests that trailing options after bounded positionals. */
|
|
256
268
|
test("trailing options after bounded positionals", () => {
|
|
257
269
|
const root = testProgram({
|
|
258
270
|
key: "app",
|
|
@@ -286,6 +298,7 @@ test("trailing options after bounded positionals", () => {
|
|
|
286
298
|
expect(pr.opts.verbose).toBe("1");
|
|
287
299
|
});
|
|
288
300
|
|
|
301
|
+
/** Tests that trailing options include parent-scoped flags. */
|
|
289
302
|
test("trailing options include parent-scoped flags", () => {
|
|
290
303
|
const root = testProgram({
|
|
291
304
|
key: "app",
|
|
@@ -338,6 +351,7 @@ test("trailing options include parent-scoped flags", () => {
|
|
|
338
351
|
expect(pr.opts.json).toBe("1");
|
|
339
352
|
});
|
|
340
353
|
|
|
354
|
+
/** Varargs tail parses trailing options. */
|
|
341
355
|
test("varargs tail parses trailing options", () => {
|
|
342
356
|
const root = testProgram({
|
|
343
357
|
key: "app",
|
|
@@ -373,6 +387,7 @@ test("varargs tail parses trailing options", () => {
|
|
|
373
387
|
expect(pr.opts.json).toBe("1");
|
|
374
388
|
});
|
|
375
389
|
|
|
390
|
+
/** Stops parsing options at --. */
|
|
376
391
|
test("stops parsing options at --", () => {
|
|
377
392
|
const root = testProgram({
|
|
378
393
|
key: "app",
|
|
@@ -411,6 +426,7 @@ test("stops parsing options at --", () => {
|
|
|
411
426
|
expect(pr.args).toEqual(["--name", "bob", "-x"]);
|
|
412
427
|
});
|
|
413
428
|
|
|
429
|
+
/** Missing required option returns error. */
|
|
414
430
|
test("missing required option returns error", () => {
|
|
415
431
|
const root = testProgram({
|
|
416
432
|
key: "app",
|
|
@@ -437,6 +453,7 @@ test("missing required option returns error", () => {
|
|
|
437
453
|
expect(pr.errorMsg).toContain("Missing required option: --req");
|
|
438
454
|
});
|
|
439
455
|
|
|
456
|
+
/** Provided required option parses ok. */
|
|
440
457
|
test("provided required option parses ok", () => {
|
|
441
458
|
const root = testProgram({
|
|
442
459
|
key: "app",
|
|
@@ -463,6 +480,7 @@ test("provided required option parses ok", () => {
|
|
|
463
480
|
expect(pr.opts.req).toBe("val");
|
|
464
481
|
});
|
|
465
482
|
|
|
483
|
+
/** Tests that presence option cannot be required. */
|
|
466
484
|
test("presence option cannot be required", () => {
|
|
467
485
|
const root = testProgram({
|
|
468
486
|
key: "app",
|
|
@@ -486,6 +504,7 @@ test("presence option cannot be required", () => {
|
|
|
486
504
|
expect(() => cliValidateProgram(root)).toThrow(/Presence option cannot be required/);
|
|
487
505
|
});
|
|
488
506
|
|
|
507
|
+
/** Leaf completion help prints correctly. */
|
|
489
508
|
test("leaf completion help prints correctly", async () => {
|
|
490
509
|
// Test the fix where `completion zsh -h` on a leaf root was incorrectly ignored.
|
|
491
510
|
// We run this as a subprocess so we don't accidentally exit the test runner.
|
|
@@ -499,6 +518,7 @@ test("leaf completion help prints correctly", async () => {
|
|
|
499
518
|
expect(stderr.toString()).toBe("");
|
|
500
519
|
});
|
|
501
520
|
|
|
521
|
+
/** Docs schema exports JSON for nested CLIs. */
|
|
502
522
|
test("docs schema exports JSON for nested CLIs", async () => {
|
|
503
523
|
const { stdout, stderr, exitCode } = await $`bun run examples/nested.ts docs schema`
|
|
504
524
|
.nothrow()
|
|
@@ -517,6 +537,7 @@ test("docs schema exports JSON for nested CLIs", async () => {
|
|
|
517
537
|
expect(lookup.positionals[0].name).toBe("path");
|
|
518
538
|
});
|
|
519
539
|
|
|
540
|
+
/** Docs schema exports JSON for leaf roots. */
|
|
520
541
|
test("docs schema exports JSON for leaf roots", async () => {
|
|
521
542
|
const { stdout, exitCode } = await $`bun run examples/minimal.ts docs schema`.nothrow().quiet();
|
|
522
543
|
expect(exitCode).toBe(0);
|
|
@@ -526,27 +547,28 @@ test("docs schema exports JSON for leaf roots", async () => {
|
|
|
526
547
|
expect(schema.positionals[0].name).toBe("name");
|
|
527
548
|
expect(schema.options[0].name).toBe("verbose");
|
|
528
549
|
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual([
|
|
529
|
-
"completion",
|
|
530
550
|
"version",
|
|
531
|
-
"
|
|
532
|
-
"uninstall",
|
|
551
|
+
"configure",
|
|
533
552
|
"docs",
|
|
534
553
|
]);
|
|
535
554
|
});
|
|
536
555
|
|
|
556
|
+
/** Tests that version builtin prints program version. */
|
|
537
557
|
test("version builtin prints program version", async () => {
|
|
538
558
|
const { stdout, exitCode } = await $`bun run examples/nested.ts version`.nothrow().quiet();
|
|
539
559
|
expect(exitCode).toBe(0);
|
|
540
560
|
expect(stdout.toString().trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
541
561
|
});
|
|
542
562
|
|
|
543
|
-
|
|
563
|
+
/** Leaf root help omits hidden completion built-in. */
|
|
564
|
+
test("leaf root help omits hidden completion built-in", async () => {
|
|
544
565
|
const { stdout, exitCode } = await $`bun run examples/minimal.ts -h`.nothrow().quiet();
|
|
545
566
|
expect(exitCode).toBe(0);
|
|
546
|
-
expect(stdout.toString()).toContain("completion");
|
|
547
|
-
expect(stdout.toString()).toContain("
|
|
567
|
+
expect(stdout.toString()).not.toContain("completion");
|
|
568
|
+
expect(stdout.toString()).toContain("configure");
|
|
548
569
|
});
|
|
549
570
|
|
|
571
|
+
/** Root --schema is no longer a flag. */
|
|
550
572
|
test("root --schema is no longer a flag", () => {
|
|
551
573
|
const root = testProgram({
|
|
552
574
|
key: "app",
|
|
@@ -569,6 +591,7 @@ test("root --schema is no longer a flag", () => {
|
|
|
569
591
|
expect(pr.kind).not.toBe(ParseKind.Ok);
|
|
570
592
|
});
|
|
571
593
|
|
|
594
|
+
/** CliSchemaJson omits handlers and completion built-ins. */
|
|
572
595
|
test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
573
596
|
const root = testProgram({
|
|
574
597
|
key: "app",
|
|
@@ -599,6 +622,7 @@ test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
|
599
622
|
expect(schema).not.toHaveProperty("handler");
|
|
600
623
|
});
|
|
601
624
|
|
|
625
|
+
/** CliSchemaExport resolves program key in install notes. */
|
|
602
626
|
test("cliSchemaExport resolves program key in install notes", () => {
|
|
603
627
|
const root = testProgram({
|
|
604
628
|
key: "myapp",
|
|
@@ -618,6 +642,7 @@ test("cliSchemaExport resolves program key in install notes", () => {
|
|
|
618
642
|
expect(json).toContain("brew install");
|
|
619
643
|
});
|
|
620
644
|
|
|
645
|
+
/** CliSchemaExport resolves {argsbarg:program} in consumer notes. */
|
|
621
646
|
test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
|
|
622
647
|
const root = testProgram({
|
|
623
648
|
key: "myapp",
|
|
@@ -637,6 +662,7 @@ test("cliSchemaExport resolves {argsbarg:program} in consumer notes", () => {
|
|
|
637
662
|
expect(schema.commands[0].notes).toBe("Run `myapp run` to start.");
|
|
638
663
|
});
|
|
639
664
|
|
|
665
|
+
/** Docs help lists schema, api, and skill subcommands. */
|
|
640
666
|
test("docs help lists schema, api, and skill subcommands", () => {
|
|
641
667
|
const root = testProgram({
|
|
642
668
|
key: "app",
|
|
@@ -663,6 +689,7 @@ test("docs help lists schema, api, and skill subcommands", () => {
|
|
|
663
689
|
expect(help).toContain("reference agent SKILL");
|
|
664
690
|
});
|
|
665
691
|
|
|
692
|
+
/** Root help omits legacy --schema flag. */
|
|
666
693
|
test("root help omits legacy --schema flag", () => {
|
|
667
694
|
const root = testProgram({
|
|
668
695
|
key: "app",
|
|
@@ -680,6 +707,7 @@ test("root help omits legacy --schema flag", () => {
|
|
|
680
707
|
expect(help).not.toContain("--schema");
|
|
681
708
|
});
|
|
682
709
|
|
|
710
|
+
/** Root help shows agent docs hint when docs enabled. */
|
|
683
711
|
test("root help shows agent docs hint when docs enabled", () => {
|
|
684
712
|
const root = testProgram({
|
|
685
713
|
key: "myapp",
|
|
@@ -696,6 +724,7 @@ test("root help shows agent docs hint when docs enabled", () => {
|
|
|
696
724
|
expect(help).not.toContain("install --skill");
|
|
697
725
|
});
|
|
698
726
|
|
|
727
|
+
/** Root help omits agent hint when docs disabled. */
|
|
699
728
|
test("root help omits agent hint when docs disabled", () => {
|
|
700
729
|
const root = testProgram({
|
|
701
730
|
key: "myapp",
|
|
@@ -708,6 +737,7 @@ test("root help omits agent hint when docs disabled", () => {
|
|
|
708
737
|
expect(help).not.toContain("docs skill");
|
|
709
738
|
});
|
|
710
739
|
|
|
740
|
+
/** Root help includes program notes and agent hint. */
|
|
711
741
|
test("root help includes program notes and agent hint", () => {
|
|
712
742
|
const root = testProgram({
|
|
713
743
|
key: "myapp",
|
|
@@ -725,6 +755,7 @@ test("root help includes program notes and agent hint", () => {
|
|
|
725
755
|
expect(help).toContain("myapp docs skill");
|
|
726
756
|
});
|
|
727
757
|
|
|
758
|
+
/** Enum option inputSchema includes enum array. */
|
|
728
759
|
test("Enum option inputSchema includes enum array", () => {
|
|
729
760
|
const tools = collectMcpTools(enumMcpFixture);
|
|
730
761
|
const run = tools.find((t) => t.name === "run")!;
|
|
@@ -732,6 +763,7 @@ test("Enum option inputSchema includes enum array", () => {
|
|
|
732
763
|
expect(schema.properties.mode.enum).toEqual(["dev", "prod"]);
|
|
733
764
|
});
|
|
734
765
|
|
|
766
|
+
/** CliValidateProgram rejects Enum with no choices. */
|
|
735
767
|
test("cliValidateProgram rejects Enum with no choices", () => {
|
|
736
768
|
const root = testProgram({
|
|
737
769
|
key: "app",
|
|
@@ -742,6 +774,7 @@ test("cliValidateProgram rejects Enum with no choices", () => {
|
|
|
742
774
|
expect(() => cliValidateProgram(root)).toThrow(/requires non-empty choices/);
|
|
743
775
|
});
|
|
744
776
|
|
|
777
|
+
/** CliValidateProgram rejects Enum with duplicate choices. */
|
|
745
778
|
test("cliValidateProgram rejects Enum with duplicate choices", () => {
|
|
746
779
|
const root = testProgram({
|
|
747
780
|
key: "app",
|
|
@@ -752,6 +785,7 @@ test("cliValidateProgram rejects Enum with duplicate choices", () => {
|
|
|
752
785
|
expect(() => cliValidateProgram(root)).toThrow(/choices must be distinct/);
|
|
753
786
|
});
|
|
754
787
|
|
|
788
|
+
/** McpTool.description override wins without env suffix. */
|
|
755
789
|
test("mcpTool.description override wins without env suffix", () => {
|
|
756
790
|
const root = testProgram({
|
|
757
791
|
key: "app",
|
|
@@ -770,6 +804,7 @@ test("mcpTool.description override wins without env suffix", () => {
|
|
|
770
804
|
expect(tools[0]?.description).toBe("custom");
|
|
771
805
|
});
|
|
772
806
|
|
|
807
|
+
/** CliValidateProgram requires program.appConfig description. */
|
|
773
808
|
test("cliValidateProgram requires program.appConfig description", () => {
|
|
774
809
|
const root = testProgram({
|
|
775
810
|
key: "app",
|
|
@@ -780,6 +815,7 @@ test("cliValidateProgram requires program.appConfig description", () => {
|
|
|
780
815
|
expect(() => cliValidateProgram(root)).toThrow(/description must be a non-empty string/);
|
|
781
816
|
});
|
|
782
817
|
|
|
818
|
+
/** CliValidateProgram rejects duplicate mcpResources URIs. */
|
|
783
819
|
test("cliValidateProgram rejects duplicate mcpResources URIs", () => {
|
|
784
820
|
const root = testProgram({
|
|
785
821
|
key: "app",
|
|
@@ -796,6 +832,7 @@ test("cliValidateProgram rejects duplicate mcpResources URIs", () => {
|
|
|
796
832
|
expect(() => cliValidateProgram(root)).toThrow(/URIs must be unique/);
|
|
797
833
|
});
|
|
798
834
|
|
|
835
|
+
/** CliValidateProgram rejects empty mcpServer. */
|
|
799
836
|
test("cliValidateProgram rejects empty mcpServer", () => {
|
|
800
837
|
const root = testProgram({
|
|
801
838
|
key: "app",
|
|
@@ -806,6 +843,7 @@ test("cliValidateProgram rejects empty mcpServer", () => {
|
|
|
806
843
|
expect(() => cliValidateProgram(root)).toThrow(/mcpServer requires enabled: true/);
|
|
807
844
|
});
|
|
808
845
|
|
|
846
|
+
/** ResolveMcpSchemaUri uses sanitized root key. */
|
|
809
847
|
test("resolveMcpSchemaUri uses sanitized root key", () => {
|
|
810
848
|
const root = testProgram({
|
|
811
849
|
key: "nested.ts",
|
|
@@ -816,6 +854,7 @@ test("resolveMcpSchemaUri uses sanitized root key", () => {
|
|
|
816
854
|
expect(resolveMcpSchemaUri(root)).toBe("nested_ts://schema");
|
|
817
855
|
});
|
|
818
856
|
|
|
857
|
+
/** ResolveMcpSchemaUri uses plain key when alphanumeric. */
|
|
819
858
|
test("resolveMcpSchemaUri uses plain key when alphanumeric", () => {
|
|
820
859
|
const root = testProgram({
|
|
821
860
|
key: "qa",
|
|
@@ -826,6 +865,7 @@ test("resolveMcpSchemaUri uses plain key when alphanumeric", () => {
|
|
|
826
865
|
expect(resolveMcpSchemaUri(root)).toBe("qa://schema");
|
|
827
866
|
});
|
|
828
867
|
|
|
868
|
+
/** CliValidateProgram rejects resource URI matching default schema URI. */
|
|
829
869
|
test("cliValidateProgram rejects resource URI matching default schema URI", () => {
|
|
830
870
|
const root = testProgram({
|
|
831
871
|
key: "app",
|
|
@@ -839,6 +879,7 @@ test("cliValidateProgram rejects resource URI matching default schema URI", () =
|
|
|
839
879
|
expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/);
|
|
840
880
|
});
|
|
841
881
|
|
|
882
|
+
/** CliValidateProgram rejects resource URI matching schemaResourceUri. */
|
|
842
883
|
test("cliValidateProgram rejects resource URI matching schemaResourceUri", () => {
|
|
843
884
|
const root = testProgram({
|
|
844
885
|
key: "app",
|
|
@@ -853,6 +894,7 @@ test("cliValidateProgram rejects resource URI matching schemaResourceUri", () =>
|
|
|
853
894
|
expect(() => cliValidateProgram(root)).toThrow(/conflicts with built-in schema resource/);
|
|
854
895
|
});
|
|
855
896
|
|
|
897
|
+
/** CliValidateProgram rejects resource URI matching auto docs topic. */
|
|
856
898
|
test("cliValidateProgram rejects resource URI matching auto docs topic", () => {
|
|
857
899
|
const root = testProgram({
|
|
858
900
|
key: "app",
|
|
@@ -867,6 +909,7 @@ test("cliValidateProgram rejects resource URI matching auto docs topic", () => {
|
|
|
867
909
|
expect(() => cliValidateProgram(root)).toThrow(/conflicts with auto docs topic resource/);
|
|
868
910
|
});
|
|
869
911
|
|
|
912
|
+
/** AllMcpResources includes custom resources. */
|
|
870
913
|
test("allMcpResources includes custom resources", () => {
|
|
871
914
|
const root = testProgram({
|
|
872
915
|
key: "app",
|
|
@@ -882,6 +925,7 @@ test("allMcpResources includes custom resources", () => {
|
|
|
882
925
|
expect(resources.map((r) => r.uri)).toContain("test://x");
|
|
883
926
|
});
|
|
884
927
|
|
|
928
|
+
/** AllMcpResources includes docs topic resources. */
|
|
885
929
|
test("allMcpResources includes docs topic resources", () => {
|
|
886
930
|
const root = testProgram({
|
|
887
931
|
key: "app",
|
|
@@ -896,6 +940,7 @@ test("allMcpResources includes docs topic resources", () => {
|
|
|
896
940
|
expect(readme?.load()).toBe("# hi\n");
|
|
897
941
|
});
|
|
898
942
|
|
|
943
|
+
/** ApplyShellEnv merges PATH and preserves host vars. */
|
|
899
944
|
test("applyShellEnv merges PATH and preserves host vars", () => {
|
|
900
945
|
const origPath = process.env.PATH ?? "";
|
|
901
946
|
const origHome = process.env.HOME;
|
|
@@ -915,6 +960,7 @@ test("applyShellEnv merges PATH and preserves host vars", () => {
|
|
|
915
960
|
delete process.env.NEWVAR;
|
|
916
961
|
});
|
|
917
962
|
|
|
963
|
+
/** Enum completions list choices in bash script. */
|
|
918
964
|
test("Enum completions list choices in bash script", () => {
|
|
919
965
|
const root = testProgram({
|
|
920
966
|
key: "app",
|
|
@@ -936,6 +982,7 @@ test("Enum completions list choices in bash script", () => {
|
|
|
936
982
|
expect(bash).toContain("prod");
|
|
937
983
|
});
|
|
938
984
|
|
|
985
|
+
/** Nested fallback routes to default when argv exhausted at router. */
|
|
939
986
|
test("nested fallback routes to default when argv exhausted at router", () => {
|
|
940
987
|
const root = nestedDocsFallbackFixture();
|
|
941
988
|
cliValidateProgram(root);
|
|
@@ -944,6 +991,7 @@ test("nested fallback routes to default when argv exhausted at router", () => {
|
|
|
944
991
|
expect(pr.path).toEqual(["docs", "guide"]);
|
|
945
992
|
});
|
|
946
993
|
|
|
994
|
+
/** Nested fallback MissingOrUnknown routes unknown token to default. */
|
|
947
995
|
test("nested fallback MissingOrUnknown routes unknown token to default", () => {
|
|
948
996
|
const root = testProgram({
|
|
949
997
|
key: "app",
|
|
@@ -985,6 +1033,7 @@ test("nested fallback MissingOrUnknown routes unknown token to default", () => {
|
|
|
985
1033
|
expect(pr.args).toEqual(["extra-topic"]);
|
|
986
1034
|
});
|
|
987
1035
|
|
|
1036
|
+
/** Nested fallback MissingOnly errors on unknown subcommand. */
|
|
988
1037
|
test("nested fallback MissingOnly errors on unknown subcommand", () => {
|
|
989
1038
|
const root = nestedDocsFallbackFixture();
|
|
990
1039
|
cliValidateProgram(root);
|
|
@@ -993,6 +1042,7 @@ test("nested fallback MissingOnly errors on unknown subcommand", () => {
|
|
|
993
1042
|
expect(pr.errorMsg).toContain("Unknown subcommand");
|
|
994
1043
|
});
|
|
995
1044
|
|
|
1045
|
+
/** CliValidateProgram rejects invalid nested fallbackCommand. */
|
|
996
1046
|
test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
|
|
997
1047
|
const root = testProgram({
|
|
998
1048
|
key: "app",
|
|
@@ -1017,11 +1067,13 @@ test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
|
|
|
1017
1067
|
);
|
|
1018
1068
|
});
|
|
1019
1069
|
|
|
1070
|
+
/** CliValidateProgram accepts nested fallbackCommand when child exists. */
|
|
1020
1071
|
test("cliValidateProgram accepts nested fallbackCommand when child exists", () => {
|
|
1021
1072
|
const root = nestedDocsFallbackFixture();
|
|
1022
1073
|
expect(() => cliValidateProgram(root)).not.toThrow();
|
|
1023
1074
|
});
|
|
1024
1075
|
|
|
1076
|
+
/** Nested router scoped help does not route to fallback. */
|
|
1025
1077
|
test("nested router scoped help does not route to fallback", () => {
|
|
1026
1078
|
const root = nestedDocsFallbackFixture();
|
|
1027
1079
|
cliValidateProgram(root);
|
|
@@ -1034,6 +1086,7 @@ test("nested router scoped help does not route to fallback", () => {
|
|
|
1034
1086
|
expect(help).toContain("guide");
|
|
1035
1087
|
});
|
|
1036
1088
|
|
|
1089
|
+
/** Varargs trailing option after positionals via Cli.invoke. */
|
|
1037
1090
|
test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
1038
1091
|
const root = varargsReadFixture();
|
|
1039
1092
|
cliValidateProgram(root);
|
|
@@ -1043,6 +1096,7 @@ test("varargs trailing option after positionals via Cli.invoke", async () => {
|
|
|
1043
1096
|
expect(pr.opts.json).toBe("1");
|
|
1044
1097
|
});
|
|
1045
1098
|
|
|
1099
|
+
/** Varargs option before positionals. */
|
|
1046
1100
|
test("varargs option before positionals", () => {
|
|
1047
1101
|
const root = varargsReadFixture();
|
|
1048
1102
|
cliValidateProgram(root);
|
|
@@ -1052,6 +1106,7 @@ test("varargs option before positionals", () => {
|
|
|
1052
1106
|
expect(pr.opts.json).toBe("1");
|
|
1053
1107
|
});
|
|
1054
1108
|
|
|
1109
|
+
/** Varargs multiple files then trailing option. */
|
|
1055
1110
|
test("varargs multiple files then trailing option", () => {
|
|
1056
1111
|
const root = varargsReadFixture();
|
|
1057
1112
|
cliValidateProgram(root);
|
|
@@ -1061,6 +1116,7 @@ test("varargs multiple files then trailing option", () => {
|
|
|
1061
1116
|
expect(pr.opts.json).toBe("1");
|
|
1062
1117
|
});
|
|
1063
1118
|
|
|
1119
|
+
/** Varargs double dash forces positional. */
|
|
1064
1120
|
test("varargs double dash forces positional", () => {
|
|
1065
1121
|
const root = varargsReadFixture();
|
|
1066
1122
|
cliValidateProgram(root);
|
|
@@ -1070,6 +1126,7 @@ test("varargs double dash forces positional", () => {
|
|
|
1070
1126
|
expect(pr.opts.json).toBeUndefined();
|
|
1071
1127
|
});
|
|
1072
1128
|
|
|
1129
|
+
/** Varargs unknown flag errors. */
|
|
1073
1130
|
test("varargs unknown flag errors", async () => {
|
|
1074
1131
|
const root = varargsReadFixture();
|
|
1075
1132
|
cliValidateProgram(root);
|
|
@@ -1078,6 +1135,7 @@ test("varargs unknown flag errors", async () => {
|
|
|
1078
1135
|
expect(result.stderr).toContain("--unknown");
|
|
1079
1136
|
});
|
|
1080
1137
|
|
|
1138
|
+
/** McpToolCallToArgv rejects comma-separated string for varargs. */
|
|
1081
1139
|
test("mcpToolCallToArgv rejects comma-separated string for varargs", () => {
|
|
1082
1140
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
1083
1141
|
const read = tools.find((t) => t.name === "read")!;
|
|
@@ -1085,6 +1143,7 @@ test("mcpToolCallToArgv rejects comma-separated string for varargs", () => {
|
|
|
1085
1143
|
expect(argv).toEqual({ error: expect.stringContaining("JSON array") });
|
|
1086
1144
|
});
|
|
1087
1145
|
|
|
1146
|
+
/** McpToolCallToArgv rejects bare string for varargs. */
|
|
1088
1147
|
test("mcpToolCallToArgv rejects bare string for varargs", () => {
|
|
1089
1148
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
1090
1149
|
const read = tools.find((t) => t.name === "read")!;
|
|
@@ -1092,6 +1151,7 @@ test("mcpToolCallToArgv rejects bare string for varargs", () => {
|
|
|
1092
1151
|
expect(argv).toEqual({ error: expect.stringContaining("JSON array") });
|
|
1093
1152
|
});
|
|
1094
1153
|
|
|
1154
|
+
/** McpToolCallToArgv array varargs unchanged. */
|
|
1095
1155
|
test("mcpToolCallToArgv array varargs unchanged", () => {
|
|
1096
1156
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
1097
1157
|
const read = tools.find((t) => t.name === "read")!;
|
|
@@ -1099,6 +1159,7 @@ test("mcpToolCallToArgv array varargs unchanged", () => {
|
|
|
1099
1159
|
expect(argv).toEqual(["read", "a", "b"]);
|
|
1100
1160
|
});
|
|
1101
1161
|
|
|
1162
|
+
/** McpToolCallToArgv empty array varargs errors when required. */
|
|
1102
1163
|
test("mcpToolCallToArgv empty array varargs errors when required", () => {
|
|
1103
1164
|
const tools = collectMcpTools(nestedMcpFixture);
|
|
1104
1165
|
const read = tools.find((t) => t.name === "read")!;
|
|
@@ -1108,7 +1169,8 @@ test("mcpToolCallToArgv empty array varargs errors when required", () => {
|
|
|
1108
1169
|
|
|
1109
1170
|
// ── Skills ────────────────────────────────────────────────────────────────────
|
|
1110
1171
|
|
|
1111
|
-
|
|
1172
|
+
/** Configure config on non-root node is rejected. */
|
|
1173
|
+
test("configure config on non-root node is rejected", () => {
|
|
1112
1174
|
const root = {
|
|
1113
1175
|
key: "app",
|
|
1114
1176
|
version: "0.0.0",
|
|
@@ -1117,25 +1179,27 @@ test("install config on non-root node is rejected", () => {
|
|
|
1117
1179
|
{
|
|
1118
1180
|
key: "x",
|
|
1119
1181
|
description: "",
|
|
1120
|
-
|
|
1182
|
+
configure: { enabled: false },
|
|
1121
1183
|
handler: () => {},
|
|
1122
1184
|
},
|
|
1123
1185
|
],
|
|
1124
1186
|
} as unknown as CliProgram;
|
|
1125
|
-
expect(() => cliValidateProgram(root)).toThrow(/
|
|
1187
|
+
expect(() => cliValidateProgram(root)).toThrow(/configure is only supported on the program root/);
|
|
1126
1188
|
});
|
|
1127
1189
|
|
|
1128
|
-
|
|
1190
|
+
/** Configure.prefix is rejected. */
|
|
1191
|
+
test("configure.prefix is rejected", () => {
|
|
1129
1192
|
const root = {
|
|
1130
1193
|
key: "app",
|
|
1131
1194
|
version: "0.0.0",
|
|
1132
1195
|
description: "",
|
|
1133
|
-
|
|
1196
|
+
configure: { prefix: "/opt/bin" },
|
|
1134
1197
|
handler: () => {},
|
|
1135
1198
|
} as unknown as CliProgram;
|
|
1136
|
-
expect(() => cliValidateProgram(root)).toThrow(/
|
|
1199
|
+
expect(() => cliValidateProgram(root)).toThrow(/configure\.prefix removed/);
|
|
1137
1200
|
});
|
|
1138
1201
|
|
|
1202
|
+
/** Tests that generateSkillBundle includes frontmatter and compact command index. */
|
|
1139
1203
|
test("generateSkillBundle includes frontmatter and compact command index", () => {
|
|
1140
1204
|
const bundle = generateSkillBundle(nestedMcpFixture, "cursor");
|
|
1141
1205
|
expect(bundle.dirName).toBe("nested_ts");
|
|
@@ -1156,6 +1220,7 @@ test("generateSkillBundle includes frontmatter and compact command index", () =>
|
|
|
1156
1220
|
expect(bundle.referenceMd).not.toContain("```json");
|
|
1157
1221
|
});
|
|
1158
1222
|
|
|
1223
|
+
/** Tests that generatePluginSkillBundle is MCP routing stub without shell catalog. */
|
|
1159
1224
|
test("generatePluginSkillBundle is MCP routing stub without shell catalog", () => {
|
|
1160
1225
|
const bundle = generatePluginSkillBundle(nestedMcpFixture);
|
|
1161
1226
|
expect(bundle.dirName).toBe("nested_ts");
|
|
@@ -1170,6 +1235,7 @@ test("generatePluginSkillBundle is MCP routing stub without shell catalog", () =
|
|
|
1170
1235
|
expect(bundle.skillMd).not.toContain("## Commands");
|
|
1171
1236
|
});
|
|
1172
1237
|
|
|
1238
|
+
/** CliSkillInstall writes project Cursor skill files. */
|
|
1173
1239
|
test("cliSkillInstall writes project Cursor skill files", () => {
|
|
1174
1240
|
const cwd = mkdtempSync(join(tmpdir(), "argsbarg-skill-"));
|
|
1175
1241
|
const prev = process.cwd();
|
|
@@ -1184,7 +1250,7 @@ test("cliSkillInstall writes project Cursor skill files", () => {
|
|
|
1184
1250
|
expect(readFileSync(join(skillDir, "reference.md"), "utf8")).toContain("CLI API reference");
|
|
1185
1251
|
const skillText = readFileSync(join(skillDir, "SKILL.md"), "utf8");
|
|
1186
1252
|
expect(skillText.startsWith("---\n")).toBe(true);
|
|
1187
|
-
const hint = "<!-- Generated by nested.ts
|
|
1253
|
+
const hint = "<!-- Generated by nested.ts configure; do not edit. -->";
|
|
1188
1254
|
expect(skillText.indexOf(hint)).toBeGreaterThan(skillText.indexOf("---\n", 4));
|
|
1189
1255
|
const refText = readFileSync(join(skillDir, "reference.md"), "utf8");
|
|
1190
1256
|
expect(refText.startsWith(hint)).toBe(true);
|
|
@@ -1194,6 +1260,7 @@ test("cliSkillInstall writes project Cursor skill files", () => {
|
|
|
1194
1260
|
}
|
|
1195
1261
|
});
|
|
1196
1262
|
|
|
1263
|
+
/** CliSkillInstall global uses HOME skills directory. */
|
|
1197
1264
|
test("cliSkillInstall global uses HOME skills directory", () => {
|
|
1198
1265
|
const home = mkdtempSync(join(tmpdir(), "argsbarg-home-"));
|
|
1199
1266
|
const prevHome = process.env.HOME;
|
|
@@ -1212,6 +1279,7 @@ test("cliSkillInstall global uses HOME skills directory", () => {
|
|
|
1212
1279
|
}
|
|
1213
1280
|
});
|
|
1214
1281
|
|
|
1282
|
+
/** CliSkillInstall rimraf overwrites existing directory. */
|
|
1215
1283
|
test("cliSkillInstall rimraf overwrites existing directory", () => {
|
|
1216
1284
|
const cwd = mkdtempSync(join(tmpdir(), "argsbarg-skill-dup-"));
|
|
1217
1285
|
const prev = process.cwd();
|
|
@@ -1230,6 +1298,7 @@ test("cliSkillInstall rimraf overwrites existing directory", () => {
|
|
|
1230
1298
|
}
|
|
1231
1299
|
});
|
|
1232
1300
|
|
|
1301
|
+
/** CliSkillInstall claude target uses .claude/skills. */
|
|
1233
1302
|
test("cliSkillInstall claude target uses .claude/skills", () => {
|
|
1234
1303
|
const cwd = mkdtempSync(join(tmpdir(), "argsbarg-skill-claude-"));
|
|
1235
1304
|
const prev = process.cwd();
|
package/src/schema.ts
CHANGED
|
@@ -13,15 +13,7 @@ import {
|
|
|
13
13
|
leafOutputSchema,
|
|
14
14
|
} from "./types.ts";
|
|
15
15
|
|
|
16
|
-
const RESERVED = new Set([
|
|
17
|
-
"completion",
|
|
18
|
-
"install",
|
|
19
|
-
"uninstall",
|
|
20
|
-
"docs",
|
|
21
|
-
"mcp",
|
|
22
|
-
"version",
|
|
23
|
-
"config",
|
|
24
|
-
]);
|
|
16
|
+
const RESERVED = new Set(["completion", "configure", "docs", "mcp", "version", "config"]);
|
|
25
17
|
|
|
26
18
|
function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
|
|
27
19
|
if (cmd.hidden) {
|
package/src/skill/hint.ts
CHANGED
|
@@ -23,9 +23,9 @@ export function insertGeneratedHint(
|
|
|
23
23
|
return `${hint}${content}`;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** Hint for `
|
|
26
|
+
/** Hint for `configure` skill output files. */
|
|
27
27
|
export function skillInstallHint(program: CliProgram): string {
|
|
28
|
-
return generatedFileHtmlComment(`${program.key}
|
|
28
|
+
return generatedFileHtmlComment(`${program.key} configure`);
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Applies install hints to SKILL.md (after frontmatter) and reference.md. */
|