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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
|
|
2
|
+
import { collectScopes } from "./scopes.ts";
|
|
3
|
+
import {
|
|
4
|
+
escFishSingleQuoted,
|
|
5
|
+
identToken,
|
|
6
|
+
kHelpLong,
|
|
7
|
+
kHelpShort,
|
|
8
|
+
kSchemaDesc,
|
|
9
|
+
kSchemaLong,
|
|
10
|
+
} from "./shell-helpers.ts";
|
|
11
|
+
|
|
12
|
+
function scopeCondition(ident: string, scopeIndex: number, path: string): string {
|
|
13
|
+
const fn = `__${ident}_scope_${scopeIndex}`;
|
|
14
|
+
let body = `function ${fn}\n`;
|
|
15
|
+
body += ` set -l tokens (commandline -opc)\n`;
|
|
16
|
+
if (path === "") {
|
|
17
|
+
body += ` test (count $tokens) -eq 0\n`;
|
|
18
|
+
} else {
|
|
19
|
+
const parts = path.split("/");
|
|
20
|
+
body += ` test (count $tokens) -eq ${parts.length}\n`;
|
|
21
|
+
for (let i = 0; i < parts.length; i++) {
|
|
22
|
+
body += ` and test $tokens[${i + 1}] = ${parts[i]}\n`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
body += `end\n\n`;
|
|
26
|
+
return body;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Returns a self-contained fish completion script for the given program schema. */
|
|
30
|
+
export function completionFishScript(schema: CliRouter): string {
|
|
31
|
+
const ident = identToken(schema.key);
|
|
32
|
+
const app = schema.key;
|
|
33
|
+
const scopes = collectScopes(schema);
|
|
34
|
+
|
|
35
|
+
let out = "# Fish completion for " + app + "\n\n";
|
|
36
|
+
|
|
37
|
+
for (const [i, sc] of scopes.entries()) {
|
|
38
|
+
out += scopeCondition(ident, i, sc.path);
|
|
39
|
+
const cond = `__${ident}_scope_${i}`;
|
|
40
|
+
|
|
41
|
+
for (const ch of sc.kids) {
|
|
42
|
+
out += `complete -c ${app} -n '${cond}' -a '${escFishSingleQuoted(ch.key)}' -d '${escFishSingleQuoted(ch.description)}'\n`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
out += `complete -c ${app} -n '${cond}' -s h -l help -d '${escFishSingleQuoted("Show help for this command.")}'\n`;
|
|
46
|
+
if (sc.path === "") {
|
|
47
|
+
out += `complete -c ${app} -n '${cond}' -l schema -d '${escFishSingleQuoted(kSchemaDesc)}'\n`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
for (const op of sc.opts) {
|
|
51
|
+
if (op.kind === CliOptionKind.Presence) {
|
|
52
|
+
const shortPart = op.shortName ? `-s ${op.shortName} ` : "";
|
|
53
|
+
out += `complete -c ${app} -n '${cond}' ${shortPart}-l ${op.name} -d '${escFishSingleQuoted(op.description)}'\n`;
|
|
54
|
+
} else if (op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0) {
|
|
55
|
+
const shortPart = op.shortName ? `-s ${op.shortName} ` : "";
|
|
56
|
+
out += `complete -c ${app} -n '${cond}' ${shortPart}-l ${op.name} -d '${escFishSingleQuoted(op.description)}'\n`;
|
|
57
|
+
const enumCond = `${cond}; and __fish_seen_argument -l ${op.name}`;
|
|
58
|
+
for (const choice of op.choices ?? []) {
|
|
59
|
+
out += `complete -c ${app} -n '${enumCond}' -a '${escFishSingleQuoted(choice)}'\n`;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const shortPart = op.shortName ? `-s ${op.shortName} ` : "";
|
|
63
|
+
out += `complete -c ${app} -n '${cond}' ${shortPart}-l ${op.name} -d '${escFishSingleQuoted(op.description)}' -r\n`;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (sc.wantsFiles && sc.kids.length === 0) {
|
|
68
|
+
out += `complete -c ${app} -n '${cond}' -F\n`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type CliLeaf, type CliNode, type CliRouter } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Builds the static `completion` / `bash` / `zsh` / `fish` command subtree (merged into the program root at runtime).
|
|
5
|
+
*/
|
|
6
|
+
export function cliBuiltinCompletionGroup(appName: string): CliRouter {
|
|
7
|
+
return {
|
|
8
|
+
key: "completion",
|
|
9
|
+
description: "Generate the autocompletion script for shells.",
|
|
10
|
+
commands: [
|
|
11
|
+
{
|
|
12
|
+
key: "bash",
|
|
13
|
+
description: "Print a bash tab-completion script.",
|
|
14
|
+
notes:
|
|
15
|
+
"Output is the whole script.\n" +
|
|
16
|
+
"Pipe it to a file, or feed it straight into your shell.\n\n" +
|
|
17
|
+
"To keep it across restarts, save it and source that file from ~/.bashrc.\n\n" +
|
|
18
|
+
"For example:\n\n" +
|
|
19
|
+
`echo 'eval \"$(${appName} completion bash)\"' >> ~/.bashrc\n` +
|
|
20
|
+
`\nor\n` +
|
|
21
|
+
` ${appName} completion bash > ~/.bash_completion.d/${appName}\n` +
|
|
22
|
+
` echo 'source ~/.bash_completion.d/${appName}' >> ~/.bashrc\n\n` +
|
|
23
|
+
"To try it only in this session (nothing written to disk):\n" +
|
|
24
|
+
` source <(${appName} completion bash)`,
|
|
25
|
+
handler: () => {},
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
key: "zsh",
|
|
29
|
+
description: "Print a zsh tab-completion script.",
|
|
30
|
+
notes:
|
|
31
|
+
"Output is the whole script.\n\n" +
|
|
32
|
+
`fpath setup: ${appName} completion zsh > ~/.zsh/completions/_${appName}\n\n` +
|
|
33
|
+
`source setup: echo 'eval \"$(${appName} completion zsh)\"' >> ~/.zshrc\n\n` +
|
|
34
|
+
"To try it only in this session (nothing written to disk):\n" +
|
|
35
|
+
` eval \"$(${appName} completion zsh)\"`,
|
|
36
|
+
handler: () => {},
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "fish",
|
|
40
|
+
description: "Print a fish tab-completion script.",
|
|
41
|
+
notes:
|
|
42
|
+
"Output is the whole script.\n\n" +
|
|
43
|
+
"Install:\n" +
|
|
44
|
+
` ${appName} completion fish > ~/.config/fish/completions/${appName}.fish\n\n` +
|
|
45
|
+
"Fish loads completions from that directory automatically.",
|
|
46
|
+
handler: () => {},
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { CliNode, CliRouter, CliOptionKind } from "../types.ts";
|
|
2
|
+
import { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
3
|
+
import {
|
|
4
|
+
escShellSingleQuoted,
|
|
5
|
+
identToken,
|
|
6
|
+
kHelpLong,
|
|
7
|
+
kHelpShort,
|
|
8
|
+
kSchemaDesc,
|
|
9
|
+
kSchemaLong,
|
|
10
|
+
mainName,
|
|
11
|
+
} from "./shell-helpers.ts";
|
|
12
|
+
|
|
13
|
+
function emitScopeArraysZsh(ident: string, scopes: ScopeRec[]): string {
|
|
14
|
+
let out = "";
|
|
15
|
+
for (const [i, sc] of scopes.entries()) {
|
|
16
|
+
out += "typeset -g A_" + ident + "_" + i + "_opts\n";
|
|
17
|
+
out += "A_" + ident + "_" + i + "_opts=(";
|
|
18
|
+
out += "'" + escShellSingleQuoted(kHelpLong) + ":" + escShellSingleQuoted("Show help for this command.") + "' '" + escShellSingleQuoted(kHelpShort) + ":" + escShellSingleQuoted("Show help for this command.") + "'";
|
|
19
|
+
if (sc.path === "") {
|
|
20
|
+
out += " '" + escShellSingleQuoted(kSchemaLong) + ":" + escShellSingleQuoted(kSchemaDesc) + "'";
|
|
21
|
+
}
|
|
22
|
+
for (const o of sc.opts) {
|
|
23
|
+
out += " '" + escShellSingleQuoted("--" + o.name) + ":" + escShellSingleQuoted(o.description) + "'";
|
|
24
|
+
if (o.shortName) {
|
|
25
|
+
out += " '" + escShellSingleQuoted("-" + o.shortName) + ":" + escShellSingleQuoted(o.description) + "'";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
out += ")\n";
|
|
29
|
+
out += "typeset -g A_" + ident + "_" + i + "_leaf=" + (sc.kids.length === 0 ? "1" : "0") + "\n";
|
|
30
|
+
out += "typeset -g A_" + ident + "_" + i + "_pos=" + (sc.wantsFiles ? "1" : "0") + "\n";
|
|
31
|
+
if (sc.kids.length > 0) {
|
|
32
|
+
out += "typeset -g A_" + ident + "_" + i + "_cmds=(";
|
|
33
|
+
for (const ch of sc.kids) {
|
|
34
|
+
out += " '" + escShellSingleQuoted(ch.key) + ":" + escShellSingleQuoted(ch.description) + "'";
|
|
35
|
+
}
|
|
36
|
+
out += ")\n";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function emitConsumeLongZsh(ident: string, scopes: ScopeRec[]): string {
|
|
43
|
+
let o = "_${ident}_nac_consume_long() {\n".replace("${ident}", ident);
|
|
44
|
+
o += " local sid=\"$1\" w=\"$2\" nw=\"$3\"\n";
|
|
45
|
+
o += " case $sid in\n";
|
|
46
|
+
for (const [i, sc] of scopes.entries()) {
|
|
47
|
+
o += " " + i + ")\n";
|
|
48
|
+
o += " case $w in\n";
|
|
49
|
+
o += " " + kHelpLong + "|${kHelpLong}=*|${kHelpShort}) echo 1 ;;\n".replace(/\$\{kHelpLong\}/g, kHelpLong).replace(/\$\{kHelpShort\}/g, kHelpShort);
|
|
50
|
+
if (sc.path === "") {
|
|
51
|
+
o += " " + kSchemaLong + ") echo 1 ;;\n";
|
|
52
|
+
}
|
|
53
|
+
for (const op of sc.opts) {
|
|
54
|
+
const base = "--" + op.name;
|
|
55
|
+
if (op.kind === "presence") {
|
|
56
|
+
o += " " + base + "|${base}=*) echo 1 ;;\n".replace(/\$\{base\}/g, base);
|
|
57
|
+
} else {
|
|
58
|
+
o += " " + base + "=*) echo 1 ;;\n";
|
|
59
|
+
o += " " + base + ") echo 2 ;;\n";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
o += " *) echo 0 ;;\n";
|
|
63
|
+
o += " esac\n";
|
|
64
|
+
o += " ;;\n";
|
|
65
|
+
}
|
|
66
|
+
o += " *) echo 0 ;;\n";
|
|
67
|
+
o += " esac\n";
|
|
68
|
+
o += "}\n";
|
|
69
|
+
return o;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function emitConsumeShortZsh(ident: string, scopes: ScopeRec[]): string {
|
|
73
|
+
let o = "_${ident}_nac_consume_short() {\n".replace("${ident}", ident);
|
|
74
|
+
o += " local sid=\"$1\" w=\"$2\"\n";
|
|
75
|
+
o += " case $sid in\n";
|
|
76
|
+
for (const [i, sc] of scopes.entries()) {
|
|
77
|
+
o += " " + i + ")\n";
|
|
78
|
+
o += " local rest=${w#-}\n";
|
|
79
|
+
o += " local ch\n";
|
|
80
|
+
o += " local saw=0\n";
|
|
81
|
+
o += " while [[ -n $rest ]]; do\n";
|
|
82
|
+
o += " ch=${rest[1,1]}\n";
|
|
83
|
+
o += " rest=${rest[2,-1]}\n";
|
|
84
|
+
o += " case $ch in\n";
|
|
85
|
+
let boolChars = "";
|
|
86
|
+
for (const op of sc.opts) {
|
|
87
|
+
if (!op.shortName) continue;
|
|
88
|
+
if (op.kind === "presence") {
|
|
89
|
+
boolChars += op.shortName + "|";
|
|
90
|
+
} else {
|
|
91
|
+
o += " " + op.shortName + ")\n";
|
|
92
|
+
o += " if [[ $saw -ne 0 || -n $rest ]]; then echo 0; return; fi\n";
|
|
93
|
+
o += " echo 2; return ;;\n";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (boolChars.length > 0) {
|
|
97
|
+
boolChars = boolChars.slice(0, -1);
|
|
98
|
+
o += " " + boolChars + ") ;;\n";
|
|
99
|
+
}
|
|
100
|
+
o += " *) echo 0; return ;;\n";
|
|
101
|
+
o += " esac\n";
|
|
102
|
+
o += " saw=1\n";
|
|
103
|
+
o += " done\n";
|
|
104
|
+
o += " echo 1\n";
|
|
105
|
+
o += " ;;\n";
|
|
106
|
+
}
|
|
107
|
+
o += " *) echo 0 ;;\n";
|
|
108
|
+
o += " esac\n";
|
|
109
|
+
o += "}\n";
|
|
110
|
+
return o;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function emitMatchChildZsh(ident: string, scopes: ScopeRec[], pathIndex: Record<string, number>): string {
|
|
114
|
+
let o = "_${ident}_nac_match_child() {\n".replace("${ident}", ident);
|
|
115
|
+
o += " local sid=\"$1\" w=\"$2\"\n";
|
|
116
|
+
o += " case $sid in\n";
|
|
117
|
+
for (const [sid, sc] of scopes.entries()) {
|
|
118
|
+
if (sc.kids.length === 0) continue;
|
|
119
|
+
o += " " + sid + ")\n";
|
|
120
|
+
o += " case $w in\n";
|
|
121
|
+
for (const ch of sc.kids) {
|
|
122
|
+
const childPath = sc.path === "" ? ch.key : sc.path + "/" + ch.key;
|
|
123
|
+
const cid = pathIndex[childPath] ?? 0;
|
|
124
|
+
o += " " + ch.key + ") echo " + cid + "; return 0 ;;\n";
|
|
125
|
+
}
|
|
126
|
+
o += " esac\n";
|
|
127
|
+
o += " ;;\n";
|
|
128
|
+
}
|
|
129
|
+
o += " esac\n";
|
|
130
|
+
o += " return 1\n";
|
|
131
|
+
o += "}\n";
|
|
132
|
+
return o;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function emitSimulateZsh(ident: string): string {
|
|
136
|
+
let o = "_${ident}_nac_simulate() {\n".replace("${ident}", ident);
|
|
137
|
+
o += " local i=2 sid=0 w steps next\n";
|
|
138
|
+
o += " while (( i < CURRENT )); do\n";
|
|
139
|
+
o += " w=$words[i]\n";
|
|
140
|
+
o += " if [[ $w == " + kHelpShort + " || $w == " + kHelpLong + " || $w == " + kSchemaLong + " ]]; then\n";
|
|
141
|
+
o += " ((i++)); continue\n";
|
|
142
|
+
o += " fi\n";
|
|
143
|
+
o += " if [[ $w == --* ]]; then\n";
|
|
144
|
+
o += " steps=$(_${ident}_nac_consume_long \"$sid\" \"$w\" \"${words[i+1]}\")\n".replace("${ident}", ident);
|
|
145
|
+
o += " case $steps in\n";
|
|
146
|
+
o += " 0) break ;;\n";
|
|
147
|
+
o += " 1) ((i++)) ;;\n";
|
|
148
|
+
o += " 2) ((i+=2)) ;;\n";
|
|
149
|
+
o += " *) break ;;\n";
|
|
150
|
+
o += " esac\n";
|
|
151
|
+
o += " continue\n";
|
|
152
|
+
o += " fi\n";
|
|
153
|
+
o += " if [[ $w == -* ]]; then\n";
|
|
154
|
+
o += " steps=$(_${ident}_nac_consume_short \"$sid\" \"$w\")\n".replace("${ident}", ident);
|
|
155
|
+
o += " case $steps in\n";
|
|
156
|
+
o += " 0) break ;;\n";
|
|
157
|
+
o += " 1) ((i++)) ;;\n";
|
|
158
|
+
o += " 2) ((i++)); break ;;\n";
|
|
159
|
+
o += " *) break ;;\n";
|
|
160
|
+
o += " esac\n";
|
|
161
|
+
o += " continue\n";
|
|
162
|
+
o += " fi\n";
|
|
163
|
+
o += " next=$(_${ident}_nac_match_child \"$sid\" \"$w\") || break\n".replace("${ident}", ident);
|
|
164
|
+
o += " sid=$next\n";
|
|
165
|
+
o += " ((i++))\n";
|
|
166
|
+
o += " done\n";
|
|
167
|
+
o += " REPLY_SID=$sid\n";
|
|
168
|
+
o += "}\n";
|
|
169
|
+
return o;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function emitEnumReplyZsh(ident: string, scopes: ScopeRec[]): string {
|
|
173
|
+
let o = "_${ident}_nac_enum_reply() {\n".replace("${ident}", ident);
|
|
174
|
+
o += " local sid=$1 prev=$2\n";
|
|
175
|
+
o += " case $sid in\n";
|
|
176
|
+
for (const [i, sc] of scopes.entries()) {
|
|
177
|
+
const enumOpts = sc.opts.filter((op) => op.kind === CliOptionKind.Enum && (op.choices?.length ?? 0) > 0);
|
|
178
|
+
if (enumOpts.length === 0) continue;
|
|
179
|
+
o += " " + i + ")\n";
|
|
180
|
+
o += " case $prev in\n";
|
|
181
|
+
for (const op of enumOpts) {
|
|
182
|
+
const vals = (op.choices ?? []).map((c) => escShellSingleQuoted(c)).join(" ");
|
|
183
|
+
o += " --" + op.name + ") _values " + vals + "; return 0 ;;\n";
|
|
184
|
+
}
|
|
185
|
+
o += " esac\n";
|
|
186
|
+
o += " ;;\n";
|
|
187
|
+
}
|
|
188
|
+
o += " esac\n";
|
|
189
|
+
o += " return 1\n";
|
|
190
|
+
o += "}\n";
|
|
191
|
+
return o;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function emitMainBodyZsh(schema: CliRouter, ident: string): string {
|
|
195
|
+
const main = mainName(schema.key);
|
|
196
|
+
let o = "_${main}() {\n".replace("${main}", main);
|
|
197
|
+
o += " local curcontext=\"$curcontext\" ret=1\n";
|
|
198
|
+
o += " _${ident}_nac_simulate\n".replace("${ident}", ident);
|
|
199
|
+
o += " local sid=$REPLY_SID\n";
|
|
200
|
+
o += " if _${ident}_nac_enum_reply \"$sid\" \"$words[CURRENT-1]\"; then return 0; fi\n".replace("${ident}", ident);
|
|
201
|
+
o += " if [[ $PREFIX == -* ]]; then\n";
|
|
202
|
+
o += " local -a optsarr\n";
|
|
203
|
+
o += " local oname=\"A_${ident}_${sid}_opts\"\n".replace("${ident}", ident);
|
|
204
|
+
o += " optsarr=(${(@P)oname})\n";
|
|
205
|
+
o += " _describe -t options 'option' optsarr && ret=0\n";
|
|
206
|
+
o += " else\n";
|
|
207
|
+
o += " local lname=\"A_${ident}_${sid}_leaf\"\n".replace("${ident}", ident);
|
|
208
|
+
o += " if [[ ${(P)lname} -eq 0 ]]; then\n";
|
|
209
|
+
o += " local -a cmdsarr\n";
|
|
210
|
+
o += " local cname=\"A_${ident}_${sid}_cmds\"\n".replace("${ident}", ident);
|
|
211
|
+
o += " cmdsarr=(${(@P)cname})\n";
|
|
212
|
+
o += " _describe -t commands 'command' cmdsarr && ret=0\n";
|
|
213
|
+
o += " else\n";
|
|
214
|
+
o += " local pname=\"A_${ident}_${sid}_pos\"\n".replace("${ident}", ident);
|
|
215
|
+
o += " if [[ ${(P)pname} -eq 1 ]]; then\n";
|
|
216
|
+
o += " _files && ret=0\n";
|
|
217
|
+
o += " fi\n";
|
|
218
|
+
o += " fi\n";
|
|
219
|
+
o += " fi\n";
|
|
220
|
+
o += " return ret\n";
|
|
221
|
+
o += "}\n\n";
|
|
222
|
+
o += "compdef _${main} ${schema.key}\n".replace("${main}", main).replace("${schema.key}", schema.key);
|
|
223
|
+
return o;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Returns a self-contained zsh completion script for the given program schema. */
|
|
227
|
+
export function completionZshScript(schema: CliRouter): string {
|
|
228
|
+
const ident = identToken(schema.key);
|
|
229
|
+
const scopes = collectScopes(schema);
|
|
230
|
+
const pathIndex: Record<string, number> = {};
|
|
231
|
+
for (const [i, s] of scopes.entries()) {
|
|
232
|
+
pathIndex[s.path] = i;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
let out = "#compdef " + schema.key + "\n\n";
|
|
236
|
+
out += emitScopeArraysZsh(ident, scopes);
|
|
237
|
+
out += emitConsumeLongZsh(ident, scopes);
|
|
238
|
+
out += emitConsumeShortZsh(ident, scopes);
|
|
239
|
+
out += emitMatchChildZsh(ident, scopes, pathIndex);
|
|
240
|
+
out += emitSimulateZsh(ident);
|
|
241
|
+
out += emitEnumReplyZsh(ident, scopes);
|
|
242
|
+
out += emitMainBodyZsh(schema, ident);
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import type { CliNode, CliProgram, CliRouter } from "../types.ts";
|
|
3
|
+
import { isCliLeaf, isCliRouter } from "../types.ts";
|
|
4
|
+
import { completionBashScript } from "./completion-bash.ts";
|
|
5
|
+
import { completionFishScript } from "./completion-fish.ts";
|
|
6
|
+
import { completionZshScript } from "./completion-zsh.ts";
|
|
7
|
+
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
8
|
+
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
9
|
+
import { cliBuiltinCompletionGroup as completionGroup } from "./completion-group.ts";
|
|
10
|
+
import { cliPresentationRoot } from "./presentation.ts";
|
|
11
|
+
import { cliMcpServeStdio } from "../mcp.ts";
|
|
12
|
+
import { cliInstall } from "../install/index.ts";
|
|
13
|
+
import { isCompiledExecutable } from "../install/compiled.ts";
|
|
14
|
+
import type { ParseResult } from "../parse.ts";
|
|
15
|
+
import { ParseKind } from "../parse.ts";
|
|
16
|
+
|
|
17
|
+
export interface DispatchBuiltinOpts {
|
|
18
|
+
isLeafCompletionIntercept: boolean;
|
|
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;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Handles built-in commands after parse.
|
|
31
|
+
*/
|
|
32
|
+
export async function dispatchBuiltin(
|
|
33
|
+
program: CliProgram,
|
|
34
|
+
pr: ParseResult,
|
|
35
|
+
opts: DispatchBuiltinOpts,
|
|
36
|
+
): Promise<void> {
|
|
37
|
+
if (pr.kind !== ParseKind.Ok) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const caps = resolveCapabilities(program);
|
|
42
|
+
|
|
43
|
+
if (pr.path[0] === "completion") {
|
|
44
|
+
const schemaForCompletion = completionSchema(program, opts);
|
|
45
|
+
if (pr.path[1] === "bash") {
|
|
46
|
+
process.stdout.write(completionBashScript(schemaForCompletion));
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
if (pr.path[1] === "zsh") {
|
|
50
|
+
process.stdout.write(completionZshScript(schemaForCompletion));
|
|
51
|
+
process.exit(0);
|
|
52
|
+
}
|
|
53
|
+
if (pr.path[1] === "fish") {
|
|
54
|
+
process.stdout.write(completionFishScript(schemaForCompletion));
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (pr.path[0] === "mcp") {
|
|
61
|
+
if (!caps.mcp) {
|
|
62
|
+
process.stderr.write("MCP is not enabled. Set mcpServer on the program root.\n");
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
if (pr.path.length !== 1) {
|
|
66
|
+
process.stderr.write("Unknown subcommand: mcp " + pr.path.slice(1).join(" ") + "\n");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
await cliMcpServeStdio(program);
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (pr.path[0] === "install") {
|
|
74
|
+
if (!isCompiledExecutable()) {
|
|
75
|
+
process.stderr.write(
|
|
76
|
+
"install is only available in compiled binaries (bun build --compile).\n",
|
|
77
|
+
);
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
if (!caps.install) {
|
|
81
|
+
process.stderr.write("install is disabled. Remove install.enabled: false from the program root.\n");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
if (pr.path.length !== 1) {
|
|
85
|
+
process.stderr.write("Unknown subcommand: install " + pr.path.slice(1).join(" ") + "\n");
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
await cliInstall(program, pr.opts);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Built-in intercept roots for leaf programs. */
|
|
93
|
+
export function builtinInterceptRoot(
|
|
94
|
+
program: CliProgram,
|
|
95
|
+
argv: string[],
|
|
96
|
+
): { parseRoot: CliNode; isLeafCompletionIntercept: boolean } {
|
|
97
|
+
if (!isCliLeaf(program) || argv.length < 1) {
|
|
98
|
+
return { parseRoot: program, isLeafCompletionIntercept: false };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const caps = resolveCapabilities(program);
|
|
102
|
+
const first = argv[0];
|
|
103
|
+
|
|
104
|
+
if (first === "completion") {
|
|
105
|
+
return {
|
|
106
|
+
parseRoot: {
|
|
107
|
+
key: program.key,
|
|
108
|
+
description: program.description,
|
|
109
|
+
commands: [completionGroup(program.key)],
|
|
110
|
+
},
|
|
111
|
+
isLeafCompletionIntercept: true,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (first === "install" && caps.install) {
|
|
116
|
+
return {
|
|
117
|
+
parseRoot: {
|
|
118
|
+
key: program.key,
|
|
119
|
+
description: program.description,
|
|
120
|
+
commands: [cliBuiltinInstallCommand(program)],
|
|
121
|
+
},
|
|
122
|
+
isLeafCompletionIntercept: false,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (first === "mcp" && caps.mcp) {
|
|
127
|
+
return {
|
|
128
|
+
parseRoot: {
|
|
129
|
+
key: program.key,
|
|
130
|
+
description: program.description,
|
|
131
|
+
commands: [cliBuiltinMcpCommand()],
|
|
132
|
+
},
|
|
133
|
+
isLeafCompletionIntercept: false,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return { parseRoot: program, isLeafCompletionIntercept: false };
|
|
138
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type CliCapabilities, resolveCapabilities } from "../capabilities.ts";
|
|
2
|
+
import type { CliFallbackMode, CliOption, CliPositional, CliProgram } from "../types.ts";
|
|
3
|
+
import { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
4
|
+
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
5
|
+
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
6
|
+
|
|
7
|
+
/** JSON-safe command node (no handlers). */
|
|
8
|
+
export interface CliSchemaExport {
|
|
9
|
+
key: string;
|
|
10
|
+
description: string;
|
|
11
|
+
notes?: string;
|
|
12
|
+
options?: CliOption[];
|
|
13
|
+
fallbackCommand?: string;
|
|
14
|
+
fallbackMode?: CliFallbackMode;
|
|
15
|
+
commands?: CliSchemaExport[];
|
|
16
|
+
positionals?: CliPositional[];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function exportBuiltinNode(cmd: {
|
|
20
|
+
key: string;
|
|
21
|
+
description: string;
|
|
22
|
+
notes?: string;
|
|
23
|
+
options?: CliOption[];
|
|
24
|
+
commands?: CliSchemaExport[];
|
|
25
|
+
}): CliSchemaExport {
|
|
26
|
+
const out: CliSchemaExport = {
|
|
27
|
+
key: cmd.key,
|
|
28
|
+
description: cmd.description,
|
|
29
|
+
};
|
|
30
|
+
if ((cmd.notes ?? "").length > 0) {
|
|
31
|
+
out.notes = cmd.notes;
|
|
32
|
+
}
|
|
33
|
+
if ((cmd.options ?? []).length > 0) {
|
|
34
|
+
out.options = cmd.options;
|
|
35
|
+
}
|
|
36
|
+
if ((cmd.commands ?? []).length > 0) {
|
|
37
|
+
out.commands = (cmd.commands ?? []).map((ch) => exportBuiltinNode(ch));
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Built-in subtrees matching help visibility for `--schema` export. */
|
|
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)));
|
|
48
|
+
}
|
|
49
|
+
if (resolved.mcp) {
|
|
50
|
+
builtins.push(exportBuiltinNode(cliBuiltinMcpCommand()));
|
|
51
|
+
}
|
|
52
|
+
return builtins;
|
|
53
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { completionBashScript } from "./completion-bash.ts";
|
|
2
|
+
export { completionZshScript } from "./completion-zsh.ts";
|
|
3
|
+
export { completionFishScript } from "./completion-fish.ts";
|
|
4
|
+
export { cliBuiltinCompletionGroup } from "./completion-group.ts";
|
|
5
|
+
export { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
|
|
6
|
+
export { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
7
|
+
export { cliPresentationRoot, presentationBuiltins } from "./presentation.ts";
|
|
8
|
+
export { exportPresentationBuiltins, type CliSchemaExport } from "./export.ts";
|
|
9
|
+
export { dispatchBuiltin, builtinInterceptRoot } from "./dispatch.ts";
|
|
10
|
+
export { collectScopes, type ScopeRec } from "./scopes.ts";
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { CliProgram, CliOption, CliOptionKind, type CliLeaf } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
/** Install command options (dynamic: `--mcp` only when MCP is enabled). */
|
|
4
|
+
export function installBuiltinOptions(root: CliProgram): CliOption[] {
|
|
5
|
+
const opts: CliOption[] = [
|
|
6
|
+
{
|
|
7
|
+
name: "all",
|
|
8
|
+
description: "Install binary, completions, skills, and MCP config (when enabled).",
|
|
9
|
+
kind: CliOptionKind.Presence,
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "bin",
|
|
13
|
+
description: "Copy this binary to your install directory (default ~/.local/bin).",
|
|
14
|
+
kind: CliOptionKind.Presence,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "completions",
|
|
18
|
+
description:
|
|
19
|
+
"Install bash, zsh, and fish tab-completion scripts (each skipped silently if that shell is not on PATH).",
|
|
20
|
+
kind: CliOptionKind.Presence,
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "skill",
|
|
24
|
+
description: "Install Cursor and Claude Code skills when ~/.cursor or ~/.claude exists.",
|
|
25
|
+
kind: CliOptionKind.Presence,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "update",
|
|
29
|
+
description: "Update only artifacts already installed (always includes the binary).",
|
|
30
|
+
kind: CliOptionKind.Presence,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "status",
|
|
34
|
+
description: "Print what is currently installed (read-only).",
|
|
35
|
+
kind: CliOptionKind.Presence,
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "uninstall",
|
|
39
|
+
description: "Remove installed artifacts (all detected, or limit with other flags).",
|
|
40
|
+
kind: CliOptionKind.Presence,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "prefix",
|
|
44
|
+
description: "Install directory for the binary (default ~/.local/bin; overrides INSTALL_PREFIX).",
|
|
45
|
+
kind: CliOptionKind.String,
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
name: "yes",
|
|
49
|
+
description: "Skip the confirmation prompt.",
|
|
50
|
+
kind: CliOptionKind.Presence,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "dry",
|
|
54
|
+
description: "Show what would change without writing files.",
|
|
55
|
+
kind: CliOptionKind.Presence,
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
name: "json",
|
|
59
|
+
description: "Print changed paths (install/update/uninstall) or status JSON on stdout.",
|
|
60
|
+
kind: CliOptionKind.Presence,
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "quiet",
|
|
64
|
+
description: "Suppress informational output (requires --yes).",
|
|
65
|
+
kind: CliOptionKind.Presence,
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
if (root.mcpServer !== undefined) {
|
|
70
|
+
opts.splice(4, 0, {
|
|
71
|
+
name: "mcp",
|
|
72
|
+
description: "Add or update MCP server entries in Cursor and Claude config files.",
|
|
73
|
+
kind: CliOptionKind.Presence,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return opts;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Builds the `install` built-in command (compiled binaries only). */
|
|
81
|
+
export function cliBuiltinInstallCommand(root: CliProgram): CliLeaf {
|
|
82
|
+
return {
|
|
83
|
+
key: "install",
|
|
84
|
+
description: "Install the binary, shell completions, agent skills, and MCP config to your user environment.",
|
|
85
|
+
notes:
|
|
86
|
+
"Requires a compiled binary (bun build --compile).\n\n" +
|
|
87
|
+
"First-time setup:\n" +
|
|
88
|
+
` {app} install --all --yes\n\n` +
|
|
89
|
+
"Refresh after upgrading:\n" +
|
|
90
|
+
` {app} install --update\n\n` +
|
|
91
|
+
"See what is installed:\n" +
|
|
92
|
+
` {app} install --status\n\n` +
|
|
93
|
+
"Remove everything:\n" +
|
|
94
|
+
` {app} install --uninstall --yes\n\n` +
|
|
95
|
+
"Use --dry to preview changes, --json for machine-readable output.",
|
|
96
|
+
options: installBuiltinOptions(root),
|
|
97
|
+
handler: () => {},
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type CliLeaf } from "../types.ts";
|
|
2
|
+
|
|
3
|
+
/** Presence options for the top-level `mcp` built-in (leaf). */
|
|
4
|
+
export function cliBuiltinMcpCommand(): CliLeaf {
|
|
5
|
+
return {
|
|
6
|
+
key: "mcp",
|
|
7
|
+
description: "Run as an MCP server over stdio for AI agents.",
|
|
8
|
+
notes:
|
|
9
|
+
"Configure MCP clients with `command` set to this program name and `args` set to `[\"mcp\"]`.\n\n" +
|
|
10
|
+
"See docs/mcp.md for setup details.",
|
|
11
|
+
handler: () => {},
|
|
12
|
+
};
|
|
13
|
+
}
|