argsbarg 1.5.0 → 2.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/.cursor/plans/cliprogram_capabilities_refactor_081e1737.plan.md +224 -0
- package/CHANGELOG.md +24 -1
- package/README.md +12 -8
- package/docs/install.md +2 -2
- package/docs/mcp.md +3 -3
- package/examples/mcp-test.ts +3 -3
- package/examples/minimal.ts +3 -3
- package/examples/nested.ts +3 -3
- package/examples/option-required.ts +3 -3
- package/index.d.ts +40 -37
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +3 -3
- package/src/builtins/completion-bash.ts +3 -3
- package/src/builtins/completion-fish.ts +2 -2
- package/src/builtins/completion-group.ts +2 -2
- package/src/builtins/completion-zsh.ts +3 -3
- package/src/builtins/dispatch.ts +41 -26
- package/src/builtins/export.ts +15 -8
- package/src/builtins/install.ts +3 -3
- package/src/builtins/mcp.ts +2 -2
- package/src/builtins/presentation.ts +34 -23
- package/src/builtins/scopes.ts +9 -8
- package/src/capabilities.ts +32 -0
- package/src/context.ts +21 -6
- package/src/help.ts +21 -9
- package/src/index.test.ts +71 -64
- package/src/index.ts +1 -1
- package/src/install/binary.ts +3 -3
- package/src/install/completions.ts +2 -2
- package/src/install/detect-installed.ts +1 -1
- package/src/install/index.ts +4 -4
- package/src/install/install.test.ts +2 -2
- package/src/install/mcp-config.ts +2 -2
- package/src/install/paths.ts +3 -3
- package/src/install/plan.ts +4 -4
- package/src/install/status.ts +2 -2
- package/src/install/uninstall.ts +2 -2
- package/src/invoke.ts +14 -5
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +16 -16
- package/src/mcp.ts +2 -2
- package/src/parse.ts +55 -27
- package/src/runtime.ts +33 -24
- package/src/schema.ts +6 -6
- package/src/skill/generate.ts +6 -6
- package/src/skill/install.ts +2 -2
- package/src/types.test.ts +40 -0
- package/src/types.ts +54 -44
- package/src/validate.ts +89 -71
package/src/index.test.ts
CHANGED
|
@@ -7,9 +7,12 @@ It keeps the CLI contract stable by catching routing, option handling, and gener
|
|
|
7
7
|
shell output regressions.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
10
11
|
import { completionBashScript, completionZshScript } from "./completion.ts";
|
|
11
12
|
import { cliHelpRender } from "./help.ts";
|
|
12
|
-
import {
|
|
13
|
+
import { CliProgram, CliFallbackMode, CliOptionKind, cliInvoke } from "./index.ts";
|
|
14
|
+
import type { CliLeaf } from "./types.ts";
|
|
15
|
+
import { isCliRouter } from "./types.ts";
|
|
13
16
|
import {
|
|
14
17
|
allMcpResources,
|
|
15
18
|
collectMcpTools,
|
|
@@ -31,7 +34,7 @@ import { tmpdir } from "node:os";
|
|
|
31
34
|
import { join } from "node:path";
|
|
32
35
|
|
|
33
36
|
test("bundled short presence flags", () => {
|
|
34
|
-
const root:
|
|
37
|
+
const root: CliProgram = {
|
|
35
38
|
key: "app",
|
|
36
39
|
description: "",
|
|
37
40
|
commands: [
|
|
@@ -64,7 +67,7 @@ test("bundled short presence flags", () => {
|
|
|
64
67
|
});
|
|
65
68
|
|
|
66
69
|
test("long option equals", () => {
|
|
67
|
-
const root:
|
|
70
|
+
const root: CliProgram = {
|
|
68
71
|
key: "app",
|
|
69
72
|
description: "",
|
|
70
73
|
commands: [
|
|
@@ -89,7 +92,7 @@ test("long option equals", () => {
|
|
|
89
92
|
});
|
|
90
93
|
|
|
91
94
|
test("fallback missing or unknown root flags", () => {
|
|
92
|
-
const root:
|
|
95
|
+
const root: CliProgram = {
|
|
93
96
|
key: "app",
|
|
94
97
|
description: "",
|
|
95
98
|
commands: [
|
|
@@ -117,7 +120,7 @@ test("fallback missing or unknown root flags", () => {
|
|
|
117
120
|
});
|
|
118
121
|
|
|
119
122
|
test("unknown command", () => {
|
|
120
|
-
const root:
|
|
123
|
+
const root: CliProgram = {
|
|
121
124
|
key: "app",
|
|
122
125
|
description: "",
|
|
123
126
|
commands: [{ key: "hello", description: "", handler: () => {} }],
|
|
@@ -129,7 +132,7 @@ test("unknown command", () => {
|
|
|
129
132
|
});
|
|
130
133
|
|
|
131
134
|
test("implicit help empty", () => {
|
|
132
|
-
const root:
|
|
135
|
+
const root: CliProgram = {
|
|
133
136
|
key: "app",
|
|
134
137
|
description: "",
|
|
135
138
|
commands: [{ key: "x", description: "", handler: () => {} }],
|
|
@@ -141,7 +144,7 @@ test("implicit help empty", () => {
|
|
|
141
144
|
});
|
|
142
145
|
|
|
143
146
|
test("invalid number post validate", () => {
|
|
144
|
-
const root:
|
|
147
|
+
const root: CliProgram = {
|
|
145
148
|
key: "app",
|
|
146
149
|
description: "",
|
|
147
150
|
commands: [
|
|
@@ -167,7 +170,7 @@ test("invalid number post validate", () => {
|
|
|
167
170
|
});
|
|
168
171
|
|
|
169
172
|
test("supports scientific notation in numbers", () => {
|
|
170
|
-
const root:
|
|
173
|
+
const root: CliProgram = {
|
|
171
174
|
key: "app",
|
|
172
175
|
description: "",
|
|
173
176
|
commands: [
|
|
@@ -195,7 +198,7 @@ test("supports scientific notation in numbers", () => {
|
|
|
195
198
|
|
|
196
199
|
|
|
197
200
|
test("completion scripts contain app name", () => {
|
|
198
|
-
const root:
|
|
201
|
+
const root: CliProgram = {
|
|
199
202
|
key: "myapp",
|
|
200
203
|
description: "Test",
|
|
201
204
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
@@ -212,7 +215,7 @@ test("completion scripts contain app name", () => {
|
|
|
212
215
|
});
|
|
213
216
|
|
|
214
217
|
test("completion scripts do not emit invalid bash substitutions", () => {
|
|
215
|
-
const root:
|
|
218
|
+
const root: CliProgram = {
|
|
216
219
|
key: "app",
|
|
217
220
|
description: "Test",
|
|
218
221
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
@@ -223,7 +226,7 @@ test("completion scripts do not emit invalid bash substitutions", () => {
|
|
|
223
226
|
});
|
|
224
227
|
|
|
225
228
|
test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
226
|
-
const root:
|
|
229
|
+
const root: CliProgram = {
|
|
227
230
|
key: "app",
|
|
228
231
|
description: "Test",
|
|
229
232
|
commands: [
|
|
@@ -240,7 +243,7 @@ test("completion scripts escape shell-sensitive command text in zsh", () => {
|
|
|
240
243
|
});
|
|
241
244
|
|
|
242
245
|
test("completion scripts keep dotted app names in registration names", () => {
|
|
243
|
-
const root:
|
|
246
|
+
const root: CliProgram = {
|
|
244
247
|
key: "minimal.ts",
|
|
245
248
|
description: "Test",
|
|
246
249
|
commands: [{ key: "hello", description: "Say hello.", handler: () => {} }],
|
|
@@ -255,7 +258,7 @@ test("completion scripts keep dotted app names in registration names", () => {
|
|
|
255
258
|
});
|
|
256
259
|
|
|
257
260
|
test("trailing options after bounded positionals", () => {
|
|
258
|
-
const root:
|
|
261
|
+
const root: CliProgram = {
|
|
259
262
|
key: "app",
|
|
260
263
|
description: "",
|
|
261
264
|
commands: [
|
|
@@ -288,7 +291,7 @@ test("trailing options after bounded positionals", () => {
|
|
|
288
291
|
});
|
|
289
292
|
|
|
290
293
|
test("trailing options include parent-scoped flags", () => {
|
|
291
|
-
const root:
|
|
294
|
+
const root: CliProgram = {
|
|
292
295
|
key: "app",
|
|
293
296
|
description: "",
|
|
294
297
|
commands: [
|
|
@@ -337,7 +340,7 @@ test("trailing options include parent-scoped flags", () => {
|
|
|
337
340
|
});
|
|
338
341
|
|
|
339
342
|
test("varargs tail parses trailing options", () => {
|
|
340
|
-
const root:
|
|
343
|
+
const root: CliProgram = {
|
|
341
344
|
key: "app",
|
|
342
345
|
description: "",
|
|
343
346
|
commands: [
|
|
@@ -372,7 +375,7 @@ test("varargs tail parses trailing options", () => {
|
|
|
372
375
|
});
|
|
373
376
|
|
|
374
377
|
test("stops parsing options at --", () => {
|
|
375
|
-
const root:
|
|
378
|
+
const root: CliProgram = {
|
|
376
379
|
key: "app",
|
|
377
380
|
description: "",
|
|
378
381
|
commands: [
|
|
@@ -407,7 +410,7 @@ test("stops parsing options at --", () => {
|
|
|
407
410
|
});
|
|
408
411
|
|
|
409
412
|
test("missing required option returns error", () => {
|
|
410
|
-
const root:
|
|
413
|
+
const root: CliProgram = {
|
|
411
414
|
key: "app",
|
|
412
415
|
description: "",
|
|
413
416
|
options: [
|
|
@@ -433,7 +436,7 @@ test("missing required option returns error", () => {
|
|
|
433
436
|
});
|
|
434
437
|
|
|
435
438
|
test("provided required option parses ok", () => {
|
|
436
|
-
const root:
|
|
439
|
+
const root: CliProgram = {
|
|
437
440
|
key: "app",
|
|
438
441
|
description: "",
|
|
439
442
|
commands: [
|
|
@@ -459,7 +462,7 @@ test("provided required option parses ok", () => {
|
|
|
459
462
|
});
|
|
460
463
|
|
|
461
464
|
test("presence option cannot be required", () => {
|
|
462
|
-
const root:
|
|
465
|
+
const root: CliProgram = {
|
|
463
466
|
key: "app",
|
|
464
467
|
description: "",
|
|
465
468
|
options: [
|
|
@@ -527,7 +530,7 @@ test("leaf root help lists completion built-in", async () => {
|
|
|
527
530
|
});
|
|
528
531
|
|
|
529
532
|
test("parse recognizes --schema at the program root", () => {
|
|
530
|
-
const root:
|
|
533
|
+
const root: CliProgram = {
|
|
531
534
|
key: "app",
|
|
532
535
|
description: "demo",
|
|
533
536
|
commands: [
|
|
@@ -544,7 +547,7 @@ test("parse recognizes --schema at the program root", () => {
|
|
|
544
547
|
});
|
|
545
548
|
|
|
546
549
|
test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
547
|
-
const root:
|
|
550
|
+
const root: CliProgram = {
|
|
548
551
|
key: "app",
|
|
549
552
|
description: "demo",
|
|
550
553
|
commands: [
|
|
@@ -574,7 +577,7 @@ test("cliSchemaJson omits handlers and completion built-ins", () => {
|
|
|
574
577
|
});
|
|
575
578
|
|
|
576
579
|
test("reserved option name schema is rejected", () => {
|
|
577
|
-
const root:
|
|
580
|
+
const root: CliProgram = {
|
|
578
581
|
key: "app",
|
|
579
582
|
description: "",
|
|
580
583
|
commands: [
|
|
@@ -596,7 +599,7 @@ test("reserved option name schema is rejected", () => {
|
|
|
596
599
|
});
|
|
597
600
|
|
|
598
601
|
test("root help lists --schema built-in", () => {
|
|
599
|
-
const root:
|
|
602
|
+
const root: CliProgram = {
|
|
600
603
|
key: "app",
|
|
601
604
|
description: "demo",
|
|
602
605
|
commands: [
|
|
@@ -607,13 +610,13 @@ test("root help lists --schema built-in", () => {
|
|
|
607
610
|
},
|
|
608
611
|
],
|
|
609
612
|
};
|
|
610
|
-
const help = cliHelpRender(root, [], false);
|
|
613
|
+
const help = cliHelpRender(cliPresentationRoot(root), [], false);
|
|
611
614
|
expect(help).toContain("--schema");
|
|
612
615
|
expect(help).toContain("Print the full command tree as JSON.");
|
|
613
616
|
});
|
|
614
617
|
|
|
615
618
|
test("nested help omits --schema built-in", () => {
|
|
616
|
-
const root:
|
|
619
|
+
const root: CliProgram = {
|
|
617
620
|
key: "app",
|
|
618
621
|
description: "demo",
|
|
619
622
|
commands: [
|
|
@@ -624,12 +627,12 @@ test("nested help omits --schema built-in", () => {
|
|
|
624
627
|
},
|
|
625
628
|
],
|
|
626
629
|
};
|
|
627
|
-
const help = cliHelpRender(root, ["x"], false);
|
|
630
|
+
const help = cliHelpRender(cliPresentationRoot(root), ["x"], false);
|
|
628
631
|
expect(help).not.toContain("--schema");
|
|
629
632
|
});
|
|
630
633
|
|
|
631
634
|
test("completion scripts offer --schema at the program root only", () => {
|
|
632
|
-
const root:
|
|
635
|
+
const root: CliProgram = {
|
|
633
636
|
key: "myapp",
|
|
634
637
|
description: "",
|
|
635
638
|
commands: [
|
|
@@ -655,7 +658,7 @@ test("completion scripts offer --schema at the program root only", () => {
|
|
|
655
658
|
expect(zsh).toContain("'--schema:Print the full command tree as JSON.'");
|
|
656
659
|
});
|
|
657
660
|
|
|
658
|
-
const nestedMcpFixture:
|
|
661
|
+
const nestedMcpFixture: CliProgram = {
|
|
659
662
|
key: "nested.ts",
|
|
660
663
|
description: "Nested groups demo.",
|
|
661
664
|
mcpServer: { name: "nested-demo", version: "1.0.0" },
|
|
@@ -811,7 +814,7 @@ test("mcpToolCallToArgv expands varargs positionals", () => {
|
|
|
811
814
|
});
|
|
812
815
|
|
|
813
816
|
test("reserved command name install is rejected", () => {
|
|
814
|
-
const root:
|
|
817
|
+
const root: CliProgram = {
|
|
815
818
|
key: "app",
|
|
816
819
|
description: "",
|
|
817
820
|
commands: [
|
|
@@ -826,7 +829,7 @@ test("reserved command name install is rejected", () => {
|
|
|
826
829
|
});
|
|
827
830
|
|
|
828
831
|
test("top-level command name mcp is allowed without mcpServer", () => {
|
|
829
|
-
const root:
|
|
832
|
+
const root: CliProgram = {
|
|
830
833
|
key: "app",
|
|
831
834
|
description: "",
|
|
832
835
|
commands: [
|
|
@@ -841,7 +844,7 @@ test("top-level command name mcp is allowed without mcpServer", () => {
|
|
|
841
844
|
});
|
|
842
845
|
|
|
843
846
|
test("top-level command name mcp is rejected when mcpServer is set", () => {
|
|
844
|
-
const root:
|
|
847
|
+
const root: CliProgram = {
|
|
845
848
|
key: "app",
|
|
846
849
|
description: "",
|
|
847
850
|
mcpServer: {},
|
|
@@ -857,7 +860,7 @@ test("top-level command name mcp is rejected when mcpServer is set", () => {
|
|
|
857
860
|
});
|
|
858
861
|
|
|
859
862
|
test("mcpServer on non-root node is rejected", () => {
|
|
860
|
-
const root
|
|
863
|
+
const root = {
|
|
861
864
|
key: "app",
|
|
862
865
|
description: "",
|
|
863
866
|
commands: [
|
|
@@ -868,12 +871,12 @@ test("mcpServer on non-root node is rejected", () => {
|
|
|
868
871
|
handler: () => {},
|
|
869
872
|
},
|
|
870
873
|
],
|
|
871
|
-
};
|
|
874
|
+
} as unknown as CliProgram;
|
|
872
875
|
expect(() => cliValidateRoot(root)).toThrow(/mcpServer is only supported on the program root/);
|
|
873
876
|
});
|
|
874
877
|
|
|
875
878
|
test("mcpTool on root is rejected", () => {
|
|
876
|
-
const root:
|
|
879
|
+
const root: CliProgram = {
|
|
877
880
|
key: "app",
|
|
878
881
|
description: "",
|
|
879
882
|
mcpTool: { enabled: false },
|
|
@@ -883,7 +886,7 @@ test("mcpTool on root is rejected", () => {
|
|
|
883
886
|
});
|
|
884
887
|
|
|
885
888
|
test("mcpTool on routing node is rejected", () => {
|
|
886
|
-
const root:
|
|
889
|
+
const root: CliProgram = {
|
|
887
890
|
key: "app",
|
|
888
891
|
description: "",
|
|
889
892
|
commands: [
|
|
@@ -1040,7 +1043,7 @@ test("minimal.ts mcp without opt-in fails", async () => {
|
|
|
1040
1043
|
test("ctx.invocation is cli via cliRun", async () => {
|
|
1041
1044
|
const indexPath = join(import.meta.dir, "index.ts");
|
|
1042
1045
|
const { stdout } = await $`bun -e ${`
|
|
1043
|
-
import { cliRun,
|
|
1046
|
+
import { cliRun, CliProgram } from ${JSON.stringify(indexPath)};
|
|
1044
1047
|
const cli = { key: "t", description: "d", handler: (ctx) => console.log(ctx.invocation) };
|
|
1045
1048
|
await cliRun(cli, []);
|
|
1046
1049
|
`}`.quiet();
|
|
@@ -1049,7 +1052,7 @@ await cliRun(cli, []);
|
|
|
1049
1052
|
|
|
1050
1053
|
test("ctx.invocation is mcp via cliInvoke", async () => {
|
|
1051
1054
|
let seen = "";
|
|
1052
|
-
const root:
|
|
1055
|
+
const root: CliProgram = {
|
|
1053
1056
|
key: "app",
|
|
1054
1057
|
description: "",
|
|
1055
1058
|
handler: (ctx) => {
|
|
@@ -1062,7 +1065,7 @@ test("ctx.invocation is mcp via cliInvoke", async () => {
|
|
|
1062
1065
|
expect(seen).toBe("mcp");
|
|
1063
1066
|
});
|
|
1064
1067
|
|
|
1065
|
-
const enumMcpFixture:
|
|
1068
|
+
const enumMcpFixture: CliProgram = {
|
|
1066
1069
|
key: "app",
|
|
1067
1070
|
description: "",
|
|
1068
1071
|
mcpServer: {},
|
|
@@ -1092,7 +1095,7 @@ test("Enum option inputSchema includes enum array", () => {
|
|
|
1092
1095
|
});
|
|
1093
1096
|
|
|
1094
1097
|
test("cliInvoke rejects invalid Enum value", async () => {
|
|
1095
|
-
const root:
|
|
1098
|
+
const root: CliProgram = {
|
|
1096
1099
|
key: "app",
|
|
1097
1100
|
description: "",
|
|
1098
1101
|
handler: () => {},
|
|
@@ -1113,7 +1116,7 @@ test("cliInvoke rejects invalid Enum value", async () => {
|
|
|
1113
1116
|
});
|
|
1114
1117
|
|
|
1115
1118
|
test("cliInvoke accepts valid Enum value", async () => {
|
|
1116
|
-
const root:
|
|
1119
|
+
const root: CliProgram = {
|
|
1117
1120
|
key: "app",
|
|
1118
1121
|
description: "",
|
|
1119
1122
|
handler: (ctx) => {
|
|
@@ -1136,7 +1139,7 @@ test("cliInvoke accepts valid Enum value", async () => {
|
|
|
1136
1139
|
});
|
|
1137
1140
|
|
|
1138
1141
|
test("cliValidateRoot rejects Enum with no choices", () => {
|
|
1139
|
-
const root:
|
|
1142
|
+
const root: CliProgram = {
|
|
1140
1143
|
key: "app",
|
|
1141
1144
|
description: "",
|
|
1142
1145
|
handler: () => {},
|
|
@@ -1146,7 +1149,7 @@ test("cliValidateRoot rejects Enum with no choices", () => {
|
|
|
1146
1149
|
});
|
|
1147
1150
|
|
|
1148
1151
|
test("cliValidateRoot rejects Enum with duplicate choices", () => {
|
|
1149
|
-
const root:
|
|
1152
|
+
const root: CliProgram = {
|
|
1150
1153
|
key: "app",
|
|
1151
1154
|
description: "",
|
|
1152
1155
|
handler: () => {},
|
|
@@ -1156,7 +1159,7 @@ test("cliValidateRoot rejects Enum with duplicate choices", () => {
|
|
|
1156
1159
|
});
|
|
1157
1160
|
|
|
1158
1161
|
test("mcpTool.description override wins without requiresEnv suffix", () => {
|
|
1159
|
-
const root:
|
|
1162
|
+
const root: CliProgram = {
|
|
1160
1163
|
key: "app",
|
|
1161
1164
|
description: "",
|
|
1162
1165
|
mcpServer: {},
|
|
@@ -1174,7 +1177,7 @@ test("mcpTool.description override wins without requiresEnv suffix", () => {
|
|
|
1174
1177
|
});
|
|
1175
1178
|
|
|
1176
1179
|
test("mcpTool.requiresEnv appended to auto description", () => {
|
|
1177
|
-
const root:
|
|
1180
|
+
const root: CliProgram = {
|
|
1178
1181
|
key: "app",
|
|
1179
1182
|
description: "",
|
|
1180
1183
|
mcpServer: {},
|
|
@@ -1192,7 +1195,7 @@ test("mcpTool.requiresEnv appended to auto description", () => {
|
|
|
1192
1195
|
});
|
|
1193
1196
|
|
|
1194
1197
|
test("cliValidateRoot rejects duplicate mcpResources URIs", () => {
|
|
1195
|
-
const root:
|
|
1198
|
+
const root: CliProgram = {
|
|
1196
1199
|
key: "app",
|
|
1197
1200
|
description: "",
|
|
1198
1201
|
mcpServer: {
|
|
@@ -1207,7 +1210,7 @@ test("cliValidateRoot rejects duplicate mcpResources URIs", () => {
|
|
|
1207
1210
|
});
|
|
1208
1211
|
|
|
1209
1212
|
test("cliValidateRoot rejects resource URI matching schemaResourceUri", () => {
|
|
1210
|
-
const root:
|
|
1213
|
+
const root: CliProgram = {
|
|
1211
1214
|
key: "app",
|
|
1212
1215
|
description: "",
|
|
1213
1216
|
mcpServer: {
|
|
@@ -1220,7 +1223,7 @@ test("cliValidateRoot rejects resource URI matching schemaResourceUri", () => {
|
|
|
1220
1223
|
});
|
|
1221
1224
|
|
|
1222
1225
|
test("allMcpResources includes custom resources", () => {
|
|
1223
|
-
const root:
|
|
1226
|
+
const root: CliProgram = {
|
|
1224
1227
|
key: "app",
|
|
1225
1228
|
description: "",
|
|
1226
1229
|
mcpServer: {
|
|
@@ -1263,7 +1266,7 @@ test("loadEnvFile overwrites existing keys", () => {
|
|
|
1263
1266
|
});
|
|
1264
1267
|
|
|
1265
1268
|
test("Enum completions list choices in bash script", () => {
|
|
1266
|
-
const root:
|
|
1269
|
+
const root: CliProgram = {
|
|
1267
1270
|
key: "app",
|
|
1268
1271
|
description: "",
|
|
1269
1272
|
commands: [
|
|
@@ -1368,7 +1371,7 @@ test("MCP envFile loads vars for tool handlers", async () => {
|
|
|
1368
1371
|
|
|
1369
1372
|
// ── v1.3 parser ergonomics ────────────────────────────────────────────────────
|
|
1370
1373
|
|
|
1371
|
-
function varargsReadFixture():
|
|
1374
|
+
function varargsReadFixture(): CliProgram {
|
|
1372
1375
|
return {
|
|
1373
1376
|
key: "app",
|
|
1374
1377
|
description: "",
|
|
@@ -1398,7 +1401,7 @@ function varargsReadFixture(): CliCommand {
|
|
|
1398
1401
|
};
|
|
1399
1402
|
}
|
|
1400
1403
|
|
|
1401
|
-
function nestedDocsFallbackFixture():
|
|
1404
|
+
function nestedDocsFallbackFixture(): CliProgram {
|
|
1402
1405
|
return {
|
|
1403
1406
|
key: "app",
|
|
1404
1407
|
description: "",
|
|
@@ -1434,7 +1437,7 @@ test("nested fallback routes to default when argv exhausted at router", () => {
|
|
|
1434
1437
|
});
|
|
1435
1438
|
|
|
1436
1439
|
test("nested fallback MissingOrUnknown routes unknown token to default", () => {
|
|
1437
|
-
const root:
|
|
1440
|
+
const root: CliProgram = {
|
|
1438
1441
|
key: "app",
|
|
1439
1442
|
description: "",
|
|
1440
1443
|
commands: [
|
|
@@ -1483,7 +1486,7 @@ test("nested fallback MissingOnly errors on unknown subcommand", () => {
|
|
|
1483
1486
|
});
|
|
1484
1487
|
|
|
1485
1488
|
test("cliValidateRoot rejects invalid nested fallbackCommand", () => {
|
|
1486
|
-
const root:
|
|
1489
|
+
const root: CliProgram = {
|
|
1487
1490
|
key: "app",
|
|
1488
1491
|
description: "",
|
|
1489
1492
|
commands: [
|
|
@@ -1516,7 +1519,7 @@ test("nested router scoped help does not route to fallback", () => {
|
|
|
1516
1519
|
expect(pr.kind).toBe(ParseKind.Help);
|
|
1517
1520
|
expect(pr.helpPath).toEqual(["docs"]);
|
|
1518
1521
|
expect(pr.helpExplicit).toBe(true);
|
|
1519
|
-
const help = cliHelpRender(root, pr.helpPath, false);
|
|
1522
|
+
const help = cliHelpRender(cliPresentationRoot(root), pr.helpPath, false);
|
|
1520
1523
|
expect(help).toContain("api");
|
|
1521
1524
|
expect(help).toContain("guide");
|
|
1522
1525
|
});
|
|
@@ -1575,7 +1578,7 @@ test("varargs scoped help in tail", () => {
|
|
|
1575
1578
|
});
|
|
1576
1579
|
|
|
1577
1580
|
test("ctx.positional returns single slot value", async () => {
|
|
1578
|
-
const root:
|
|
1581
|
+
const root: CliProgram = {
|
|
1579
1582
|
key: "app",
|
|
1580
1583
|
description: "",
|
|
1581
1584
|
commands: [
|
|
@@ -1598,16 +1601,18 @@ test("ctx.positional returns single slot value", async () => {
|
|
|
1598
1601
|
test("ctx.positional returns varargs array", async () => {
|
|
1599
1602
|
const root = varargsReadFixture();
|
|
1600
1603
|
let captured: string | string[] | undefined;
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
+
if (isCliRouter(root)) {
|
|
1605
|
+
(root.commands[0] as CliLeaf).handler = (ctx) => {
|
|
1606
|
+
captured = ctx.positional("files");
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1604
1609
|
cliValidateRoot(root);
|
|
1605
1610
|
await cliInvoke(root, ["read", "a.txt", "b.txt"]);
|
|
1606
1611
|
expect(captured).toEqual(["a.txt", "b.txt"]);
|
|
1607
1612
|
});
|
|
1608
1613
|
|
|
1609
1614
|
test("ctx.positional returns undefined for absent optional slot", async () => {
|
|
1610
|
-
const root:
|
|
1615
|
+
const root: CliProgram = {
|
|
1611
1616
|
key: "app",
|
|
1612
1617
|
description: "",
|
|
1613
1618
|
commands: [
|
|
@@ -1633,10 +1638,12 @@ test("ctx.positional varargs matches ctx.args", async () => {
|
|
|
1633
1638
|
const root = varargsReadFixture();
|
|
1634
1639
|
let positional: string | string[] | undefined;
|
|
1635
1640
|
let args: string[] = [];
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1641
|
+
if (isCliRouter(root)) {
|
|
1642
|
+
(root.commands[0] as CliLeaf).handler = (ctx) => {
|
|
1643
|
+
positional = ctx.positional("files");
|
|
1644
|
+
args = ctx.args;
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1640
1647
|
cliValidateRoot(root);
|
|
1641
1648
|
await cliInvoke(root, ["read", "a.txt", "b.txt"]);
|
|
1642
1649
|
expect(positional).toEqual(args);
|
|
@@ -1673,7 +1680,7 @@ test("mcpToolCallToArgv empty string varargs appends nothing", () => {
|
|
|
1673
1680
|
// ── Skills ────────────────────────────────────────────────────────────────────
|
|
1674
1681
|
|
|
1675
1682
|
test("install config on non-root node is rejected", () => {
|
|
1676
|
-
const root
|
|
1683
|
+
const root = {
|
|
1677
1684
|
key: "app",
|
|
1678
1685
|
description: "",
|
|
1679
1686
|
commands: [
|
|
@@ -1684,7 +1691,7 @@ test("install config on non-root node is rejected", () => {
|
|
|
1684
1691
|
handler: () => {},
|
|
1685
1692
|
},
|
|
1686
1693
|
],
|
|
1687
|
-
};
|
|
1694
|
+
} as unknown as CliProgram;
|
|
1688
1695
|
expect(() => cliValidateRoot(root)).toThrow(/install is only supported on the program root/);
|
|
1689
1696
|
});
|
|
1690
1697
|
|
package/src/index.ts
CHANGED
|
@@ -13,7 +13,7 @@ export { CliContext } from "./context.ts";
|
|
|
13
13
|
export { cliErrWithHelp, cliRun } from "./runtime";
|
|
14
14
|
export { CliFallbackMode, CliOptionKind, CliSchemaValidationError } from "./types.ts";
|
|
15
15
|
export type {
|
|
16
|
-
|
|
16
|
+
CliProgram,
|
|
17
17
|
CliHandler,
|
|
18
18
|
CliInvocation,
|
|
19
19
|
CliMcpResource,
|
package/src/install/binary.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { InstallPaths } from "./paths.ts";
|
|
5
5
|
import {
|
|
6
6
|
buildPathRcBlock,
|
|
@@ -17,7 +17,7 @@ export interface BinaryInstallResult {
|
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
/** Copies the running binary to the install path and patches rc files when shells are detected. */
|
|
20
|
-
export function installBinary(root:
|
|
20
|
+
export function installBinary(root: CliProgram, paths: InstallPaths, dry: boolean): BinaryInstallResult {
|
|
21
21
|
const changed: string[] = [];
|
|
22
22
|
const shells = detectShells();
|
|
23
23
|
let patchedBashRc = false;
|
|
@@ -58,7 +58,7 @@ export function installBinary(root: CliCommand, paths: InstallPaths, dry: boolea
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
/** Removes binary and rc marker blocks. */
|
|
61
|
-
export function uninstallBinary(root:
|
|
61
|
+
export function uninstallBinary(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
|
|
62
62
|
const changed: string[] = [];
|
|
63
63
|
if (existsSync(paths.binaryPath)) {
|
|
64
64
|
if (!dry) unlinkSync(paths.binaryPath);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { completionBashScript, completionFishScript, completionZshScript } from "../builtins/index.ts";
|
|
5
5
|
import { cliPresentationRoot } from "../builtins/presentation.ts";
|
|
6
6
|
import { InstallPaths } from "./paths.ts";
|
|
7
7
|
import { detectShells } from "./shell.ts";
|
|
8
8
|
|
|
9
9
|
/** Writes shell completion scripts for detected shells. */
|
|
10
|
-
export function installCompletions(root:
|
|
10
|
+
export function installCompletions(root: CliProgram, paths: InstallPaths, dry: boolean): string[] {
|
|
11
11
|
const changed: string[] = [];
|
|
12
12
|
const shells = detectShells();
|
|
13
13
|
const schema = cliPresentationRoot(root);
|
package/src/install/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { readSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { CliProgram } from "../types.ts";
|
|
3
3
|
import { cliSkillInstall } from "../skill/install.ts";
|
|
4
4
|
import { checkMcpConflict, expectedMcpEntry } from "./mcp-config.ts";
|
|
5
5
|
import {
|
|
@@ -74,7 +74,7 @@ function promptConfirm(): boolean {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
function runSkillAction(
|
|
77
|
-
root:
|
|
77
|
+
root: CliProgram,
|
|
78
78
|
kind: "cursor-skill" | "claude-skill",
|
|
79
79
|
opts: InstallOpts,
|
|
80
80
|
): string[] {
|
|
@@ -87,7 +87,7 @@ function runSkillAction(
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
function executePlan(
|
|
90
|
-
root:
|
|
90
|
+
root: CliProgram,
|
|
91
91
|
actions: Array<InstallAction | UninstallAction>,
|
|
92
92
|
opts: InstallOpts,
|
|
93
93
|
): string[] {
|
|
@@ -114,7 +114,7 @@ function executePlan(
|
|
|
114
114
|
}
|
|
115
115
|
|
|
116
116
|
/** Main install command orchestrator. */
|
|
117
|
-
export async function cliInstall(root:
|
|
117
|
+
export async function cliInstall(root: CliProgram, rawOpts: Record<string, string>): Promise<never> {
|
|
118
118
|
const opts = parseInstallOpts(rawOpts);
|
|
119
119
|
const err = validateOpts(opts);
|
|
120
120
|
if (err) {
|
|
@@ -2,14 +2,14 @@ import { describe, expect, test, beforeEach, afterEach } from "bun:test";
|
|
|
2
2
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import {
|
|
5
|
+
import { CliProgram } from "../types.ts";
|
|
6
6
|
import { detectInstalledArtifacts } from "./detect-installed.ts";
|
|
7
7
|
import { resolveInstallPaths } from "./paths.ts";
|
|
8
8
|
import { buildInstallPlan } from "./plan.ts";
|
|
9
9
|
import { printInstallStatus } from "./status.ts";
|
|
10
10
|
import { parseInstallOpts } from "./index.ts";
|
|
11
11
|
|
|
12
|
-
const fixture:
|
|
12
|
+
const fixture: CliProgram = {
|
|
13
13
|
key: "testapp",
|
|
14
14
|
description: "Test",
|
|
15
15
|
mcpServer: { name: "testapp" },
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { InstallPaths } from "./paths.ts";
|
|
5
5
|
|
|
6
6
|
export interface McpServerEntry {
|
|
@@ -8,7 +8,7 @@ export interface McpServerEntry {
|
|
|
8
8
|
args: string[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export function expectedMcpEntry(root:
|
|
11
|
+
export function expectedMcpEntry(root: CliProgram): McpServerEntry {
|
|
12
12
|
return { command: root.key, args: ["mcp"] };
|
|
13
13
|
}
|
|
14
14
|
|
package/src/install/paths.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { CliProgram } from "../types.ts";
|
|
4
4
|
import { sanitizeToolSegment } from "../mcp/tools.ts";
|
|
5
5
|
|
|
6
6
|
export interface InstallPaths {
|
|
@@ -35,7 +35,7 @@ function expandTilde(path: string): string {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/** Resolves the binary install directory from CLI flag, env, or config. */
|
|
38
|
-
export function resolveBindir(root:
|
|
38
|
+
export function resolveBindir(root: CliProgram, prefix?: string): string {
|
|
39
39
|
const raw = prefix ?? process.env.INSTALL_PREFIX ?? root.install?.prefix;
|
|
40
40
|
if (raw) {
|
|
41
41
|
return expandTilde(raw);
|
|
@@ -44,7 +44,7 @@ export function resolveBindir(root: CliCommand, prefix?: string): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/** Resolves all install artifact paths for a program root. */
|
|
47
|
-
export function resolveInstallPaths(root:
|
|
47
|
+
export function resolveInstallPaths(root: CliProgram, opts: { prefix?: string }): InstallPaths {
|
|
48
48
|
const home = userHome();
|
|
49
49
|
const bindir = resolveBindir(root, opts.prefix);
|
|
50
50
|
const key = root.key;
|