argsbarg 3.4.1 → 3.5.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 +26 -1
- package/biome.json +29 -6
- package/bun.lock +22 -0
- package/docs/install.md +1 -1
- package/docs/mcp.md +63 -3
- package/index.d.ts +51 -51
- package/justfile +27 -6
- package/package.json +4 -2
- package/scripts/release.ts +26 -9
- package/src/builtins/builtins.test.ts +9 -4
- package/src/builtins/completion-bash.ts +74 -50
- package/src/builtins/completion-fish.ts +3 -8
- package/src/builtins/completion-group.ts +1 -1
- package/src/builtins/completion-zsh.ts +80 -42
- package/src/builtins/dispatch.ts +20 -16
- package/src/builtins/export.ts +19 -10
- package/src/builtins/index.ts +9 -4
- package/src/builtins/install.ts +10 -10
- package/src/builtins/mcp.ts +3 -3
- package/src/builtins/presentation.ts +8 -8
- package/src/builtins/scopes.ts +1 -1
- package/src/builtins/version.ts +1 -1
- package/src/completion.ts +4 -4
- package/src/docs/api-guide.test.ts +2 -2
- package/src/docs/api-guide.ts +2 -2
- package/src/docs/builtin.ts +27 -8
- package/src/docs/docs.test.ts +23 -12
- package/src/docs/mcp-guide.ts +112 -11
- package/src/docs/resolve.ts +10 -3
- package/src/docs/save.ts +11 -3
- package/src/headless.test.ts +8 -16
- package/src/help.ts +73 -43
- package/src/hidden-mcpb.test.ts +8 -10
- package/src/hidden.ts +2 -2
- package/src/index.test.ts +113 -89
- package/src/index.ts +24 -24
- package/src/install/binary.ts +12 -5
- package/src/install/completions.ts +7 -3
- package/src/install/detect-installed.ts +35 -4
- package/src/install/gh-release-update.ts +31 -23
- package/src/install/index.ts +69 -19
- package/src/install/install.test.ts +57 -8
- package/src/install/mcp-codex.test.ts +57 -0
- package/src/install/mcp-codex.ts +125 -0
- package/src/install/mcp-config.ts +12 -5
- package/src/install/mcp-opencode.test.ts +98 -0
- package/src/install/mcp-opencode.ts +149 -0
- package/src/install/paths.ts +51 -3
- package/src/install/plan.ts +96 -7
- package/src/install/shell.ts +1 -4
- package/src/install/status.ts +15 -7
- package/src/install/uninstall.ts +49 -5
- package/src/install/update.test.ts +2 -2
- package/src/install/update.ts +3 -1
- package/src/invoke.ts +12 -9
- package/src/mcp/bundle.ts +38 -14
- package/src/mcp/env.ts +7 -13
- package/src/mcp/server.ts +12 -6
- package/src/mcp/tools.ts +20 -4
- package/src/mcp.ts +3 -3
- package/src/parse.ts +96 -24
- package/src/runtime.ts +22 -12
- package/src/schema.ts +11 -5
- package/src/skill/generate.ts +4 -4
- package/src/skill/install.ts +6 -2
- package/src/validate.ts +21 -16
package/src/parse.ts
CHANGED
|
@@ -7,12 +7,11 @@ It keeps handler dispatch and help on one parser so the CLI behavior stays consi
|
|
|
7
7
|
across every entry path.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { CliContext } from "./context.ts";
|
|
11
10
|
import {
|
|
12
|
-
type CliLeaf,
|
|
13
|
-
CliNode,
|
|
14
11
|
CliFallbackMode,
|
|
15
|
-
|
|
12
|
+
type CliLeaf,
|
|
13
|
+
type CliNode,
|
|
14
|
+
type CliOption,
|
|
16
15
|
CliOptionKind,
|
|
17
16
|
isCliLeaf,
|
|
18
17
|
isCliRouter,
|
|
@@ -191,17 +190,30 @@ function consumeOptions(
|
|
|
191
190
|
|
|
192
191
|
if (tok === "--") {
|
|
193
192
|
idx += 1;
|
|
194
|
-
return {
|
|
193
|
+
return {
|
|
194
|
+
report: { err: null, stoppedOnUnknown: false, sawDoubleDash: true },
|
|
195
|
+
nextIndex: idx,
|
|
196
|
+
};
|
|
195
197
|
}
|
|
196
198
|
|
|
197
199
|
if (tok.startsWith("--")) {
|
|
198
200
|
const err = consumeLong(tok);
|
|
199
|
-
if (err === "")
|
|
200
|
-
|
|
201
|
+
if (err === "")
|
|
202
|
+
return {
|
|
203
|
+
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
204
|
+
nextIndex: idx,
|
|
205
|
+
};
|
|
206
|
+
if (err)
|
|
207
|
+
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
201
208
|
} else {
|
|
202
209
|
const err = consumeShort(tok);
|
|
203
|
-
if (err === "")
|
|
204
|
-
|
|
210
|
+
if (err === "")
|
|
211
|
+
return {
|
|
212
|
+
report: { err: null, stoppedOnUnknown: true, sawDoubleDash: false },
|
|
213
|
+
nextIndex: idx,
|
|
214
|
+
};
|
|
215
|
+
if (err)
|
|
216
|
+
return { report: { err, stoppedOnUnknown: false, sawDoubleDash: false }, nextIndex: idx };
|
|
205
217
|
}
|
|
206
218
|
}
|
|
207
219
|
|
|
@@ -212,7 +224,7 @@ function consumeOptions(
|
|
|
212
224
|
|
|
213
225
|
/** Merges option defs from the program root along the routed command path. */
|
|
214
226
|
export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
|
|
215
|
-
|
|
227
|
+
const defs = [...(root.options ?? [])];
|
|
216
228
|
let node: CliNode = root;
|
|
217
229
|
|
|
218
230
|
for (const seg of path) {
|
|
@@ -343,7 +355,16 @@ function finishLeaf(
|
|
|
343
355
|
}
|
|
344
356
|
}
|
|
345
357
|
|
|
346
|
-
return {
|
|
358
|
+
return {
|
|
359
|
+
kind: ParseKind.Ok,
|
|
360
|
+
path,
|
|
361
|
+
opts,
|
|
362
|
+
args,
|
|
363
|
+
helpExplicit: false,
|
|
364
|
+
helpPath: [],
|
|
365
|
+
errorMsg: "",
|
|
366
|
+
errorHelpPath: [],
|
|
367
|
+
};
|
|
347
368
|
}
|
|
348
369
|
|
|
349
370
|
// ── Main Parser ───────────────────────────────────────────────────────────────
|
|
@@ -414,7 +435,16 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
414
435
|
cmdName = root.fallbackCommand;
|
|
415
436
|
node = findChild(root.commands, cmdName);
|
|
416
437
|
if (!node) {
|
|
417
|
-
return {
|
|
438
|
+
return {
|
|
439
|
+
kind: ParseKind.Error,
|
|
440
|
+
path: [],
|
|
441
|
+
opts: {},
|
|
442
|
+
args: [],
|
|
443
|
+
helpExplicit: false,
|
|
444
|
+
helpPath: [],
|
|
445
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
446
|
+
errorHelpPath: path,
|
|
447
|
+
};
|
|
418
448
|
}
|
|
419
449
|
} else {
|
|
420
450
|
return helpResult([], false);
|
|
@@ -428,16 +458,26 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
428
458
|
i += 1;
|
|
429
459
|
node = childPick;
|
|
430
460
|
} else {
|
|
461
|
+
const fallbackCommand = root.fallbackCommand;
|
|
431
462
|
const canRouteUnknown =
|
|
432
|
-
|
|
463
|
+
fallbackCommand !== undefined &&
|
|
433
464
|
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
434
465
|
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
|
|
435
466
|
|
|
436
467
|
if (canRouteUnknown) {
|
|
437
|
-
cmdName =
|
|
468
|
+
cmdName = fallbackCommand;
|
|
438
469
|
node = findChild(root.commands, cmdName);
|
|
439
470
|
if (!node) {
|
|
440
|
-
return {
|
|
471
|
+
return {
|
|
472
|
+
kind: ParseKind.Error,
|
|
473
|
+
path: [],
|
|
474
|
+
opts: {},
|
|
475
|
+
args: [],
|
|
476
|
+
helpExplicit: false,
|
|
477
|
+
helpPath: [],
|
|
478
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
479
|
+
errorHelpPath: path,
|
|
480
|
+
};
|
|
441
481
|
}
|
|
442
482
|
} else {
|
|
443
483
|
cmdName = peek;
|
|
@@ -451,7 +491,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
451
491
|
args: [],
|
|
452
492
|
helpExplicit: false,
|
|
453
493
|
helpPath: [],
|
|
454
|
-
errorMsg: forcePositionals
|
|
494
|
+
errorMsg: forcePositionals
|
|
495
|
+
? `Expected subcommand but got positional: ${cmdName}`
|
|
496
|
+
: `Unknown command: ${cmdName}`,
|
|
455
497
|
errorHelpPath: path,
|
|
456
498
|
};
|
|
457
499
|
}
|
|
@@ -460,7 +502,19 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
460
502
|
}
|
|
461
503
|
|
|
462
504
|
path.push(cmdName);
|
|
463
|
-
|
|
505
|
+
if (!node) {
|
|
506
|
+
return {
|
|
507
|
+
kind: ParseKind.Error,
|
|
508
|
+
path,
|
|
509
|
+
opts: {},
|
|
510
|
+
args: [],
|
|
511
|
+
helpExplicit: false,
|
|
512
|
+
helpPath: [],
|
|
513
|
+
errorMsg: `Unknown command: ${cmdName}`,
|
|
514
|
+
errorHelpPath: path,
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
let current = node;
|
|
464
518
|
|
|
465
519
|
// Walk the command tree
|
|
466
520
|
while (true) {
|
|
@@ -508,7 +562,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
508
562
|
if (!isCliLeaf(current)) {
|
|
509
563
|
return helpResult(path, false);
|
|
510
564
|
}
|
|
511
|
-
return finishLeaf(
|
|
565
|
+
return finishLeaf(
|
|
566
|
+
current,
|
|
567
|
+
i,
|
|
568
|
+
argv,
|
|
569
|
+
path,
|
|
570
|
+
opts,
|
|
571
|
+
collectOptionDefs(root, path),
|
|
572
|
+
forcePositionals,
|
|
573
|
+
);
|
|
512
574
|
}
|
|
513
575
|
|
|
514
576
|
const tok = argv[i];
|
|
@@ -542,10 +604,10 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
542
604
|
fb !== undefined &&
|
|
543
605
|
(fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
544
606
|
|
|
545
|
-
if (canRouteUnknown) {
|
|
546
|
-
const fbNode = findChild(current.commands, fb
|
|
607
|
+
if (canRouteUnknown && fb !== undefined) {
|
|
608
|
+
const fbNode = findChild(current.commands, fb);
|
|
547
609
|
if (fbNode) {
|
|
548
|
-
path.push(fb
|
|
610
|
+
path.push(fb);
|
|
549
611
|
current = fbNode;
|
|
550
612
|
continue;
|
|
551
613
|
}
|
|
@@ -558,7 +620,9 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
558
620
|
args: [],
|
|
559
621
|
helpExplicit: false,
|
|
560
622
|
helpPath: [],
|
|
561
|
-
errorMsg: forcePositionals
|
|
623
|
+
errorMsg: forcePositionals
|
|
624
|
+
? `Expected subcommand but got positional: ${tok}`
|
|
625
|
+
: `Unknown subcommand: ${tok}`,
|
|
562
626
|
errorHelpPath: path,
|
|
563
627
|
};
|
|
564
628
|
}
|
|
@@ -566,7 +630,15 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
566
630
|
if (!isCliLeaf(current)) {
|
|
567
631
|
return helpResult(path, false);
|
|
568
632
|
}
|
|
569
|
-
return finishLeaf(
|
|
633
|
+
return finishLeaf(
|
|
634
|
+
current,
|
|
635
|
+
i,
|
|
636
|
+
argv,
|
|
637
|
+
path,
|
|
638
|
+
opts,
|
|
639
|
+
collectOptionDefs(root, path),
|
|
640
|
+
forcePositionals,
|
|
641
|
+
);
|
|
570
642
|
}
|
|
571
643
|
}
|
|
572
644
|
|
|
@@ -578,7 +650,7 @@ export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
|
578
650
|
export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
579
651
|
if (pr.kind !== ParseKind.Ok) return pr;
|
|
580
652
|
|
|
581
|
-
|
|
653
|
+
const defs = [...(root.options ?? [])];
|
|
582
654
|
let node: CliNode = root;
|
|
583
655
|
|
|
584
656
|
for (const seg of pr.path) {
|
package/src/runtime.ts
CHANGED
|
@@ -2,26 +2,29 @@
|
|
|
2
2
|
This module runs parsed commands, help, errors, completion, and leaf handlers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { resolveCapabilities } from "./capabilities.ts";
|
|
6
5
|
import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
|
|
7
6
|
import { cliParseRoot, cliPresentationRoot } from "./builtins/presentation.ts";
|
|
8
|
-
import
|
|
9
|
-
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
7
|
+
import { resolveCapabilities } from "./capabilities.ts";
|
|
10
8
|
import { CliContext } from "./context.ts";
|
|
11
9
|
import { cliHelpRender } from "./help.ts";
|
|
12
|
-
import { parse, postParseValidate
|
|
10
|
+
import { ParseKind, parse, postParseValidate } from "./parse.ts";
|
|
11
|
+
import type { CliRouter } from "./types.ts";
|
|
12
|
+
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
13
13
|
import { cliValidateProgram } from "./validate.ts";
|
|
14
14
|
|
|
15
15
|
function cliRootMergedWithBuiltins(program: CliProgram): CliRouter {
|
|
16
16
|
return cliParseRoot(program);
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
export async function cliRun(
|
|
19
|
+
export async function cliRun(
|
|
20
|
+
program: CliProgram,
|
|
21
|
+
argv: string[] = process.argv.slice(2),
|
|
22
|
+
): Promise<never> {
|
|
20
23
|
try {
|
|
21
24
|
cliValidateProgram(program);
|
|
22
25
|
} catch (err) {
|
|
23
26
|
if (err instanceof Error) {
|
|
24
|
-
process.stderr.write(err.message
|
|
27
|
+
process.stderr.write(`${err.message}\n`);
|
|
25
28
|
} else {
|
|
26
29
|
process.stderr.write("Invalid CLI definition.\n");
|
|
27
30
|
}
|
|
@@ -31,12 +34,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
31
34
|
const caps = resolveCapabilities(program);
|
|
32
35
|
|
|
33
36
|
if (argv.length >= 1 && argv[0] === "mcp" && !caps.mcp) {
|
|
34
|
-
process.stderr.write(
|
|
37
|
+
process.stderr.write(
|
|
38
|
+
"MCP is not enabled. Set mcpServer: { enabled: true } on the program root.\n",
|
|
39
|
+
);
|
|
35
40
|
process.exit(1);
|
|
36
41
|
}
|
|
37
42
|
|
|
38
43
|
if (argv.length >= 1 && argv[0] === "install" && !caps.install) {
|
|
39
|
-
process.stderr.write(
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
"install is disabled. Remove install.enabled: false from the program root.\n",
|
|
46
|
+
);
|
|
40
47
|
process.exit(1);
|
|
41
48
|
}
|
|
42
49
|
|
|
@@ -75,13 +82,16 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
75
82
|
if (pr.kind === "error") {
|
|
76
83
|
const color = process.stderr.isTTY;
|
|
77
84
|
const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
|
|
78
|
-
process.stderr.write(msg
|
|
85
|
+
process.stderr.write(`${msg}\n`);
|
|
79
86
|
process.stderr.write(cliHelpRender(cliPresentationRoot(program), pr.errorHelpPath, true));
|
|
80
87
|
process.exit(1);
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
if (pr.kind === ParseKind.Ok) {
|
|
84
|
-
await dispatchBuiltin(program, pr, {
|
|
91
|
+
await dispatchBuiltin(program, pr, {
|
|
92
|
+
isLeafCompletionIntercept,
|
|
93
|
+
parseRoot: completionParseRoot,
|
|
94
|
+
});
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
let current: CliNode = parseRoot;
|
|
@@ -109,7 +119,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
109
119
|
process.exit(0);
|
|
110
120
|
} catch (err) {
|
|
111
121
|
if (err instanceof Error) {
|
|
112
|
-
process.stderr.write(err.message
|
|
122
|
+
process.stderr.write(`${err.message}\n`);
|
|
113
123
|
}
|
|
114
124
|
process.exit(1);
|
|
115
125
|
}
|
|
@@ -118,7 +128,7 @@ export async function cliRun(program: CliProgram, argv: string[] = process.argv.
|
|
|
118
128
|
export function cliErrWithHelp(ctx: CliContext, msg: string): never {
|
|
119
129
|
const color = process.stderr.isTTY;
|
|
120
130
|
const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
|
|
121
|
-
process.stderr.write(line
|
|
131
|
+
process.stderr.write(`${line}\n`);
|
|
122
132
|
process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.program), ctx.commandPath, true));
|
|
123
133
|
process.exit(1);
|
|
124
134
|
}
|
package/src/schema.ts
CHANGED
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
This module serializes the CLI schema tree to JSON for machine-readable introspection.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { type
|
|
6
|
-
import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
|
|
5
|
+
import { type CliSchemaExport, exportPresentationBuiltins } from "./builtins/export.ts";
|
|
7
6
|
import { cliResolveNotes } from "./help.ts";
|
|
8
7
|
import { visibleOptions } from "./hidden.ts";
|
|
8
|
+
import {
|
|
9
|
+
type CliNode,
|
|
10
|
+
type CliProgram,
|
|
11
|
+
isCliLeaf,
|
|
12
|
+
isCliRouter,
|
|
13
|
+
leafOutputSchema,
|
|
14
|
+
} from "./types.ts";
|
|
9
15
|
|
|
10
16
|
const RESERVED = new Set(["completion", "install", "docs", "mcp", "version"]);
|
|
11
17
|
|
|
@@ -60,8 +66,8 @@ function exportCommand(cmd: CliNode, root: CliProgram): CliSchemaExport | null {
|
|
|
60
66
|
/** Resolves `{argsbarg:program}` in exported notes using the root program key. */
|
|
61
67
|
function resolveSchemaNotes(node: CliSchemaExport, appKey: string): CliSchemaExport {
|
|
62
68
|
const out: CliSchemaExport = { ...node };
|
|
63
|
-
if ((out.notes ?? "").length > 0) {
|
|
64
|
-
out.notes = cliResolveNotes(out.notes
|
|
69
|
+
if ((out.notes ?? "").length > 0 && out.notes !== undefined) {
|
|
70
|
+
out.notes = cliResolveNotes(out.notes, appKey);
|
|
65
71
|
}
|
|
66
72
|
if (out.commands) {
|
|
67
73
|
out.commands = out.commands.map((ch) => resolveSchemaNotes(ch, appKey));
|
|
@@ -83,7 +89,7 @@ export function cliSchemaExport(root: CliProgram): CliSchemaExport {
|
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
export function cliSchemaJson(root: CliProgram): string {
|
|
86
|
-
return JSON.stringify(cliSchemaExport(root), null, 2)
|
|
92
|
+
return `${JSON.stringify(cliSchemaExport(root), null, 2)}\n`;
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
export type { CliSchemaExport };
|
package/src/skill/generate.ts
CHANGED
|
@@ -3,9 +3,9 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { generateApiGuide } from "../docs/api-guide.ts";
|
|
6
|
+
import { collectMcpTools, type McpToolDef, sanitizeToolSegment } from "../mcp/tools.ts";
|
|
6
7
|
import { collectOptionDefs } from "../parse.ts";
|
|
7
|
-
import {
|
|
8
|
-
import { CliProgram, CliOptionKind } from "../types.ts";
|
|
8
|
+
import { CliOptionKind, type CliProgram } from "../types.ts";
|
|
9
9
|
|
|
10
10
|
export type SkillTarget = "cursor" | "claude";
|
|
11
11
|
|
|
@@ -18,7 +18,7 @@ export interface SkillBundle {
|
|
|
18
18
|
/** Truncates text to maxLen with ellipsis. */
|
|
19
19
|
function truncate(text: string, maxLen: number): string {
|
|
20
20
|
if (text.length <= maxLen) return text;
|
|
21
|
-
return text.slice(0, maxLen - 1)
|
|
21
|
+
return `${text.slice(0, maxLen - 1)}…`;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Builds third-person skill description for YAML frontmatter. */
|
|
@@ -54,7 +54,7 @@ function formatCommandEntry(root: CliProgram, tool: McpToolDef): string {
|
|
|
54
54
|
}
|
|
55
55
|
const enums = opts.filter((o) => o.kind === CliOptionKind.Enum && o.choices?.length);
|
|
56
56
|
for (const e of enums) {
|
|
57
|
-
line += ` (\`--${e.name}\`: ${e.choices
|
|
57
|
+
line += ` (\`--${e.name}\`: ${e.choices?.join(" | ")})`;
|
|
58
58
|
}
|
|
59
59
|
const varargs = (tool.leaf.positionals ?? []).filter((p) => (p.argMax ?? 1) === 0);
|
|
60
60
|
if (varargs.length > 0) {
|
package/src/skill/install.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { CliProgram } from "../types.ts";
|
|
4
|
+
import type { CliProgram } from "../types.ts";
|
|
5
5
|
import { generateSkillBundle, type SkillTarget } from "./generate.ts";
|
|
6
6
|
import { applySkillInstallHints } from "./hint.ts";
|
|
7
7
|
|
|
@@ -25,7 +25,11 @@ function resolveSkillDir(target: SkillTarget, dirName: string, global: boolean):
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
/** Writes SKILL.md and reference.md; returns changed file paths. */
|
|
28
|
-
export function cliSkillInstall(
|
|
28
|
+
export function cliSkillInstall(
|
|
29
|
+
root: CliProgram,
|
|
30
|
+
target: SkillTarget,
|
|
31
|
+
opts: SkillInstallOpts,
|
|
32
|
+
): string[] {
|
|
29
33
|
const bundle = generateSkillBundle(root, target);
|
|
30
34
|
const { skillMd, referenceMd } = applySkillInstallHints(root, bundle.skillMd, bundle.referenceMd);
|
|
31
35
|
const dir = resolveSkillDir(target, bundle.dirName, opts.global ?? false);
|
package/src/validate.ts
CHANGED
|
@@ -3,17 +3,17 @@ This module validates CLI schemas before execution.
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { reservedCommandNames, resolveCapabilities } from "./capabilities.ts";
|
|
6
|
+
import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
|
|
7
|
+
import { resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
6
8
|
import {
|
|
7
9
|
type CliLeaf,
|
|
8
10
|
type CliNode,
|
|
9
|
-
type CliProgram,
|
|
10
11
|
CliOptionKind,
|
|
12
|
+
type CliProgram,
|
|
11
13
|
CliSchemaValidationError,
|
|
12
14
|
isCliLeaf,
|
|
13
15
|
isCliRouter,
|
|
14
16
|
} from "./types.ts";
|
|
15
|
-
import { resolveMcpSchemaUri } from "./mcp/tools.ts";
|
|
16
|
-
import { DOCS_BUILTIN_TOPIC_KEYS } from "./docs/resolve.ts";
|
|
17
17
|
|
|
18
18
|
/** Validates `docs` configuration on the program root. */
|
|
19
19
|
function validateDocsConfig(docs: import("./types.ts").CliDocsConfig): void {
|
|
@@ -93,17 +93,17 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
93
93
|
const rogue = node as CliProgram;
|
|
94
94
|
if (rogue.mcpServer !== undefined) {
|
|
95
95
|
throw new CliSchemaValidationError(
|
|
96
|
-
|
|
96
|
+
`mcpServer is only supported on the program root (not on ${node.key})`,
|
|
97
97
|
);
|
|
98
98
|
}
|
|
99
99
|
if (rogue.install !== undefined) {
|
|
100
100
|
throw new CliSchemaValidationError(
|
|
101
|
-
|
|
101
|
+
`install is only supported on the program root (not on ${node.key})`,
|
|
102
102
|
);
|
|
103
103
|
}
|
|
104
104
|
if (rogue.docs !== undefined) {
|
|
105
105
|
throw new CliSchemaValidationError(
|
|
106
|
-
|
|
106
|
+
`docs is only supported on the program root (not on ${node.key})`,
|
|
107
107
|
);
|
|
108
108
|
}
|
|
109
109
|
}
|
|
@@ -115,9 +115,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
115
115
|
const outputSchema = node.outputSchema;
|
|
116
116
|
const legacyOutputSchema = node.mcpTool?.outputSchema;
|
|
117
117
|
if (outputSchema !== undefined && legacyOutputSchema !== undefined) {
|
|
118
|
-
throw new CliSchemaValidationError(
|
|
119
|
-
"Set outputSchema on the leaf only, not under mcpTool",
|
|
120
|
-
);
|
|
118
|
+
throw new CliSchemaValidationError("Set outputSchema on the leaf only, not under mcpTool");
|
|
121
119
|
}
|
|
122
120
|
const resolved = outputSchema ?? legacyOutputSchema;
|
|
123
121
|
if (
|
|
@@ -132,7 +130,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
132
130
|
const rogue = node as unknown as CliLeaf;
|
|
133
131
|
if (rogue.mcpTool !== undefined) {
|
|
134
132
|
throw new CliSchemaValidationError(
|
|
135
|
-
|
|
133
|
+
`mcpTool is only supported on leaf commands (not on ${node.key})`,
|
|
136
134
|
);
|
|
137
135
|
}
|
|
138
136
|
}
|
|
@@ -160,9 +158,7 @@ function walkNode(node: CliNode, program: CliProgram, isRoot: boolean): void {
|
|
|
160
158
|
}
|
|
161
159
|
|
|
162
160
|
if (node.fallbackMode !== undefined && node.fallbackCommand === undefined) {
|
|
163
|
-
throw new CliSchemaValidationError(
|
|
164
|
-
`fallbackMode requires fallbackCommand on '${node.key}'`,
|
|
165
|
-
);
|
|
161
|
+
throw new CliSchemaValidationError(`fallbackMode requires fallbackCommand on '${node.key}'`);
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
if (node.fallbackCommand !== undefined) {
|
|
@@ -233,10 +229,15 @@ function validateOptions(scopeKey: string, options: import("./types.ts").CliOpti
|
|
|
233
229
|
}
|
|
234
230
|
}
|
|
235
231
|
|
|
236
|
-
function validatePositionals(
|
|
232
|
+
function validatePositionals(
|
|
233
|
+
scopeKey: string,
|
|
234
|
+
positionals: import("./types.ts").CliPositional[],
|
|
235
|
+
): void {
|
|
237
236
|
for (const p of positionals) {
|
|
238
237
|
if (p.argMin !== undefined && p.argMin < 0) {
|
|
239
|
-
throw new CliSchemaValidationError(
|
|
238
|
+
throw new CliSchemaValidationError(
|
|
239
|
+
`argMin must be >= 0 for positional ${scopeKey}/${p.name}`,
|
|
240
|
+
);
|
|
240
241
|
}
|
|
241
242
|
if (p.argMax !== undefined && p.argMax < 0) {
|
|
242
243
|
throw new CliSchemaValidationError(
|
|
@@ -262,7 +263,11 @@ function validatePositionals(scopeKey: string, positionals: import("./types.ts")
|
|
|
262
263
|
}
|
|
263
264
|
|
|
264
265
|
for (let idx = 0; idx < positionals.length; idx++) {
|
|
265
|
-
const
|
|
266
|
+
const positional = positionals[idx];
|
|
267
|
+
if (!positional) {
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const { argMax = 1 } = positional;
|
|
266
271
|
if (argMax === 0 && idx + 1 < positionals.length) {
|
|
267
272
|
throw new CliSchemaValidationError(
|
|
268
273
|
`Unlimited positional (argMax == 0) must be last in scope ${scopeKey}`,
|