argsbarg 1.5.0 → 2.0.1
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 +31 -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 +38 -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 +17 -7
- package/src/help.ts +21 -9
- package/src/index.test.ts +128 -121
- 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 +34 -25
- 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 +87 -72
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
|
|
2
2
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
3
3
|
import {
|
|
4
4
|
escShellSingleQuoted,
|
|
@@ -161,7 +161,7 @@ function emitEnumReplyBash(ident: string, scopes: ScopeRec[]): string {
|
|
|
161
161
|
return o;
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
function emitMainBodyBash(schema:
|
|
164
|
+
function emitMainBodyBash(schema: CliRouter, ident: string): string {
|
|
165
165
|
const main = mainName(schema.key);
|
|
166
166
|
let o = "_${main}() {\n".replace("${main}", main);
|
|
167
167
|
o += " local cur=\"${COMP_WORDS[COMP_CWORD]}\"\n";
|
|
@@ -196,7 +196,7 @@ function emitMainBodyBash(schema: CliCommand, ident: string): string {
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
/** Returns a self-contained bash `complete` script for the given program schema. */
|
|
199
|
-
export function completionBashScript(schema:
|
|
199
|
+
export function completionBashScript(schema: CliRouter): string {
|
|
200
200
|
const ident = identToken(schema.key);
|
|
201
201
|
const scopes = collectScopes(schema);
|
|
202
202
|
const pathIndex: Record<string, number> = {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
|
|
2
2
|
import { collectScopes } from "./scopes.ts";
|
|
3
3
|
import {
|
|
4
4
|
escFishSingleQuoted,
|
|
@@ -27,7 +27,7 @@ function scopeCondition(ident: string, scopeIndex: number, path: string): string
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/** Returns a self-contained fish completion script for the given program schema. */
|
|
30
|
-
export function completionFishScript(schema:
|
|
30
|
+
export function completionFishScript(schema: CliRouter): string {
|
|
31
31
|
const ident = identToken(schema.key);
|
|
32
32
|
const app = schema.key;
|
|
33
33
|
const scopes = collectScopes(schema);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type CliLeaf, type CliNode, type CliRouter } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Builds the static `completion` / `bash` / `zsh` / `fish` command subtree (merged into the program root at runtime).
|
|
5
5
|
*/
|
|
6
|
-
export function cliBuiltinCompletionGroup(appName: string):
|
|
6
|
+
export function cliBuiltinCompletionGroup(appName: string): CliRouter {
|
|
7
7
|
return {
|
|
8
8
|
key: "completion",
|
|
9
9
|
description: "Generate the autocompletion script for shells.",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
|
|
2
2
|
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
3
3
|
import {
|
|
4
4
|
escShellSingleQuoted,
|
|
@@ -191,7 +191,7 @@ function emitEnumReplyZsh(ident: string, scopes: ScopeRec[]): string {
|
|
|
191
191
|
return o;
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
-
function emitMainBodyZsh(schema:
|
|
194
|
+
function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
195
195
|
const main = mainName(schema.key);
|
|
196
196
|
let o = "_${main}() {\n".replace("${main}", main);
|
|
197
197
|
o += " local curcontext=\"$curcontext\" ret=1\n";
|
|
@@ -224,7 +224,7 @@ function emitMainBodyZsh(schema: CliCommand, ident: string): string {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
/** Returns a self-contained zsh completion script for the given program schema. */
|
|
227
|
-
export function completionZshScript(schema:
|
|
227
|
+
export function completionZshScript(schema: CliRouter): string {
|
|
228
228
|
const ident = identToken(schema.key);
|
|
229
229
|
const scopes = collectScopes(schema);
|
|
230
230
|
const pathIndex: Record<string, number> = {};
|
package/src/builtins/dispatch.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import type { CliNode, CliProgram, CliRouter } from "../types.ts";
|
|
3
|
+
import { isCliLeaf, isCliRouter } from "../types.ts";
|
|
2
4
|
import { completionBashScript } from "./completion-bash.ts";
|
|
3
5
|
import { completionFishScript } from "./completion-fish.ts";
|
|
4
6
|
import { completionZshScript } from "./completion-zsh.ts";
|
|
5
7
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
6
8
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
7
9
|
import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
|
|
10
|
+
import { cliPresentationRoot } from "./presentation.ts";
|
|
8
11
|
import { cliMcpServeStdio } from "../mcp.ts";
|
|
9
12
|
import { cliInstall } from "../install/index.ts";
|
|
10
13
|
import { isCompiledExecutable } from "../install/compiled.ts";
|
|
@@ -13,22 +16,32 @@ import { ParseKind } from "../parse.ts";
|
|
|
13
16
|
|
|
14
17
|
export interface DispatchBuiltinOpts {
|
|
15
18
|
isLeafCompletionIntercept: boolean;
|
|
16
|
-
parseRoot:
|
|
19
|
+
parseRoot: CliRouter;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function completionSchema(program: CliProgram, opts: DispatchBuiltinOpts): CliRouter {
|
|
23
|
+
if (opts.isLeafCompletionIntercept) {
|
|
24
|
+
return cliPresentationRoot(program);
|
|
25
|
+
}
|
|
26
|
+
return opts.parseRoot;
|
|
17
27
|
}
|
|
18
28
|
|
|
19
29
|
/**
|
|
20
30
|
* Handles built-in commands after parse.
|
|
21
31
|
*/
|
|
22
32
|
export async function dispatchBuiltin(
|
|
23
|
-
|
|
33
|
+
program: CliProgram,
|
|
24
34
|
pr: ParseResult,
|
|
25
35
|
opts: DispatchBuiltinOpts,
|
|
26
36
|
): Promise<void> {
|
|
27
37
|
if (pr.kind !== ParseKind.Ok) {
|
|
28
38
|
return;
|
|
29
39
|
}
|
|
40
|
+
|
|
41
|
+
const caps = resolveCapabilities(program);
|
|
42
|
+
|
|
30
43
|
if (pr.path[0] === "completion") {
|
|
31
|
-
const schemaForCompletion =
|
|
44
|
+
const schemaForCompletion = completionSchema(program, opts);
|
|
32
45
|
if (pr.path[1] === "bash") {
|
|
33
46
|
process.stdout.write(completionBashScript(schemaForCompletion));
|
|
34
47
|
process.exit(0);
|
|
@@ -45,7 +58,7 @@ export async function dispatchBuiltin(
|
|
|
45
58
|
}
|
|
46
59
|
|
|
47
60
|
if (pr.path[0] === "mcp") {
|
|
48
|
-
if (!
|
|
61
|
+
if (!caps.mcp) {
|
|
49
62
|
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
50
63
|
process.exit(1);
|
|
51
64
|
}
|
|
@@ -53,7 +66,7 @@ export async function dispatchBuiltin(
|
|
|
53
66
|
process.stderr.write("Unknown subcommand: mcp " + pr.path.slice(1).join(" ") + "\n");
|
|
54
67
|
process.exit(1);
|
|
55
68
|
}
|
|
56
|
-
await cliMcpServeStdio(
|
|
69
|
+
await cliMcpServeStdio(program);
|
|
57
70
|
process.exit(0);
|
|
58
71
|
}
|
|
59
72
|
|
|
@@ -64,7 +77,7 @@ export async function dispatchBuiltin(
|
|
|
64
77
|
);
|
|
65
78
|
process.exit(1);
|
|
66
79
|
}
|
|
67
|
-
if (
|
|
80
|
+
if (!caps.install) {
|
|
68
81
|
process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
|
|
69
82
|
process.exit(1);
|
|
70
83
|
}
|
|
@@ -72,52 +85,54 @@ export async function dispatchBuiltin(
|
|
|
72
85
|
process.stderr.write("Unknown subcommand: install " + pr.path.slice(1).join(" ") + "\n");
|
|
73
86
|
process.exit(1);
|
|
74
87
|
}
|
|
75
|
-
await cliInstall(
|
|
88
|
+
await cliInstall(program, pr.opts);
|
|
76
89
|
}
|
|
77
90
|
}
|
|
78
91
|
|
|
79
92
|
/** Built-in intercept roots for leaf programs. */
|
|
80
93
|
export function builtinInterceptRoot(
|
|
81
|
-
|
|
94
|
+
program: CliProgram,
|
|
82
95
|
argv: string[],
|
|
83
|
-
): { parseRoot:
|
|
84
|
-
if (!
|
|
85
|
-
return { parseRoot:
|
|
96
|
+
): { parseRoot: CliNode; isLeafCompletionIntercept: boolean } {
|
|
97
|
+
if (!isCliLeaf(program) || argv.length < 1) {
|
|
98
|
+
return { parseRoot: program, isLeafCompletionIntercept: false };
|
|
86
99
|
}
|
|
87
100
|
|
|
101
|
+
const caps = resolveCapabilities(program);
|
|
88
102
|
const first = argv[0];
|
|
103
|
+
|
|
89
104
|
if (first === "completion") {
|
|
90
105
|
return {
|
|
91
106
|
parseRoot: {
|
|
92
|
-
key:
|
|
93
|
-
description:
|
|
94
|
-
commands: [completionGroup(
|
|
95
|
-
}
|
|
107
|
+
key: program.key,
|
|
108
|
+
description: program.description,
|
|
109
|
+
commands: [completionGroup(program.key)],
|
|
110
|
+
},
|
|
96
111
|
isLeafCompletionIntercept: true,
|
|
97
112
|
};
|
|
98
113
|
}
|
|
99
114
|
|
|
100
|
-
if (first === "install" &&
|
|
115
|
+
if (first === "install" && caps.install) {
|
|
101
116
|
return {
|
|
102
117
|
parseRoot: {
|
|
103
|
-
key:
|
|
104
|
-
description:
|
|
105
|
-
commands: [cliBuiltinInstallCommand(
|
|
106
|
-
}
|
|
118
|
+
key: program.key,
|
|
119
|
+
description: program.description,
|
|
120
|
+
commands: [cliBuiltinInstallCommand(program)],
|
|
121
|
+
},
|
|
107
122
|
isLeafCompletionIntercept: false,
|
|
108
123
|
};
|
|
109
124
|
}
|
|
110
125
|
|
|
111
|
-
if (first === "mcp" &&
|
|
126
|
+
if (first === "mcp" && caps.mcp) {
|
|
112
127
|
return {
|
|
113
128
|
parseRoot: {
|
|
114
|
-
key:
|
|
115
|
-
description:
|
|
129
|
+
key: program.key,
|
|
130
|
+
description: program.description,
|
|
116
131
|
commands: [cliBuiltinMcpCommand()],
|
|
117
|
-
}
|
|
132
|
+
},
|
|
118
133
|
isLeafCompletionIntercept: false,
|
|
119
134
|
};
|
|
120
135
|
}
|
|
121
136
|
|
|
122
|
-
return { parseRoot:
|
|
137
|
+
return { parseRoot: program, isLeafCompletionIntercept: false };
|
|
123
138
|
}
|
package/src/builtins/export.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type CliCapabilities, resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import type { CliFallbackMode, CliOption, CliPositional, CliProgram } from "../types.ts";
|
|
2
3
|
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
3
4
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
4
5
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
5
|
-
import { isCompiledExecutable } from "../install/compiled.ts";
|
|
6
6
|
|
|
7
7
|
/** JSON-safe command node (no handlers). */
|
|
8
8
|
export interface CliSchemaExport {
|
|
@@ -16,7 +16,13 @@ export interface CliSchemaExport {
|
|
|
16
16
|
positionals?: CliPositional[];
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function exportBuiltinNode(cmd:
|
|
19
|
+
function exportBuiltinNode(cmd: {
|
|
20
|
+
key: string;
|
|
21
|
+
description: string;
|
|
22
|
+
notes?: string;
|
|
23
|
+
options?: CliOption[];
|
|
24
|
+
commands?: CliSchemaExport[];
|
|
25
|
+
}): CliSchemaExport {
|
|
20
26
|
const out: CliSchemaExport = {
|
|
21
27
|
key: cmd.key,
|
|
22
28
|
description: cmd.description,
|
|
@@ -34,12 +40,13 @@ function exportBuiltinNode(cmd: CliCommand): CliSchemaExport {
|
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/** Built-in subtrees matching help visibility for `--schema` export. */
|
|
37
|
-
export function exportPresentationBuiltins(
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
export function exportPresentationBuiltins(program: CliProgram, caps?: CliCapabilities): CliSchemaExport[] {
|
|
44
|
+
const resolved = caps ?? resolveCapabilities(program);
|
|
45
|
+
const builtins: CliSchemaExport[] = [exportBuiltinNode(cliBuiltinCompletionGroup(program.key))];
|
|
46
|
+
if (resolved.install) {
|
|
47
|
+
builtins.push(exportBuiltinNode(cliBuiltinInstallCommand(program)));
|
|
41
48
|
}
|
|
42
|
-
if (
|
|
49
|
+
if (resolved.mcp) {
|
|
43
50
|
builtins.push(exportBuiltinNode(cliBuiltinMcpCommand()));
|
|
44
51
|
}
|
|
45
52
|
return builtins;
|
package/src/builtins/install.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CliProgram, CliOption, CliOptionKind, type CliLeaf } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
/** Install command options (dynamic: `--mcp` only when MCP is enabled). */
|
|
4
|
-
export function installBuiltinOptions(root:
|
|
4
|
+
export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
5
5
|
const opts: CliOption[] = [
|
|
6
6
|
{
|
|
7
7
|
name: "all",
|
|
@@ -78,7 +78,7 @@ export function installBuiltinOptions(root: CliCommand): CliOption[] {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/** Builds the `install` built-in command (compiled binaries only). */
|
|
81
|
-
export function cliBuiltinInstallCommand(root:
|
|
81
|
+
export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
|
|
82
82
|
return {
|
|
83
83
|
key: "install",
|
|
84
84
|
description: "Install the binary, shell completions, agent skills, and MCP config to your user environment.",
|
package/src/builtins/mcp.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type CliLeaf } from "../types.ts";
|
|
2
2
|
|
|
3
3
|
/** Presence options for the top-level `mcp` built-in (leaf). */
|
|
4
|
-
export function cliBuiltinMcpCommand():
|
|
4
|
+
export function cliBuiltinMcpCommand(): CliLeaf {
|
|
5
5
|
return {
|
|
6
6
|
key: "mcp",
|
|
7
7
|
description: "Run as an MCP server over stdio for AI agents.",
|
|
@@ -1,39 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import type { CliCapabilities } from "../capabilities.ts";
|
|
2
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
3
|
+
import type { CliLeaf, CliNode, CliProgram, CliRouter } from "../types.ts";
|
|
4
|
+
import { isCliLeaf, isCliRouter } from "../types.ts";
|
|
3
5
|
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
4
6
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
5
7
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
6
8
|
|
|
7
|
-
/** Built-in
|
|
8
|
-
export function presentationBuiltins(
|
|
9
|
-
const builtins:
|
|
10
|
-
if (
|
|
11
|
-
builtins.push(cliBuiltinInstallCommand(
|
|
9
|
+
/** Built-in command nodes injected for help, schema, and completions. */
|
|
10
|
+
export function presentationBuiltins(program: CliProgram, caps: CliCapabilities): CliNode[] {
|
|
11
|
+
const builtins: CliNode[] = [cliBuiltinCompletionGroup(program.key)];
|
|
12
|
+
if (caps.install) {
|
|
13
|
+
builtins.push(cliBuiltinInstallCommand(program));
|
|
12
14
|
}
|
|
13
|
-
if (
|
|
15
|
+
if (caps.mcp) {
|
|
14
16
|
builtins.push(cliBuiltinMcpCommand());
|
|
15
17
|
}
|
|
16
18
|
return builtins;
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
|
-
* Returns a schema suitable for help display, including
|
|
21
|
-
* Routing
|
|
22
|
+
* Returns a schema suitable for help display, including capability-built-in subtrees.
|
|
23
|
+
* Routing programs get builtins merged; leaf programs are wrapped as a tiny router.
|
|
22
24
|
*/
|
|
23
|
-
export function cliPresentationRoot(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if (
|
|
25
|
+
export function cliPresentationRoot(program: CliProgram): CliRouter {
|
|
26
|
+
const caps = resolveCapabilities(program);
|
|
27
|
+
const builtins = presentationBuiltins(program, caps);
|
|
28
|
+
|
|
29
|
+
if (isCliLeaf(program)) {
|
|
28
30
|
return {
|
|
29
|
-
key:
|
|
30
|
-
description:
|
|
31
|
-
options:
|
|
32
|
-
commands:
|
|
33
|
-
}
|
|
31
|
+
key: program.key,
|
|
32
|
+
description: program.description,
|
|
33
|
+
options: program.options,
|
|
34
|
+
commands: builtins,
|
|
35
|
+
};
|
|
34
36
|
}
|
|
37
|
+
|
|
35
38
|
return {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
key: program.key,
|
|
40
|
+
description: program.description,
|
|
41
|
+
notes: program.notes,
|
|
42
|
+
options: program.options,
|
|
43
|
+
fallbackCommand: program.fallbackCommand,
|
|
44
|
+
fallbackMode: program.fallbackMode,
|
|
45
|
+
commands: [...program.commands, ...builtins],
|
|
46
|
+
};
|
|
39
47
|
}
|
|
48
|
+
|
|
49
|
+
/** Presentation tree may include builtin leaf stubs. */
|
|
50
|
+
export type CliPresentationNode = CliNode | CliLeaf;
|
package/src/builtins/scopes.ts
CHANGED
|
@@ -2,35 +2,36 @@
|
|
|
2
2
|
Shared completion scope walk used by bash, zsh, and fish emitters.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { type CliNode, type CliRouter, isCliLeaf, isCliRouter } from "../types.ts";
|
|
6
6
|
|
|
7
7
|
/** One tab-completion scope: child commands, options, and path key for the schema walk. */
|
|
8
8
|
export interface ScopeRec {
|
|
9
|
-
kids:
|
|
9
|
+
kids: CliNode[];
|
|
10
10
|
opts: import("../types.ts").CliOption[];
|
|
11
11
|
path: string;
|
|
12
12
|
wantsFiles: boolean;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
function hasPositionalArguments(cmd:
|
|
16
|
-
return (cmd.positionals ?? []).length > 0;
|
|
15
|
+
function hasPositionalArguments(cmd: CliNode): boolean {
|
|
16
|
+
return isCliLeaf(cmd) && (cmd.positionals ?? []).length > 0;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function walkScopes(cmdPath: string, cmd:
|
|
19
|
+
function walkScopes(cmdPath: string, cmd: CliNode, acc: ScopeRec[]): void {
|
|
20
|
+
const kids = isCliRouter(cmd) ? cmd.commands : [];
|
|
20
21
|
acc.push({
|
|
21
|
-
kids
|
|
22
|
+
kids,
|
|
22
23
|
opts: cmd.options ?? [],
|
|
23
24
|
path: cmdPath,
|
|
24
25
|
wantsFiles: hasPositionalArguments(cmd),
|
|
25
26
|
});
|
|
26
|
-
for (const ch of
|
|
27
|
+
for (const ch of kids) {
|
|
27
28
|
const nextPath = cmdPath === "" ? ch.key : cmdPath + "/" + ch.key;
|
|
28
29
|
walkScopes(nextPath, ch, acc);
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
/** Flattens the schema into a list of completion scopes (root + every command path). */
|
|
33
|
-
export function collectScopes(schema:
|
|
34
|
+
export function collectScopes(schema: CliRouter): ScopeRec[] {
|
|
34
35
|
const acc: ScopeRec[] = [];
|
|
35
36
|
acc.push({
|
|
36
37
|
kids: schema.commands ?? [],
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Internal capability resolver — decides which platform builtins are active for a program.
|
|
3
|
+
Not exported from the public package barrel.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { CliProgram } from "./types.ts";
|
|
7
|
+
import { isCompiledExecutable } from "./install/compiled.ts";
|
|
8
|
+
|
|
9
|
+
/** Platform builtins derived from program config and runtime. */
|
|
10
|
+
export interface CliCapabilities {
|
|
11
|
+
completion: true;
|
|
12
|
+
mcp: boolean;
|
|
13
|
+
install: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Resolves which capabilities are enabled for a program. */
|
|
17
|
+
export function resolveCapabilities(program: CliProgram): CliCapabilities {
|
|
18
|
+
return {
|
|
19
|
+
completion: true,
|
|
20
|
+
mcp: program.mcpServer !== undefined,
|
|
21
|
+
install: isCompiledExecutable() && program.install?.enabled !== false,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Reserved top-level command names for the given capabilities. */
|
|
26
|
+
export function reservedCommandNames(caps: CliCapabilities): string[] {
|
|
27
|
+
const names = ["completion", "install"];
|
|
28
|
+
if (caps.mcp) {
|
|
29
|
+
names.push("mcp");
|
|
30
|
+
}
|
|
31
|
+
return names;
|
|
32
|
+
}
|
package/src/context.ts
CHANGED
|
@@ -7,7 +7,8 @@ It keeps handlers small with a typed read API for flags, strings, numbers, and c
|
|
|
7
7
|
parsed values.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type {
|
|
10
|
+
import type { CliInvocation, CliNode, CliProgram } from "./types.ts";
|
|
11
|
+
import { isCliLeaf, isCliRouter } from "./types.ts";
|
|
11
12
|
import { strictParseDouble } from "./utils.ts";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -17,24 +18,24 @@ export class CliContext {
|
|
|
17
18
|
readonly appName: string;
|
|
18
19
|
readonly commandPath: string[];
|
|
19
20
|
readonly args: string[];
|
|
20
|
-
readonly
|
|
21
|
+
readonly program: CliProgram;
|
|
21
22
|
readonly opts: Record<string, string>;
|
|
22
23
|
readonly invocation: CliInvocation;
|
|
23
24
|
|
|
24
|
-
/** Captures the
|
|
25
|
+
/** Captures the program root, routed path, positional words, and option map for a leaf handler. */
|
|
25
26
|
constructor(
|
|
26
27
|
appName: string,
|
|
27
28
|
commandPath: string[],
|
|
28
29
|
args: string[],
|
|
29
30
|
opts: Record<string, string>,
|
|
30
|
-
|
|
31
|
+
program: CliProgram,
|
|
31
32
|
invocation: CliInvocation = "cli",
|
|
32
33
|
) {
|
|
33
34
|
this.appName = appName;
|
|
34
35
|
this.commandPath = commandPath;
|
|
35
36
|
this.args = args;
|
|
36
37
|
this.opts = opts;
|
|
37
|
-
this.
|
|
38
|
+
this.program = program;
|
|
38
39
|
this.invocation = invocation;
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -79,9 +80,13 @@ export class CliContext {
|
|
|
79
80
|
private _positionalMap(): Record<string, string | string[]> {
|
|
80
81
|
if (this._posMap) return this._posMap;
|
|
81
82
|
|
|
82
|
-
let node:
|
|
83
|
+
let node: CliNode = this.program;
|
|
83
84
|
for (const seg of this.commandPath) {
|
|
84
|
-
|
|
85
|
+
if (!isCliRouter(node)) {
|
|
86
|
+
this._posMap = {};
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
const child = node.commands.find((c) => c.key === seg);
|
|
85
90
|
if (!child) {
|
|
86
91
|
this._posMap = {};
|
|
87
92
|
return {};
|
|
@@ -89,6 +94,11 @@ export class CliContext {
|
|
|
89
94
|
node = child;
|
|
90
95
|
}
|
|
91
96
|
|
|
97
|
+
if (!isCliLeaf(node)) {
|
|
98
|
+
this._posMap = {};
|
|
99
|
+
return {};
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
const map: Record<string, string | string[]> = {};
|
|
93
103
|
let argIdx = 0;
|
|
94
104
|
for (const p of node.positionals ?? []) {
|
package/src/help.ts
CHANGED
|
@@ -7,7 +7,7 @@ It keeps help formatting shared across help and error paths so users see one con
|
|
|
7
7
|
style no matter how help is reached.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { CliNode, CliOption, CliOptionKind, CliPositional, CliRouter, isCliLeaf, isCliRouter } from "./types.ts";
|
|
11
11
|
|
|
12
12
|
// ── ANSI Style Helpers ────────────────────────────────────────────────────────
|
|
13
13
|
|
|
@@ -369,7 +369,7 @@ function rowsForPositionals(defs: CliPositional[], color: boolean): HelpRow[] {
|
|
|
369
369
|
}
|
|
370
370
|
|
|
371
371
|
/** Table rows for subcommands, sorted by key. */
|
|
372
|
-
function rowsForSubcommands(cmds:
|
|
372
|
+
function rowsForSubcommands(cmds: CliNode[]): HelpRow[] {
|
|
373
373
|
return cmds
|
|
374
374
|
.sort((a, b) => a.key.localeCompare(b.key))
|
|
375
375
|
.map((c) => ({ label: c.key, description: c.description }));
|
|
@@ -381,7 +381,7 @@ function rowsForSubcommands(cmds: CliCommand[]): HelpRow[] {
|
|
|
381
381
|
* Renders full help for the app root or a nested command, following `helpPath` from the root key.
|
|
382
382
|
* `useStderr` is reserved for call-site consistency; width and color use stdout TTY.
|
|
383
383
|
*/
|
|
384
|
-
export function cliHelpRender(schema:
|
|
384
|
+
export function cliHelpRender(schema: CliRouter, helpPath: string[], useStderr: boolean): string {
|
|
385
385
|
const hw = getHelpWidth();
|
|
386
386
|
const color = isStdoutTTY();
|
|
387
387
|
|
|
@@ -416,14 +416,14 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
|
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
let layer = schema.commands ?? [];
|
|
419
|
-
let node:
|
|
419
|
+
let node: CliNode | undefined;
|
|
420
420
|
for (const seg of helpPath) {
|
|
421
|
-
const ch = layer.find((c) => c.key === seg);
|
|
421
|
+
const ch = layer.find((c: CliNode) => c.key === seg);
|
|
422
422
|
if (!ch) {
|
|
423
423
|
return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
|
|
424
424
|
}
|
|
425
425
|
node = ch;
|
|
426
|
-
layer = ch.commands
|
|
426
|
+
layer = isCliRouter(ch) ? ch.commands : [];
|
|
427
427
|
}
|
|
428
428
|
if (!node) {
|
|
429
429
|
return (color ? style.red("Unknown help path.") : "Unknown help path.") + "\n";
|
|
@@ -438,7 +438,13 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
|
|
|
438
438
|
lines.push(
|
|
439
439
|
renderTextBox(
|
|
440
440
|
"Usage",
|
|
441
|
-
usageLines(
|
|
441
|
+
usageLines(
|
|
442
|
+
schema.key,
|
|
443
|
+
helpPath,
|
|
444
|
+
isCliRouter(node) && node.commands.length > 0,
|
|
445
|
+
isCliLeaf(node) && (node.positionals ?? []).length > 0,
|
|
446
|
+
color,
|
|
447
|
+
),
|
|
442
448
|
hw,
|
|
443
449
|
color,
|
|
444
450
|
).join("\n"),
|
|
@@ -450,13 +456,19 @@ export function cliHelpRender(schema: CliCommand, helpPath: string[], useStderr:
|
|
|
450
456
|
lines.push(optBox.join("\n"));
|
|
451
457
|
}
|
|
452
458
|
|
|
453
|
-
const posBox = renderTableBox(
|
|
459
|
+
const posBox = renderTableBox(
|
|
460
|
+
"Arguments",
|
|
461
|
+
rowsForPositionals(isCliLeaf(node) ? (node.positionals ?? []) : [], color),
|
|
462
|
+
hw,
|
|
463
|
+
color,
|
|
464
|
+
);
|
|
454
465
|
if (posBox.length > 0) {
|
|
455
466
|
lines.push("");
|
|
456
467
|
lines.push(posBox.join("\n"));
|
|
457
468
|
}
|
|
458
469
|
|
|
459
|
-
const
|
|
470
|
+
const subcmds = isCliRouter(node) ? node.commands : [];
|
|
471
|
+
const subBox = renderTableBox("Subcommands", rowsForSubcommands(subcmds), hw, color);
|
|
460
472
|
if (subBox.length > 0) {
|
|
461
473
|
lines.push("");
|
|
462
474
|
lines.push(subBox.join("\n"));
|