incur 0.4.0 → 0.4.2
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/README.md +83 -22
- package/SKILL.md +6 -6
- package/dist/Cli.d.ts +46 -26
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +728 -441
- package/dist/Cli.js.map +1 -1
- package/dist/Completions.d.ts +4 -3
- package/dist/Completions.d.ts.map +1 -1
- package/dist/Completions.js +17 -10
- package/dist/Completions.js.map +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js +10 -9
- package/dist/Fetch.js.map +1 -1
- package/dist/Filter.js +0 -18
- package/dist/Filter.js.map +1 -1
- package/dist/Formatter.d.ts.map +1 -1
- package/dist/Formatter.js +6 -1
- package/dist/Formatter.js.map +1 -1
- package/dist/Help.d.ts +7 -1
- package/dist/Help.d.ts.map +1 -1
- package/dist/Help.js +44 -27
- package/dist/Help.js.map +1 -1
- package/dist/Mcp.d.ts +37 -5
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +71 -72
- package/dist/Mcp.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +22 -14
- package/dist/Openapi.js.map +1 -1
- package/dist/Parser.d.ts +4 -0
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +70 -38
- package/dist/Parser.js.map +1 -1
- package/dist/Schema.d.ts +5 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +13 -2
- package/dist/Schema.js.map +1 -1
- package/dist/Skill.d.ts +2 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +33 -19
- package/dist/Skill.js.map +1 -1
- package/dist/Skillgen.js +1 -1
- package/dist/Skillgen.js.map +1 -1
- package/dist/SyncSkills.d.ts +48 -0
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +108 -10
- package/dist/SyncSkills.js.map +1 -1
- package/dist/Typegen.js +4 -2
- package/dist/Typegen.js.map +1 -1
- package/dist/bin.d.ts +2 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +17 -2
- package/dist/bin.js.map +1 -1
- package/dist/internal/command.d.ts +170 -0
- package/dist/internal/command.d.ts.map +1 -0
- package/dist/internal/command.js +292 -0
- package/dist/internal/command.js.map +1 -0
- package/dist/internal/configSchema.d.ts +8 -0
- package/dist/internal/configSchema.d.ts.map +1 -0
- package/dist/internal/configSchema.js +57 -0
- package/dist/internal/configSchema.js.map +1 -0
- package/dist/internal/dereference.d.ts +12 -0
- package/dist/internal/dereference.d.ts.map +1 -0
- package/dist/internal/dereference.js +71 -0
- package/dist/internal/dereference.js.map +1 -0
- package/dist/internal/helpers.d.ts +9 -0
- package/dist/internal/helpers.d.ts.map +1 -0
- package/dist/internal/helpers.js +54 -0
- package/dist/internal/helpers.js.map +1 -0
- package/dist/middleware.d.ts +6 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/examples/npm/.npmrc.json +21 -0
- package/examples/npm/config.schema.json +134 -0
- package/package.json +6 -29
- package/src/Cli.test-d.ts +44 -33
- package/src/Cli.test.ts +1231 -101
- package/src/Cli.ts +877 -569
- package/src/Completions.test.ts +136 -12
- package/src/Completions.ts +18 -13
- package/src/Fetch.test.ts +21 -0
- package/src/Fetch.ts +8 -10
- package/src/Filter.ts +0 -17
- package/src/Formatter.test.ts +15 -2
- package/src/Formatter.ts +5 -1
- package/src/Help.test.ts +184 -20
- package/src/Help.ts +52 -28
- package/src/Mcp.test.ts +159 -0
- package/src/Mcp.ts +108 -86
- package/src/Openapi.test.ts +17 -5
- package/src/Openapi.ts +21 -15
- package/src/Parser.test-d.ts +22 -0
- package/src/Parser.test.ts +89 -0
- package/src/Parser.ts +87 -36
- package/src/Schema.test.ts +29 -0
- package/src/Schema.ts +12 -2
- package/src/Skill.test.ts +87 -6
- package/src/Skill.ts +38 -21
- package/src/Skillgen.ts +1 -1
- package/src/SyncMcp.test.ts +6 -8
- package/src/SyncSkills.test.ts +146 -3
- package/src/SyncSkills.ts +191 -10
- package/src/Typegen.test.ts +15 -0
- package/src/Typegen.ts +4 -2
- package/src/bin.ts +21 -2
- package/src/e2e.test.ts +188 -98
- package/src/internal/command.ts +449 -0
- package/src/internal/configSchema.test.ts +193 -0
- package/src/internal/configSchema.ts +66 -0
- package/src/internal/dereference.test.ts +695 -0
- package/src/internal/dereference.ts +75 -0
- package/src/internal/helpers.test.ts +75 -0
- package/src/internal/helpers.ts +59 -0
- package/src/middleware.ts +5 -12
package/dist/Cli.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as os from 'node:os';
|
|
3
|
+
import * as path from 'node:path';
|
|
1
4
|
import { estimateTokenCount, sliceByTokens } from 'tokenx';
|
|
5
|
+
import { z } from 'zod';
|
|
2
6
|
import * as Completions from './Completions.js';
|
|
3
|
-
import { IncurError, ValidationError } from './Errors.js';
|
|
7
|
+
import { IncurError, ParseError, ValidationError } from './Errors.js';
|
|
4
8
|
import * as Fetch from './Fetch.js';
|
|
5
9
|
import * as Filter from './Filter.js';
|
|
6
10
|
import * as Formatter from './Formatter.js';
|
|
7
11
|
import * as Help from './Help.js';
|
|
12
|
+
import { builtinCommands, findBuiltin, shells, } from './internal/command.js';
|
|
13
|
+
import * as Command from './internal/command.js';
|
|
14
|
+
import { isRecord, suggest } from './internal/helpers.js';
|
|
8
15
|
import { detectRunner } from './internal/pm.js';
|
|
9
16
|
import * as Mcp from './Mcp.js';
|
|
10
17
|
import * as Openapi from './Openapi.js';
|
|
@@ -26,7 +33,6 @@ export function create(nameOrDefinition, definition) {
|
|
|
26
33
|
name,
|
|
27
34
|
description: def.description,
|
|
28
35
|
env: def.env,
|
|
29
|
-
options: def.options,
|
|
30
36
|
vars: def.vars,
|
|
31
37
|
command(nameOrCli, def) {
|
|
32
38
|
if (typeof nameOrCli === 'string') {
|
|
@@ -53,11 +59,18 @@ export function create(nameOrDefinition, definition) {
|
|
|
53
59
|
return cli;
|
|
54
60
|
}
|
|
55
61
|
commands.set(nameOrCli, def);
|
|
62
|
+
if (def.aliases)
|
|
63
|
+
for (const a of def.aliases)
|
|
64
|
+
commands.set(a, { _alias: true, target: nameOrCli });
|
|
56
65
|
return cli;
|
|
57
66
|
}
|
|
58
67
|
const mountedRootDef = toRootDefinition.get(nameOrCli);
|
|
59
68
|
if (mountedRootDef) {
|
|
60
69
|
commands.set(nameOrCli.name, mountedRootDef);
|
|
70
|
+
const rootAliases = toRootAliases.get(nameOrCli);
|
|
71
|
+
if (rootAliases)
|
|
72
|
+
for (const a of rootAliases)
|
|
73
|
+
commands.set(a, { _alias: true, target: nameOrCli.name });
|
|
61
74
|
return cli;
|
|
62
75
|
}
|
|
63
76
|
const sub = nameOrCli;
|
|
@@ -77,10 +90,13 @@ export function create(nameOrDefinition, definition) {
|
|
|
77
90
|
if (pending.length > 0)
|
|
78
91
|
await Promise.all(pending);
|
|
79
92
|
return fetchImpl(name, commands, req, {
|
|
93
|
+
envSchema: def.env,
|
|
80
94
|
mcpHandler,
|
|
81
95
|
middlewares,
|
|
96
|
+
name,
|
|
82
97
|
rootCommand: rootDef,
|
|
83
98
|
vars: def.vars,
|
|
99
|
+
version: def.version,
|
|
84
100
|
});
|
|
85
101
|
},
|
|
86
102
|
async serve(argv = process.argv.slice(2), serveOptions = {}) {
|
|
@@ -89,12 +105,12 @@ export function create(nameOrDefinition, definition) {
|
|
|
89
105
|
return serveImpl(name, commands, argv, {
|
|
90
106
|
...serveOptions,
|
|
91
107
|
aliases: def.aliases,
|
|
108
|
+
config: def.config,
|
|
92
109
|
description: def.description,
|
|
93
110
|
envSchema: def.env,
|
|
94
111
|
format: def.format,
|
|
95
112
|
mcp: def.mcp,
|
|
96
113
|
middlewares,
|
|
97
|
-
optionsSchema: def.options,
|
|
98
114
|
outputPolicy: def.outputPolicy,
|
|
99
115
|
rootCommand: rootDef,
|
|
100
116
|
rootFetch,
|
|
@@ -110,6 +126,12 @@ export function create(nameOrDefinition, definition) {
|
|
|
110
126
|
};
|
|
111
127
|
if (rootDef)
|
|
112
128
|
toRootDefinition.set(cli, rootDef);
|
|
129
|
+
if (rootDef && def.aliases)
|
|
130
|
+
toRootAliases.set(cli, def.aliases);
|
|
131
|
+
if (def.options)
|
|
132
|
+
toRootOptions.set(cli, def.options);
|
|
133
|
+
if (def.config !== undefined)
|
|
134
|
+
toConfigEnabled.set(cli, true);
|
|
113
135
|
if (def.outputPolicy)
|
|
114
136
|
toOutputPolicy.set(cli, def.outputPolicy);
|
|
115
137
|
toMiddlewares.set(cli, middlewares);
|
|
@@ -121,10 +143,35 @@ export function create(nameOrDefinition, definition) {
|
|
|
121
143
|
async function serveImpl(name, commands, argv, options = {}) {
|
|
122
144
|
const stdout = options.stdout ?? ((s) => process.stdout.write(s));
|
|
123
145
|
const exit = options.exit ?? ((code) => process.exit(code));
|
|
124
|
-
const
|
|
146
|
+
const human = process.stdout.isTTY === true;
|
|
147
|
+
const configEnabled = options.config !== undefined;
|
|
148
|
+
const configFlag = options.config?.flag;
|
|
149
|
+
const displayName = resolveDisplayName(name, options.aliases);
|
|
150
|
+
function writeln(s) {
|
|
151
|
+
stdout(s.endsWith('\n') ? s : `${s}\n`);
|
|
152
|
+
}
|
|
153
|
+
let builtinFlags;
|
|
154
|
+
try {
|
|
155
|
+
builtinFlags = extractBuiltinFlags(argv, { configFlag });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
159
|
+
if (human)
|
|
160
|
+
writeln(formatHumanError({ code: 'UNKNOWN', message }));
|
|
161
|
+
else
|
|
162
|
+
writeln(Formatter.format({ code: 'UNKNOWN', message }, 'toon'));
|
|
163
|
+
exit(1);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
const { fullOutput, format: formatFlag, formatExplicit, filterOutput, tokenLimit, tokenOffset, tokenCount, llms, llmsFull, mcp: mcpFlag, help, version, schema, configPath, configDisabled, rest: filtered, } = builtinFlags;
|
|
125
167
|
// --mcp: start as MCP stdio server
|
|
126
168
|
if (mcpFlag) {
|
|
127
|
-
await Mcp.serve(name, options.version ?? '0.0.0', commands
|
|
169
|
+
await Mcp.serve(name, options.version ?? '0.0.0', commands, {
|
|
170
|
+
middlewares: options.middlewares,
|
|
171
|
+
env: options.envSchema,
|
|
172
|
+
vars: options.vars,
|
|
173
|
+
version: options.version,
|
|
174
|
+
});
|
|
128
175
|
return;
|
|
129
176
|
}
|
|
130
177
|
// COMPLETE: dynamic shell completions (called by shell hook at tab-press)
|
|
@@ -141,30 +188,51 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
141
188
|
else {
|
|
142
189
|
const index = Number(process.env._COMPLETE_INDEX ?? words.length - 1);
|
|
143
190
|
const candidates = Completions.complete(commands, options.rootCommand, words, index);
|
|
191
|
+
// Add built-in commands (completions, mcp, skills) to completions
|
|
192
|
+
const current = words[index] ?? '';
|
|
193
|
+
const nonFlags = words.slice(0, index).filter((w) => !w.startsWith('-'));
|
|
194
|
+
if (nonFlags.length <= 1) {
|
|
195
|
+
for (const b of builtinCommands) {
|
|
196
|
+
if (b.name.startsWith(current) && !candidates.some((c) => c.value === b.name))
|
|
197
|
+
candidates.push({
|
|
198
|
+
value: b.name,
|
|
199
|
+
description: b.description,
|
|
200
|
+
...(b.subcommands ? { noSpace: true } : undefined),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
else if (nonFlags.length === 2) {
|
|
205
|
+
const parent = nonFlags[nonFlags.length - 1];
|
|
206
|
+
const builtin = findBuiltin(parent);
|
|
207
|
+
if (builtin?.subcommands)
|
|
208
|
+
for (const sub of builtin.subcommands)
|
|
209
|
+
if (sub.name.startsWith(current))
|
|
210
|
+
candidates.push({ value: sub.name, description: sub.description });
|
|
211
|
+
}
|
|
144
212
|
const out = Completions.format(completeShell, candidates);
|
|
145
213
|
if (out)
|
|
146
214
|
stdout(out);
|
|
147
215
|
}
|
|
148
216
|
return;
|
|
149
217
|
}
|
|
150
|
-
// Human mode: stdout is a TTY.
|
|
151
|
-
const human = process.stdout.isTTY === true;
|
|
152
|
-
function writeln(s) {
|
|
153
|
-
stdout(s.endsWith('\n') ? s : `${s}\n`);
|
|
154
|
-
}
|
|
155
218
|
// Skills staleness check (skip for built-in commands)
|
|
219
|
+
let skillsCta;
|
|
156
220
|
if (!llms && !llmsFull && !schema && !help && !version) {
|
|
157
|
-
const isSkillsAdd = filtered
|
|
158
|
-
const isMcpAdd = filtered
|
|
221
|
+
const isSkillsAdd = builtinIdx(filtered, name, 'skills') !== -1;
|
|
222
|
+
const isMcpAdd = builtinIdx(filtered, name, 'mcp') !== -1;
|
|
159
223
|
if (!isSkillsAdd && !isMcpAdd) {
|
|
160
224
|
const stored = SyncSkills.readHash(name);
|
|
161
|
-
if (stored) {
|
|
225
|
+
if (stored && SyncSkills.hasInstalledSkills(name, { cwd: options.sync?.cwd })) {
|
|
162
226
|
const groups = new Map();
|
|
163
|
-
const entries = collectSkillCommands(commands, [], groups);
|
|
227
|
+
const entries = collectSkillCommands(commands, [], groups, options.rootCommand);
|
|
164
228
|
if (Skill.hash(entries) !== stored) {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
229
|
+
const command = process.env.npm_config_user_agent || process.env.npm_execpath
|
|
230
|
+
? `${detectRunner()} ${SyncMcp.detectPackageSpecifier(name)} skills add`
|
|
231
|
+
: `${displayName} skills add`;
|
|
232
|
+
skillsCta = {
|
|
233
|
+
description: 'Skills are out of date:',
|
|
234
|
+
commands: [{ command, description: 'sync outdated skills' }],
|
|
235
|
+
};
|
|
168
236
|
}
|
|
169
237
|
}
|
|
170
238
|
}
|
|
@@ -175,9 +243,10 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
175
243
|
const prefix = [];
|
|
176
244
|
let scopedDescription = options.description;
|
|
177
245
|
for (const token of filtered) {
|
|
178
|
-
const
|
|
179
|
-
if (!
|
|
246
|
+
const rawEntry = scopedCommands.get(token);
|
|
247
|
+
if (!rawEntry)
|
|
180
248
|
break;
|
|
249
|
+
const entry = resolveAlias(scopedCommands, rawEntry);
|
|
181
250
|
if (isGroup(entry)) {
|
|
182
251
|
scopedCommands = entry.commands;
|
|
183
252
|
scopedDescription = entry.description;
|
|
@@ -189,10 +258,11 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
189
258
|
break;
|
|
190
259
|
}
|
|
191
260
|
}
|
|
261
|
+
const scopedRoot = prefix.length === 0 ? options.rootCommand : undefined;
|
|
192
262
|
if (llmsFull) {
|
|
193
263
|
if (!formatExplicit || formatFlag === 'md') {
|
|
194
264
|
const groups = new Map();
|
|
195
|
-
const cmds = collectSkillCommands(scopedCommands, prefix, groups);
|
|
265
|
+
const cmds = collectSkillCommands(scopedCommands, prefix, groups, scopedRoot);
|
|
196
266
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name;
|
|
197
267
|
writeln(Skill.generate(scopedName, cmds, groups));
|
|
198
268
|
return;
|
|
@@ -202,7 +272,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
202
272
|
}
|
|
203
273
|
if (!formatExplicit || formatFlag === 'md') {
|
|
204
274
|
const groups = new Map();
|
|
205
|
-
const cmds = collectSkillCommands(scopedCommands, prefix, groups);
|
|
275
|
+
const cmds = collectSkillCommands(scopedCommands, prefix, groups, scopedRoot);
|
|
206
276
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name;
|
|
207
277
|
writeln(Skill.index(scopedName, cmds, scopedDescription));
|
|
208
278
|
return;
|
|
@@ -211,51 +281,23 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
211
281
|
return;
|
|
212
282
|
}
|
|
213
283
|
// completions <shell>: print shell hook script to stdout
|
|
214
|
-
const completionsIdx = (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (help) {
|
|
226
|
-
writeln([
|
|
227
|
-
`${name} completions — Generate shell completion script`,
|
|
228
|
-
'',
|
|
229
|
-
`Usage: ${name} completions <shell>`,
|
|
230
|
-
'',
|
|
231
|
-
'Shells:',
|
|
232
|
-
' bash',
|
|
233
|
-
' fish',
|
|
234
|
-
' nushell',
|
|
235
|
-
' zsh',
|
|
236
|
-
'',
|
|
237
|
-
'Setup:',
|
|
238
|
-
...(() => {
|
|
239
|
-
const rows = [
|
|
240
|
-
['bash', `eval "$(${name} completions bash)"`, '# add to ~/.bashrc'],
|
|
241
|
-
['zsh', `eval "$(${name} completions zsh)"`, '# add to ~/.zshrc'],
|
|
242
|
-
['fish', `${name} completions fish | source`, '# add to ~/.config/fish/config.fish'],
|
|
243
|
-
['nushell', `see \`${name} completions nushell\``, '# add to config.nu'],
|
|
244
|
-
];
|
|
245
|
-
const shellW = Math.max(...rows.map((r) => r[0].length));
|
|
246
|
-
const cmdW = Math.max(...rows.map((r) => r[1].length));
|
|
247
|
-
return rows.map(([shell, cmd, comment]) => ` ${shell.padEnd(shellW)} ${cmd.padEnd(cmdW)} ${comment}`);
|
|
248
|
-
})(),
|
|
249
|
-
].join('\n'));
|
|
284
|
+
const completionsIdx = builtinIdx(filtered, name, 'completions');
|
|
285
|
+
if (completionsIdx !== -1) {
|
|
286
|
+
const shell = filtered[completionsIdx + 1];
|
|
287
|
+
if (help || !shell) {
|
|
288
|
+
const b = findBuiltin('completions');
|
|
289
|
+
writeln(Help.formatCommand(`${name} completions`, {
|
|
290
|
+
args: b.args,
|
|
291
|
+
description: b.description,
|
|
292
|
+
hideGlobalOptions: true,
|
|
293
|
+
hint: b.hint?.(name),
|
|
294
|
+
}));
|
|
250
295
|
return;
|
|
251
296
|
}
|
|
252
|
-
|
|
253
|
-
if (!shell || !['bash', 'fish', 'nushell', 'zsh'].includes(shell)) {
|
|
297
|
+
if (!shells.includes(shell)) {
|
|
254
298
|
writeln(formatHumanError({
|
|
255
299
|
code: 'INVALID_SHELL',
|
|
256
|
-
message: shell
|
|
257
|
-
? `Unknown shell '${shell}'. Supported: bash, fish, nushell, zsh`
|
|
258
|
-
: `Missing shell argument. Usage: ${name} completions <bash|fish|nushell|zsh>`,
|
|
300
|
+
message: `Unknown shell '${shell}'. Supported: ${shells.join(', ')}`,
|
|
259
301
|
}));
|
|
260
302
|
exit(1);
|
|
261
303
|
return;
|
|
@@ -265,18 +307,84 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
265
307
|
return;
|
|
266
308
|
}
|
|
267
309
|
// skills add: generate skill files and install via `<pm>x skills add` (only when sync is configured)
|
|
268
|
-
const skillsIdx = filtered
|
|
269
|
-
if (skillsIdx !== -1
|
|
310
|
+
const skillsIdx = builtinIdx(filtered, name, 'skills');
|
|
311
|
+
if (skillsIdx !== -1) {
|
|
312
|
+
const skillsSub = filtered[skillsIdx + 1];
|
|
313
|
+
if (skillsSub && skillsSub !== 'add' && skillsSub !== 'list') {
|
|
314
|
+
const suggestion = suggest(skillsSub, ['add', 'list']);
|
|
315
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
316
|
+
const message = `'${skillsSub}' is not a command for '${name} skills'.${didYouMean}`;
|
|
317
|
+
const ctaCommands = [];
|
|
318
|
+
if (suggestion) {
|
|
319
|
+
const corrected = argv.map((t) => (t === skillsSub ? suggestion : t));
|
|
320
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` });
|
|
321
|
+
}
|
|
322
|
+
ctaCommands.push({
|
|
323
|
+
command: `${name} skills --help`,
|
|
324
|
+
description: 'see all available commands',
|
|
325
|
+
});
|
|
326
|
+
const cta = {
|
|
327
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
328
|
+
commands: ctaCommands,
|
|
329
|
+
};
|
|
330
|
+
if (human) {
|
|
331
|
+
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }));
|
|
332
|
+
writeln(formatHumanCta(cta));
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
writeln(Formatter.format({ code: 'COMMAND_NOT_FOUND', message, cta }, 'toon'));
|
|
336
|
+
exit(1);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
if (!skillsSub) {
|
|
340
|
+
const b = findBuiltin('skills');
|
|
341
|
+
writeln(formatBuiltinHelp(name, b));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (skillsSub === 'list') {
|
|
345
|
+
if (help) {
|
|
346
|
+
const b = findBuiltin('skills');
|
|
347
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'list'));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
try {
|
|
351
|
+
const result = await SyncSkills.list(name, commands, {
|
|
352
|
+
cwd: options.sync?.cwd,
|
|
353
|
+
depth: options.sync?.depth ?? 1,
|
|
354
|
+
description: options.description,
|
|
355
|
+
include: options.sync?.include,
|
|
356
|
+
rootCommand: options.rootCommand,
|
|
357
|
+
});
|
|
358
|
+
if (result.length === 0) {
|
|
359
|
+
writeln('No skills found.');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
const lines = [];
|
|
363
|
+
const maxLen = Math.max(...result.map((s) => s.name.length));
|
|
364
|
+
for (const s of result) {
|
|
365
|
+
const icon = s.installed ? '✓' : '✗';
|
|
366
|
+
const padding = s.description
|
|
367
|
+
? `${' '.repeat(maxLen - s.name.length)} ${s.description}`
|
|
368
|
+
: '';
|
|
369
|
+
lines.push(` ${icon} ${s.name}${padding}`);
|
|
370
|
+
}
|
|
371
|
+
const installedCount = result.filter((s) => s.installed).length;
|
|
372
|
+
lines.push('');
|
|
373
|
+
lines.push(`${result.length} skill${result.length === 1 ? '' : 's'} (${installedCount} installed)`);
|
|
374
|
+
writeln(lines.join('\n'));
|
|
375
|
+
}
|
|
376
|
+
catch (err) {
|
|
377
|
+
writeln(Formatter.format({
|
|
378
|
+
code: 'LIST_SKILLS_FAILED',
|
|
379
|
+
message: err instanceof Error ? err.message : String(err),
|
|
380
|
+
}, formatExplicit ? formatFlag : 'toon'));
|
|
381
|
+
exit(1);
|
|
382
|
+
}
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
270
385
|
if (help) {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
'',
|
|
274
|
-
`Usage: ${name} skills add [options]`,
|
|
275
|
-
'',
|
|
276
|
-
'Options:',
|
|
277
|
-
' --depth <number> Grouping depth for skill files (default: 1)',
|
|
278
|
-
' --no-global Install to project instead of globally',
|
|
279
|
-
].join('\n'));
|
|
386
|
+
const b = findBuiltin('skills');
|
|
387
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'));
|
|
280
388
|
return;
|
|
281
389
|
}
|
|
282
390
|
const rest = filtered.slice(skillsIdx + 2);
|
|
@@ -296,10 +404,11 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
296
404
|
description: options.description,
|
|
297
405
|
global,
|
|
298
406
|
include: options.sync?.include,
|
|
407
|
+
rootCommand: options.rootCommand,
|
|
299
408
|
});
|
|
300
409
|
stdout('\r\x1b[K');
|
|
301
410
|
const lines = [];
|
|
302
|
-
const skillLabel = (s) => s.
|
|
411
|
+
const skillLabel = (s) => s.name;
|
|
303
412
|
const maxLen = Math.max(...result.skills.map((s) => skillLabel(s).length));
|
|
304
413
|
for (const s of result.skills) {
|
|
305
414
|
const label = skillLabel(s);
|
|
@@ -320,9 +429,9 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
320
429
|
lines.push('');
|
|
321
430
|
lines.push(`Run \`${name} --help\` to see the full command reference.`);
|
|
322
431
|
writeln(lines.join('\n'));
|
|
323
|
-
if (
|
|
432
|
+
if (fullOutput || formatExplicit) {
|
|
324
433
|
const output = { skills: result.paths };
|
|
325
|
-
if (
|
|
434
|
+
if (fullOutput && result.agents.length > 0)
|
|
326
435
|
output.agents = result.agents;
|
|
327
436
|
writeln(Formatter.format(output, formatExplicit ? formatFlag : 'toon'));
|
|
328
437
|
}
|
|
@@ -334,19 +443,40 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
334
443
|
return;
|
|
335
444
|
}
|
|
336
445
|
// mcp add: register CLI as MCP server via `npx add-mcp`
|
|
337
|
-
const mcpIdx = filtered
|
|
338
|
-
if (mcpIdx !== -1
|
|
446
|
+
const mcpIdx = builtinIdx(filtered, name, 'mcp');
|
|
447
|
+
if (mcpIdx !== -1) {
|
|
448
|
+
const mcpSub = filtered[mcpIdx + 1];
|
|
449
|
+
if (mcpSub && mcpSub !== 'add') {
|
|
450
|
+
const suggestion = suggest(mcpSub, ['add']);
|
|
451
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
452
|
+
const message = `'${mcpSub}' is not a command for '${name} mcp'.${didYouMean}`;
|
|
453
|
+
const ctaCommands = [];
|
|
454
|
+
if (suggestion) {
|
|
455
|
+
const corrected = argv.map((t) => (t === mcpSub ? suggestion : t));
|
|
456
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` });
|
|
457
|
+
}
|
|
458
|
+
ctaCommands.push({ command: `${name} mcp --help`, description: 'see all available commands' });
|
|
459
|
+
const cta = {
|
|
460
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
461
|
+
commands: ctaCommands,
|
|
462
|
+
};
|
|
463
|
+
if (human) {
|
|
464
|
+
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }));
|
|
465
|
+
writeln(formatHumanCta(cta));
|
|
466
|
+
}
|
|
467
|
+
else
|
|
468
|
+
writeln(Formatter.format({ code: 'COMMAND_NOT_FOUND', message, cta }, 'toon'));
|
|
469
|
+
exit(1);
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (!mcpSub) {
|
|
473
|
+
const b = findBuiltin('mcp');
|
|
474
|
+
writeln(formatBuiltinHelp(name, b));
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
339
477
|
if (help) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
'',
|
|
343
|
-
`Usage: ${name} mcp add [options]`,
|
|
344
|
-
'',
|
|
345
|
-
'Options:',
|
|
346
|
-
' -c, --command <cmd> Override the command agents will run (e.g. "pnpm my-cli --mcp")',
|
|
347
|
-
' --no-global Install to project instead of globally',
|
|
348
|
-
' --agent <agent> Target a specific agent (e.g. claude-code, cursor)',
|
|
349
|
-
].join('\n'));
|
|
478
|
+
const b = findBuiltin('mcp');
|
|
479
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'));
|
|
350
480
|
return;
|
|
351
481
|
}
|
|
352
482
|
const rest = filtered.slice(mcpIdx + 2);
|
|
@@ -382,7 +512,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
382
512
|
lines.push(` "${s}"`);
|
|
383
513
|
}
|
|
384
514
|
writeln(lines.join('\n'));
|
|
385
|
-
if (
|
|
515
|
+
if (fullOutput || formatExplicit)
|
|
386
516
|
writeln(Formatter.format({ name, command: result.command, agents: result.agents }, formatExplicit ? formatFlag : 'toon'));
|
|
387
517
|
}
|
|
388
518
|
catch (err) {
|
|
@@ -406,6 +536,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
406
536
|
writeln(Help.formatCommand(name, {
|
|
407
537
|
alias: cmd.alias,
|
|
408
538
|
aliases: options.aliases,
|
|
539
|
+
configFlag,
|
|
409
540
|
description: cmd.description ?? options.description,
|
|
410
541
|
version: options.version,
|
|
411
542
|
args: cmd.args,
|
|
@@ -426,6 +557,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
426
557
|
else {
|
|
427
558
|
writeln(Help.formatRoot(name, {
|
|
428
559
|
aliases: options.aliases,
|
|
560
|
+
configFlag,
|
|
429
561
|
description: options.description,
|
|
430
562
|
version: options.version,
|
|
431
563
|
commands: collectHelpCommands(commands),
|
|
@@ -468,6 +600,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
468
600
|
writeln(Help.formatCommand(name, {
|
|
469
601
|
alias: cmd.alias,
|
|
470
602
|
aliases: options.aliases,
|
|
603
|
+
configFlag,
|
|
471
604
|
description: cmd.description ?? options.description,
|
|
472
605
|
version: options.version,
|
|
473
606
|
args: cmd.args,
|
|
@@ -484,6 +617,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
484
617
|
else {
|
|
485
618
|
writeln(Help.formatRoot(helpName, {
|
|
486
619
|
aliases: isRoot ? options.aliases : undefined,
|
|
620
|
+
configFlag,
|
|
487
621
|
description: helpDesc,
|
|
488
622
|
version: isRoot ? options.version : undefined,
|
|
489
623
|
commands: collectHelpCommands(helpCmds),
|
|
@@ -500,7 +634,8 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
500
634
|
: undefined;
|
|
501
635
|
writeln(Help.formatCommand(commandName, {
|
|
502
636
|
alias: cmd.alias,
|
|
503
|
-
aliases: isRootCmd ? options.aliases :
|
|
637
|
+
aliases: isRootCmd ? options.aliases : cmd.aliases,
|
|
638
|
+
configFlag,
|
|
504
639
|
description: cmd.description,
|
|
505
640
|
version: isRootCmd ? options.version : undefined,
|
|
506
641
|
args: cmd.args,
|
|
@@ -520,6 +655,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
520
655
|
if (schema) {
|
|
521
656
|
if ('help' in resolved) {
|
|
522
657
|
writeln(Help.formatRoot(`${name} ${resolved.path}`, {
|
|
658
|
+
configFlag,
|
|
523
659
|
description: resolved.description,
|
|
524
660
|
commands: collectHelpCommands(resolved.commands),
|
|
525
661
|
}));
|
|
@@ -527,7 +663,9 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
527
663
|
}
|
|
528
664
|
if ('error' in resolved) {
|
|
529
665
|
const parent = resolved.path ? `${name} ${resolved.path}` : name;
|
|
530
|
-
|
|
666
|
+
const suggestion = suggest(resolved.error, resolved.commands.keys());
|
|
667
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
668
|
+
writeln(`Error: '${resolved.error}' is not a command for '${parent}'.${didYouMean}`);
|
|
531
669
|
exit(1);
|
|
532
670
|
return;
|
|
533
671
|
}
|
|
@@ -552,24 +690,27 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
552
690
|
}
|
|
553
691
|
if ('help' in resolved) {
|
|
554
692
|
writeln(Help.formatRoot(`${name} ${resolved.path}`, {
|
|
693
|
+
configFlag,
|
|
555
694
|
description: resolved.description,
|
|
556
695
|
commands: collectHelpCommands(resolved.commands),
|
|
557
696
|
}));
|
|
558
697
|
return;
|
|
559
698
|
}
|
|
560
699
|
const start = performance.now();
|
|
561
|
-
// Parse root CLI-level options (available to middleware via c.options)
|
|
562
|
-
const rootOptions = options.optionsSchema
|
|
563
|
-
? Parser.parse(filtered, {
|
|
564
|
-
alias: options.rootCommand?.alias,
|
|
565
|
-
options: options.optionsSchema,
|
|
566
|
-
}).options
|
|
567
|
-
: {};
|
|
568
700
|
// Resolve effective format: explicit --format/--json → command default → CLI default → toon
|
|
569
701
|
const resolvedFormat = 'command' in resolved && resolved.command.format;
|
|
570
702
|
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon';
|
|
571
|
-
// Fall back to root fetch when no subcommand matches
|
|
572
|
-
|
|
703
|
+
// Fall back to root fetch/command when no subcommand matches,
|
|
704
|
+
// but only if the token doesn't look like a typo of a known command.
|
|
705
|
+
const rootFallbackBlocked = 'error' in resolved &&
|
|
706
|
+
!resolved.path &&
|
|
707
|
+
(() => {
|
|
708
|
+
const candidates = [...resolved.commands.keys()];
|
|
709
|
+
for (const b of builtinCommands)
|
|
710
|
+
candidates.push(b.name);
|
|
711
|
+
return suggest(resolved.error, candidates) !== undefined;
|
|
712
|
+
})();
|
|
713
|
+
const effective = 'error' in resolved && options.rootFetch && !resolved.path && !rootFallbackBlocked
|
|
573
714
|
? {
|
|
574
715
|
fetchGateway: {
|
|
575
716
|
_fetch: true,
|
|
@@ -580,7 +721,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
580
721
|
path: name,
|
|
581
722
|
rest: filtered,
|
|
582
723
|
}
|
|
583
|
-
: 'error' in resolved && options.rootCommand && !resolved.path
|
|
724
|
+
: 'error' in resolved && options.rootCommand && !resolved.path && !rootFallbackBlocked
|
|
584
725
|
? { command: options.rootCommand, path: name, rest: filtered }
|
|
585
726
|
: resolved;
|
|
586
727
|
// Resolve outputPolicy: command/group → CLI-level → default ('all')
|
|
@@ -607,13 +748,28 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
607
748
|
function write(output) {
|
|
608
749
|
if (filterPaths && output.ok && output.data != null)
|
|
609
750
|
output = { ...output, data: Filter.apply(output.data, filterPaths) };
|
|
751
|
+
if (skillsCta) {
|
|
752
|
+
const existing = output.meta.cta;
|
|
753
|
+
output = {
|
|
754
|
+
...output,
|
|
755
|
+
meta: {
|
|
756
|
+
...output.meta,
|
|
757
|
+
cta: existing
|
|
758
|
+
? {
|
|
759
|
+
description: existing.description,
|
|
760
|
+
commands: [...existing.commands, ...skillsCta.commands],
|
|
761
|
+
}
|
|
762
|
+
: skillsCta,
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
}
|
|
610
766
|
if (tokenCount) {
|
|
611
767
|
const base = output.ok ? output.data : output.error;
|
|
612
768
|
const formatted = base != null ? Formatter.format(base, format) : '';
|
|
613
769
|
return writeln(String(estimateTokenCount(formatted)));
|
|
614
770
|
}
|
|
615
771
|
const cta = output.meta.cta;
|
|
616
|
-
if (human && !
|
|
772
|
+
if (human && !fullOutput) {
|
|
617
773
|
if (output.ok && output.data != null && renderOutput) {
|
|
618
774
|
const t = truncate(Formatter.format(output.data, format));
|
|
619
775
|
writeln(t.text);
|
|
@@ -624,7 +780,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
624
780
|
writeln(formatHumanCta(cta));
|
|
625
781
|
return;
|
|
626
782
|
}
|
|
627
|
-
if (
|
|
783
|
+
if (fullOutput) {
|
|
628
784
|
if (tokenLimit != null || tokenOffset != null) {
|
|
629
785
|
// Truncate data separately so meta (including nextOffset) is always visible
|
|
630
786
|
const dataFormatted = output.ok && output.data != null
|
|
@@ -659,14 +815,29 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
659
815
|
if ('error' in effective) {
|
|
660
816
|
const helpCmd = effective.path ? `${name} ${effective.path} --help` : `${name} --help`;
|
|
661
817
|
const parent = effective.path ? `${name} ${effective.path}` : name;
|
|
662
|
-
const
|
|
818
|
+
const candidates = 'commands' in effective ? [...effective.commands.keys()] : [];
|
|
819
|
+
if (!effective.path)
|
|
820
|
+
for (const b of builtinCommands)
|
|
821
|
+
candidates.push(b.name);
|
|
822
|
+
const suggestion = suggest(effective.error, candidates);
|
|
823
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
824
|
+
const message = `'${effective.error}' is not a command for '${parent}'.${didYouMean}`;
|
|
825
|
+
const ctaCommands = [];
|
|
826
|
+
if (suggestion) {
|
|
827
|
+
const corrected = argv.map((t) => (t === effective.error ? suggestion : t));
|
|
828
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` });
|
|
829
|
+
}
|
|
830
|
+
ctaCommands.push({ command: helpCmd, description: 'see all available commands' });
|
|
663
831
|
const cta = {
|
|
664
|
-
description: '
|
|
665
|
-
commands:
|
|
832
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
833
|
+
commands: ctaCommands,
|
|
666
834
|
};
|
|
667
|
-
if (human && !
|
|
835
|
+
if (human && !fullOutput) {
|
|
668
836
|
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }));
|
|
669
|
-
|
|
837
|
+
const mergedCta = skillsCta
|
|
838
|
+
? { ...cta, commands: [...cta.commands, ...skillsCta.commands] }
|
|
839
|
+
: cta;
|
|
840
|
+
writeln(formatHumanCta(mergedCta));
|
|
670
841
|
exit(1);
|
|
671
842
|
return;
|
|
672
843
|
}
|
|
@@ -706,7 +877,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
706
877
|
formatExplicit,
|
|
707
878
|
human,
|
|
708
879
|
renderOutput,
|
|
709
|
-
|
|
880
|
+
fullOutput,
|
|
710
881
|
truncate,
|
|
711
882
|
write,
|
|
712
883
|
writeln,
|
|
@@ -754,12 +925,12 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
754
925
|
const mwCtx = {
|
|
755
926
|
agent: !human,
|
|
756
927
|
command: path,
|
|
928
|
+
displayName,
|
|
757
929
|
env: cliEnv,
|
|
758
930
|
error: errorFn,
|
|
759
931
|
format,
|
|
760
932
|
formatExplicit,
|
|
761
933
|
name,
|
|
762
|
-
options: rootOptions,
|
|
763
934
|
set(key, value) {
|
|
764
935
|
varsMap[key] = value;
|
|
765
936
|
},
|
|
@@ -770,7 +941,7 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
770
941
|
if (!isSentinel(result) || result[sentinel] !== 'error')
|
|
771
942
|
return;
|
|
772
943
|
const err = result;
|
|
773
|
-
const cta = formatCtaBlock(
|
|
944
|
+
const cta = formatCtaBlock(displayName, err.cta);
|
|
774
945
|
write({
|
|
775
946
|
ok: false,
|
|
776
947
|
error: {
|
|
@@ -817,182 +988,113 @@ async function serveImpl(name, commands, argv, options = {}) {
|
|
|
817
988
|
: []),
|
|
818
989
|
...(command.middleware ?? []),
|
|
819
990
|
];
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
emitDeprecationWarnings(rest, command.options, command.alias);
|
|
831
|
-
const env = command.env ? Parser.parseEnv(command.env, envSource) : {};
|
|
832
|
-
const okFn = (data, meta = {}) => {
|
|
833
|
-
return { [sentinel]: 'ok', data, cta: meta.cta };
|
|
834
|
-
};
|
|
835
|
-
const errorFn = (opts) => {
|
|
836
|
-
return { [sentinel]: 'error', ...opts };
|
|
837
|
-
};
|
|
838
|
-
const result = command.run({
|
|
839
|
-
agent: !human,
|
|
840
|
-
args,
|
|
841
|
-
env,
|
|
842
|
-
error: errorFn,
|
|
843
|
-
format,
|
|
844
|
-
formatExplicit,
|
|
845
|
-
name,
|
|
846
|
-
ok: okFn,
|
|
847
|
-
options: parsedOptions,
|
|
848
|
-
var: varsMap,
|
|
849
|
-
version: options.version,
|
|
850
|
-
});
|
|
851
|
-
// Streaming path — async generator
|
|
852
|
-
if (isAsyncGenerator(result)) {
|
|
853
|
-
await handleStreaming(result, {
|
|
854
|
-
name,
|
|
855
|
-
path,
|
|
856
|
-
start,
|
|
857
|
-
format,
|
|
858
|
-
formatExplicit,
|
|
859
|
-
human,
|
|
860
|
-
renderOutput,
|
|
861
|
-
verbose,
|
|
862
|
-
truncate,
|
|
863
|
-
write,
|
|
864
|
-
writeln,
|
|
865
|
-
exit,
|
|
991
|
+
if (human)
|
|
992
|
+
emitDeprecationWarnings(rest, command.options, command.alias);
|
|
993
|
+
let defaults;
|
|
994
|
+
if (configEnabled) {
|
|
995
|
+
try {
|
|
996
|
+
defaults = await loadCommandOptionDefaults(name, path, {
|
|
997
|
+
configDisabled,
|
|
998
|
+
configPath,
|
|
999
|
+
files: options.config?.files,
|
|
1000
|
+
loader: options.config?.loader,
|
|
866
1001
|
});
|
|
867
|
-
return;
|
|
868
|
-
}
|
|
869
|
-
const awaited = await result;
|
|
870
|
-
if (isSentinel(awaited)) {
|
|
871
|
-
const cta = formatCtaBlock(name, awaited.cta);
|
|
872
|
-
if (awaited[sentinel] === 'ok') {
|
|
873
|
-
write({
|
|
874
|
-
ok: true,
|
|
875
|
-
data: awaited.data,
|
|
876
|
-
meta: {
|
|
877
|
-
command: path,
|
|
878
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
879
|
-
...(cta ? { cta } : undefined),
|
|
880
|
-
},
|
|
881
|
-
});
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
const err = awaited;
|
|
885
|
-
write({
|
|
886
|
-
ok: false,
|
|
887
|
-
error: {
|
|
888
|
-
code: err.code,
|
|
889
|
-
message: err.message,
|
|
890
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
891
|
-
},
|
|
892
|
-
meta: {
|
|
893
|
-
command: path,
|
|
894
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
895
|
-
...(cta ? { cta } : undefined),
|
|
896
|
-
},
|
|
897
|
-
});
|
|
898
|
-
exit(err.exitCode ?? 1);
|
|
899
|
-
}
|
|
900
1002
|
}
|
|
901
|
-
|
|
1003
|
+
catch (error) {
|
|
902
1004
|
write({
|
|
903
|
-
ok:
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1005
|
+
ok: false,
|
|
1006
|
+
error: {
|
|
1007
|
+
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
1008
|
+
message: error instanceof Error ? error.message : String(error),
|
|
908
1009
|
},
|
|
1010
|
+
meta: { command: path, duration: `${Math.round(performance.now() - start)}ms` },
|
|
909
1011
|
});
|
|
1012
|
+
exit(error instanceof IncurError ? (error.exitCode ?? 1) : 1);
|
|
1013
|
+
return;
|
|
910
1014
|
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
1015
|
+
}
|
|
1016
|
+
const result = await Command.execute(command, {
|
|
1017
|
+
agent: !human,
|
|
1018
|
+
argv: rest,
|
|
1019
|
+
defaults,
|
|
1020
|
+
displayName,
|
|
1021
|
+
env: options.envSchema,
|
|
1022
|
+
envSource: options.env,
|
|
1023
|
+
format,
|
|
1024
|
+
formatExplicit,
|
|
1025
|
+
inputOptions: {},
|
|
1026
|
+
middlewares: allMiddleware,
|
|
1027
|
+
name,
|
|
1028
|
+
path,
|
|
1029
|
+
vars: options.vars,
|
|
1030
|
+
version: options.version,
|
|
1031
|
+
});
|
|
1032
|
+
const duration = `${Math.round(performance.now() - start)}ms`;
|
|
1033
|
+
// Streaming path — async generator
|
|
1034
|
+
if ('stream' in result) {
|
|
1035
|
+
await handleStreaming(result.stream, {
|
|
1036
|
+
name: displayName,
|
|
1037
|
+
path,
|
|
1038
|
+
start,
|
|
1039
|
+
format,
|
|
1040
|
+
formatExplicit,
|
|
1041
|
+
human,
|
|
1042
|
+
renderOutput,
|
|
1043
|
+
fullOutput,
|
|
1044
|
+
truncate,
|
|
1045
|
+
write,
|
|
1046
|
+
writeln,
|
|
1047
|
+
exit,
|
|
1048
|
+
});
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
1051
|
+
if (result.ok) {
|
|
1052
|
+
const cta = formatCtaBlock(displayName, result.cta);
|
|
1053
|
+
write({
|
|
1054
|
+
ok: true,
|
|
1055
|
+
data: result.data,
|
|
1056
|
+
meta: {
|
|
920
1057
|
command: path,
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
name,
|
|
926
|
-
options: rootOptions,
|
|
927
|
-
set(key, value) {
|
|
928
|
-
varsMap[key] = value;
|
|
929
|
-
},
|
|
930
|
-
var: varsMap,
|
|
931
|
-
version: options.version,
|
|
932
|
-
};
|
|
933
|
-
const handleMwSentinel = (result) => {
|
|
934
|
-
if (!isSentinel(result) || result[sentinel] !== 'error')
|
|
935
|
-
return;
|
|
936
|
-
const err = result;
|
|
937
|
-
const cta = formatCtaBlock(name, err.cta);
|
|
938
|
-
write({
|
|
939
|
-
ok: false,
|
|
940
|
-
error: {
|
|
941
|
-
code: err.code,
|
|
942
|
-
message: err.message,
|
|
943
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
944
|
-
},
|
|
945
|
-
meta: {
|
|
946
|
-
command: path,
|
|
947
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
948
|
-
...(cta ? { cta } : undefined),
|
|
949
|
-
},
|
|
950
|
-
});
|
|
951
|
-
exit(err.exitCode ?? 1);
|
|
952
|
-
};
|
|
953
|
-
const composed = allMiddleware.reduceRight((next, mw) => async () => {
|
|
954
|
-
handleMwSentinel(await mw(mwCtx, next));
|
|
955
|
-
}, runCommand);
|
|
956
|
-
await composed();
|
|
957
|
-
}
|
|
958
|
-
else {
|
|
959
|
-
await runCommand();
|
|
960
|
-
}
|
|
1058
|
+
duration,
|
|
1059
|
+
...(cta ? { cta } : undefined),
|
|
1060
|
+
},
|
|
1061
|
+
});
|
|
961
1062
|
}
|
|
962
|
-
|
|
963
|
-
const
|
|
1063
|
+
else {
|
|
1064
|
+
const cta = formatCtaBlock(displayName, result.cta);
|
|
1065
|
+
if (human && !formatExplicit && result.error.fieldErrors) {
|
|
1066
|
+
writeln(formatHumanValidationError(displayName, path, command, new ValidationError({
|
|
1067
|
+
message: result.error.message,
|
|
1068
|
+
fieldErrors: result.error.fieldErrors,
|
|
1069
|
+
}), options.env, configFlag));
|
|
1070
|
+
exit(1);
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
write({
|
|
964
1074
|
ok: false,
|
|
965
1075
|
error: {
|
|
966
|
-
code: error
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
973
|
-
...(error instanceof ValidationError ? { fieldErrors: error.fieldErrors } : undefined),
|
|
1076
|
+
code: result.error.code,
|
|
1077
|
+
message: result.error.message,
|
|
1078
|
+
...(result.error.retryable !== undefined
|
|
1079
|
+
? { retryable: result.error.retryable }
|
|
1080
|
+
: undefined),
|
|
1081
|
+
...(result.error.fieldErrors ? { fieldErrors: result.error.fieldErrors } : undefined),
|
|
974
1082
|
},
|
|
975
1083
|
meta: {
|
|
976
1084
|
command: path,
|
|
977
|
-
duration
|
|
1085
|
+
duration,
|
|
1086
|
+
...(cta ? { cta } : undefined),
|
|
978
1087
|
},
|
|
979
|
-
};
|
|
980
|
-
|
|
981
|
-
writeln(formatHumanValidationError(name, path, command, error, options.env));
|
|
982
|
-
exit(1);
|
|
983
|
-
return;
|
|
984
|
-
}
|
|
985
|
-
write(errorOutput);
|
|
986
|
-
exit(error instanceof IncurError ? (error.exitCode ?? 1) : 1);
|
|
1088
|
+
});
|
|
1089
|
+
exit(result.exitCode ?? 1);
|
|
987
1090
|
}
|
|
988
1091
|
}
|
|
989
1092
|
/** @internal Creates a lazy MCP HTTP handler scoped to a CLI instance. */
|
|
990
1093
|
function createMcpHttpHandler(name, version) {
|
|
991
1094
|
let transport;
|
|
992
|
-
return async (req, commands) => {
|
|
1095
|
+
return async (req, commands, mcpOptions) => {
|
|
993
1096
|
if (!transport) {
|
|
994
|
-
const { McpServer } = await import('@modelcontextprotocol/
|
|
995
|
-
const { WebStandardStreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js');
|
|
1097
|
+
const { McpServer, WebStandardStreamableHTTPServerTransport } = await import('@modelcontextprotocol/server');
|
|
996
1098
|
const server = new McpServer({ name, version });
|
|
997
1099
|
for (const tool of Mcp.collectTools(commands, [])) {
|
|
998
1100
|
const mergedShape = {
|
|
@@ -1002,10 +1104,16 @@ function createMcpHttpHandler(name, version) {
|
|
|
1002
1104
|
const hasInput = Object.keys(mergedShape).length > 0;
|
|
1003
1105
|
server.registerTool(tool.name, {
|
|
1004
1106
|
...(tool.description ? { description: tool.description } : undefined),
|
|
1005
|
-
...(hasInput ? { inputSchema: mergedShape } : undefined),
|
|
1107
|
+
...(hasInput ? { inputSchema: z.object(mergedShape) } : undefined),
|
|
1006
1108
|
}, async (...callArgs) => {
|
|
1007
1109
|
const params = hasInput ? callArgs[0] : {};
|
|
1008
|
-
return Mcp.callTool(tool, params
|
|
1110
|
+
return Mcp.callTool(tool, params, {
|
|
1111
|
+
name,
|
|
1112
|
+
version,
|
|
1113
|
+
middlewares: mcpOptions?.middlewares,
|
|
1114
|
+
env: mcpOptions?.env,
|
|
1115
|
+
vars: mcpOptions?.vars,
|
|
1116
|
+
});
|
|
1009
1117
|
});
|
|
1010
1118
|
}
|
|
1011
1119
|
transport = new WebStandardStreamableHTTPServerTransport({
|
|
@@ -1024,14 +1132,18 @@ async function fetchImpl(name, commands, req, options = {}) {
|
|
|
1024
1132
|
const segments = url.pathname.split('/').filter(Boolean);
|
|
1025
1133
|
// MCP over HTTP: route /mcp to the MCP transport
|
|
1026
1134
|
if (segments[0] === 'mcp' && segments.length === 1 && options.mcpHandler)
|
|
1027
|
-
return options.mcpHandler(req, commands
|
|
1135
|
+
return options.mcpHandler(req, commands, {
|
|
1136
|
+
middlewares: options.middlewares,
|
|
1137
|
+
env: options.envSchema,
|
|
1138
|
+
vars: options.vars,
|
|
1139
|
+
});
|
|
1028
1140
|
// .well-known/skills/ — Agent Skills Discovery (RFC)
|
|
1029
1141
|
if (segments[0] === '.well-known' &&
|
|
1030
1142
|
segments[1] === 'skills' &&
|
|
1031
1143
|
segments.length >= 3 &&
|
|
1032
1144
|
req.method === 'GET') {
|
|
1033
1145
|
const groups = new Map();
|
|
1034
|
-
const cmds = collectSkillCommands(commands, [], groups);
|
|
1146
|
+
const cmds = collectSkillCommands(commands, [], groups, options.rootCommand);
|
|
1035
1147
|
// GET /.well-known/skills/index.json
|
|
1036
1148
|
if (segments[2] === 'index.json' && segments.length === 3) {
|
|
1037
1149
|
const files = Skill.split(name, cmds, 1, groups);
|
|
@@ -1093,15 +1205,19 @@ async function fetchImpl(name, commands, req, options = {}) {
|
|
|
1093
1205
|
}, 404);
|
|
1094
1206
|
}
|
|
1095
1207
|
const resolved = resolveCommand(commands, segments);
|
|
1096
|
-
if ('error' in resolved)
|
|
1208
|
+
if ('error' in resolved) {
|
|
1209
|
+
const parent = resolved.path ? `${name} ${resolved.path}` : name;
|
|
1210
|
+
const suggestion = suggest(resolved.error, resolved.commands.keys());
|
|
1211
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : '';
|
|
1097
1212
|
return jsonResponse({
|
|
1098
1213
|
ok: false,
|
|
1099
1214
|
error: {
|
|
1100
1215
|
code: 'COMMAND_NOT_FOUND',
|
|
1101
|
-
message: `'${resolved.error}' is not a command for '${
|
|
1216
|
+
message: `'${resolved.error}' is not a command for '${parent}'.${didYouMean}`,
|
|
1102
1217
|
},
|
|
1103
1218
|
meta: { command: resolved.error, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1104
1219
|
}, 404);
|
|
1220
|
+
}
|
|
1105
1221
|
if ('help' in resolved)
|
|
1106
1222
|
return jsonResponse({
|
|
1107
1223
|
ok: false,
|
|
@@ -1114,7 +1230,11 @@ async function fetchImpl(name, commands, req, options = {}) {
|
|
|
1114
1230
|
if ('fetchGateway' in resolved)
|
|
1115
1231
|
return resolved.fetchGateway.fetch(req);
|
|
1116
1232
|
const { command, path, rest } = resolved;
|
|
1117
|
-
|
|
1233
|
+
const groupMiddlewares = 'middlewares' in resolved ? resolved.middlewares : [];
|
|
1234
|
+
return executeCommand(path, command, rest, inputOptions, start, {
|
|
1235
|
+
...options,
|
|
1236
|
+
groupMiddlewares,
|
|
1237
|
+
});
|
|
1118
1238
|
}
|
|
1119
1239
|
/** @internal Executes a resolved command for the fetch handler and returns a JSON Response. */
|
|
1120
1240
|
async function executeCommand(path, command, rest, inputOptions, start, options) {
|
|
@@ -1124,157 +1244,90 @@ async function executeCommand(path, command, rest, inputOptions, start, options)
|
|
|
1124
1244
|
headers: { 'content-type': 'application/json' },
|
|
1125
1245
|
});
|
|
1126
1246
|
}
|
|
1127
|
-
const
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
while (true) {
|
|
1156
|
-
const { value, done } = await result.next();
|
|
1157
|
-
if (done) {
|
|
1158
|
-
returnValue = value;
|
|
1159
|
-
break;
|
|
1160
|
-
}
|
|
1161
|
-
if (isSentinel(value) && value[sentinel] === 'error') {
|
|
1162
|
-
const tagged = value;
|
|
1163
|
-
controller.enqueue(encoder.encode(JSON.stringify({
|
|
1164
|
-
type: 'error',
|
|
1165
|
-
ok: false,
|
|
1166
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1167
|
-
}) + '\n'));
|
|
1168
|
-
controller.close();
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
controller.enqueue(encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'));
|
|
1172
|
-
}
|
|
1173
|
-
const meta = { command: path };
|
|
1174
|
-
if (isSentinel(returnValue) && returnValue[sentinel] === 'error') {
|
|
1175
|
-
const tagged = returnValue;
|
|
1176
|
-
controller.enqueue(encoder.encode(JSON.stringify({
|
|
1177
|
-
type: 'error',
|
|
1178
|
-
ok: false,
|
|
1179
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1180
|
-
}) + '\n'));
|
|
1181
|
-
}
|
|
1182
|
-
else {
|
|
1183
|
-
controller.enqueue(encoder.encode(JSON.stringify({ type: 'done', ok: true, meta }) + '\n'));
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
catch (error) {
|
|
1187
|
-
controller.enqueue(encoder.encode(JSON.stringify({
|
|
1188
|
-
type: 'error',
|
|
1189
|
-
ok: false,
|
|
1190
|
-
error: {
|
|
1191
|
-
code: 'UNKNOWN',
|
|
1192
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1193
|
-
},
|
|
1194
|
-
}) + '\n'));
|
|
1247
|
+
const allMiddleware = [
|
|
1248
|
+
...(options.middlewares ?? []),
|
|
1249
|
+
...(options.groupMiddlewares ?? []),
|
|
1250
|
+
...(command.middleware ?? []),
|
|
1251
|
+
];
|
|
1252
|
+
const result = await Command.execute(command, {
|
|
1253
|
+
agent: true,
|
|
1254
|
+
argv: rest,
|
|
1255
|
+
env: options.envSchema,
|
|
1256
|
+
format: 'json',
|
|
1257
|
+
formatExplicit: true,
|
|
1258
|
+
inputOptions,
|
|
1259
|
+
middlewares: allMiddleware,
|
|
1260
|
+
name: options.name ?? path,
|
|
1261
|
+
parseMode: 'split',
|
|
1262
|
+
path,
|
|
1263
|
+
vars: options.vars,
|
|
1264
|
+
version: options.version,
|
|
1265
|
+
});
|
|
1266
|
+
const duration = `${Math.round(performance.now() - start)}ms`;
|
|
1267
|
+
// Streaming path — async generator → NDJSON response
|
|
1268
|
+
if ('stream' in result) {
|
|
1269
|
+
const stream = new ReadableStream({
|
|
1270
|
+
async start(controller) {
|
|
1271
|
+
const encoder = new TextEncoder();
|
|
1272
|
+
try {
|
|
1273
|
+
for await (const value of result.stream) {
|
|
1274
|
+
controller.enqueue(encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'));
|
|
1195
1275
|
}
|
|
1196
|
-
controller.
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
}
|
|
1219
|
-
response = jsonResponse({ ok: true, data: awaited, meta: { command: path, duration } }, 200);
|
|
1220
|
-
};
|
|
1221
|
-
try {
|
|
1222
|
-
const allMiddleware = options.middlewares ?? [];
|
|
1223
|
-
if (allMiddleware.length > 0) {
|
|
1224
|
-
const errorFn = (opts) => {
|
|
1225
|
-
const duration = `${Math.round(performance.now() - start)}ms`;
|
|
1226
|
-
response = jsonResponse({
|
|
1227
|
-
ok: false,
|
|
1228
|
-
error: { code: opts.code, message: opts.message },
|
|
1229
|
-
meta: { command: path, duration },
|
|
1230
|
-
}, 500);
|
|
1231
|
-
return undefined;
|
|
1232
|
-
};
|
|
1233
|
-
const mwCtx = {
|
|
1234
|
-
agent: true,
|
|
1235
|
-
command: path,
|
|
1236
|
-
env: {},
|
|
1237
|
-
error: errorFn,
|
|
1238
|
-
format: 'json',
|
|
1239
|
-
formatExplicit: true,
|
|
1240
|
-
name: path,
|
|
1241
|
-
options: {},
|
|
1242
|
-
set(key, value) {
|
|
1243
|
-
varsMap[key] = value;
|
|
1244
|
-
},
|
|
1245
|
-
var: varsMap,
|
|
1246
|
-
version: undefined,
|
|
1247
|
-
};
|
|
1248
|
-
const composed = allMiddleware.reduceRight((next, mw) => async () => {
|
|
1249
|
-
await mw(mwCtx, next);
|
|
1250
|
-
}, runCommand);
|
|
1251
|
-
await composed();
|
|
1252
|
-
}
|
|
1253
|
-
else {
|
|
1254
|
-
await runCommand();
|
|
1255
|
-
}
|
|
1276
|
+
controller.enqueue(encoder.encode(JSON.stringify({
|
|
1277
|
+
type: 'done',
|
|
1278
|
+
ok: true,
|
|
1279
|
+
meta: { command: path },
|
|
1280
|
+
}) + '\n'));
|
|
1281
|
+
}
|
|
1282
|
+
catch (error) {
|
|
1283
|
+
controller.enqueue(encoder.encode(JSON.stringify({
|
|
1284
|
+
type: 'error',
|
|
1285
|
+
ok: false,
|
|
1286
|
+
error: {
|
|
1287
|
+
code: 'UNKNOWN',
|
|
1288
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1289
|
+
},
|
|
1290
|
+
}) + '\n'));
|
|
1291
|
+
}
|
|
1292
|
+
controller.close();
|
|
1293
|
+
},
|
|
1294
|
+
});
|
|
1295
|
+
return new Response(stream, {
|
|
1296
|
+
status: 200,
|
|
1297
|
+
headers: { 'content-type': 'application/x-ndjson' },
|
|
1298
|
+
});
|
|
1256
1299
|
}
|
|
1257
|
-
|
|
1258
|
-
const
|
|
1259
|
-
if (error instanceof ValidationError)
|
|
1260
|
-
return jsonResponse({
|
|
1261
|
-
ok: false,
|
|
1262
|
-
error: { code: 'VALIDATION_ERROR', message: error.message },
|
|
1263
|
-
meta: { command: path, duration },
|
|
1264
|
-
}, 400);
|
|
1300
|
+
if (!result.ok) {
|
|
1301
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta);
|
|
1265
1302
|
return jsonResponse({
|
|
1266
1303
|
ok: false,
|
|
1267
1304
|
error: {
|
|
1268
|
-
code: error
|
|
1269
|
-
message: error
|
|
1305
|
+
code: result.error.code,
|
|
1306
|
+
message: result.error.message,
|
|
1307
|
+
...(result.error.retryable !== undefined
|
|
1308
|
+
? { retryable: result.error.retryable }
|
|
1309
|
+
: undefined),
|
|
1310
|
+
},
|
|
1311
|
+
meta: {
|
|
1312
|
+
command: path,
|
|
1313
|
+
duration,
|
|
1314
|
+
...(cta ? { cta } : undefined),
|
|
1270
1315
|
},
|
|
1271
|
-
|
|
1272
|
-
}, 500);
|
|
1316
|
+
}, result.error.code === 'VALIDATION_ERROR' ? 400 : 500);
|
|
1273
1317
|
}
|
|
1274
|
-
|
|
1318
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta);
|
|
1319
|
+
return jsonResponse({
|
|
1320
|
+
ok: true,
|
|
1321
|
+
data: result.data,
|
|
1322
|
+
meta: {
|
|
1323
|
+
command: path,
|
|
1324
|
+
duration,
|
|
1325
|
+
...(cta ? { cta } : undefined),
|
|
1326
|
+
},
|
|
1327
|
+
}, 200);
|
|
1275
1328
|
}
|
|
1276
1329
|
/** @internal Formats a validation error for TTY with usage hint. */
|
|
1277
|
-
function formatHumanValidationError(cli, path, command, error, envSource) {
|
|
1330
|
+
function formatHumanValidationError(cli, path, command, error, envSource, configFlag) {
|
|
1278
1331
|
const lines = [];
|
|
1279
1332
|
for (const fe of error.fieldErrors)
|
|
1280
1333
|
lines.push(`Error: missing required argument <${fe.path}>`);
|
|
@@ -1282,6 +1335,7 @@ function formatHumanValidationError(cli, path, command, error, envSource) {
|
|
|
1282
1335
|
lines.push('');
|
|
1283
1336
|
lines.push(Help.formatCommand(path === cli ? cli : `${cli} ${path}`, {
|
|
1284
1337
|
alias: command.alias,
|
|
1338
|
+
configFlag,
|
|
1285
1339
|
description: command.description,
|
|
1286
1340
|
args: command.args,
|
|
1287
1341
|
env: command.env,
|
|
@@ -1297,8 +1351,8 @@ function formatHumanValidationError(cli, path, command, error, envSource) {
|
|
|
1297
1351
|
function resolveCommand(commands, tokens) {
|
|
1298
1352
|
const [first, ...rest] = tokens;
|
|
1299
1353
|
if (!first || !commands.has(first))
|
|
1300
|
-
return { error: first ?? '(none)', path: '' };
|
|
1301
|
-
let entry = commands.get(first);
|
|
1354
|
+
return { error: first ?? '(none)', path: '', commands, rest };
|
|
1355
|
+
let entry = resolveAlias(commands, commands.get(first));
|
|
1302
1356
|
const path = [first];
|
|
1303
1357
|
let remaining = rest;
|
|
1304
1358
|
let inheritedOutputPolicy;
|
|
@@ -1327,10 +1381,16 @@ function resolveCommand(commands, tokens) {
|
|
|
1327
1381
|
description: entry.description,
|
|
1328
1382
|
commands: entry.commands,
|
|
1329
1383
|
};
|
|
1330
|
-
const
|
|
1331
|
-
if (!
|
|
1332
|
-
return {
|
|
1384
|
+
const rawChild = entry.commands.get(next);
|
|
1385
|
+
if (!rawChild) {
|
|
1386
|
+
return {
|
|
1387
|
+
error: next,
|
|
1388
|
+
path: path.join(' '),
|
|
1389
|
+
commands: entry.commands,
|
|
1390
|
+
rest: remaining.slice(1),
|
|
1391
|
+
};
|
|
1333
1392
|
}
|
|
1393
|
+
let child = resolveAlias(entry.commands, rawChild);
|
|
1334
1394
|
path.push(next);
|
|
1335
1395
|
remaining = remaining.slice(1);
|
|
1336
1396
|
entry = child;
|
|
@@ -1354,9 +1414,10 @@ function resolveCommand(commands, tokens) {
|
|
|
1354
1414
|
...(outputPolicy ? { outputPolicy } : undefined),
|
|
1355
1415
|
};
|
|
1356
1416
|
}
|
|
1357
|
-
/** @internal Extracts built-in flags (--
|
|
1358
|
-
|
|
1359
|
-
|
|
1417
|
+
/** @internal Extracts built-in flags (--full-output, --format, --json, --llms, --help, --version) from argv. */
|
|
1418
|
+
const validFormats = new Set(['toon', 'json', 'yaml', 'md', 'jsonl']);
|
|
1419
|
+
function extractBuiltinFlags(argv, options = {}) {
|
|
1420
|
+
let fullOutput = false;
|
|
1360
1421
|
let llms = false;
|
|
1361
1422
|
let llmsFull = false;
|
|
1362
1423
|
let mcp = false;
|
|
@@ -1365,15 +1426,20 @@ function extractBuiltinFlags(argv) {
|
|
|
1365
1426
|
let schema = false;
|
|
1366
1427
|
let format = 'toon';
|
|
1367
1428
|
let formatExplicit = false;
|
|
1429
|
+
let configPath;
|
|
1430
|
+
let configDisabled = false;
|
|
1368
1431
|
let filterOutput;
|
|
1369
1432
|
let tokenLimit;
|
|
1370
1433
|
let tokenOffset;
|
|
1371
1434
|
let tokenCount = false;
|
|
1372
1435
|
const rest = [];
|
|
1436
|
+
const cfgFlag = options.configFlag ? `--${options.configFlag}` : undefined;
|
|
1437
|
+
const cfgFlagEq = options.configFlag ? `--${options.configFlag}=` : undefined;
|
|
1438
|
+
const noCfgFlag = options.configFlag ? `--no-${options.configFlag}` : undefined;
|
|
1373
1439
|
for (let i = 0; i < argv.length; i++) {
|
|
1374
1440
|
const token = argv[i];
|
|
1375
|
-
if (token === '--
|
|
1376
|
-
|
|
1441
|
+
if (token === '--full-output')
|
|
1442
|
+
fullOutput = true;
|
|
1377
1443
|
else if (token === '--llms')
|
|
1378
1444
|
llms = true;
|
|
1379
1445
|
else if (token === '--llms-full')
|
|
@@ -1391,20 +1457,49 @@ function extractBuiltinFlags(argv) {
|
|
|
1391
1457
|
formatExplicit = true;
|
|
1392
1458
|
}
|
|
1393
1459
|
else if (token === '--format' && argv[i + 1]) {
|
|
1460
|
+
if (!validFormats.has(argv[i + 1]))
|
|
1461
|
+
throw new ParseError({
|
|
1462
|
+
message: `Invalid format: "${argv[i + 1]}". Expected one of: ${[...validFormats].join(', ')}`,
|
|
1463
|
+
});
|
|
1394
1464
|
format = argv[i + 1];
|
|
1395
1465
|
formatExplicit = true;
|
|
1396
1466
|
i++;
|
|
1397
1467
|
}
|
|
1468
|
+
else if (cfgFlag && token === cfgFlag) {
|
|
1469
|
+
const value = argv[i + 1];
|
|
1470
|
+
if (value === undefined)
|
|
1471
|
+
throw new ParseError({ message: `Missing value for flag: ${cfgFlag}` });
|
|
1472
|
+
configPath = value;
|
|
1473
|
+
configDisabled = false;
|
|
1474
|
+
i++;
|
|
1475
|
+
}
|
|
1476
|
+
else if (cfgFlagEq && token.startsWith(cfgFlagEq)) {
|
|
1477
|
+
const value = token.slice(cfgFlagEq.length);
|
|
1478
|
+
if (value.length === 0)
|
|
1479
|
+
throw new ParseError({ message: `Missing value for flag: ${cfgFlag}` });
|
|
1480
|
+
configPath = value;
|
|
1481
|
+
configDisabled = false;
|
|
1482
|
+
}
|
|
1483
|
+
else if (noCfgFlag && token === noCfgFlag) {
|
|
1484
|
+
configPath = undefined;
|
|
1485
|
+
configDisabled = true;
|
|
1486
|
+
}
|
|
1398
1487
|
else if (token === '--filter-output' && argv[i + 1]) {
|
|
1399
1488
|
filterOutput = argv[i + 1];
|
|
1400
1489
|
i++;
|
|
1401
1490
|
}
|
|
1402
1491
|
else if (token === '--token-limit' && argv[i + 1]) {
|
|
1403
|
-
|
|
1492
|
+
const n = Number(argv[i + 1]);
|
|
1493
|
+
if (!Number.isFinite(n) || argv[i + 1].trim() === '')
|
|
1494
|
+
throw new ParseError({ message: `Invalid value for --token-limit: "${argv[i + 1]}"` });
|
|
1495
|
+
tokenLimit = n;
|
|
1404
1496
|
i++;
|
|
1405
1497
|
}
|
|
1406
1498
|
else if (token === '--token-offset' && argv[i + 1]) {
|
|
1407
|
-
|
|
1499
|
+
const n = Number(argv[i + 1]);
|
|
1500
|
+
if (!Number.isFinite(n) || argv[i + 1].trim() === '')
|
|
1501
|
+
throw new ParseError({ message: `Invalid value for --token-offset: "${argv[i + 1]}"` });
|
|
1502
|
+
tokenOffset = n;
|
|
1408
1503
|
i++;
|
|
1409
1504
|
}
|
|
1410
1505
|
else if (token === '--token-count')
|
|
@@ -1413,9 +1508,11 @@ function extractBuiltinFlags(argv) {
|
|
|
1413
1508
|
rest.push(token);
|
|
1414
1509
|
}
|
|
1415
1510
|
return {
|
|
1416
|
-
|
|
1511
|
+
fullOutput,
|
|
1417
1512
|
format,
|
|
1418
1513
|
formatExplicit,
|
|
1514
|
+
configPath,
|
|
1515
|
+
configDisabled,
|
|
1419
1516
|
filterOutput,
|
|
1420
1517
|
tokenLimit,
|
|
1421
1518
|
tokenOffset,
|
|
@@ -1429,14 +1526,156 @@ function extractBuiltinFlags(argv) {
|
|
|
1429
1526
|
rest,
|
|
1430
1527
|
};
|
|
1431
1528
|
}
|
|
1529
|
+
/** @internal Loads config-backed option defaults for the active command. */
|
|
1530
|
+
async function loadCommandOptionDefaults(cli, path, options = {}) {
|
|
1531
|
+
if (options.configDisabled)
|
|
1532
|
+
return undefined;
|
|
1533
|
+
const { loader } = options;
|
|
1534
|
+
// Resolve the target file path
|
|
1535
|
+
let targetPath;
|
|
1536
|
+
if (options.configPath) {
|
|
1537
|
+
targetPath = resolveConfigPath(options.configPath);
|
|
1538
|
+
}
|
|
1539
|
+
else {
|
|
1540
|
+
const searchPaths = options.files ?? [`${cli}.json`];
|
|
1541
|
+
targetPath = await findFirstExisting(searchPaths);
|
|
1542
|
+
}
|
|
1543
|
+
// Load and parse the config
|
|
1544
|
+
let parsed;
|
|
1545
|
+
if (loader) {
|
|
1546
|
+
const result = await loader(targetPath);
|
|
1547
|
+
if (result === undefined)
|
|
1548
|
+
return undefined;
|
|
1549
|
+
if (!isRecord(result))
|
|
1550
|
+
throw new ParseError({ message: 'Config loader must return a plain object or undefined' });
|
|
1551
|
+
parsed = result;
|
|
1552
|
+
}
|
|
1553
|
+
else {
|
|
1554
|
+
if (!targetPath)
|
|
1555
|
+
return undefined;
|
|
1556
|
+
const result = await readJsonConfig(targetPath, !!options.configPath);
|
|
1557
|
+
if (!result)
|
|
1558
|
+
return undefined;
|
|
1559
|
+
parsed = result;
|
|
1560
|
+
}
|
|
1561
|
+
// Extract the command section from the config tree
|
|
1562
|
+
return extractCommandSection(parsed, cli, path);
|
|
1563
|
+
}
|
|
1564
|
+
/** @internal Resolves a config file path, expanding `~` to home dir. */
|
|
1565
|
+
function resolveConfigPath(filePath) {
|
|
1566
|
+
if (filePath.startsWith('~/') || filePath === '~') {
|
|
1567
|
+
return path.join(os.homedir(), filePath.slice(1));
|
|
1568
|
+
}
|
|
1569
|
+
return path.resolve(process.cwd(), filePath);
|
|
1570
|
+
}
|
|
1571
|
+
/** @internal Returns the first readable file from a list of paths, or `undefined`. */
|
|
1572
|
+
async function findFirstExisting(paths) {
|
|
1573
|
+
for (const p of paths) {
|
|
1574
|
+
const resolved = resolveConfigPath(p);
|
|
1575
|
+
try {
|
|
1576
|
+
await fs.access(resolved, fs.constants.R_OK);
|
|
1577
|
+
return resolved;
|
|
1578
|
+
}
|
|
1579
|
+
catch { }
|
|
1580
|
+
}
|
|
1581
|
+
return undefined;
|
|
1582
|
+
}
|
|
1583
|
+
/** @internal Reads and parses a JSON config file. */
|
|
1584
|
+
async function readJsonConfig(targetPath, explicit) {
|
|
1585
|
+
let raw;
|
|
1586
|
+
try {
|
|
1587
|
+
raw = await fs.readFile(targetPath, 'utf8');
|
|
1588
|
+
}
|
|
1589
|
+
catch (error) {
|
|
1590
|
+
if (error.code === 'ENOENT') {
|
|
1591
|
+
if (explicit)
|
|
1592
|
+
throw new ParseError({ message: `Config file not found: ${targetPath}` });
|
|
1593
|
+
return undefined;
|
|
1594
|
+
}
|
|
1595
|
+
throw error;
|
|
1596
|
+
}
|
|
1597
|
+
let parsed;
|
|
1598
|
+
try {
|
|
1599
|
+
parsed = JSON.parse(raw);
|
|
1600
|
+
}
|
|
1601
|
+
catch (error) {
|
|
1602
|
+
throw new ParseError({
|
|
1603
|
+
message: `Invalid JSON config file: ${targetPath}`,
|
|
1604
|
+
cause: error instanceof Error ? error : undefined,
|
|
1605
|
+
});
|
|
1606
|
+
}
|
|
1607
|
+
if (!isRecord(parsed))
|
|
1608
|
+
throw new ParseError({
|
|
1609
|
+
message: `Invalid config file: expected a top-level object in ${targetPath}`,
|
|
1610
|
+
});
|
|
1611
|
+
return parsed;
|
|
1612
|
+
}
|
|
1613
|
+
/** @internal Walks the nested config tree to extract option defaults for a command path. */
|
|
1614
|
+
function extractCommandSection(parsed, cli, path) {
|
|
1615
|
+
const segments = path === cli ? [] : path.split(' ');
|
|
1616
|
+
let node = parsed;
|
|
1617
|
+
for (const seg of segments) {
|
|
1618
|
+
if (!isRecord(node))
|
|
1619
|
+
return undefined;
|
|
1620
|
+
const commands = node.commands;
|
|
1621
|
+
if (!isRecord(commands))
|
|
1622
|
+
return undefined;
|
|
1623
|
+
node = commands[seg];
|
|
1624
|
+
if (node === undefined)
|
|
1625
|
+
return undefined;
|
|
1626
|
+
}
|
|
1627
|
+
if (!isRecord(node))
|
|
1628
|
+
throw new ParseError({
|
|
1629
|
+
message: `Invalid config section for '${path}': expected an object`,
|
|
1630
|
+
});
|
|
1631
|
+
const options = node.options;
|
|
1632
|
+
if (options === undefined)
|
|
1633
|
+
return undefined;
|
|
1634
|
+
if (!isRecord(options))
|
|
1635
|
+
throw new ParseError({
|
|
1636
|
+
message: `Invalid config 'options' for '${path}': expected an object`,
|
|
1637
|
+
});
|
|
1638
|
+
return Object.keys(options).length > 0 ? options : undefined;
|
|
1639
|
+
}
|
|
1432
1640
|
/** @internal Collects immediate child commands/groups for help output. */
|
|
1433
1641
|
function collectHelpCommands(commands) {
|
|
1434
1642
|
const result = [];
|
|
1435
1643
|
for (const [name, entry] of commands) {
|
|
1644
|
+
if (isAlias(entry))
|
|
1645
|
+
continue;
|
|
1436
1646
|
result.push({ name, description: entry.description });
|
|
1437
1647
|
}
|
|
1438
1648
|
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
1439
1649
|
}
|
|
1650
|
+
/** @internal Finds the index of a builtin command token in the filtered argv. Returns -1 if not found. */
|
|
1651
|
+
function builtinIdx(filtered, cliName, builtin) {
|
|
1652
|
+
// e.g. `skills add` or `skill add`
|
|
1653
|
+
if (findBuiltin(filtered[0])?.name === builtin)
|
|
1654
|
+
return 0;
|
|
1655
|
+
// e.g. `my-cli skills add`
|
|
1656
|
+
if (filtered[0] === cliName && findBuiltin(filtered[1])?.name === builtin)
|
|
1657
|
+
return 1;
|
|
1658
|
+
// not a match
|
|
1659
|
+
return -1;
|
|
1660
|
+
}
|
|
1661
|
+
/** @internal Formats group-level help for a built-in command (e.g. `cli skills`). */
|
|
1662
|
+
function formatBuiltinHelp(cli, builtin) {
|
|
1663
|
+
return Help.formatRoot(`${cli} ${builtin.name}`, {
|
|
1664
|
+
aliases: builtin.aliases,
|
|
1665
|
+
description: builtin.description,
|
|
1666
|
+
commands: builtin.subcommands?.map((s) => ({ name: s.name, description: s.description })),
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
/** @internal Formats subcommand-level help for a built-in command (e.g. `cli skills add --help`). */
|
|
1670
|
+
function formatBuiltinSubcommandHelp(cli, builtin, subName) {
|
|
1671
|
+
const sub = builtin.subcommands?.find((s) => s.name === subName);
|
|
1672
|
+
return Help.formatCommand(`${cli} ${builtin.name} ${subName}`, {
|
|
1673
|
+
alias: sub?.alias,
|
|
1674
|
+
description: sub?.description,
|
|
1675
|
+
hideGlobalOptions: true,
|
|
1676
|
+
options: sub?.options,
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1440
1679
|
/** @internal Formats help text for a fetch gateway command. */
|
|
1441
1680
|
function formatFetchHelp(name, description) {
|
|
1442
1681
|
const lines = [];
|
|
@@ -1465,14 +1704,30 @@ function isGroup(entry) {
|
|
|
1465
1704
|
function isFetchGateway(entry) {
|
|
1466
1705
|
return '_fetch' in entry;
|
|
1467
1706
|
}
|
|
1707
|
+
/** @internal Type guard for alias entries. */
|
|
1708
|
+
function isAlias(entry) {
|
|
1709
|
+
return '_alias' in entry;
|
|
1710
|
+
}
|
|
1711
|
+
/** @internal Follows an alias entry to its canonical target. Returns the entry unchanged if not an alias. */
|
|
1712
|
+
function resolveAlias(commands, entry) {
|
|
1713
|
+
if (isAlias(entry))
|
|
1714
|
+
return commands.get(entry.target);
|
|
1715
|
+
return entry;
|
|
1716
|
+
}
|
|
1468
1717
|
/** @internal Maps CLI instances to their command maps. */
|
|
1469
1718
|
export const toCommands = new WeakMap();
|
|
1470
1719
|
/** @internal Maps CLI instances to their middleware arrays. */
|
|
1471
1720
|
const toMiddlewares = new WeakMap();
|
|
1472
1721
|
/** @internal Maps root CLI instances to their command definitions. */
|
|
1473
|
-
const toRootDefinition = new WeakMap();
|
|
1722
|
+
export const toRootDefinition = new WeakMap();
|
|
1723
|
+
/** @internal Maps CLI instances to their root options schema. */
|
|
1724
|
+
export const toRootOptions = new WeakMap();
|
|
1725
|
+
/** @internal Maps CLI instances to whether config file loading is enabled. */
|
|
1726
|
+
export const toConfigEnabled = new WeakMap();
|
|
1474
1727
|
/** @internal Maps CLI instances to their output policy. */
|
|
1475
1728
|
const toOutputPolicy = new WeakMap();
|
|
1729
|
+
/** @internal Maps root CLI instances to their command aliases. */
|
|
1730
|
+
const toRootAliases = new WeakMap();
|
|
1476
1731
|
/** @internal Sentinel symbol for `ok()` and `error()` return values. */
|
|
1477
1732
|
const sentinel = Symbol.for('incur.sentinel');
|
|
1478
1733
|
/** @internal Formats an error for human-readable TTY output. */
|
|
@@ -1489,8 +1744,9 @@ function formatHumanError(error) {
|
|
|
1489
1744
|
/** @internal Formats a CTA block for human-readable TTY output. */
|
|
1490
1745
|
function formatHumanCta(cta) {
|
|
1491
1746
|
const lines = ['', cta.description];
|
|
1747
|
+
const maxLen = Math.max(...cta.commands.map((c) => c.command.length));
|
|
1492
1748
|
for (const c of cta.commands) {
|
|
1493
|
-
const desc = c.description ? ` # ${c.description}` : '';
|
|
1749
|
+
const desc = c.description ? ` ${''.padEnd(maxLen - c.command.length)}# ${c.description}` : '';
|
|
1494
1750
|
lines.push(` ${c.command}${desc}`);
|
|
1495
1751
|
}
|
|
1496
1752
|
return lines.join('\n');
|
|
@@ -1502,13 +1758,6 @@ function hasRequiredArgs(args) {
|
|
|
1502
1758
|
function isSentinel(value) {
|
|
1503
1759
|
return typeof value === 'object' && value !== null && sentinel in value;
|
|
1504
1760
|
}
|
|
1505
|
-
/** @internal Type guard for async generators returned by streaming `run` handlers. */
|
|
1506
|
-
function isAsyncGenerator(value) {
|
|
1507
|
-
return (typeof value === 'object' &&
|
|
1508
|
-
value !== null &&
|
|
1509
|
-
Symbol.asyncIterator in value &&
|
|
1510
|
-
typeof value.next === 'function');
|
|
1511
|
-
}
|
|
1512
1761
|
/** @internal Handles streaming output from an async generator `run` handler. */
|
|
1513
1762
|
async function handleStreaming(generator, ctx) {
|
|
1514
1763
|
// Incremental: no explicit format (default toon), or explicit jsonl
|
|
@@ -1686,7 +1935,8 @@ function formatCtaBlock(name, block) {
|
|
|
1686
1935
|
if (!block || block.commands.length === 0)
|
|
1687
1936
|
return undefined;
|
|
1688
1937
|
return {
|
|
1689
|
-
description: block.description ??
|
|
1938
|
+
description: block.description ??
|
|
1939
|
+
(block.commands.length === 1 ? 'Suggested command:' : 'Suggested commands:'),
|
|
1690
1940
|
commands: block.commands.map((c) => formatCta(name, c)),
|
|
1691
1941
|
};
|
|
1692
1942
|
}
|
|
@@ -1715,6 +1965,8 @@ function buildIndexManifest(commands, prefix = []) {
|
|
|
1715
1965
|
function collectIndexCommands(commands, prefix) {
|
|
1716
1966
|
const result = [];
|
|
1717
1967
|
for (const [name, entry] of commands) {
|
|
1968
|
+
if (isAlias(entry))
|
|
1969
|
+
continue;
|
|
1718
1970
|
const path = [...prefix, name];
|
|
1719
1971
|
if (isGroup(entry)) {
|
|
1720
1972
|
result.push(...collectIndexCommands(entry.commands, path));
|
|
@@ -1743,6 +1995,8 @@ function buildManifest(commands, prefix = []) {
|
|
|
1743
1995
|
function collectCommands(commands, prefix) {
|
|
1744
1996
|
const result = [];
|
|
1745
1997
|
for (const [name, entry] of commands) {
|
|
1998
|
+
if (isAlias(entry))
|
|
1999
|
+
continue;
|
|
1746
2000
|
const path = [...prefix, name];
|
|
1747
2001
|
if (isFetchGateway(entry)) {
|
|
1748
2002
|
const cmd = { name: path.join(' ') };
|
|
@@ -1784,9 +2038,30 @@ function collectCommands(commands, prefix) {
|
|
|
1784
2038
|
return result;
|
|
1785
2039
|
}
|
|
1786
2040
|
/** @internal Recursively collects leaf commands as `Skill.CommandInfo` for `--llms --format md`. */
|
|
1787
|
-
function collectSkillCommands(commands, prefix, groups) {
|
|
2041
|
+
function collectSkillCommands(commands, prefix, groups, rootCommand) {
|
|
1788
2042
|
const result = [];
|
|
2043
|
+
if (rootCommand) {
|
|
2044
|
+
const cmd = {};
|
|
2045
|
+
if (rootCommand.description)
|
|
2046
|
+
cmd.description = rootCommand.description;
|
|
2047
|
+
if (rootCommand.args)
|
|
2048
|
+
cmd.args = rootCommand.args;
|
|
2049
|
+
if (rootCommand.env)
|
|
2050
|
+
cmd.env = rootCommand.env;
|
|
2051
|
+
if (rootCommand.hint)
|
|
2052
|
+
cmd.hint = rootCommand.hint;
|
|
2053
|
+
if (rootCommand.options)
|
|
2054
|
+
cmd.options = rootCommand.options;
|
|
2055
|
+
if (rootCommand.output)
|
|
2056
|
+
cmd.output = rootCommand.output;
|
|
2057
|
+
const examples = formatExamples(rootCommand.examples);
|
|
2058
|
+
if (examples)
|
|
2059
|
+
cmd.examples = examples;
|
|
2060
|
+
result.push(cmd);
|
|
2061
|
+
}
|
|
1789
2062
|
for (const [name, entry] of commands) {
|
|
2063
|
+
if (isAlias(entry))
|
|
2064
|
+
continue;
|
|
1790
2065
|
const path = [...prefix, name];
|
|
1791
2066
|
if (isFetchGateway(entry)) {
|
|
1792
2067
|
const cmd = { name: path.join(' ') };
|
|
@@ -1825,7 +2100,7 @@ function collectSkillCommands(commands, prefix, groups) {
|
|
|
1825
2100
|
result.push(cmd);
|
|
1826
2101
|
}
|
|
1827
2102
|
}
|
|
1828
|
-
return result.sort((a, b) => a.name.localeCompare(b.name));
|
|
2103
|
+
return result.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''));
|
|
1829
2104
|
}
|
|
1830
2105
|
/** @internal Formats examples into `{ command, description }` objects. `command` is the args/options suffix only. */
|
|
1831
2106
|
export function formatExamples(examples) {
|
|
@@ -1890,4 +2165,16 @@ function emitDeprecationWarnings(argv, optionsSchema, alias) {
|
|
|
1890
2165
|
}
|
|
1891
2166
|
}
|
|
1892
2167
|
}
|
|
2168
|
+
/** @internal Resolves the display name from `process.argv[1]` basename. Returns the basename if it matches `name` or one of the `aliases`, otherwise falls back to `name`. */
|
|
2169
|
+
function resolveDisplayName(name, aliases) {
|
|
2170
|
+
const bin = process.argv[1];
|
|
2171
|
+
if (!bin)
|
|
2172
|
+
return name;
|
|
2173
|
+
const basename = path.basename(bin);
|
|
2174
|
+
if (basename === name)
|
|
2175
|
+
return name;
|
|
2176
|
+
if (aliases?.includes(basename))
|
|
2177
|
+
return basename;
|
|
2178
|
+
return name;
|
|
2179
|
+
}
|
|
1893
2180
|
//# sourceMappingURL=Cli.js.map
|