argsbarg 1.4.3 → 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/.private/scratch.md +1 -1
- package/CHANGELOG.md +39 -1
- package/README.md +29 -21
- package/docs/ai-skills.md +24 -52
- package/docs/install.md +84 -0
- package/docs/mcp.md +8 -8
- 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 +44 -50
- package/package.json +1 -1
- package/src/builtins/builtins.test.ts +101 -0
- package/src/builtins/completion-bash.ts +240 -0
- package/src/builtins/completion-fish.ts +73 -0
- package/src/builtins/completion-group.ts +50 -0
- package/src/builtins/completion-zsh.ts +244 -0
- package/src/builtins/dispatch.ts +138 -0
- package/src/builtins/export.ts +53 -0
- package/src/builtins/index.ts +10 -0
- package/src/builtins/install.ts +99 -0
- package/src/builtins/mcp.ts +13 -0
- package/src/builtins/presentation.ts +50 -0
- package/src/builtins/scopes.ts +46 -0
- package/src/builtins/shell-helpers.ts +24 -0
- package/src/capabilities.ts +32 -0
- package/src/completion.ts +10 -693
- package/src/context.ts +21 -6
- package/src/help.ts +21 -9
- package/src/index.test.ts +114 -118
- package/src/index.ts +2 -1
- package/src/install/binary.ts +82 -0
- package/src/install/compiled.ts +15 -0
- package/src/install/completions.ts +52 -0
- package/src/install/detect-installed.ts +67 -0
- package/src/install/index.ts +196 -0
- package/src/install/install.test.ts +124 -0
- package/src/install/mcp-config.ts +70 -0
- package/src/install/paths.ts +69 -0
- package/src/install/plan.ts +183 -0
- package/src/install/shell.ts +56 -0
- package/src/install/status.ts +63 -0
- package/src/install/uninstall.ts +111 -0
- package/src/invoke.ts +14 -5
- package/src/mcp/server.ts +3 -3
- package/src/mcp/tools.ts +17 -17
- package/src/mcp.ts +2 -2
- package/src/parse.ts +55 -27
- package/src/runtime.ts +47 -100
- package/src/schema.ts +10 -52
- package/src/skill/generate.ts +10 -10
- package/src/skill/install.ts +21 -19
- package/src/types.test.ts +40 -0
- package/src/types.ts +59 -49
- package/src/validate.ts +89 -83
- package/src/ai.ts +0 -7
package/src/parse.ts
CHANGED
|
@@ -9,10 +9,13 @@ across every entry path.
|
|
|
9
9
|
|
|
10
10
|
import { CliContext } from "./context.ts";
|
|
11
11
|
import {
|
|
12
|
-
|
|
12
|
+
type CliLeaf,
|
|
13
|
+
CliNode,
|
|
13
14
|
CliFallbackMode,
|
|
14
15
|
CliOption,
|
|
15
16
|
CliOptionKind,
|
|
17
|
+
isCliLeaf,
|
|
18
|
+
isCliRouter,
|
|
16
19
|
} from "./types.ts";
|
|
17
20
|
import { fullStringIsDouble } from "./utils.ts";
|
|
18
21
|
|
|
@@ -69,7 +72,7 @@ function isSchemaTok(tok: string): boolean {
|
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
/** Looks up a subcommand or routing node by `key`. */
|
|
72
|
-
function findChild(cmds:
|
|
75
|
+
function findChild(cmds: CliNode[], name: string): CliNode | undefined {
|
|
73
76
|
return cmds.find((c) => c.key === name);
|
|
74
77
|
}
|
|
75
78
|
|
|
@@ -217,15 +220,16 @@ function consumeOptions(
|
|
|
217
220
|
// ── Positional Collection ─────────────────────────────────────────────────────
|
|
218
221
|
|
|
219
222
|
/** Merges option defs from the program root along the routed command path. */
|
|
220
|
-
export function collectOptionDefs(root:
|
|
223
|
+
export function collectOptionDefs(root: CliNode, path: string[]): CliOption[] {
|
|
221
224
|
let defs = [...(root.options ?? [])];
|
|
222
|
-
let
|
|
225
|
+
let node: CliNode = root;
|
|
223
226
|
|
|
224
227
|
for (const seg of path) {
|
|
225
|
-
|
|
228
|
+
if (!isCliRouter(node)) break;
|
|
229
|
+
const ch = findChild(node.commands, seg);
|
|
226
230
|
if (!ch) break;
|
|
227
231
|
defs.push(...(ch.options ?? []));
|
|
228
|
-
|
|
232
|
+
node = ch;
|
|
229
233
|
}
|
|
230
234
|
|
|
231
235
|
return defs;
|
|
@@ -233,7 +237,7 @@ export function collectOptionDefs(root: CliCommand, path: string[]): CliOption[]
|
|
|
233
237
|
|
|
234
238
|
/** Fills `args` for a leaf from `startIdx` according to `node.positionals`. */
|
|
235
239
|
function finishLeaf(
|
|
236
|
-
node:
|
|
240
|
+
node: CliLeaf,
|
|
237
241
|
startIdx: number,
|
|
238
242
|
argv: string[],
|
|
239
243
|
path: string[],
|
|
@@ -384,14 +388,16 @@ function schemaResult(): ParseResult {
|
|
|
384
388
|
/**
|
|
385
389
|
* Parses `argv` against the program root, routing into subcommands and filling `opts` / `args`.
|
|
386
390
|
*/
|
|
387
|
-
export function parse(root:
|
|
391
|
+
export function parse(root: CliNode, argv: string[]): ParseResult {
|
|
388
392
|
let i = 0;
|
|
389
393
|
const path: string[] = [];
|
|
390
394
|
const opts: Record<string, string> = {};
|
|
391
395
|
|
|
392
396
|
const rootLenient =
|
|
397
|
+
isCliRouter(root) &&
|
|
393
398
|
root.fallbackCommand !== undefined &&
|
|
394
|
-
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
399
|
+
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown ||
|
|
400
|
+
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.UnknownOnly);
|
|
395
401
|
|
|
396
402
|
// Consume root-level options first
|
|
397
403
|
const rootRep = consumeOptions(root.options ?? [], rootLenient, argv, i, opts);
|
|
@@ -420,16 +426,20 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
420
426
|
|
|
421
427
|
// Determine which subcommand to route to
|
|
422
428
|
let cmdName: string;
|
|
423
|
-
let node:
|
|
429
|
+
let node: CliNode | undefined;
|
|
424
430
|
|
|
425
|
-
if (root
|
|
426
|
-
return finishLeaf(root
|
|
431
|
+
if (isCliLeaf(root)) {
|
|
432
|
+
return finishLeaf(root, i, argv, path, opts, root.options ?? [], forcePositionals);
|
|
427
433
|
}
|
|
428
434
|
|
|
429
435
|
if (i >= argv.length) {
|
|
430
|
-
if (
|
|
436
|
+
if (
|
|
437
|
+
root.fallbackCommand !== undefined &&
|
|
438
|
+
((root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOnly ||
|
|
439
|
+
(root.fallbackMode ?? CliFallbackMode.MissingOnly) === CliFallbackMode.MissingOrUnknown)
|
|
440
|
+
) {
|
|
431
441
|
cmdName = root.fallbackCommand;
|
|
432
|
-
node = findChild(root.commands
|
|
442
|
+
node = findChild(root.commands, cmdName);
|
|
433
443
|
if (!node) {
|
|
434
444
|
return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
|
|
435
445
|
}
|
|
@@ -438,7 +448,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
438
448
|
}
|
|
439
449
|
} else {
|
|
440
450
|
const peek = argv[i];
|
|
441
|
-
const childPick = !forcePositionals ? findChild(root.commands
|
|
451
|
+
const childPick = !forcePositionals ? findChild(root.commands, peek) : undefined;
|
|
442
452
|
|
|
443
453
|
if (childPick !== undefined) {
|
|
444
454
|
cmdName = peek;
|
|
@@ -452,14 +462,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
452
462
|
|
|
453
463
|
if (canRouteUnknown) {
|
|
454
464
|
cmdName = root.fallbackCommand!;
|
|
455
|
-
node = findChild(root.commands
|
|
465
|
+
node = findChild(root.commands, cmdName);
|
|
456
466
|
if (!node) {
|
|
457
467
|
return { kind: ParseKind.Error, path: [], opts: {}, args: [], helpExplicit: false, helpPath: [], errorMsg: `Unknown command: ${cmdName}`, errorHelpPath: path };
|
|
458
468
|
}
|
|
459
469
|
} else {
|
|
460
470
|
cmdName = peek;
|
|
461
471
|
if (!forcePositionals) i += 1;
|
|
462
|
-
node = findChild(root.commands
|
|
472
|
+
node = findChild(root.commands, cmdName);
|
|
463
473
|
if (!node) {
|
|
464
474
|
return {
|
|
465
475
|
kind: ParseKind.Error,
|
|
@@ -506,14 +516,14 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
506
516
|
}
|
|
507
517
|
|
|
508
518
|
if (i >= argv.length) {
|
|
509
|
-
if ((current
|
|
519
|
+
if (isCliRouter(current) && current.commands.length > 0) {
|
|
510
520
|
const fb = current.fallbackCommand;
|
|
511
521
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
512
522
|
if (
|
|
513
523
|
fb !== undefined &&
|
|
514
524
|
(fm === CliFallbackMode.MissingOnly || fm === CliFallbackMode.MissingOrUnknown)
|
|
515
525
|
) {
|
|
516
|
-
const fbNode = findChild(current.commands
|
|
526
|
+
const fbNode = findChild(current.commands, fb);
|
|
517
527
|
if (fbNode) {
|
|
518
528
|
path.push(fb);
|
|
519
529
|
current = fbNode;
|
|
@@ -522,6 +532,9 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
522
532
|
}
|
|
523
533
|
return helpResult(path, false);
|
|
524
534
|
}
|
|
535
|
+
if (!isCliLeaf(current)) {
|
|
536
|
+
return helpResult(path, false);
|
|
537
|
+
}
|
|
525
538
|
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
526
539
|
}
|
|
527
540
|
|
|
@@ -539,8 +552,8 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
539
552
|
};
|
|
540
553
|
}
|
|
541
554
|
|
|
542
|
-
if (!forcePositionals) {
|
|
543
|
-
const childOpt = findChild(current.commands
|
|
555
|
+
if (!forcePositionals && isCliRouter(current)) {
|
|
556
|
+
const childOpt = findChild(current.commands, tok);
|
|
544
557
|
if (childOpt !== undefined) {
|
|
545
558
|
i += 1;
|
|
546
559
|
path.push(tok);
|
|
@@ -549,7 +562,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
549
562
|
}
|
|
550
563
|
}
|
|
551
564
|
|
|
552
|
-
if ((current
|
|
565
|
+
if (isCliRouter(current) && current.commands.length > 0) {
|
|
553
566
|
const fb = current.fallbackCommand;
|
|
554
567
|
const fm = current.fallbackMode ?? CliFallbackMode.MissingOnly;
|
|
555
568
|
const canRouteUnknown =
|
|
@@ -557,7 +570,7 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
557
570
|
(fm === CliFallbackMode.MissingOrUnknown || fm === CliFallbackMode.UnknownOnly);
|
|
558
571
|
|
|
559
572
|
if (canRouteUnknown) {
|
|
560
|
-
const fbNode = findChild(current.commands
|
|
573
|
+
const fbNode = findChild(current.commands, fb!);
|
|
561
574
|
if (fbNode) {
|
|
562
575
|
path.push(fb!);
|
|
563
576
|
current = fbNode;
|
|
@@ -577,6 +590,9 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
577
590
|
};
|
|
578
591
|
}
|
|
579
592
|
|
|
593
|
+
if (!isCliLeaf(current)) {
|
|
594
|
+
return helpResult(path, false);
|
|
595
|
+
}
|
|
580
596
|
return finishLeaf(current, i, argv, path, opts, collectOptionDefs(root, path), forcePositionals);
|
|
581
597
|
}
|
|
582
598
|
}
|
|
@@ -586,14 +602,26 @@ export function parse(root: CliCommand, argv: string[]): ParseResult {
|
|
|
586
602
|
/**
|
|
587
603
|
* Validates option keys and numeric values for an Ok parse, merging in-scope options along `pr.path`.
|
|
588
604
|
*/
|
|
589
|
-
export function postParseValidate(root:
|
|
605
|
+
export function postParseValidate(root: CliNode, pr: ParseResult): ParseResult {
|
|
590
606
|
if (pr.kind !== ParseKind.Ok) return pr;
|
|
591
607
|
|
|
592
608
|
let defs = [...(root.options ?? [])];
|
|
593
|
-
let
|
|
609
|
+
let node: CliNode = root;
|
|
594
610
|
|
|
595
611
|
for (const seg of pr.path) {
|
|
596
|
-
|
|
612
|
+
if (!isCliRouter(node)) {
|
|
613
|
+
return {
|
|
614
|
+
kind: ParseKind.Error,
|
|
615
|
+
path: pr.path,
|
|
616
|
+
opts: {},
|
|
617
|
+
args: [],
|
|
618
|
+
helpExplicit: false,
|
|
619
|
+
helpPath: [],
|
|
620
|
+
errorMsg: "Internal path error",
|
|
621
|
+
errorHelpPath: pr.path,
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
const ch = findChild(node.commands, seg);
|
|
597
625
|
if (!ch) {
|
|
598
626
|
return {
|
|
599
627
|
kind: ParseKind.Error,
|
|
@@ -607,7 +635,7 @@ export function postParseValidate(root: CliCommand, pr: ParseResult): ParseResul
|
|
|
607
635
|
};
|
|
608
636
|
}
|
|
609
637
|
defs.push(...(ch.options ?? []));
|
|
610
|
-
|
|
638
|
+
node = ch;
|
|
611
639
|
}
|
|
612
640
|
|
|
613
641
|
for (const d of defs) {
|
package/src/runtime.ts
CHANGED
|
@@ -1,41 +1,26 @@
|
|
|
1
1
|
/*
|
|
2
2
|
This module runs parsed commands, help, errors, completion, and leaf handlers.
|
|
3
|
-
It owns the top-level control flow after parsing, including validation failures,
|
|
4
|
-
shell completion dispatch, and leaf handler invocation.
|
|
5
|
-
|
|
6
|
-
It keeps execution flow out of the public barrel so the exported API stays small and
|
|
7
|
-
the runtime responsibilities remain easy to reason about.
|
|
8
3
|
*/
|
|
9
4
|
|
|
10
|
-
import {
|
|
5
|
+
import { resolveCapabilities } from "./capabilities.ts";
|
|
6
|
+
import { builtinInterceptRoot, dispatchBuiltin } from "./builtins/dispatch.ts";
|
|
7
|
+
import { cliPresentationRoot } from "./builtins/presentation.ts";
|
|
8
|
+
import type { CliRouter } from "./types.ts";
|
|
9
|
+
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
10
|
+
import { isCompiledExecutable } from "./install/compiled.ts";
|
|
11
11
|
import { CliContext } from "./context.ts";
|
|
12
12
|
import { cliHelpRender } from "./help.ts";
|
|
13
|
-
import { cliSkillInstall } from "./skill/install.ts";
|
|
14
|
-
import { cliMcpServeStdio } from "./mcp.ts";
|
|
15
13
|
import { parse, postParseValidate, ParseKind } from "./parse.ts";
|
|
16
14
|
import { cliSchemaJson } from "./schema.ts";
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* Merges the caller's program root with the reserved `completion` subtree.
|
|
22
|
-
*/
|
|
23
|
-
function cliRootMergedWithBuiltins(root: CliCommand): CliCommand {
|
|
24
|
-
if (root.handler) {
|
|
25
|
-
return root;
|
|
26
|
-
}
|
|
27
|
-
return cliPresentationRoot(root);
|
|
15
|
+
import { cliValidateProgram } from "./validate.ts";
|
|
16
|
+
|
|
17
|
+
function cliRootMergedWithBuiltins(program: CliProgram): CliRouter {
|
|
18
|
+
return cliPresentationRoot(program);
|
|
28
19
|
}
|
|
29
20
|
|
|
30
|
-
|
|
31
|
-
* Validates the schema, parses argv, prints help or errors, runs completion or the leaf handler, then exits.
|
|
32
|
-
*
|
|
33
|
-
* @param root The root CliCommand.
|
|
34
|
-
* @param argv Override the default argv (process.argv.slice(2)).
|
|
35
|
-
*/
|
|
36
|
-
export async function cliRun(root: CliCommand, argv: string[] = process.argv.slice(2)): Promise<never> {
|
|
21
|
+
export async function cliRun(program: CliProgram, argv: string[] = process.argv.slice(2)): Promise<never> {
|
|
37
22
|
try {
|
|
38
|
-
|
|
23
|
+
cliValidateProgram(program);
|
|
39
24
|
} catch (err) {
|
|
40
25
|
if (err instanceof Error) {
|
|
41
26
|
process.stderr.write(err.message + "\n");
|
|
@@ -45,41 +30,47 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
45
30
|
process.exit(1);
|
|
46
31
|
}
|
|
47
32
|
|
|
48
|
-
|
|
33
|
+
const caps = resolveCapabilities(program);
|
|
34
|
+
|
|
35
|
+
if (argv.length >= 1 && argv[0] === "mcp" && !caps.mcp) {
|
|
49
36
|
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
50
37
|
process.exit(1);
|
|
51
38
|
}
|
|
52
39
|
|
|
53
|
-
|
|
40
|
+
if (argv.length >= 1 && argv[0] === "install" && !isCompiledExecutable()) {
|
|
41
|
+
process.stderr.write("install is only available in compiled binaries (bun build --compile).\n");
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let parseRoot: CliNode;
|
|
46
|
+
let completionParseRoot: CliRouter = cliRootMergedWithBuiltins(program);
|
|
54
47
|
let isLeafCompletionIntercept = false;
|
|
55
48
|
|
|
56
|
-
if (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
commands: [cliBuiltinCompletionGroup(root.key)],
|
|
68
|
-
} as any;
|
|
49
|
+
if (isCliLeaf(program)) {
|
|
50
|
+
const intercept = builtinInterceptRoot(program, argv);
|
|
51
|
+
if (intercept.isLeafCompletionIntercept || intercept.parseRoot !== program) {
|
|
52
|
+
parseRoot = intercept.parseRoot;
|
|
53
|
+
completionParseRoot = isCliRouter(intercept.parseRoot)
|
|
54
|
+
? intercept.parseRoot
|
|
55
|
+
: cliRootMergedWithBuiltins(program);
|
|
56
|
+
isLeafCompletionIntercept = intercept.isLeafCompletionIntercept;
|
|
57
|
+
} else {
|
|
58
|
+
parseRoot = program;
|
|
59
|
+
}
|
|
69
60
|
} else {
|
|
70
|
-
parseRoot = cliRootMergedWithBuiltins(
|
|
61
|
+
parseRoot = cliRootMergedWithBuiltins(program);
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
let pr = parse(parseRoot, argv);
|
|
74
65
|
pr = postParseValidate(parseRoot, pr);
|
|
75
66
|
|
|
76
67
|
if (pr.kind === ParseKind.Help) {
|
|
77
|
-
process.stdout.write(cliHelpRender(cliPresentationRoot(
|
|
68
|
+
process.stdout.write(cliHelpRender(cliPresentationRoot(program), pr.helpPath, false));
|
|
78
69
|
process.exit(pr.helpExplicit ? 0 : 1);
|
|
79
70
|
}
|
|
80
71
|
|
|
81
72
|
if (pr.kind === ParseKind.Schema) {
|
|
82
|
-
process.stdout.write(cliSchemaJson(
|
|
73
|
+
process.stdout.write(cliSchemaJson(program));
|
|
83
74
|
process.exit(0);
|
|
84
75
|
}
|
|
85
76
|
|
|
@@ -87,62 +78,21 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
87
78
|
const color = process.stderr.isTTY;
|
|
88
79
|
const msg = color ? `\u001B[31m${pr.errorMsg}\u001B[0m` : pr.errorMsg;
|
|
89
80
|
process.stderr.write(msg + "\n");
|
|
90
|
-
process.stderr.write(cliHelpRender(cliPresentationRoot(
|
|
81
|
+
process.stderr.write(cliHelpRender(cliPresentationRoot(program), pr.errorHelpPath, true));
|
|
91
82
|
process.exit(1);
|
|
92
83
|
}
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (pr.path[0] === "completion") {
|
|
97
|
-
// If we intercepted a leaf, we MUST pass the original `root` to generate completions
|
|
98
|
-
// because `parseRoot` is just a dummy router!
|
|
99
|
-
const schemaForCompletion = isLeafCompletionIntercept ? root : parseRoot;
|
|
100
|
-
|
|
101
|
-
if (pr.path[1] === "bash") {
|
|
102
|
-
process.stdout.write(completionBashScript(schemaForCompletion));
|
|
103
|
-
process.exit(0);
|
|
104
|
-
}
|
|
105
|
-
if (pr.path[1] === "zsh") {
|
|
106
|
-
process.stdout.write(completionZshScript(schemaForCompletion));
|
|
107
|
-
process.exit(0);
|
|
108
|
-
}
|
|
85
|
+
if (pr.kind === ParseKind.Ok) {
|
|
86
|
+
await dispatchBuiltin(program, pr, { isLeafCompletionIntercept, parseRoot: completionParseRoot });
|
|
109
87
|
}
|
|
110
88
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
process.exit(1);
|
|
116
|
-
}
|
|
117
|
-
if (pr.path.length !== 2) {
|
|
118
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
await cliMcpServeStdio(root);
|
|
122
|
-
} else if (pr.path[1] === "skill" && (pr.path[2] === "cursor" || pr.path[2] === "claude")) {
|
|
123
|
-
if (root.aiSkill?.enabled === false) {
|
|
124
|
-
process.stderr.write("AI skills are disabled. Remove aiSkill.enabled: false from the program root.\n");
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
if (pr.path.length !== 3) {
|
|
128
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
const msg = cliSkillInstall(root, pr.path[2], {
|
|
132
|
-
global: pr.opts.global === "1",
|
|
133
|
-
force: pr.opts.force === "1",
|
|
134
|
-
});
|
|
135
|
-
process.stderr.write(msg + "\n");
|
|
136
|
-
process.exit(0);
|
|
137
|
-
} else {
|
|
138
|
-
process.stderr.write("Unknown subcommand: ai " + pr.path.slice(1).join(" ") + "\n");
|
|
89
|
+
let current: CliNode = parseRoot;
|
|
90
|
+
for (const seg of pr.path) {
|
|
91
|
+
if (!isCliRouter(current)) {
|
|
92
|
+
process.stderr.write("Internal error: missing handler for path.\n");
|
|
139
93
|
process.exit(1);
|
|
140
94
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
let current = parseRoot;
|
|
144
|
-
for (const seg of pr.path) {
|
|
145
|
-
const ch = (current.commands ?? []).find((candidate: CliCommand) => candidate.key === seg);
|
|
95
|
+
const ch = current.commands.find((candidate) => candidate.key === seg);
|
|
146
96
|
if (!ch) {
|
|
147
97
|
process.stderr.write("Internal error: missing handler for path.\n");
|
|
148
98
|
process.exit(1);
|
|
@@ -150,12 +100,12 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
150
100
|
current = ch;
|
|
151
101
|
}
|
|
152
102
|
|
|
153
|
-
if (!current.handler) {
|
|
103
|
+
if (!isCliLeaf(current) || !current.handler) {
|
|
154
104
|
process.stderr.write("Internal error: missing handler for path.\n");
|
|
155
105
|
process.exit(1);
|
|
156
106
|
}
|
|
157
107
|
|
|
158
|
-
const ctx = new CliContext(
|
|
108
|
+
const ctx = new CliContext(program.key, pr.path, pr.args, pr.opts, program, "cli");
|
|
159
109
|
try {
|
|
160
110
|
await Promise.resolve(current.handler(ctx));
|
|
161
111
|
process.exit(0);
|
|
@@ -167,13 +117,10 @@ export async function cliRun(root: CliCommand, argv: string[] = process.argv.sli
|
|
|
167
117
|
}
|
|
168
118
|
}
|
|
169
119
|
|
|
170
|
-
/**
|
|
171
|
-
* Prints a red error line and contextual help on stderr, then exits with status 1.
|
|
172
|
-
*/
|
|
173
120
|
export function cliErrWithHelp(ctx: CliContext, msg: string): never {
|
|
174
121
|
const color = process.stderr.isTTY;
|
|
175
122
|
const line = color ? `\u001B[31m${msg}\u001B[0m` : msg;
|
|
176
123
|
process.stderr.write(line + "\n");
|
|
177
124
|
process.stderr.write(cliHelpRender(cliPresentationRoot(ctx.schema), ctx.commandPath, true));
|
|
178
125
|
process.exit(1);
|
|
179
|
-
}
|
|
126
|
+
}
|
package/src/schema.ts
CHANGED
|
@@ -1,56 +1,13 @@
|
|
|
1
1
|
/*
|
|
2
2
|
This module serializes the CLI schema tree to JSON for machine-readable introspection.
|
|
3
|
-
It strips handlers and runtime-only nodes so agents can discover commands, options,
|
|
4
|
-
and positionals in one shot.
|
|
5
|
-
|
|
6
|
-
It keeps schema export aligned with the declarative CliCommand model that drives help
|
|
7
|
-
and completion.
|
|
8
3
|
*/
|
|
9
4
|
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
CliFallbackMode,
|
|
13
|
-
CliOption,
|
|
14
|
-
CliPositional,
|
|
15
|
-
} from "./types.ts";
|
|
16
|
-
import { cliBuiltinCompletionGroup } from "./completion.ts";
|
|
17
|
-
|
|
18
|
-
/** JSON-safe command node (no handlers). */
|
|
19
|
-
export interface CliSchemaExport {
|
|
20
|
-
/** Program or command key. */
|
|
21
|
-
key: string;
|
|
22
|
-
/** Short description shown in help. */
|
|
23
|
-
description: string;
|
|
24
|
-
/** Additional notes shown in help (supports {app} placeholder). */
|
|
25
|
-
notes?: string;
|
|
26
|
-
/** Global or command-level flags/options. */
|
|
27
|
-
options?: CliOption[];
|
|
28
|
-
/** Default top-level subcommand (program root only). */
|
|
29
|
-
fallbackCommand?: string;
|
|
30
|
-
/** How fallbackCommand is applied (program root only). */
|
|
31
|
-
fallbackMode?: CliFallbackMode;
|
|
32
|
-
/** Nested subcommands (routing nodes only). */
|
|
33
|
-
commands?: CliSchemaExport[];
|
|
34
|
-
/** Positional argument definitions (leaf nodes only). */
|
|
35
|
-
positionals?: CliPositional[];
|
|
36
|
-
}
|
|
5
|
+
import { type CliNode, type CliProgram, isCliLeaf, isCliRouter } from "./types.ts";
|
|
6
|
+
import { exportPresentationBuiltins, type CliSchemaExport } from "./builtins/export.ts";
|
|
37
7
|
|
|
38
|
-
|
|
39
|
-
function exportBuiltinCompletionGroup(appName: string): CliSchemaExport {
|
|
40
|
-
const group = cliBuiltinCompletionGroup(appName);
|
|
41
|
-
return {
|
|
42
|
-
key: group.key,
|
|
43
|
-
description: group.description,
|
|
44
|
-
commands: (group.commands ?? []).map((ch) => ({
|
|
45
|
-
key: ch.key,
|
|
46
|
-
description: ch.description,
|
|
47
|
-
...((ch.notes ?? "").length > 0 ? { notes: ch.notes } : {}),
|
|
48
|
-
})),
|
|
49
|
-
};
|
|
50
|
-
}
|
|
8
|
+
const RESERVED = new Set(["completion", "install", "mcp"]);
|
|
51
9
|
|
|
52
|
-
|
|
53
|
-
function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
10
|
+
function exportCommand(cmd: CliNode): CliSchemaExport {
|
|
54
11
|
const out: CliSchemaExport = {
|
|
55
12
|
key: cmd.key,
|
|
56
13
|
description: cmd.description,
|
|
@@ -64,11 +21,11 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
64
21
|
out.options = cmd.options;
|
|
65
22
|
}
|
|
66
23
|
|
|
67
|
-
if (
|
|
24
|
+
if (isCliLeaf(cmd)) {
|
|
68
25
|
if ((cmd.positionals ?? []).length > 0) {
|
|
69
26
|
out.positionals = cmd.positionals;
|
|
70
27
|
}
|
|
71
|
-
out.commands =
|
|
28
|
+
out.commands = exportPresentationBuiltins(cmd as CliProgram);
|
|
72
29
|
return out;
|
|
73
30
|
}
|
|
74
31
|
|
|
@@ -79,7 +36,7 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
79
36
|
out.fallbackMode = cmd.fallbackMode;
|
|
80
37
|
}
|
|
81
38
|
|
|
82
|
-
const children = (cmd
|
|
39
|
+
const children = isCliRouter(cmd) ? cmd.commands.filter((ch) => !RESERVED.has(ch.key)) : [];
|
|
83
40
|
if (children.length > 0) {
|
|
84
41
|
out.commands = children.map(exportCommand);
|
|
85
42
|
}
|
|
@@ -87,7 +44,8 @@ function exportCommand(cmd: CliCommand): CliSchemaExport {
|
|
|
87
44
|
return out;
|
|
88
45
|
}
|
|
89
46
|
|
|
90
|
-
|
|
91
|
-
export function cliSchemaJson(root: CliCommand): string {
|
|
47
|
+
export function cliSchemaJson(root: CliProgram): string {
|
|
92
48
|
return JSON.stringify(exportCommand(root), null, 2) + "\n";
|
|
93
49
|
}
|
|
50
|
+
|
|
51
|
+
export type { CliSchemaExport };
|
package/src/skill/generate.ts
CHANGED
|
@@ -5,7 +5,7 @@ This module generates Agent Skills content (SKILL.md + reference.md) from a CLI
|
|
|
5
5
|
import { collectOptionDefs } from "../parse.ts";
|
|
6
6
|
import { cliSchemaJson } from "../schema.ts";
|
|
7
7
|
import { collectMcpTools, sanitizeToolSegment } from "../mcp/tools.ts";
|
|
8
|
-
import {
|
|
8
|
+
import { CliProgram, CliOptionKind } from "../types.ts";
|
|
9
9
|
|
|
10
10
|
export type SkillTarget = "cursor" | "claude";
|
|
11
11
|
|
|
@@ -22,7 +22,7 @@ function truncate(text: string, maxLen: number): string {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/** Builds third-person skill description for YAML frontmatter. */
|
|
25
|
-
function skillDescription(root:
|
|
25
|
+
function skillDescription(root: CliProgram): string {
|
|
26
26
|
const tools = collectMcpTools(root);
|
|
27
27
|
const paths = tools.map((t) => (t.path.length > 0 ? t.path.join(" ") : root.key));
|
|
28
28
|
const sample = paths.slice(0, 5).join(", ");
|
|
@@ -32,7 +32,7 @@ function skillDescription(root: CliCommand): string {
|
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/** Formats one command line for the catalog section. */
|
|
35
|
-
function formatCommandEntry(root:
|
|
35
|
+
function formatCommandEntry(root: CliProgram, tool: ReturnType<typeof collectMcpTools>[number]): string {
|
|
36
36
|
const cliPath = tool.path.length > 0 ? `${root.key} ${tool.path.join(" ")}` : root.key;
|
|
37
37
|
let line = `- **\`${cliPath}\`** — ${tool.description}`;
|
|
38
38
|
const opts = collectOptionDefs(root, tool.path);
|
|
@@ -52,8 +52,8 @@ function formatCommandEntry(root: CliCommand, tool: ReturnType<typeof collectMcp
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
/** Builds SKILL.md body for the given target. */
|
|
55
|
-
function buildSkillMd(root:
|
|
56
|
-
const name =
|
|
55
|
+
function buildSkillMd(root: CliProgram, target: SkillTarget, dirName: string): string {
|
|
56
|
+
const name = sanitizeToolSegment(root.key);
|
|
57
57
|
const description = skillDescription(root);
|
|
58
58
|
const tools = collectMcpTools(root);
|
|
59
59
|
|
|
@@ -80,7 +80,7 @@ function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): s
|
|
|
80
80
|
"**Prefer MCP** when a host has the server connected:",
|
|
81
81
|
"",
|
|
82
82
|
"```bash",
|
|
83
|
-
`${root.key}
|
|
83
|
+
`${root.key} mcp`,
|
|
84
84
|
"```",
|
|
85
85
|
"",
|
|
86
86
|
"Example Cursor `mcp.json` entry:",
|
|
@@ -91,7 +91,7 @@ function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): s
|
|
|
91
91
|
mcpServers: {
|
|
92
92
|
[root.mcpServer.name ?? root.key]: {
|
|
93
93
|
command: root.key,
|
|
94
|
-
args: ["
|
|
94
|
+
args: ["mcp"],
|
|
95
95
|
},
|
|
96
96
|
},
|
|
97
97
|
},
|
|
@@ -159,7 +159,7 @@ function buildSkillMd(root: CliCommand, target: SkillTarget, dirName: string): s
|
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/** Builds reference.md with pretty-printed schema JSON. */
|
|
162
|
-
function buildReferenceMd(root:
|
|
162
|
+
function buildReferenceMd(root: CliProgram): string {
|
|
163
163
|
return [
|
|
164
164
|
`# ${root.key} — CLI reference`,
|
|
165
165
|
"",
|
|
@@ -173,8 +173,8 @@ function buildReferenceMd(root: CliCommand): string {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
/** Generates SKILL.md and reference.md for Cursor or Claude Code. */
|
|
176
|
-
export function generateSkillBundle(root:
|
|
177
|
-
const dirName =
|
|
176
|
+
export function generateSkillBundle(root: CliProgram, target: SkillTarget): SkillBundle {
|
|
177
|
+
const dirName = sanitizeToolSegment(root.key);
|
|
178
178
|
return {
|
|
179
179
|
dirName,
|
|
180
180
|
skillMd: buildSkillMd(root, target, dirName),
|