argsbarg 2.1.1 → 3.0.0
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 +14 -1
- package/README.md +9 -7
- package/docs/install.md +2 -2
- package/docs/mcp.md +18 -13
- package/examples/mcp-test.ts +2 -2
- package/examples/minimal.ts +2 -0
- package/examples/nested.ts +3 -1
- package/examples/option-required.ts +2 -0
- package/index.d.ts +8 -7
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +8 -3
- package/src/builtins/dispatch.ts +22 -1
- package/src/builtins/export.ts +5 -1
- package/src/builtins/install.ts +2 -1
- package/src/builtins/presentation.ts +5 -1
- package/src/builtins/version.ts +10 -0
- package/src/capabilities.ts +2 -2
- package/src/index.test.ts +182 -122
- package/src/index.ts +0 -0
- package/src/install/index.ts +2 -1
- package/src/install/install.test.ts +2 -1
- package/src/install/paths.ts +2 -2
- package/src/install/plan.ts +3 -2
- package/src/install/uninstall.ts +2 -1
- package/src/invoke.ts +1 -1
- package/src/mcp/tools.ts +21 -24
- package/src/runtime.ts +1 -1
- package/src/skill/generate.ts +3 -3
- package/src/types.test.ts +3 -2
- package/src/types.ts +8 -7
- package/src/validate.ts +13 -3
package/src/index.test.ts
CHANGED
|
@@ -10,7 +10,7 @@ shell output regressions.
|
|
|
10
10
|
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
11
11
|
import { completionBashScript, completionZshScript } from "./completion.ts";
|
|
12
12
|
import { cliHelpRender } from "./help.ts";
|
|
13
|
-
import { CliProgram, CliFallbackMode, CliOptionKind, cliInvoke } from "./index.ts";
|
|
13
|
+
import { CliProgram, CliFallbackMode, CliOptionKind, cliInvoke, CliContext } from "./index.ts";
|
|
14
14
|
import type { CliLeaf } from "./types.ts";
|
|
15
15
|
import { isCliRouter } from "./types.ts";
|
|
16
16
|
import {
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
collectMcpTools,
|
|
19
19
|
mcpToolCallToArgv,
|
|
20
20
|
mcpToolDescription,
|
|
21
|
+
resolveMcpSchemaUri,
|
|
21
22
|
sanitizeToolSegment,
|
|
22
23
|
} from "./mcp/tools.ts";
|
|
23
24
|
import { applyShellEnv, loadEnvFile } from "./mcp/env.ts";
|
|
@@ -28,13 +29,17 @@ import { ParseKind, parse, postParseValidate } from "./parse.ts";
|
|
|
28
29
|
import { cliSchemaJson } from "./schema.ts";
|
|
29
30
|
import { cliValidateProgram } from "./validate.ts";
|
|
30
31
|
import { expect, test } from "bun:test";
|
|
32
|
+
|
|
33
|
+
function testProgram(prog: Record<string, unknown> & { key: string; description: string }): CliProgram {
|
|
34
|
+
return { version: "0.0.0", ...prog } as CliProgram;
|
|
35
|
+
}
|
|
31
36
|
import { $ } from "bun";
|
|
32
37
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
33
38
|
import { tmpdir } from "node:os";
|
|
34
39
|
import { join } from "node:path";
|
|
35
40
|
|
|
36
41
|
test("bundled short presence flags", () => {
|
|
37
|
-
const root
|
|
42
|
+
const root= testProgram({
|
|
38
43
|
key: "app",
|
|
39
44
|
description: "",
|
|
40
45
|
commands: [
|
|
@@ -58,7 +63,7 @@ test("bundled short presence flags", () => {
|
|
|
58
63
|
handler: () => {},
|
|
59
64
|
},
|
|
60
65
|
],
|
|
61
|
-
};
|
|
66
|
+
});
|
|
62
67
|
cliValidateProgram(root);
|
|
63
68
|
const pr = postParseValidate(root, parse(root, ["x", "-ab"]));
|
|
64
69
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -67,7 +72,7 @@ test("bundled short presence flags", () => {
|
|
|
67
72
|
});
|
|
68
73
|
|
|
69
74
|
test("long option equals", () => {
|
|
70
|
-
const root
|
|
75
|
+
const root= testProgram({
|
|
71
76
|
key: "app",
|
|
72
77
|
description: "",
|
|
73
78
|
commands: [
|
|
@@ -84,7 +89,7 @@ test("long option equals", () => {
|
|
|
84
89
|
handler: () => {},
|
|
85
90
|
},
|
|
86
91
|
],
|
|
87
|
-
};
|
|
92
|
+
});
|
|
88
93
|
cliValidateProgram(root);
|
|
89
94
|
const pr = postParseValidate(root, parse(root, ["x", "--name=pat"]));
|
|
90
95
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -92,7 +97,7 @@ test("long option equals", () => {
|
|
|
92
97
|
});
|
|
93
98
|
|
|
94
99
|
test("fallback missing or unknown root flags", () => {
|
|
95
|
-
const root
|
|
100
|
+
const root= testProgram({
|
|
96
101
|
key: "app",
|
|
97
102
|
description: "",
|
|
98
103
|
commands: [
|
|
@@ -111,7 +116,7 @@ test("fallback missing or unknown root flags", () => {
|
|
|
111
116
|
],
|
|
112
117
|
fallbackCommand: "hello",
|
|
113
118
|
fallbackMode: CliFallbackMode.MissingOrUnknown,
|
|
114
|
-
};
|
|
119
|
+
});
|
|
115
120
|
cliValidateProgram(root);
|
|
116
121
|
const pr = postParseValidate(root, parse(root, ["--name", "bob"]));
|
|
117
122
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -120,11 +125,11 @@ test("fallback missing or unknown root flags", () => {
|
|
|
120
125
|
});
|
|
121
126
|
|
|
122
127
|
test("unknown command", () => {
|
|
123
|
-
const root
|
|
128
|
+
const root= testProgram({
|
|
124
129
|
key: "app",
|
|
125
130
|
description: "",
|
|
126
131
|
commands: [{ key: "hello", description: "", handler: () => {} }],
|
|
127
|
-
};
|
|
132
|
+
});
|
|
128
133
|
cliValidateProgram(root);
|
|
129
134
|
const pr = parse(root, ["nope"]);
|
|
130
135
|
expect(pr.kind).toBe(ParseKind.Error);
|
|
@@ -132,11 +137,11 @@ test("unknown command", () => {
|
|
|
132
137
|
});
|
|
133
138
|
|
|
134
139
|
test("implicit help empty", () => {
|
|
135
|
-
const root
|
|
140
|
+
const root= testProgram({
|
|
136
141
|
key: "app",
|
|
137
142
|
description: "",
|
|
138
143
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
139
|
-
};
|
|
144
|
+
});
|
|
140
145
|
cliValidateProgram(root);
|
|
141
146
|
const pr = parse(root, []);
|
|
142
147
|
expect(pr.kind).toBe(ParseKind.Help);
|
|
@@ -144,7 +149,7 @@ test("implicit help empty", () => {
|
|
|
144
149
|
});
|
|
145
150
|
|
|
146
151
|
test("invalid number post validate", () => {
|
|
147
|
-
const root
|
|
152
|
+
const root= testProgram({
|
|
148
153
|
key: "app",
|
|
149
154
|
description: "",
|
|
150
155
|
commands: [
|
|
@@ -161,7 +166,7 @@ test("invalid number post validate", () => {
|
|
|
161
166
|
handler: () => {},
|
|
162
167
|
},
|
|
163
168
|
],
|
|
164
|
-
};
|
|
169
|
+
});
|
|
165
170
|
cliValidateProgram(root);
|
|
166
171
|
let pr = parse(root, ["x", "--n", "notnum"]);
|
|
167
172
|
pr = postParseValidate(root, pr);
|
|
@@ -170,7 +175,7 @@ test("invalid number post validate", () => {
|
|
|
170
175
|
});
|
|
171
176
|
|
|
172
177
|
test("supports scientific notation in numbers", () => {
|
|
173
|
-
const root
|
|
178
|
+
const root= testProgram({
|
|
174
179
|
key: "app",
|
|
175
180
|
description: "",
|
|
176
181
|
commands: [
|
|
@@ -187,7 +192,7 @@ test("supports scientific notation in numbers", () => {
|
|
|
187
192
|
handler: () => {},
|
|
188
193
|
},
|
|
189
194
|
],
|
|
190
|
-
};
|
|
195
|
+
});
|
|
191
196
|
cliValidateProgram(root);
|
|
192
197
|
let pr = parse(root, ["x", "--n", "1.23e4"]);
|
|
193
198
|
pr = postParseValidate(root, pr);
|
|
@@ -198,35 +203,35 @@ test("supports scientific notation in numbers", () => {
|
|
|
198
203
|
|
|
199
204
|
|
|
200
205
|
test("completion scripts contain app name", () => {
|
|
201
|
-
const root
|
|
206
|
+
const root= testProgram({
|
|
202
207
|
key: "myapp",
|
|
203
208
|
description: "Test",
|
|
204
209
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
205
|
-
};
|
|
210
|
+
});
|
|
206
211
|
cliValidateProgram(root);
|
|
207
|
-
const bash = completionBashScript(root);
|
|
212
|
+
const bash = completionBashScript(cliPresentationRoot(root));
|
|
208
213
|
expect(bash).toContain("bash completion for myapp");
|
|
209
214
|
expect(bash).toContain("complete -F _myapp myapp");
|
|
210
215
|
|
|
211
|
-
const zsh = completionZshScript(root);
|
|
216
|
+
const zsh = completionZshScript(cliPresentationRoot(root));
|
|
212
217
|
expect(zsh).toContain("#compdef myapp");
|
|
213
218
|
expect(zsh).toContain("compdef _myapp myapp");
|
|
214
219
|
expect(zsh).toContain("hello:Say hello.");
|
|
215
220
|
});
|
|
216
221
|
|
|
217
222
|
test("completion scripts do not emit invalid bash substitutions", () => {
|
|
218
|
-
const root
|
|
223
|
+
const root= testProgram({
|
|
219
224
|
key: "app",
|
|
220
225
|
description: "Test",
|
|
221
226
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
222
|
-
};
|
|
227
|
+
});
|
|
223
228
|
cliValidateProgram(root);
|
|
224
|
-
const bash = completionBashScript(root);
|
|
229
|
+
const bash = completionBashScript(cliPresentationRoot(root));
|
|
225
230
|
expect(bash).not.toContain("${${");
|
|
226
231
|
});
|
|
227
232
|
|
|
228
233
|
test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
229
|
-
const root
|
|
234
|
+
const root= testProgram({
|
|
230
235
|
key: "app",
|
|
231
236
|
description: "Test",
|
|
232
237
|
commands: [
|
|
@@ -236,29 +241,29 @@ test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
|
236
241
|
handler: () => {},
|
|
237
242
|
},
|
|
238
243
|
],
|
|
239
|
-
};
|
|
244
|
+
});
|
|
240
245
|
cliValidateProgram(root);
|
|
241
|
-
const zsh = completionZshScript(root);
|
|
246
|
+
const zsh = completionZshScript(cliPresentationRoot(root));
|
|
242
247
|
expect(zsh).toContain("quote'\\''cmd:Say '\\''hello'\\'' and keep going.");
|
|
243
248
|
});
|
|
244
249
|
|
|
245
250
|
test("completion scripts keep dotted app names in registration names", () => {
|
|
246
|
-
const root
|
|
251
|
+
const root= testProgram({
|
|
247
252
|
key: "minimal.ts",
|
|
248
253
|
description: "Test",
|
|
249
254
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
250
|
-
};
|
|
255
|
+
});
|
|
251
256
|
cliValidateProgram(root);
|
|
252
257
|
|
|
253
|
-
const bash = completionBashScript(root);
|
|
258
|
+
const bash = completionBashScript(cliPresentationRoot(root));
|
|
254
259
|
expect(bash).toContain("complete -F _minimal_ts minimal.ts");
|
|
255
260
|
|
|
256
|
-
const zsh = completionZshScript(root);
|
|
261
|
+
const zsh = completionZshScript(cliPresentationRoot(root));
|
|
257
262
|
expect(zsh).toContain("compdef _minimal_ts minimal.ts");
|
|
258
263
|
});
|
|
259
264
|
|
|
260
265
|
test("trailing options after bounded positionals", () => {
|
|
261
|
-
const root
|
|
266
|
+
const root= testProgram({
|
|
262
267
|
key: "app",
|
|
263
268
|
description: "",
|
|
264
269
|
commands: [
|
|
@@ -282,7 +287,7 @@ test("trailing options after bounded positionals", () => {
|
|
|
282
287
|
handler: () => {},
|
|
283
288
|
},
|
|
284
289
|
],
|
|
285
|
-
};
|
|
290
|
+
});
|
|
286
291
|
cliValidateProgram(root);
|
|
287
292
|
const pr = postParseValidate(root, parse(root, ["x", "./file", "--verbose"]));
|
|
288
293
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -291,7 +296,7 @@ test("trailing options after bounded positionals", () => {
|
|
|
291
296
|
});
|
|
292
297
|
|
|
293
298
|
test("trailing options include parent-scoped flags", () => {
|
|
294
|
-
const root
|
|
299
|
+
const root= testProgram({
|
|
295
300
|
key: "app",
|
|
296
301
|
description: "",
|
|
297
302
|
commands: [
|
|
@@ -329,7 +334,7 @@ test("trailing options include parent-scoped flags", () => {
|
|
|
329
334
|
],
|
|
330
335
|
},
|
|
331
336
|
],
|
|
332
|
-
};
|
|
337
|
+
});
|
|
333
338
|
cliValidateProgram(root);
|
|
334
339
|
const pr = postParseValidate(root, parse(root, ["group", "leaf", "-u", "alice", "./file", "--json"]));
|
|
335
340
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -340,7 +345,7 @@ test("trailing options include parent-scoped flags", () => {
|
|
|
340
345
|
});
|
|
341
346
|
|
|
342
347
|
test("varargs tail parses trailing options", () => {
|
|
343
|
-
const root
|
|
348
|
+
const root= testProgram({
|
|
344
349
|
key: "app",
|
|
345
350
|
description: "",
|
|
346
351
|
commands: [
|
|
@@ -366,7 +371,7 @@ test("varargs tail parses trailing options", () => {
|
|
|
366
371
|
handler: () => {},
|
|
367
372
|
},
|
|
368
373
|
],
|
|
369
|
-
};
|
|
374
|
+
});
|
|
370
375
|
cliValidateProgram(root);
|
|
371
376
|
const pr = postParseValidate(root, parse(root, ["x", "./file", "--json"]));
|
|
372
377
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -375,7 +380,7 @@ test("varargs tail parses trailing options", () => {
|
|
|
375
380
|
});
|
|
376
381
|
|
|
377
382
|
test("stops parsing options at --", () => {
|
|
378
|
-
const root
|
|
383
|
+
const root= testProgram({
|
|
379
384
|
key: "app",
|
|
380
385
|
description: "",
|
|
381
386
|
commands: [
|
|
@@ -401,7 +406,7 @@ test("stops parsing options at --", () => {
|
|
|
401
406
|
handler: () => {},
|
|
402
407
|
},
|
|
403
408
|
],
|
|
404
|
-
};
|
|
409
|
+
});
|
|
405
410
|
cliValidateProgram(root);
|
|
406
411
|
const pr = postParseValidate(root, parse(root, ["x", "--name", "pat", "--", "--name", "bob", "-x"]));
|
|
407
412
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -410,7 +415,7 @@ test("stops parsing options at --", () => {
|
|
|
410
415
|
});
|
|
411
416
|
|
|
412
417
|
test("missing required option returns error", () => {
|
|
413
|
-
const root
|
|
418
|
+
const root= testProgram({
|
|
414
419
|
key: "app",
|
|
415
420
|
description: "",
|
|
416
421
|
options: [
|
|
@@ -428,7 +433,7 @@ test("missing required option returns error", () => {
|
|
|
428
433
|
handler: () => {},
|
|
429
434
|
},
|
|
430
435
|
],
|
|
431
|
-
};
|
|
436
|
+
});
|
|
432
437
|
cliValidateProgram(root);
|
|
433
438
|
const pr = postParseValidate(root, parse(root, ["x"]));
|
|
434
439
|
expect(pr.kind).toBe(ParseKind.Error);
|
|
@@ -436,7 +441,7 @@ test("missing required option returns error", () => {
|
|
|
436
441
|
});
|
|
437
442
|
|
|
438
443
|
test("provided required option parses ok", () => {
|
|
439
|
-
const root
|
|
444
|
+
const root= testProgram({
|
|
440
445
|
key: "app",
|
|
441
446
|
description: "",
|
|
442
447
|
commands: [
|
|
@@ -454,7 +459,7 @@ test("provided required option parses ok", () => {
|
|
|
454
459
|
handler: () => {},
|
|
455
460
|
},
|
|
456
461
|
],
|
|
457
|
-
};
|
|
462
|
+
});
|
|
458
463
|
cliValidateProgram(root);
|
|
459
464
|
const pr = postParseValidate(root, parse(root, ["x", "--req", "val"]));
|
|
460
465
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -462,7 +467,7 @@ test("provided required option parses ok", () => {
|
|
|
462
467
|
});
|
|
463
468
|
|
|
464
469
|
test("presence option cannot be required", () => {
|
|
465
|
-
const root
|
|
470
|
+
const root= testProgram({
|
|
466
471
|
key: "app",
|
|
467
472
|
description: "",
|
|
468
473
|
options: [
|
|
@@ -480,7 +485,7 @@ test("presence option cannot be required", () => {
|
|
|
480
485
|
handler: () => {},
|
|
481
486
|
},
|
|
482
487
|
],
|
|
483
|
-
};
|
|
488
|
+
});
|
|
484
489
|
expect(() => cliValidateProgram(root)).toThrow(/Presence option cannot be required/);
|
|
485
490
|
});
|
|
486
491
|
|
|
@@ -519,7 +524,13 @@ test("--schema exports JSON for leaf roots", async () => {
|
|
|
519
524
|
expect(schema.key).toBe("minimal.ts");
|
|
520
525
|
expect(schema.positionals[0].name).toBe("name");
|
|
521
526
|
expect(schema.options[0].name).toBe("verbose");
|
|
522
|
-
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["completion", "install"]);
|
|
527
|
+
expect(schema.commands.map((c: { key: string }) => c.key)).toEqual(["completion", "version", "install"]);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
test("version builtin prints program version", async () => {
|
|
531
|
+
const { stdout, exitCode } = await $`bun run examples/nested.ts version`.nothrow().quiet();
|
|
532
|
+
expect(exitCode).toBe(0);
|
|
533
|
+
expect(stdout.toString().trim()).toMatch(/^\d+\.\d+\.\d+/);
|
|
523
534
|
});
|
|
524
535
|
|
|
525
536
|
test("leaf root help lists completion built-in", async () => {
|
|
@@ -530,7 +541,7 @@ test("leaf root help lists completion built-in", async () => {
|
|
|
530
541
|
});
|
|
531
542
|
|
|
532
543
|
test("parse recognizes --schema at the program root", () => {
|
|
533
|
-
const root
|
|
544
|
+
const root= testProgram({
|
|
534
545
|
key: "app",
|
|
535
546
|
description: "demo",
|
|
536
547
|
commands: [
|
|
@@ -540,14 +551,14 @@ test("parse recognizes --schema at the program root", () => {
|
|
|
540
551
|
handler: () => {},
|
|
541
552
|
},
|
|
542
553
|
],
|
|
543
|
-
};
|
|
554
|
+
});
|
|
544
555
|
cliValidateProgram(root);
|
|
545
556
|
const pr = parse(root, ["--schema"]);
|
|
546
557
|
expect(pr.kind).toBe(ParseKind.Schema);
|
|
547
558
|
});
|
|
548
559
|
|
|
549
560
|
test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
550
|
-
const root
|
|
561
|
+
const root= testProgram({
|
|
551
562
|
key: "app",
|
|
552
563
|
description: "demo",
|
|
553
564
|
commands: [
|
|
@@ -568,7 +579,7 @@ test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
|
568
579
|
],
|
|
569
580
|
},
|
|
570
581
|
],
|
|
571
|
-
};
|
|
582
|
+
});
|
|
572
583
|
|
|
573
584
|
const schema = JSON.parse(cliSchemaJson(root));
|
|
574
585
|
expect(schema.commands).toHaveLength(1);
|
|
@@ -577,7 +588,7 @@ test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
|
577
588
|
});
|
|
578
589
|
|
|
579
590
|
test("reserved option name schema is rejected", () => {
|
|
580
|
-
const root
|
|
591
|
+
const root= testProgram({
|
|
581
592
|
key: "app",
|
|
582
593
|
description: "",
|
|
583
594
|
commands: [
|
|
@@ -594,12 +605,12 @@ test("reserved option name schema is rejected", () => {
|
|
|
594
605
|
handler: () => {},
|
|
595
606
|
},
|
|
596
607
|
],
|
|
597
|
-
};
|
|
608
|
+
});
|
|
598
609
|
expect(() => cliValidateProgram(root)).toThrow(/reserved for --schema/);
|
|
599
610
|
});
|
|
600
611
|
|
|
601
612
|
test("root help lists --schema built-in", () => {
|
|
602
|
-
const root
|
|
613
|
+
const root= testProgram({
|
|
603
614
|
key: "app",
|
|
604
615
|
description: "demo",
|
|
605
616
|
commands: [
|
|
@@ -609,14 +620,14 @@ test("root help lists --schema built-in", () => {
|
|
|
609
620
|
handler: () => {},
|
|
610
621
|
},
|
|
611
622
|
],
|
|
612
|
-
};
|
|
623
|
+
});
|
|
613
624
|
const help = cliHelpRender(cliPresentationRoot(root), [], false);
|
|
614
625
|
expect(help).toContain("--schema");
|
|
615
626
|
expect(help).toContain("Print the full command tree as JSON.");
|
|
616
627
|
});
|
|
617
628
|
|
|
618
629
|
test("nested help omits --schema built-in", () => {
|
|
619
|
-
const root
|
|
630
|
+
const root= testProgram({
|
|
620
631
|
key: "app",
|
|
621
632
|
description: "demo",
|
|
622
633
|
commands: [
|
|
@@ -626,13 +637,13 @@ test("nested help omits --schema built-in", () => {
|
|
|
626
637
|
handler: () => {},
|
|
627
638
|
},
|
|
628
639
|
],
|
|
629
|
-
};
|
|
640
|
+
});
|
|
630
641
|
const help = cliHelpRender(cliPresentationRoot(root), ["x"], false);
|
|
631
642
|
expect(help).not.toContain("--schema");
|
|
632
643
|
});
|
|
633
644
|
|
|
634
645
|
test("completion scripts offer --schema at the program root only", () => {
|
|
635
|
-
const root
|
|
646
|
+
const root= testProgram({
|
|
636
647
|
key: "myapp",
|
|
637
648
|
description: "",
|
|
638
649
|
commands: [
|
|
@@ -648,20 +659,21 @@ test("completion scripts offer --schema at the program root only", () => {
|
|
|
648
659
|
],
|
|
649
660
|
},
|
|
650
661
|
],
|
|
651
|
-
};
|
|
662
|
+
});
|
|
652
663
|
|
|
653
|
-
const bash = completionBashScript(root);
|
|
664
|
+
const bash = completionBashScript(cliPresentationRoot(root));
|
|
654
665
|
expect(bash).toContain("A_myapp_0_opts+=('--schema')");
|
|
655
666
|
expect(bash).not.toContain("A_myapp_1_opts+=('--schema')");
|
|
656
667
|
|
|
657
|
-
const zsh = completionZshScript(root);
|
|
668
|
+
const zsh = completionZshScript(cliPresentationRoot(root));
|
|
658
669
|
expect(zsh).toContain("'--schema:Print the full command tree as JSON.'");
|
|
659
670
|
});
|
|
660
671
|
|
|
661
|
-
const nestedMcpFixture
|
|
672
|
+
const nestedMcpFixture = testProgram({
|
|
662
673
|
key: "nested.ts",
|
|
663
674
|
description: "Nested groups demo.",
|
|
664
|
-
|
|
675
|
+
version: "1.0.0",
|
|
676
|
+
mcpServer: { enabled: true },
|
|
665
677
|
commands: [
|
|
666
678
|
{
|
|
667
679
|
key: "stat",
|
|
@@ -724,7 +736,7 @@ const nestedMcpFixture: CliProgram = {
|
|
|
724
736
|
],
|
|
725
737
|
fallbackCommand: "read",
|
|
726
738
|
fallbackMode: CliFallbackMode.MissingOrUnknown,
|
|
727
|
-
};
|
|
739
|
+
});
|
|
728
740
|
|
|
729
741
|
/** Sends NDJSON MCP requests to a subprocess and collects responses by id. */
|
|
730
742
|
async function mcpRequest(
|
|
@@ -814,7 +826,7 @@ test("mcpToolCallToArgv expands varargs positionals", () => {
|
|
|
814
826
|
});
|
|
815
827
|
|
|
816
828
|
test("reserved command name install is rejected", () => {
|
|
817
|
-
const root
|
|
829
|
+
const root= testProgram({
|
|
818
830
|
key: "app",
|
|
819
831
|
description: "",
|
|
820
832
|
commands: [
|
|
@@ -824,12 +836,12 @@ test("reserved command name install is rejected", () => {
|
|
|
824
836
|
handler: () => {},
|
|
825
837
|
},
|
|
826
838
|
],
|
|
827
|
-
};
|
|
839
|
+
});
|
|
828
840
|
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: install/);
|
|
829
841
|
});
|
|
830
842
|
|
|
831
843
|
test("top-level command name mcp is allowed without mcpServer", () => {
|
|
832
|
-
const root
|
|
844
|
+
const root= testProgram({
|
|
833
845
|
key: "app",
|
|
834
846
|
description: "",
|
|
835
847
|
commands: [
|
|
@@ -839,15 +851,15 @@ test("top-level command name mcp is allowed without mcpServer", () => {
|
|
|
839
851
|
handler: () => {},
|
|
840
852
|
},
|
|
841
853
|
],
|
|
842
|
-
};
|
|
854
|
+
});
|
|
843
855
|
expect(() => cliValidateProgram(root)).not.toThrow();
|
|
844
856
|
});
|
|
845
857
|
|
|
846
|
-
test("top-level command name mcp is rejected when mcpServer is
|
|
847
|
-
const root
|
|
858
|
+
test("top-level command name mcp is rejected when mcpServer is enabled", () => {
|
|
859
|
+
const root = testProgram({
|
|
848
860
|
key: "app",
|
|
849
861
|
description: "",
|
|
850
|
-
mcpServer: {},
|
|
862
|
+
mcpServer: { enabled: true },
|
|
851
863
|
commands: [
|
|
852
864
|
{
|
|
853
865
|
key: "mcp",
|
|
@@ -855,19 +867,20 @@ test("top-level command name mcp is rejected when mcpServer is set", () => {
|
|
|
855
867
|
handler: () => {},
|
|
856
868
|
},
|
|
857
869
|
],
|
|
858
|
-
};
|
|
870
|
+
});
|
|
859
871
|
expect(() => cliValidateProgram(root)).toThrow(/Reserved command name: mcp/);
|
|
860
872
|
});
|
|
861
873
|
|
|
862
874
|
test("mcpServer on non-root node is rejected", () => {
|
|
863
875
|
const root = {
|
|
864
876
|
key: "app",
|
|
877
|
+
version: "0.0.0",
|
|
865
878
|
description: "",
|
|
866
879
|
commands: [
|
|
867
880
|
{
|
|
868
881
|
key: "x",
|
|
869
882
|
description: "cmd",
|
|
870
|
-
mcpServer: {},
|
|
883
|
+
mcpServer: { enabled: true },
|
|
871
884
|
handler: () => {},
|
|
872
885
|
},
|
|
873
886
|
],
|
|
@@ -876,17 +889,17 @@ test("mcpServer on non-root node is rejected", () => {
|
|
|
876
889
|
});
|
|
877
890
|
|
|
878
891
|
test("mcpTool on root is rejected", () => {
|
|
879
|
-
const root
|
|
892
|
+
const root= testProgram({
|
|
880
893
|
key: "app",
|
|
881
894
|
description: "",
|
|
882
895
|
mcpTool: { enabled: false },
|
|
883
896
|
handler: () => {},
|
|
884
|
-
};
|
|
897
|
+
});
|
|
885
898
|
expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
|
|
886
899
|
});
|
|
887
900
|
|
|
888
901
|
test("mcpTool on routing node is rejected", () => {
|
|
889
|
-
const root
|
|
902
|
+
const root= testProgram({
|
|
890
903
|
key: "app",
|
|
891
904
|
description: "",
|
|
892
905
|
commands: [
|
|
@@ -903,7 +916,7 @@ test("mcpTool on routing node is rejected", () => {
|
|
|
903
916
|
],
|
|
904
917
|
},
|
|
905
918
|
],
|
|
906
|
-
};
|
|
919
|
+
});
|
|
907
920
|
expect(() => cliValidateProgram(root)).toThrow(/mcpTool is only supported on leaf commands/);
|
|
908
921
|
});
|
|
909
922
|
|
|
@@ -964,7 +977,7 @@ test("MCP tools/list includes stat_owner_lookup", async () => {
|
|
|
964
977
|
|
|
965
978
|
test("MCP resources/read returns schema JSON", async () => {
|
|
966
979
|
const responses = await mcpRequest([
|
|
967
|
-
{ jsonrpc: "2.0", id: 3, method: "resources/read", params: { uri: "
|
|
980
|
+
{ jsonrpc: "2.0", id: 3, method: "resources/read", params: { uri: "nested_ts://schema" } },
|
|
968
981
|
]);
|
|
969
982
|
const res = responses.get(3) as { result: { contents: { text: string }[] } };
|
|
970
983
|
const schema = JSON.parse(res.result.contents[0]!.text);
|
|
@@ -1044,7 +1057,7 @@ test("ctx.invocation is cli via cliRun", async () => {
|
|
|
1044
1057
|
const indexPath = join(import.meta.dir, "index.ts");
|
|
1045
1058
|
const { stdout } = await $`bun -e ${`
|
|
1046
1059
|
import { cliRun, CliProgram } from ${JSON.stringify(indexPath)};
|
|
1047
|
-
const cli = { key: "t", description: "d", handler: (ctx) => console.log(ctx.invocation) };
|
|
1060
|
+
const cli = { key: "t", description: "d", version: "0.0.0", handler: (ctx) => console.log(ctx.invocation) };
|
|
1048
1061
|
await cliRun(cli, []);
|
|
1049
1062
|
`}`.quiet();
|
|
1050
1063
|
expect(stdout.toString().trim()).toBe("cli");
|
|
@@ -1052,23 +1065,23 @@ await cliRun(cli, []);
|
|
|
1052
1065
|
|
|
1053
1066
|
test("ctx.invocation is mcp via cliInvoke", async () => {
|
|
1054
1067
|
let seen = "";
|
|
1055
|
-
const root
|
|
1068
|
+
const root= testProgram({
|
|
1056
1069
|
key: "app",
|
|
1057
1070
|
description: "",
|
|
1058
|
-
handler: (ctx) => {
|
|
1071
|
+
handler: (ctx: CliContext) => {
|
|
1059
1072
|
seen = ctx.invocation;
|
|
1060
1073
|
},
|
|
1061
|
-
};
|
|
1074
|
+
});
|
|
1062
1075
|
cliValidateProgram(root);
|
|
1063
1076
|
const result = await cliInvoke(root, []);
|
|
1064
1077
|
expect(result.kind).toBe("ok");
|
|
1065
1078
|
expect(seen).toBe("mcp");
|
|
1066
1079
|
});
|
|
1067
1080
|
|
|
1068
|
-
const enumMcpFixture
|
|
1081
|
+
const enumMcpFixture = testProgram({
|
|
1069
1082
|
key: "app",
|
|
1070
1083
|
description: "",
|
|
1071
|
-
mcpServer: {},
|
|
1084
|
+
mcpServer: { enabled: true },
|
|
1072
1085
|
commands: [
|
|
1073
1086
|
{
|
|
1074
1087
|
key: "run",
|
|
@@ -1085,7 +1098,7 @@ const enumMcpFixture: CliProgram = {
|
|
|
1085
1098
|
handler: () => {},
|
|
1086
1099
|
},
|
|
1087
1100
|
],
|
|
1088
|
-
};
|
|
1101
|
+
});
|
|
1089
1102
|
|
|
1090
1103
|
test("Enum option inputSchema includes enum array", () => {
|
|
1091
1104
|
const tools = collectMcpTools(enumMcpFixture);
|
|
@@ -1095,7 +1108,7 @@ test("Enum option inputSchema includes enum array", () => {
|
|
|
1095
1108
|
});
|
|
1096
1109
|
|
|
1097
1110
|
test("cliInvoke rejects invalid Enum value", async () => {
|
|
1098
|
-
const root
|
|
1111
|
+
const root= testProgram({
|
|
1099
1112
|
key: "app",
|
|
1100
1113
|
description: "",
|
|
1101
1114
|
handler: () => {},
|
|
@@ -1108,7 +1121,7 @@ test("cliInvoke rejects invalid Enum value", async () => {
|
|
|
1108
1121
|
required: true,
|
|
1109
1122
|
},
|
|
1110
1123
|
],
|
|
1111
|
-
};
|
|
1124
|
+
});
|
|
1112
1125
|
cliValidateProgram(root);
|
|
1113
1126
|
const result = await cliInvoke(root, ["--mode", "staging"]);
|
|
1114
1127
|
expect(result.kind).toBe("error");
|
|
@@ -1116,10 +1129,10 @@ test("cliInvoke rejects invalid Enum value", async () => {
|
|
|
1116
1129
|
});
|
|
1117
1130
|
|
|
1118
1131
|
test("cliInvoke accepts valid Enum value", async () => {
|
|
1119
|
-
const root
|
|
1132
|
+
const root= testProgram({
|
|
1120
1133
|
key: "app",
|
|
1121
1134
|
description: "",
|
|
1122
|
-
handler: (ctx) => {
|
|
1135
|
+
handler: (ctx: CliContext) => {
|
|
1123
1136
|
console.log(ctx.stringOpt("mode"));
|
|
1124
1137
|
},
|
|
1125
1138
|
options: [
|
|
@@ -1131,7 +1144,7 @@ test("cliInvoke accepts valid Enum value", async () => {
|
|
|
1131
1144
|
required: true,
|
|
1132
1145
|
},
|
|
1133
1146
|
],
|
|
1134
|
-
};
|
|
1147
|
+
});
|
|
1135
1148
|
cliValidateProgram(root);
|
|
1136
1149
|
const result = await cliInvoke(root, ["--mode", "dev"]);
|
|
1137
1150
|
expect(result.kind).toBe("ok");
|
|
@@ -1139,30 +1152,30 @@ test("cliInvoke accepts valid Enum value", async () => {
|
|
|
1139
1152
|
});
|
|
1140
1153
|
|
|
1141
1154
|
test("cliValidateProgram rejects Enum with no choices", () => {
|
|
1142
|
-
const root
|
|
1155
|
+
const root= testProgram({
|
|
1143
1156
|
key: "app",
|
|
1144
1157
|
description: "",
|
|
1145
1158
|
handler: () => {},
|
|
1146
1159
|
options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: [] }],
|
|
1147
|
-
};
|
|
1160
|
+
});
|
|
1148
1161
|
expect(() => cliValidateProgram(root)).toThrow(/requires non-empty choices/);
|
|
1149
1162
|
});
|
|
1150
1163
|
|
|
1151
1164
|
test("cliValidateProgram rejects Enum with duplicate choices", () => {
|
|
1152
|
-
const root
|
|
1165
|
+
const root= testProgram({
|
|
1153
1166
|
key: "app",
|
|
1154
1167
|
description: "",
|
|
1155
1168
|
handler: () => {},
|
|
1156
1169
|
options: [{ name: "mode", description: "", kind: CliOptionKind.Enum, choices: ["a", "a"] }],
|
|
1157
|
-
};
|
|
1170
|
+
});
|
|
1158
1171
|
expect(() => cliValidateProgram(root)).toThrow(/choices must be distinct/);
|
|
1159
1172
|
});
|
|
1160
1173
|
|
|
1161
1174
|
test("mcpTool.description override wins without requiresEnv suffix", () => {
|
|
1162
|
-
const root
|
|
1175
|
+
const root= testProgram({
|
|
1163
1176
|
key: "app",
|
|
1164
1177
|
description: "",
|
|
1165
|
-
mcpServer: {},
|
|
1178
|
+
mcpServer: { enabled: true },
|
|
1166
1179
|
commands: [
|
|
1167
1180
|
{
|
|
1168
1181
|
key: "x",
|
|
@@ -1171,16 +1184,16 @@ test("mcpTool.description override wins without requiresEnv suffix", () => {
|
|
|
1171
1184
|
handler: () => {},
|
|
1172
1185
|
},
|
|
1173
1186
|
],
|
|
1174
|
-
};
|
|
1187
|
+
});
|
|
1175
1188
|
const tools = collectMcpTools(root);
|
|
1176
1189
|
expect(tools[0]!.description).toBe("custom");
|
|
1177
1190
|
});
|
|
1178
1191
|
|
|
1179
1192
|
test("mcpTool.requiresEnv appended to auto description", () => {
|
|
1180
|
-
const root
|
|
1193
|
+
const root= testProgram({
|
|
1181
1194
|
key: "app",
|
|
1182
1195
|
description: "",
|
|
1183
|
-
mcpServer: {},
|
|
1196
|
+
mcpServer: { enabled: true },
|
|
1184
1197
|
commands: [
|
|
1185
1198
|
{
|
|
1186
1199
|
key: "x",
|
|
@@ -1189,50 +1202,96 @@ test("mcpTool.requiresEnv appended to auto description", () => {
|
|
|
1189
1202
|
handler: () => {},
|
|
1190
1203
|
},
|
|
1191
1204
|
],
|
|
1192
|
-
};
|
|
1205
|
+
});
|
|
1193
1206
|
const tools = collectMcpTools(root);
|
|
1194
1207
|
expect(tools[0]!.description).toContain("[requires env: TOKEN]");
|
|
1195
1208
|
});
|
|
1196
1209
|
|
|
1197
1210
|
test("cliValidateProgram rejects duplicate mcpResources URIs", () => {
|
|
1198
|
-
const root
|
|
1211
|
+
const root = testProgram({
|
|
1199
1212
|
key: "app",
|
|
1200
1213
|
description: "",
|
|
1201
1214
|
mcpServer: {
|
|
1215
|
+
enabled: true,
|
|
1202
1216
|
resources: [
|
|
1203
1217
|
{ uri: "a://1", name: "a", load: () => "a" },
|
|
1204
1218
|
{ uri: "a://1", name: "b", load: () => "b" },
|
|
1205
1219
|
],
|
|
1206
1220
|
},
|
|
1207
1221
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
1208
|
-
};
|
|
1222
|
+
});
|
|
1209
1223
|
expect(() => cliValidateProgram(root)).toThrow(/URIs must be unique/);
|
|
1210
1224
|
});
|
|
1211
1225
|
|
|
1226
|
+
test("cliValidateProgram rejects empty mcpServer", () => {
|
|
1227
|
+
const root = testProgram({
|
|
1228
|
+
key: "app",
|
|
1229
|
+
description: "",
|
|
1230
|
+
mcpServer: {} as { enabled: boolean },
|
|
1231
|
+
handler: () => {},
|
|
1232
|
+
});
|
|
1233
|
+
expect(() => cliValidateProgram(root)).toThrow(/mcpServer requires enabled: true/);
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
test("resolveMcpSchemaUri uses sanitized root key", () => {
|
|
1237
|
+
const root = testProgram({
|
|
1238
|
+
key: "nested.ts",
|
|
1239
|
+
description: "",
|
|
1240
|
+
mcpServer: { enabled: true },
|
|
1241
|
+
handler: () => {},
|
|
1242
|
+
});
|
|
1243
|
+
expect(resolveMcpSchemaUri(root)).toBe("nested_ts://schema");
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
test("resolveMcpSchemaUri uses plain key when alphanumeric", () => {
|
|
1247
|
+
const root = testProgram({
|
|
1248
|
+
key: "qa",
|
|
1249
|
+
description: "",
|
|
1250
|
+
mcpServer: { enabled: true },
|
|
1251
|
+
handler: () => {},
|
|
1252
|
+
});
|
|
1253
|
+
expect(resolveMcpSchemaUri(root)).toBe("qa://schema");
|
|
1254
|
+
});
|
|
1255
|
+
|
|
1256
|
+
test("cliValidateProgram rejects resource URI matching default schema URI", () => {
|
|
1257
|
+
const root = testProgram({
|
|
1258
|
+
key: "app",
|
|
1259
|
+
description: "",
|
|
1260
|
+
mcpServer: {
|
|
1261
|
+
enabled: true,
|
|
1262
|
+
resources: [{ uri: "app://schema", name: "dup", load: () => "" }],
|
|
1263
|
+
},
|
|
1264
|
+
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
1265
|
+
});
|
|
1266
|
+
expect(() => cliValidateProgram(root)).toThrow(/conflicts with the built-in schema resource/);
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1212
1269
|
test("cliValidateProgram rejects resource URI matching schemaResourceUri", () => {
|
|
1213
|
-
const root
|
|
1270
|
+
const root = testProgram({
|
|
1214
1271
|
key: "app",
|
|
1215
1272
|
description: "",
|
|
1216
1273
|
mcpServer: {
|
|
1274
|
+
enabled: true,
|
|
1217
1275
|
schemaResourceUri: "custom://schema",
|
|
1218
1276
|
resources: [{ uri: "custom://schema", name: "dup", load: () => "" }],
|
|
1219
1277
|
},
|
|
1220
1278
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
1221
|
-
};
|
|
1279
|
+
});
|
|
1222
1280
|
expect(() => cliValidateProgram(root)).toThrow(/conflicts with the built-in schema resource/);
|
|
1223
1281
|
});
|
|
1224
1282
|
|
|
1225
1283
|
test("allMcpResources includes custom resources", () => {
|
|
1226
|
-
const root
|
|
1284
|
+
const root = testProgram({
|
|
1227
1285
|
key: "app",
|
|
1228
1286
|
description: "",
|
|
1229
1287
|
mcpServer: {
|
|
1288
|
+
enabled: true,
|
|
1230
1289
|
resources: [{ uri: "test://x", name: "x", load: () => "body" }],
|
|
1231
1290
|
},
|
|
1232
1291
|
commands: [{ key: "leaf", description: "", handler: () => {} }],
|
|
1233
|
-
};
|
|
1292
|
+
});
|
|
1234
1293
|
const resources = allMcpResources(root);
|
|
1235
|
-
expect(resources.map((r) => r.uri)).toContain("
|
|
1294
|
+
expect(resources.map((r) => r.uri)).toContain("app://schema");
|
|
1236
1295
|
expect(resources.map((r) => r.uri)).toContain("test://x");
|
|
1237
1296
|
});
|
|
1238
1297
|
|
|
@@ -1266,7 +1325,7 @@ test("loadEnvFile overwrites existing keys", () => {
|
|
|
1266
1325
|
});
|
|
1267
1326
|
|
|
1268
1327
|
test("Enum completions list choices in bash script", () => {
|
|
1269
|
-
const root
|
|
1328
|
+
const root= testProgram({
|
|
1270
1329
|
key: "app",
|
|
1271
1330
|
description: "",
|
|
1272
1331
|
commands: [
|
|
@@ -1279,8 +1338,8 @@ test("Enum completions list choices in bash script", () => {
|
|
|
1279
1338
|
handler: () => {},
|
|
1280
1339
|
},
|
|
1281
1340
|
],
|
|
1282
|
-
};
|
|
1283
|
-
const bash = completionBashScript(root);
|
|
1341
|
+
});
|
|
1342
|
+
const bash = completionBashScript(cliPresentationRoot(root));
|
|
1284
1343
|
expect(bash).toContain("--mode) COMPREPLY=");
|
|
1285
1344
|
expect(bash).toContain("dev");
|
|
1286
1345
|
expect(bash).toContain("prod");
|
|
@@ -1293,7 +1352,7 @@ test("MCP resources/list includes custom resource", async () => {
|
|
|
1293
1352
|
);
|
|
1294
1353
|
const res = responses.get(10) as { result: { resources: { uri: string }[] } };
|
|
1295
1354
|
const uris = res.result.resources.map((r) => r.uri);
|
|
1296
|
-
expect(uris).toContain("
|
|
1355
|
+
expect(uris).toContain("mcp_test://schema");
|
|
1297
1356
|
expect(uris).toContain("test://hello");
|
|
1298
1357
|
});
|
|
1299
1358
|
|
|
@@ -1372,7 +1431,7 @@ test("MCP envFile loads vars for tool handlers", async () => {
|
|
|
1372
1431
|
// ── v1.3 parser ergonomics ────────────────────────────────────────────────────
|
|
1373
1432
|
|
|
1374
1433
|
function varargsReadFixture(): CliProgram {
|
|
1375
|
-
return {
|
|
1434
|
+
return testProgram({
|
|
1376
1435
|
key: "app",
|
|
1377
1436
|
description: "",
|
|
1378
1437
|
commands: [
|
|
@@ -1398,11 +1457,11 @@ function varargsReadFixture(): CliProgram {
|
|
|
1398
1457
|
handler: () => {},
|
|
1399
1458
|
},
|
|
1400
1459
|
],
|
|
1401
|
-
};
|
|
1460
|
+
});
|
|
1402
1461
|
}
|
|
1403
1462
|
|
|
1404
1463
|
function nestedDocsFallbackFixture(): CliProgram {
|
|
1405
|
-
return {
|
|
1464
|
+
return testProgram({
|
|
1406
1465
|
key: "app",
|
|
1407
1466
|
description: "",
|
|
1408
1467
|
commands: [
|
|
@@ -1425,7 +1484,7 @@ function nestedDocsFallbackFixture(): CliProgram {
|
|
|
1425
1484
|
],
|
|
1426
1485
|
},
|
|
1427
1486
|
],
|
|
1428
|
-
};
|
|
1487
|
+
});
|
|
1429
1488
|
}
|
|
1430
1489
|
|
|
1431
1490
|
test("nested fallback routes to default when argv exhausted at router", () => {
|
|
@@ -1437,7 +1496,7 @@ test("nested fallback routes to default when argv exhausted at router", () => {
|
|
|
1437
1496
|
});
|
|
1438
1497
|
|
|
1439
1498
|
test("nested fallback MissingOrUnknown routes unknown token to default", () => {
|
|
1440
|
-
const root
|
|
1499
|
+
const root= testProgram({
|
|
1441
1500
|
key: "app",
|
|
1442
1501
|
description: "",
|
|
1443
1502
|
commands: [
|
|
@@ -1469,7 +1528,7 @@ test("nested fallback MissingOrUnknown routes unknown token to default", () => {
|
|
|
1469
1528
|
],
|
|
1470
1529
|
},
|
|
1471
1530
|
],
|
|
1472
|
-
};
|
|
1531
|
+
});
|
|
1473
1532
|
cliValidateProgram(root);
|
|
1474
1533
|
const pr = postParseValidate(root, parse(root, ["docs", "extra-topic"]));
|
|
1475
1534
|
expect(pr.kind).toBe(ParseKind.Ok);
|
|
@@ -1486,7 +1545,7 @@ test("nested fallback MissingOnly errors on unknown subcommand", () => {
|
|
|
1486
1545
|
});
|
|
1487
1546
|
|
|
1488
1547
|
test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
|
|
1489
|
-
const root
|
|
1548
|
+
const root= testProgram({
|
|
1490
1549
|
key: "app",
|
|
1491
1550
|
description: "",
|
|
1492
1551
|
commands: [
|
|
@@ -1503,7 +1562,7 @@ test("cliValidateProgram rejects invalid nested fallbackCommand", () => {
|
|
|
1503
1562
|
],
|
|
1504
1563
|
},
|
|
1505
1564
|
],
|
|
1506
|
-
};
|
|
1565
|
+
});
|
|
1507
1566
|
expect(() => cliValidateProgram(root)).toThrow(/fallbackCommand 'missing' is not a child of 'docs'/);
|
|
1508
1567
|
});
|
|
1509
1568
|
|
|
@@ -1578,7 +1637,7 @@ test("varargs scoped help in tail", () => {
|
|
|
1578
1637
|
});
|
|
1579
1638
|
|
|
1580
1639
|
test("ctx.positional returns single slot value", async () => {
|
|
1581
|
-
const root
|
|
1640
|
+
const root= testProgram({
|
|
1582
1641
|
key: "app",
|
|
1583
1642
|
description: "",
|
|
1584
1643
|
commands: [
|
|
@@ -1586,12 +1645,12 @@ test("ctx.positional returns single slot value", async () => {
|
|
|
1586
1645
|
key: "x",
|
|
1587
1646
|
description: "",
|
|
1588
1647
|
positionals: [{ name: "path", description: "", kind: CliOptionKind.String }],
|
|
1589
|
-
handler: (ctx) => {
|
|
1648
|
+
handler: (ctx: CliContext) => {
|
|
1590
1649
|
captured = ctx.positional("path");
|
|
1591
1650
|
},
|
|
1592
1651
|
},
|
|
1593
1652
|
],
|
|
1594
|
-
};
|
|
1653
|
+
});
|
|
1595
1654
|
let captured: string | string[] | undefined;
|
|
1596
1655
|
cliValidateProgram(root);
|
|
1597
1656
|
await cliInvoke(root, ["x", "./file"]);
|
|
@@ -1612,7 +1671,7 @@ test("ctx.positional returns varargs array", async () => {
|
|
|
1612
1671
|
});
|
|
1613
1672
|
|
|
1614
1673
|
test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
1615
|
-
const root
|
|
1674
|
+
const root= testProgram({
|
|
1616
1675
|
key: "app",
|
|
1617
1676
|
description: "",
|
|
1618
1677
|
commands: [
|
|
@@ -1622,12 +1681,12 @@ test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
|
1622
1681
|
positionals: [
|
|
1623
1682
|
{ name: "opt", description: "", kind: CliOptionKind.String, argMin: 0, argMax: 1 },
|
|
1624
1683
|
],
|
|
1625
|
-
handler: (ctx) => {
|
|
1684
|
+
handler: (ctx: CliContext) => {
|
|
1626
1685
|
captured = ctx.positional("opt");
|
|
1627
1686
|
},
|
|
1628
1687
|
},
|
|
1629
1688
|
],
|
|
1630
|
-
};
|
|
1689
|
+
});
|
|
1631
1690
|
let captured: string | string[] | undefined;
|
|
1632
1691
|
cliValidateProgram(root);
|
|
1633
1692
|
await cliInvoke(root, ["x"]);
|
|
@@ -1682,6 +1741,7 @@ test("mcpToolCallToArgv empty string varargs appends nothing", () => {
|
|
|
1682
1741
|
test("install config on non-root node is rejected", () => {
|
|
1683
1742
|
const root = {
|
|
1684
1743
|
key: "app",
|
|
1744
|
+
version: "0.0.0",
|
|
1685
1745
|
description: "",
|
|
1686
1746
|
commands: [
|
|
1687
1747
|
{
|