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/src/Cli.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
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'
|
|
2
|
-
import
|
|
5
|
+
import { z } from 'zod'
|
|
3
6
|
|
|
4
7
|
import * as Completions from './Completions.js'
|
|
5
8
|
import type { FieldError } from './Errors.js'
|
|
6
|
-
import { IncurError, ValidationError } from './Errors.js'
|
|
9
|
+
import { IncurError, ParseError, ValidationError } from './Errors.js'
|
|
7
10
|
import * as Fetch from './Fetch.js'
|
|
8
11
|
import * as Filter from './Filter.js'
|
|
9
12
|
import * as Formatter from './Formatter.js'
|
|
10
13
|
import * as Help from './Help.js'
|
|
14
|
+
import {
|
|
15
|
+
builtinCommands,
|
|
16
|
+
type CommandMeta,
|
|
17
|
+
findBuiltin,
|
|
18
|
+
type Shell,
|
|
19
|
+
shells,
|
|
20
|
+
} from './internal/command.js'
|
|
21
|
+
import * as Command from './internal/command.js'
|
|
22
|
+
import { isRecord, suggest } from './internal/helpers.js'
|
|
11
23
|
import { detectRunner } from './internal/pm.js'
|
|
12
24
|
import type { OneOf } from './internal/types.js'
|
|
13
25
|
import * as Mcp from './Mcp.js'
|
|
@@ -26,7 +38,6 @@ export type Cli<
|
|
|
26
38
|
commands extends CommandsMap = {},
|
|
27
39
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
28
40
|
env extends z.ZodObject<any> | undefined = undefined,
|
|
29
|
-
opts extends z.ZodObject<any> | undefined = undefined,
|
|
30
41
|
> = {
|
|
31
42
|
/** Registers a root command or mounts a sub-CLI as a command group. */
|
|
32
43
|
command: {
|
|
@@ -43,30 +54,23 @@ export type Cli<
|
|
|
43
54
|
): Cli<
|
|
44
55
|
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<options> } },
|
|
45
56
|
vars,
|
|
46
|
-
env
|
|
47
|
-
opts
|
|
57
|
+
env
|
|
48
58
|
>
|
|
49
59
|
/** Mounts a sub-CLI as a command group. */
|
|
50
60
|
<const name extends string, const sub extends CommandsMap>(
|
|
51
|
-
cli: Cli<sub, any, any
|
|
52
|
-
): Cli<
|
|
53
|
-
commands & { [key in keyof sub & string as `${name} ${key}`]: sub[key] },
|
|
54
|
-
vars,
|
|
55
|
-
env,
|
|
56
|
-
opts
|
|
57
|
-
>
|
|
61
|
+
cli: Cli<sub, any, any> & { name: name },
|
|
62
|
+
): Cli<commands & { [key in keyof sub & string as `${name} ${key}`]: sub[key] }, vars, env>
|
|
58
63
|
/** Mounts a root CLI as a single command. */
|
|
59
64
|
<
|
|
60
65
|
const name extends string,
|
|
61
66
|
const args extends z.ZodObject<any> | undefined,
|
|
62
|
-
const
|
|
67
|
+
const opts extends z.ZodObject<any> | undefined,
|
|
63
68
|
>(
|
|
64
|
-
cli: Root<args,
|
|
69
|
+
cli: Root<args, opts> & { name: name },
|
|
65
70
|
): Cli<
|
|
66
|
-
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<
|
|
71
|
+
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<opts> } },
|
|
67
72
|
vars,
|
|
68
|
-
env
|
|
69
|
-
opts
|
|
73
|
+
env
|
|
70
74
|
>
|
|
71
75
|
/** Mounts a fetch handler as a command, optionally with OpenAPI spec for typed subcommands. */
|
|
72
76
|
<const name extends string>(
|
|
@@ -78,7 +82,7 @@ export type Cli<
|
|
|
78
82
|
openapi?: Openapi.OpenAPISpec | undefined
|
|
79
83
|
outputPolicy?: OutputPolicy | undefined
|
|
80
84
|
},
|
|
81
|
-
): Cli<commands, vars, env
|
|
85
|
+
): Cli<commands, vars, env>
|
|
82
86
|
}
|
|
83
87
|
/** A short description of the CLI. */
|
|
84
88
|
description?: string | undefined
|
|
@@ -90,10 +94,8 @@ export type Cli<
|
|
|
90
94
|
fetch(req: Request): Promise<Response>
|
|
91
95
|
/** Parses argv, runs the matched command, and writes the output envelope to stdout. */
|
|
92
96
|
serve(argv?: string[], options?: serve.Options): Promise<void>
|
|
93
|
-
/** The options schema, if declared. Use `typeof cli.options` with `middleware<vars, env, options>()` for typed middleware. */
|
|
94
|
-
options: opts
|
|
95
97
|
/** Registers middleware that runs around every command. */
|
|
96
|
-
use(handler: MiddlewareHandler<vars, env
|
|
98
|
+
use(handler: MiddlewareHandler<vars, env>): Cli<commands, vars, env>
|
|
97
99
|
/** The vars schema, if declared. Use `typeof cli.vars` with `middleware<vars, env>()` for typed middleware. */
|
|
98
100
|
vars: vars
|
|
99
101
|
}
|
|
@@ -161,12 +163,7 @@ export function create<
|
|
|
161
163
|
>(
|
|
162
164
|
name: string,
|
|
163
165
|
definition: create.Options<args, env, opts, output, vars> & { run: Function },
|
|
164
|
-
): Cli<
|
|
165
|
-
{ [key in typeof name]: { args: InferOutput<args>; options: InferOutput<opts> } },
|
|
166
|
-
vars,
|
|
167
|
-
env,
|
|
168
|
-
opts
|
|
169
|
-
>
|
|
166
|
+
): Cli<{ [key in typeof name]: { args: InferOutput<args>; options: InferOutput<opts> } }, vars, env>
|
|
170
167
|
/** Creates a router CLI that registers subcommands. */
|
|
171
168
|
export function create<
|
|
172
169
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -174,10 +171,7 @@ export function create<
|
|
|
174
171
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
175
172
|
const output extends z.ZodType | undefined = undefined,
|
|
176
173
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
177
|
-
>(
|
|
178
|
-
name: string,
|
|
179
|
-
definition?: create.Options<args, env, opts, output, vars>,
|
|
180
|
-
): Cli<{}, vars, env, opts>
|
|
174
|
+
>(name: string, definition?: create.Options<args, env, opts, output, vars>): Cli<{}, vars, env>
|
|
181
175
|
/** Creates a CLI with a root handler from a single options object. Can still register subcommands. */
|
|
182
176
|
export function create<
|
|
183
177
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -192,8 +186,7 @@ export function create<
|
|
|
192
186
|
[key in (typeof definition)['name']]: { args: InferOutput<args>; options: InferOutput<opts> }
|
|
193
187
|
},
|
|
194
188
|
vars,
|
|
195
|
-
env
|
|
196
|
-
opts
|
|
189
|
+
env
|
|
197
190
|
>
|
|
198
191
|
/** Creates a router CLI from a single options object (e.g. package.json). */
|
|
199
192
|
export function create<
|
|
@@ -202,9 +195,7 @@ export function create<
|
|
|
202
195
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
203
196
|
const output extends z.ZodType | undefined = undefined,
|
|
204
197
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
205
|
-
>(
|
|
206
|
-
definition: create.Options<args, env, opts, output, vars> & { name: string },
|
|
207
|
-
): Cli<{}, vars, env, opts>
|
|
198
|
+
>(definition: create.Options<args, env, opts, output, vars> & { name: string }): Cli<{}, vars, env>
|
|
208
199
|
export function create(
|
|
209
200
|
nameOrDefinition: string | (any & { name: string }),
|
|
210
201
|
definition?: any,
|
|
@@ -223,7 +214,6 @@ export function create(
|
|
|
223
214
|
name,
|
|
224
215
|
description: def.description,
|
|
225
216
|
env: def.env,
|
|
226
|
-
options: def.options,
|
|
227
217
|
vars: def.vars,
|
|
228
218
|
|
|
229
219
|
command(nameOrCli: any, def?: any): any {
|
|
@@ -255,11 +245,16 @@ export function create(
|
|
|
255
245
|
return cli
|
|
256
246
|
}
|
|
257
247
|
commands.set(nameOrCli, def)
|
|
248
|
+
if (def.aliases)
|
|
249
|
+
for (const a of def.aliases) commands.set(a, { _alias: true, target: nameOrCli })
|
|
258
250
|
return cli
|
|
259
251
|
}
|
|
260
252
|
const mountedRootDef = toRootDefinition.get(nameOrCli)
|
|
261
253
|
if (mountedRootDef) {
|
|
262
254
|
commands.set(nameOrCli.name, mountedRootDef)
|
|
255
|
+
const rootAliases = toRootAliases.get(nameOrCli)
|
|
256
|
+
if (rootAliases)
|
|
257
|
+
for (const a of rootAliases) commands.set(a, { _alias: true, target: nameOrCli.name })
|
|
263
258
|
return cli
|
|
264
259
|
}
|
|
265
260
|
const sub = nameOrCli as Cli
|
|
@@ -279,10 +274,13 @@ export function create(
|
|
|
279
274
|
async fetch(req: Request) {
|
|
280
275
|
if (pending.length > 0) await Promise.all(pending)
|
|
281
276
|
return fetchImpl(name, commands, req, {
|
|
277
|
+
envSchema: def.env,
|
|
282
278
|
mcpHandler,
|
|
283
279
|
middlewares,
|
|
280
|
+
name,
|
|
284
281
|
rootCommand: rootDef,
|
|
285
282
|
vars: def.vars,
|
|
283
|
+
version: def.version,
|
|
286
284
|
})
|
|
287
285
|
},
|
|
288
286
|
|
|
@@ -291,12 +289,12 @@ export function create(
|
|
|
291
289
|
return serveImpl(name, commands, argv, {
|
|
292
290
|
...serveOptions,
|
|
293
291
|
aliases: def.aliases,
|
|
292
|
+
config: def.config,
|
|
294
293
|
description: def.description,
|
|
295
294
|
envSchema: def.env,
|
|
296
295
|
format: def.format,
|
|
297
296
|
mcp: def.mcp,
|
|
298
297
|
middlewares,
|
|
299
|
-
optionsSchema: def.options,
|
|
300
298
|
outputPolicy: def.outputPolicy,
|
|
301
299
|
rootCommand: rootDef,
|
|
302
300
|
rootFetch,
|
|
@@ -313,6 +311,9 @@ export function create(
|
|
|
313
311
|
}
|
|
314
312
|
|
|
315
313
|
if (rootDef) toRootDefinition.set(cli as unknown as Root, rootDef)
|
|
314
|
+
if (rootDef && def.aliases) toRootAliases.set(cli as unknown as Root, def.aliases)
|
|
315
|
+
if (def.options) toRootOptions.set(cli, def.options)
|
|
316
|
+
if (def.config !== undefined) toConfigEnabled.set(cli, true)
|
|
316
317
|
if (def.outputPolicy) toOutputPolicy.set(cli, def.outputPolicy)
|
|
317
318
|
toMiddlewares.set(cli, middlewares)
|
|
318
319
|
toCommands.set(cli, commands)
|
|
@@ -336,6 +337,24 @@ export declare namespace create {
|
|
|
336
337
|
aliases?: string[] | undefined
|
|
337
338
|
/** Zod schema for positional arguments. */
|
|
338
339
|
args?: args | undefined
|
|
340
|
+
/** Enable config-file defaults for command options. */
|
|
341
|
+
config?:
|
|
342
|
+
| {
|
|
343
|
+
/** Global flag name for specifying a config file path (e.g. `'config'` → `--config <path>`). Omit to auto-load only, with no CLI flag. */
|
|
344
|
+
flag?: string | undefined
|
|
345
|
+
/** Ordered list of file paths to search. First existing file wins. Supports `~` for home dir. Defaults to `['<cli>.json']` relative to cwd. */
|
|
346
|
+
files?: string[] | undefined
|
|
347
|
+
/** Custom config loader. Receives the resolved file path (or `undefined` if no file was found). Returns the parsed config tree, or `undefined` for no defaults. When omitted, the framework reads and parses JSON. */
|
|
348
|
+
loader?:
|
|
349
|
+
| ((
|
|
350
|
+
path: string | undefined,
|
|
351
|
+
) =>
|
|
352
|
+
| Record<string, unknown>
|
|
353
|
+
| undefined
|
|
354
|
+
| Promise<Record<string, unknown> | undefined>)
|
|
355
|
+
| undefined
|
|
356
|
+
}
|
|
357
|
+
| undefined
|
|
339
358
|
/** A short description of what the CLI does. */
|
|
340
359
|
description?: string | undefined
|
|
341
360
|
/** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
|
|
@@ -370,6 +389,8 @@ export declare namespace create {
|
|
|
370
389
|
agent: boolean
|
|
371
390
|
/** Positional arguments. */
|
|
372
391
|
args: InferOutput<args>
|
|
392
|
+
/** The binary name the user invoked (e.g. an alias). Falls back to `name` when not resolvable. */
|
|
393
|
+
displayName: string
|
|
373
394
|
/** Parsed environment variables. */
|
|
374
395
|
env: InferOutput<env>
|
|
375
396
|
/** Return an error result with optional CTAs. */
|
|
@@ -445,9 +466,28 @@ async function serveImpl(
|
|
|
445
466
|
) {
|
|
446
467
|
const stdout = options.stdout ?? ((s: string) => process.stdout.write(s))
|
|
447
468
|
const exit = options.exit ?? ((code: number) => process.exit(code))
|
|
469
|
+
const human = process.stdout.isTTY === true
|
|
470
|
+
const configEnabled = options.config !== undefined
|
|
471
|
+
const configFlag = options.config?.flag
|
|
472
|
+
const displayName = resolveDisplayName(name, options.aliases)
|
|
473
|
+
|
|
474
|
+
function writeln(s: string) {
|
|
475
|
+
stdout(s.endsWith('\n') ? s : `${s}\n`)
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
let builtinFlags: ReturnType<typeof extractBuiltinFlags>
|
|
479
|
+
try {
|
|
480
|
+
builtinFlags = extractBuiltinFlags(argv, { configFlag })
|
|
481
|
+
} catch (error) {
|
|
482
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
483
|
+
if (human) writeln(formatHumanError({ code: 'UNKNOWN', message }))
|
|
484
|
+
else writeln(Formatter.format({ code: 'UNKNOWN', message }, 'toon'))
|
|
485
|
+
exit(1)
|
|
486
|
+
return
|
|
487
|
+
}
|
|
448
488
|
|
|
449
489
|
const {
|
|
450
|
-
|
|
490
|
+
fullOutput,
|
|
451
491
|
format: formatFlag,
|
|
452
492
|
formatExplicit,
|
|
453
493
|
filterOutput,
|
|
@@ -460,17 +500,24 @@ async function serveImpl(
|
|
|
460
500
|
help,
|
|
461
501
|
version,
|
|
462
502
|
schema,
|
|
503
|
+
configPath,
|
|
504
|
+
configDisabled,
|
|
463
505
|
rest: filtered,
|
|
464
|
-
} =
|
|
506
|
+
} = builtinFlags
|
|
465
507
|
|
|
466
508
|
// --mcp: start as MCP stdio server
|
|
467
509
|
if (mcpFlag) {
|
|
468
|
-
await Mcp.serve(name, options.version ?? '0.0.0', commands
|
|
510
|
+
await Mcp.serve(name, options.version ?? '0.0.0', commands, {
|
|
511
|
+
middlewares: options.middlewares,
|
|
512
|
+
env: options.envSchema,
|
|
513
|
+
vars: options.vars,
|
|
514
|
+
version: options.version,
|
|
515
|
+
})
|
|
469
516
|
return
|
|
470
517
|
}
|
|
471
518
|
|
|
472
519
|
// COMPLETE: dynamic shell completions (called by shell hook at tab-press)
|
|
473
|
-
const completeShell = process.env.COMPLETE as
|
|
520
|
+
const completeShell = process.env.COMPLETE as Shell | undefined
|
|
474
521
|
if (completeShell) {
|
|
475
522
|
// Remove separator `--` from argv
|
|
476
523
|
const sepIdx = argv.indexOf('--')
|
|
@@ -482,35 +529,51 @@ async function serveImpl(
|
|
|
482
529
|
} else {
|
|
483
530
|
const index = Number(process.env._COMPLETE_INDEX ?? words.length - 1)
|
|
484
531
|
const candidates = Completions.complete(commands, options.rootCommand, words, index)
|
|
532
|
+
// Add built-in commands (completions, mcp, skills) to completions
|
|
533
|
+
const current = words[index] ?? ''
|
|
534
|
+
const nonFlags = words.slice(0, index).filter((w) => !w.startsWith('-'))
|
|
535
|
+
if (nonFlags.length <= 1) {
|
|
536
|
+
for (const b of builtinCommands) {
|
|
537
|
+
if (b.name.startsWith(current) && !candidates.some((c) => c.value === b.name))
|
|
538
|
+
candidates.push({
|
|
539
|
+
value: b.name,
|
|
540
|
+
description: b.description,
|
|
541
|
+
...(b.subcommands ? { noSpace: true } : undefined),
|
|
542
|
+
})
|
|
543
|
+
}
|
|
544
|
+
} else if (nonFlags.length === 2) {
|
|
545
|
+
const parent = nonFlags[nonFlags.length - 1]!
|
|
546
|
+
const builtin = findBuiltin(parent)
|
|
547
|
+
if (builtin?.subcommands)
|
|
548
|
+
for (const sub of builtin.subcommands)
|
|
549
|
+
if (sub.name.startsWith(current))
|
|
550
|
+
candidates.push({ value: sub.name, description: sub.description })
|
|
551
|
+
}
|
|
485
552
|
const out = Completions.format(completeShell, candidates)
|
|
486
553
|
if (out) stdout(out)
|
|
487
554
|
}
|
|
488
555
|
return
|
|
489
556
|
}
|
|
490
557
|
|
|
491
|
-
// Human mode: stdout is a TTY.
|
|
492
|
-
const human = process.stdout.isTTY === true
|
|
493
|
-
|
|
494
|
-
function writeln(s: string) {
|
|
495
|
-
stdout(s.endsWith('\n') ? s : `${s}\n`)
|
|
496
|
-
}
|
|
497
|
-
|
|
498
558
|
// Skills staleness check (skip for built-in commands)
|
|
559
|
+
let skillsCta: FormattedCtaBlock | undefined
|
|
499
560
|
if (!llms && !llmsFull && !schema && !help && !version) {
|
|
500
|
-
const isSkillsAdd =
|
|
501
|
-
|
|
502
|
-
const isMcpAdd = filtered[0] === 'mcp' || (filtered[0] === name && filtered[1] === 'mcp')
|
|
561
|
+
const isSkillsAdd = builtinIdx(filtered, name, 'skills') !== -1
|
|
562
|
+
const isMcpAdd = builtinIdx(filtered, name, 'mcp') !== -1
|
|
503
563
|
if (!isSkillsAdd && !isMcpAdd) {
|
|
504
564
|
const stored = SyncSkills.readHash(name)
|
|
505
|
-
if (stored) {
|
|
565
|
+
if (stored && SyncSkills.hasInstalledSkills(name, { cwd: options.sync?.cwd })) {
|
|
506
566
|
const groups = new Map<string, string>()
|
|
507
|
-
const entries = collectSkillCommands(commands, [], groups)
|
|
567
|
+
const entries = collectSkillCommands(commands, [], groups, options.rootCommand)
|
|
508
568
|
if (Skill.hash(entries) !== stored) {
|
|
509
|
-
const
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
569
|
+
const command =
|
|
570
|
+
process.env.npm_config_user_agent || process.env.npm_execpath
|
|
571
|
+
? `${detectRunner()} ${SyncMcp.detectPackageSpecifier(name)} skills add`
|
|
572
|
+
: `${displayName} skills add`
|
|
573
|
+
skillsCta = {
|
|
574
|
+
description: 'Skills are out of date:',
|
|
575
|
+
commands: [{ command, description: 'sync outdated skills' }],
|
|
576
|
+
}
|
|
514
577
|
}
|
|
515
578
|
}
|
|
516
579
|
}
|
|
@@ -522,8 +585,9 @@ async function serveImpl(
|
|
|
522
585
|
const prefix: string[] = []
|
|
523
586
|
let scopedDescription: string | undefined = options.description
|
|
524
587
|
for (const token of filtered) {
|
|
525
|
-
const
|
|
526
|
-
if (!
|
|
588
|
+
const rawEntry = scopedCommands.get(token)
|
|
589
|
+
if (!rawEntry) break
|
|
590
|
+
const entry = resolveAlias(scopedCommands, rawEntry)
|
|
527
591
|
if (isGroup(entry)) {
|
|
528
592
|
scopedCommands = entry.commands
|
|
529
593
|
scopedDescription = entry.description
|
|
@@ -535,10 +599,12 @@ async function serveImpl(
|
|
|
535
599
|
}
|
|
536
600
|
}
|
|
537
601
|
|
|
602
|
+
const scopedRoot = prefix.length === 0 ? options.rootCommand : undefined
|
|
603
|
+
|
|
538
604
|
if (llmsFull) {
|
|
539
605
|
if (!formatExplicit || formatFlag === 'md') {
|
|
540
606
|
const groups = new Map<string, string>()
|
|
541
|
-
const cmds = collectSkillCommands(scopedCommands, prefix, groups)
|
|
607
|
+
const cmds = collectSkillCommands(scopedCommands, prefix, groups, scopedRoot)
|
|
542
608
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name
|
|
543
609
|
writeln(Skill.generate(scopedName, cmds, groups))
|
|
544
610
|
return
|
|
@@ -549,7 +615,7 @@ async function serveImpl(
|
|
|
549
615
|
|
|
550
616
|
if (!formatExplicit || formatFlag === 'md') {
|
|
551
617
|
const groups = new Map<string, string>()
|
|
552
|
-
const cmds = collectSkillCommands(scopedCommands, prefix, groups)
|
|
618
|
+
const cmds = collectSkillCommands(scopedCommands, prefix, groups, scopedRoot)
|
|
553
619
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name
|
|
554
620
|
writeln(Skill.index(scopedName, cmds, scopedDescription))
|
|
555
621
|
return
|
|
@@ -559,81 +625,119 @@ async function serveImpl(
|
|
|
559
625
|
}
|
|
560
626
|
|
|
561
627
|
// completions <shell>: print shell hook script to stdout
|
|
562
|
-
const completionsIdx = (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
// not a completions invocation
|
|
568
|
-
return -1
|
|
569
|
-
})()
|
|
570
|
-
if (completionsIdx !== -1 && filtered[completionsIdx] === 'completions') {
|
|
571
|
-
if (help) {
|
|
628
|
+
const completionsIdx = builtinIdx(filtered, name, 'completions')
|
|
629
|
+
if (completionsIdx !== -1) {
|
|
630
|
+
const shell = filtered[completionsIdx + 1]
|
|
631
|
+
if (help || !shell) {
|
|
632
|
+
const b = findBuiltin('completions')!
|
|
572
633
|
writeln(
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
' bash',
|
|
580
|
-
' fish',
|
|
581
|
-
' nushell',
|
|
582
|
-
' zsh',
|
|
583
|
-
'',
|
|
584
|
-
'Setup:',
|
|
585
|
-
...(() => {
|
|
586
|
-
const rows = [
|
|
587
|
-
['bash', `eval "$(${name} completions bash)"`, '# add to ~/.bashrc'],
|
|
588
|
-
['zsh', `eval "$(${name} completions zsh)"`, '# add to ~/.zshrc'],
|
|
589
|
-
['fish', `${name} completions fish | source`, '# add to ~/.config/fish/config.fish'],
|
|
590
|
-
['nushell', `see \`${name} completions nushell\``, '# add to config.nu'],
|
|
591
|
-
]
|
|
592
|
-
const shellW = Math.max(...rows.map((r) => r[0]!.length))
|
|
593
|
-
const cmdW = Math.max(...rows.map((r) => r[1]!.length))
|
|
594
|
-
return rows.map(
|
|
595
|
-
([shell, cmd, comment]) =>
|
|
596
|
-
` ${shell!.padEnd(shellW)} ${cmd!.padEnd(cmdW)} ${comment}`,
|
|
597
|
-
)
|
|
598
|
-
})(),
|
|
599
|
-
].join('\n'),
|
|
634
|
+
Help.formatCommand(`${name} completions`, {
|
|
635
|
+
args: b.args,
|
|
636
|
+
description: b.description,
|
|
637
|
+
hideGlobalOptions: true,
|
|
638
|
+
hint: b.hint?.(name),
|
|
639
|
+
}),
|
|
600
640
|
)
|
|
601
641
|
return
|
|
602
642
|
}
|
|
603
|
-
|
|
604
|
-
if (!shell || !['bash', 'fish', 'nushell', 'zsh'].includes(shell)) {
|
|
643
|
+
if (!shells.includes(shell as any)) {
|
|
605
644
|
writeln(
|
|
606
645
|
formatHumanError({
|
|
607
646
|
code: 'INVALID_SHELL',
|
|
608
|
-
message: shell
|
|
609
|
-
? `Unknown shell '${shell}'. Supported: bash, fish, nushell, zsh`
|
|
610
|
-
: `Missing shell argument. Usage: ${name} completions <bash|fish|nushell|zsh>`,
|
|
647
|
+
message: `Unknown shell '${shell}'. Supported: ${shells.join(', ')}`,
|
|
611
648
|
}),
|
|
612
649
|
)
|
|
613
650
|
exit(1)
|
|
614
651
|
return
|
|
615
652
|
}
|
|
616
653
|
const names = [name, ...(options.aliases ?? [])]
|
|
617
|
-
writeln(names.map((n) => Completions.register(shell as
|
|
654
|
+
writeln(names.map((n) => Completions.register(shell as Shell, n)).join('\n'))
|
|
618
655
|
return
|
|
619
656
|
}
|
|
620
657
|
|
|
621
658
|
// skills add: generate skill files and install via `<pm>x skills add` (only when sync is configured)
|
|
622
|
-
const skillsIdx =
|
|
623
|
-
|
|
624
|
-
|
|
659
|
+
const skillsIdx = builtinIdx(filtered, name, 'skills')
|
|
660
|
+
if (skillsIdx !== -1) {
|
|
661
|
+
const skillsSub = filtered[skillsIdx + 1]
|
|
662
|
+
if (skillsSub && skillsSub !== 'add' && skillsSub !== 'list') {
|
|
663
|
+
const suggestion = suggest(skillsSub, ['add', 'list'])
|
|
664
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
665
|
+
const message = `'${skillsSub}' is not a command for '${name} skills'.${didYouMean}`
|
|
666
|
+
const ctaCommands: FormattedCta[] = []
|
|
667
|
+
if (suggestion) {
|
|
668
|
+
const corrected = argv.map((t) => (t === skillsSub ? suggestion : t))
|
|
669
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` })
|
|
670
|
+
}
|
|
671
|
+
ctaCommands.push({
|
|
672
|
+
command: `${name} skills --help`,
|
|
673
|
+
description: 'see all available commands',
|
|
674
|
+
})
|
|
675
|
+
const cta: FormattedCtaBlock = {
|
|
676
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
677
|
+
commands: ctaCommands,
|
|
678
|
+
}
|
|
679
|
+
if (human) {
|
|
680
|
+
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }))
|
|
681
|
+
writeln(formatHumanCta(cta))
|
|
682
|
+
} else writeln(Formatter.format({ code: 'COMMAND_NOT_FOUND', message, cta }, 'toon'))
|
|
683
|
+
exit(1)
|
|
684
|
+
return
|
|
685
|
+
}
|
|
686
|
+
if (!skillsSub) {
|
|
687
|
+
const b = findBuiltin('skills')!
|
|
688
|
+
writeln(formatBuiltinHelp(name, b))
|
|
689
|
+
return
|
|
690
|
+
}
|
|
691
|
+
if (skillsSub === 'list') {
|
|
692
|
+
if (help) {
|
|
693
|
+
const b = findBuiltin('skills')!
|
|
694
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'list'))
|
|
695
|
+
return
|
|
696
|
+
}
|
|
697
|
+
try {
|
|
698
|
+
const result = await SyncSkills.list(name, commands, {
|
|
699
|
+
cwd: options.sync?.cwd,
|
|
700
|
+
depth: options.sync?.depth ?? 1,
|
|
701
|
+
description: options.description,
|
|
702
|
+
include: options.sync?.include,
|
|
703
|
+
rootCommand: options.rootCommand,
|
|
704
|
+
})
|
|
705
|
+
if (result.length === 0) {
|
|
706
|
+
writeln('No skills found.')
|
|
707
|
+
return
|
|
708
|
+
}
|
|
709
|
+
const lines: string[] = []
|
|
710
|
+
const maxLen = Math.max(...result.map((s) => s.name.length))
|
|
711
|
+
for (const s of result) {
|
|
712
|
+
const icon = s.installed ? '✓' : '✗'
|
|
713
|
+
const padding = s.description
|
|
714
|
+
? `${' '.repeat(maxLen - s.name.length)} ${s.description}`
|
|
715
|
+
: ''
|
|
716
|
+
lines.push(` ${icon} ${s.name}${padding}`)
|
|
717
|
+
}
|
|
718
|
+
const installedCount = result.filter((s) => s.installed).length
|
|
719
|
+
lines.push('')
|
|
720
|
+
lines.push(
|
|
721
|
+
`${result.length} skill${result.length === 1 ? '' : 's'} (${installedCount} installed)`,
|
|
722
|
+
)
|
|
723
|
+
writeln(lines.join('\n'))
|
|
724
|
+
} catch (err) {
|
|
725
|
+
writeln(
|
|
726
|
+
Formatter.format(
|
|
727
|
+
{
|
|
728
|
+
code: 'LIST_SKILLS_FAILED',
|
|
729
|
+
message: err instanceof Error ? err.message : String(err),
|
|
730
|
+
},
|
|
731
|
+
formatExplicit ? formatFlag : 'toon',
|
|
732
|
+
),
|
|
733
|
+
)
|
|
734
|
+
exit(1)
|
|
735
|
+
}
|
|
736
|
+
return
|
|
737
|
+
}
|
|
625
738
|
if (help) {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
`${name} skills add — Sync skill files to your agent`,
|
|
629
|
-
'',
|
|
630
|
-
`Usage: ${name} skills add [options]`,
|
|
631
|
-
'',
|
|
632
|
-
'Options:',
|
|
633
|
-
' --depth <number> Grouping depth for skill files (default: 1)',
|
|
634
|
-
' --no-global Install to project instead of globally',
|
|
635
|
-
].join('\n'),
|
|
636
|
-
)
|
|
739
|
+
const b = findBuiltin('skills')!
|
|
740
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'))
|
|
637
741
|
return
|
|
638
742
|
}
|
|
639
743
|
const rest = filtered.slice(skillsIdx + 2)
|
|
@@ -654,11 +758,11 @@ async function serveImpl(
|
|
|
654
758
|
description: options.description,
|
|
655
759
|
global,
|
|
656
760
|
include: options.sync?.include,
|
|
761
|
+
rootCommand: options.rootCommand,
|
|
657
762
|
})
|
|
658
763
|
stdout('\r\x1b[K')
|
|
659
764
|
const lines: string[] = []
|
|
660
|
-
const skillLabel = (s: (typeof result.skills)[number]) =>
|
|
661
|
-
s.external || s.name === name ? s.name : `${name}-${s.name}`
|
|
765
|
+
const skillLabel = (s: (typeof result.skills)[number]) => s.name
|
|
662
766
|
const maxLen = Math.max(...result.skills.map((s) => skillLabel(s).length))
|
|
663
767
|
for (const s of result.skills) {
|
|
664
768
|
const label = skillLabel(s)
|
|
@@ -678,9 +782,9 @@ async function serveImpl(
|
|
|
678
782
|
lines.push('')
|
|
679
783
|
lines.push(`Run \`${name} --help\` to see the full command reference.`)
|
|
680
784
|
writeln(lines.join('\n'))
|
|
681
|
-
if (
|
|
785
|
+
if (fullOutput || formatExplicit) {
|
|
682
786
|
const output: Record<string, unknown> = { skills: result.paths }
|
|
683
|
-
if (
|
|
787
|
+
if (fullOutput && result.agents.length > 0) output.agents = result.agents
|
|
684
788
|
writeln(Formatter.format(output, formatExplicit ? formatFlag : 'toon'))
|
|
685
789
|
}
|
|
686
790
|
} catch (err) {
|
|
@@ -696,21 +800,38 @@ async function serveImpl(
|
|
|
696
800
|
}
|
|
697
801
|
|
|
698
802
|
// mcp add: register CLI as MCP server via `npx add-mcp`
|
|
699
|
-
const mcpIdx = filtered
|
|
700
|
-
if (mcpIdx !== -1
|
|
803
|
+
const mcpIdx = builtinIdx(filtered, name, 'mcp')
|
|
804
|
+
if (mcpIdx !== -1) {
|
|
805
|
+
const mcpSub = filtered[mcpIdx + 1]
|
|
806
|
+
if (mcpSub && mcpSub !== 'add') {
|
|
807
|
+
const suggestion = suggest(mcpSub, ['add'])
|
|
808
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
809
|
+
const message = `'${mcpSub}' is not a command for '${name} mcp'.${didYouMean}`
|
|
810
|
+
const ctaCommands: FormattedCta[] = []
|
|
811
|
+
if (suggestion) {
|
|
812
|
+
const corrected = argv.map((t) => (t === mcpSub ? suggestion : t))
|
|
813
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` })
|
|
814
|
+
}
|
|
815
|
+
ctaCommands.push({ command: `${name} mcp --help`, description: 'see all available commands' })
|
|
816
|
+
const cta: FormattedCtaBlock = {
|
|
817
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
818
|
+
commands: ctaCommands,
|
|
819
|
+
}
|
|
820
|
+
if (human) {
|
|
821
|
+
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }))
|
|
822
|
+
writeln(formatHumanCta(cta))
|
|
823
|
+
} else writeln(Formatter.format({ code: 'COMMAND_NOT_FOUND', message, cta }, 'toon'))
|
|
824
|
+
exit(1)
|
|
825
|
+
return
|
|
826
|
+
}
|
|
827
|
+
if (!mcpSub) {
|
|
828
|
+
const b = findBuiltin('mcp')!
|
|
829
|
+
writeln(formatBuiltinHelp(name, b))
|
|
830
|
+
return
|
|
831
|
+
}
|
|
701
832
|
if (help) {
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
`${name} mcp add — Register as an MCP server for your agent`,
|
|
705
|
-
'',
|
|
706
|
-
`Usage: ${name} mcp add [options]`,
|
|
707
|
-
'',
|
|
708
|
-
'Options:',
|
|
709
|
-
' -c, --command <cmd> Override the command agents will run (e.g. "pnpm my-cli --mcp")',
|
|
710
|
-
' --no-global Install to project instead of globally',
|
|
711
|
-
' --agent <agent> Target a specific agent (e.g. claude-code, cursor)',
|
|
712
|
-
].join('\n'),
|
|
713
|
-
)
|
|
833
|
+
const b = findBuiltin('mcp')!
|
|
834
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'))
|
|
714
835
|
return
|
|
715
836
|
}
|
|
716
837
|
const rest = filtered.slice(mcpIdx + 2)
|
|
@@ -744,7 +865,7 @@ async function serveImpl(
|
|
|
744
865
|
for (const s of suggestions) lines.push(` "${s}"`)
|
|
745
866
|
}
|
|
746
867
|
writeln(lines.join('\n'))
|
|
747
|
-
if (
|
|
868
|
+
if (fullOutput || formatExplicit)
|
|
748
869
|
writeln(
|
|
749
870
|
Formatter.format(
|
|
750
871
|
{ name, command: result.command, agents: result.agents },
|
|
@@ -782,6 +903,7 @@ async function serveImpl(
|
|
|
782
903
|
Help.formatCommand(name, {
|
|
783
904
|
alias: cmd.alias as Record<string, string> | undefined,
|
|
784
905
|
aliases: options.aliases,
|
|
906
|
+
configFlag,
|
|
785
907
|
description: cmd.description ?? options.description,
|
|
786
908
|
version: options.version,
|
|
787
909
|
args: cmd.args,
|
|
@@ -803,6 +925,7 @@ async function serveImpl(
|
|
|
803
925
|
writeln(
|
|
804
926
|
Help.formatRoot(name, {
|
|
805
927
|
aliases: options.aliases,
|
|
928
|
+
configFlag,
|
|
806
929
|
description: options.description,
|
|
807
930
|
version: options.version,
|
|
808
931
|
commands: collectHelpCommands(commands),
|
|
@@ -851,6 +974,7 @@ async function serveImpl(
|
|
|
851
974
|
Help.formatCommand(name, {
|
|
852
975
|
alias: cmd.alias as Record<string, string> | undefined,
|
|
853
976
|
aliases: options.aliases,
|
|
977
|
+
configFlag,
|
|
854
978
|
description: cmd.description ?? options.description,
|
|
855
979
|
version: options.version,
|
|
856
980
|
args: cmd.args,
|
|
@@ -868,6 +992,7 @@ async function serveImpl(
|
|
|
868
992
|
writeln(
|
|
869
993
|
Help.formatRoot(helpName, {
|
|
870
994
|
aliases: isRoot ? options.aliases : undefined,
|
|
995
|
+
configFlag,
|
|
871
996
|
description: helpDesc,
|
|
872
997
|
version: isRoot ? options.version : undefined,
|
|
873
998
|
commands: collectHelpCommands(helpCmds),
|
|
@@ -886,7 +1011,8 @@ async function serveImpl(
|
|
|
886
1011
|
writeln(
|
|
887
1012
|
Help.formatCommand(commandName, {
|
|
888
1013
|
alias: cmd.alias as Record<string, string> | undefined,
|
|
889
|
-
aliases: isRootCmd ? options.aliases :
|
|
1014
|
+
aliases: isRootCmd ? options.aliases : cmd.aliases,
|
|
1015
|
+
configFlag,
|
|
890
1016
|
description: cmd.description,
|
|
891
1017
|
version: isRootCmd ? options.version : undefined,
|
|
892
1018
|
args: cmd.args,
|
|
@@ -909,6 +1035,7 @@ async function serveImpl(
|
|
|
909
1035
|
if ('help' in resolved) {
|
|
910
1036
|
writeln(
|
|
911
1037
|
Help.formatRoot(`${name} ${resolved.path}`, {
|
|
1038
|
+
configFlag,
|
|
912
1039
|
description: resolved.description,
|
|
913
1040
|
commands: collectHelpCommands(resolved.commands),
|
|
914
1041
|
}),
|
|
@@ -917,7 +1044,9 @@ async function serveImpl(
|
|
|
917
1044
|
}
|
|
918
1045
|
if ('error' in resolved) {
|
|
919
1046
|
const parent = resolved.path ? `${name} ${resolved.path}` : name
|
|
920
|
-
|
|
1047
|
+
const suggestion = suggest(resolved.error, resolved.commands.keys())
|
|
1048
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
1049
|
+
writeln(`Error: '${resolved.error}' is not a command for '${parent}'.${didYouMean}`)
|
|
921
1050
|
exit(1)
|
|
922
1051
|
return
|
|
923
1052
|
}
|
|
@@ -940,6 +1069,7 @@ async function serveImpl(
|
|
|
940
1069
|
if ('help' in resolved) {
|
|
941
1070
|
writeln(
|
|
942
1071
|
Help.formatRoot(`${name} ${resolved.path}`, {
|
|
1072
|
+
configFlag,
|
|
943
1073
|
description: resolved.description,
|
|
944
1074
|
commands: collectHelpCommands(resolved.commands),
|
|
945
1075
|
}),
|
|
@@ -949,21 +1079,22 @@ async function serveImpl(
|
|
|
949
1079
|
|
|
950
1080
|
const start = performance.now()
|
|
951
1081
|
|
|
952
|
-
// Parse root CLI-level options (available to middleware via c.options)
|
|
953
|
-
const rootOptions = options.optionsSchema
|
|
954
|
-
? Parser.parse(filtered, {
|
|
955
|
-
alias: options.rootCommand?.alias as Record<string, string> | undefined,
|
|
956
|
-
options: options.optionsSchema,
|
|
957
|
-
}).options
|
|
958
|
-
: {}
|
|
959
|
-
|
|
960
1082
|
// Resolve effective format: explicit --format/--json → command default → CLI default → toon
|
|
961
1083
|
const resolvedFormat = 'command' in resolved && (resolved as any).command.format
|
|
962
1084
|
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon'
|
|
963
1085
|
|
|
964
|
-
// Fall back to root fetch when no subcommand matches
|
|
1086
|
+
// Fall back to root fetch/command when no subcommand matches,
|
|
1087
|
+
// but only if the token doesn't look like a typo of a known command.
|
|
1088
|
+
const rootFallbackBlocked =
|
|
1089
|
+
'error' in resolved &&
|
|
1090
|
+
!resolved.path &&
|
|
1091
|
+
(() => {
|
|
1092
|
+
const candidates = [...resolved.commands.keys()]
|
|
1093
|
+
for (const b of builtinCommands) candidates.push(b.name)
|
|
1094
|
+
return suggest(resolved.error, candidates) !== undefined
|
|
1095
|
+
})()
|
|
965
1096
|
const effective =
|
|
966
|
-
'error' in resolved && options.rootFetch && !resolved.path
|
|
1097
|
+
'error' in resolved && options.rootFetch && !resolved.path && !rootFallbackBlocked
|
|
967
1098
|
? {
|
|
968
1099
|
fetchGateway: {
|
|
969
1100
|
_fetch: true as const,
|
|
@@ -974,7 +1105,7 @@ async function serveImpl(
|
|
|
974
1105
|
path: name,
|
|
975
1106
|
rest: filtered,
|
|
976
1107
|
}
|
|
977
|
-
: 'error' in resolved && options.rootCommand && !resolved.path
|
|
1108
|
+
: 'error' in resolved && options.rootCommand && !resolved.path && !rootFallbackBlocked
|
|
978
1109
|
? { command: options.rootCommand, path: name, rest: filtered }
|
|
979
1110
|
: resolved
|
|
980
1111
|
|
|
@@ -1008,13 +1139,28 @@ async function serveImpl(
|
|
|
1008
1139
|
function write(output: Output) {
|
|
1009
1140
|
if (filterPaths && output.ok && output.data != null)
|
|
1010
1141
|
output = { ...output, data: Filter.apply(output.data, filterPaths) }
|
|
1142
|
+
if (skillsCta) {
|
|
1143
|
+
const existing = output.meta.cta
|
|
1144
|
+
output = {
|
|
1145
|
+
...output,
|
|
1146
|
+
meta: {
|
|
1147
|
+
...output.meta,
|
|
1148
|
+
cta: existing
|
|
1149
|
+
? {
|
|
1150
|
+
description: existing.description,
|
|
1151
|
+
commands: [...existing.commands, ...skillsCta.commands],
|
|
1152
|
+
}
|
|
1153
|
+
: skillsCta,
|
|
1154
|
+
},
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1011
1157
|
if (tokenCount) {
|
|
1012
1158
|
const base = output.ok ? output.data : output.error
|
|
1013
1159
|
const formatted = base != null ? Formatter.format(base, format) : ''
|
|
1014
1160
|
return writeln(String(estimateTokenCount(formatted)))
|
|
1015
1161
|
}
|
|
1016
1162
|
const cta = output.meta.cta
|
|
1017
|
-
if (human && !
|
|
1163
|
+
if (human && !fullOutput) {
|
|
1018
1164
|
if (output.ok && output.data != null && renderOutput) {
|
|
1019
1165
|
const t = truncate(Formatter.format(output.data, format))
|
|
1020
1166
|
writeln(t.text)
|
|
@@ -1022,7 +1168,7 @@ async function serveImpl(
|
|
|
1022
1168
|
if (cta) writeln(formatHumanCta(cta))
|
|
1023
1169
|
return
|
|
1024
1170
|
}
|
|
1025
|
-
if (
|
|
1171
|
+
if (fullOutput) {
|
|
1026
1172
|
if (tokenLimit != null || tokenOffset != null) {
|
|
1027
1173
|
// Truncate data separately so meta (including nextOffset) is always visible
|
|
1028
1174
|
const dataFormatted =
|
|
@@ -1058,14 +1204,27 @@ async function serveImpl(
|
|
|
1058
1204
|
if ('error' in effective) {
|
|
1059
1205
|
const helpCmd = effective.path ? `${name} ${effective.path} --help` : `${name} --help`
|
|
1060
1206
|
const parent = effective.path ? `${name} ${effective.path}` : name
|
|
1061
|
-
const
|
|
1207
|
+
const candidates = 'commands' in effective ? [...effective.commands.keys()] : []
|
|
1208
|
+
if (!effective.path) for (const b of builtinCommands) candidates.push(b.name)
|
|
1209
|
+
const suggestion = suggest(effective.error, candidates)
|
|
1210
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
1211
|
+
const message = `'${effective.error}' is not a command for '${parent}'.${didYouMean}`
|
|
1212
|
+
const ctaCommands: FormattedCta[] = []
|
|
1213
|
+
if (suggestion) {
|
|
1214
|
+
const corrected = argv.map((t) => (t === effective.error ? suggestion : t))
|
|
1215
|
+
ctaCommands.push({ command: `${name} ${corrected.join(' ')}` })
|
|
1216
|
+
}
|
|
1217
|
+
ctaCommands.push({ command: helpCmd, description: 'see all available commands' })
|
|
1062
1218
|
const cta: FormattedCtaBlock = {
|
|
1063
|
-
description: '
|
|
1064
|
-
commands:
|
|
1219
|
+
description: ctaCommands.length === 1 ? 'Suggested command:' : 'Suggested commands:',
|
|
1220
|
+
commands: ctaCommands,
|
|
1065
1221
|
}
|
|
1066
|
-
if (human && !
|
|
1222
|
+
if (human && !fullOutput) {
|
|
1067
1223
|
writeln(formatHumanError({ code: 'COMMAND_NOT_FOUND', message }))
|
|
1068
|
-
|
|
1224
|
+
const mergedCta = skillsCta
|
|
1225
|
+
? { ...cta, commands: [...cta.commands, ...skillsCta.commands] }
|
|
1226
|
+
: cta
|
|
1227
|
+
writeln(formatHumanCta(mergedCta))
|
|
1069
1228
|
exit(1)
|
|
1070
1229
|
return
|
|
1071
1230
|
}
|
|
@@ -1107,7 +1266,7 @@ async function serveImpl(
|
|
|
1107
1266
|
formatExplicit,
|
|
1108
1267
|
human,
|
|
1109
1268
|
renderOutput,
|
|
1110
|
-
|
|
1269
|
+
fullOutput,
|
|
1111
1270
|
truncate,
|
|
1112
1271
|
write,
|
|
1113
1272
|
writeln,
|
|
@@ -1164,12 +1323,12 @@ async function serveImpl(
|
|
|
1164
1323
|
const mwCtx: MiddlewareContext = {
|
|
1165
1324
|
agent: !human,
|
|
1166
1325
|
command: path,
|
|
1326
|
+
displayName,
|
|
1167
1327
|
env: cliEnv,
|
|
1168
1328
|
error: errorFn,
|
|
1169
1329
|
format,
|
|
1170
1330
|
formatExplicit,
|
|
1171
1331
|
name,
|
|
1172
|
-
options: rootOptions,
|
|
1173
1332
|
set(key: string, value: unknown) {
|
|
1174
1333
|
varsMap[key] = value
|
|
1175
1334
|
},
|
|
@@ -1179,7 +1338,7 @@ async function serveImpl(
|
|
|
1179
1338
|
const handleMwSentinel = (result: unknown) => {
|
|
1180
1339
|
if (!isSentinel(result) || result[sentinel] !== 'error') return
|
|
1181
1340
|
const err = result as ErrorResult
|
|
1182
|
-
const cta = formatCtaBlock(
|
|
1341
|
+
const cta = formatCtaBlock(displayName, err.cta)
|
|
1183
1342
|
write({
|
|
1184
1343
|
ok: false,
|
|
1185
1344
|
error: {
|
|
@@ -1230,212 +1389,151 @@ async function serveImpl(
|
|
|
1230
1389
|
...((command.middleware as MiddlewareHandler[] | undefined) ?? []),
|
|
1231
1390
|
]
|
|
1232
1391
|
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
alias: command.alias as Record<string, string> | undefined,
|
|
1240
|
-
args: command.args,
|
|
1241
|
-
options: command.options,
|
|
1242
|
-
})
|
|
1243
|
-
|
|
1244
|
-
if (human)
|
|
1245
|
-
emitDeprecationWarnings(
|
|
1246
|
-
rest,
|
|
1247
|
-
command.options,
|
|
1248
|
-
command.alias as Record<string, string> | undefined,
|
|
1249
|
-
)
|
|
1250
|
-
|
|
1251
|
-
const env = command.env ? Parser.parseEnv(command.env, envSource) : {}
|
|
1252
|
-
|
|
1253
|
-
const okFn = (data: unknown, meta: { cta?: CtaBlock | undefined } = {}): never => {
|
|
1254
|
-
return { [sentinel]: 'ok', data, cta: meta.cta } as never
|
|
1255
|
-
}
|
|
1256
|
-
const errorFn = (opts: {
|
|
1257
|
-
code: string
|
|
1258
|
-
exitCode?: number | undefined
|
|
1259
|
-
message: string
|
|
1260
|
-
retryable?: boolean | undefined
|
|
1261
|
-
cta?: CtaBlock | undefined
|
|
1262
|
-
}): never => {
|
|
1263
|
-
return { [sentinel]: 'error', ...opts } as never
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
const result = command.run({
|
|
1267
|
-
agent: !human,
|
|
1268
|
-
args,
|
|
1269
|
-
env,
|
|
1270
|
-
error: errorFn,
|
|
1271
|
-
format,
|
|
1272
|
-
formatExplicit,
|
|
1273
|
-
name,
|
|
1274
|
-
ok: okFn,
|
|
1275
|
-
options: parsedOptions,
|
|
1276
|
-
var: varsMap,
|
|
1277
|
-
version: options.version,
|
|
1278
|
-
})
|
|
1392
|
+
if (human)
|
|
1393
|
+
emitDeprecationWarnings(
|
|
1394
|
+
rest,
|
|
1395
|
+
command.options,
|
|
1396
|
+
command.alias as Record<string, string> | undefined,
|
|
1397
|
+
)
|
|
1279
1398
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
human,
|
|
1289
|
-
renderOutput,
|
|
1290
|
-
verbose,
|
|
1291
|
-
truncate,
|
|
1292
|
-
write,
|
|
1293
|
-
writeln,
|
|
1294
|
-
exit,
|
|
1399
|
+
let defaults: Record<string, unknown> | undefined
|
|
1400
|
+
if (configEnabled) {
|
|
1401
|
+
try {
|
|
1402
|
+
defaults = await loadCommandOptionDefaults(name, path, {
|
|
1403
|
+
configDisabled,
|
|
1404
|
+
configPath,
|
|
1405
|
+
files: options.config?.files,
|
|
1406
|
+
loader: options.config?.loader,
|
|
1295
1407
|
})
|
|
1296
|
-
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
const awaited = await result
|
|
1300
|
-
|
|
1301
|
-
if (isSentinel(awaited)) {
|
|
1302
|
-
const cta = formatCtaBlock(name, awaited.cta)
|
|
1303
|
-
if (awaited[sentinel] === 'ok') {
|
|
1304
|
-
write({
|
|
1305
|
-
ok: true,
|
|
1306
|
-
data: awaited.data,
|
|
1307
|
-
meta: {
|
|
1308
|
-
command: path,
|
|
1309
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1310
|
-
...(cta ? { cta } : undefined),
|
|
1311
|
-
},
|
|
1312
|
-
})
|
|
1313
|
-
} else {
|
|
1314
|
-
const err = awaited as ErrorResult
|
|
1315
|
-
write({
|
|
1316
|
-
ok: false,
|
|
1317
|
-
error: {
|
|
1318
|
-
code: err.code,
|
|
1319
|
-
message: err.message,
|
|
1320
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
1321
|
-
},
|
|
1322
|
-
meta: {
|
|
1323
|
-
command: path,
|
|
1324
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1325
|
-
...(cta ? { cta } : undefined),
|
|
1326
|
-
},
|
|
1327
|
-
})
|
|
1328
|
-
exit(err.exitCode ?? 1)
|
|
1329
|
-
}
|
|
1330
|
-
} else {
|
|
1408
|
+
} catch (error) {
|
|
1331
1409
|
write({
|
|
1332
|
-
ok:
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1410
|
+
ok: false,
|
|
1411
|
+
error: {
|
|
1412
|
+
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
1413
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1337
1414
|
},
|
|
1415
|
+
meta: { command: path, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1338
1416
|
})
|
|
1417
|
+
exit(error instanceof IncurError ? (error.exitCode ?? 1) : 1)
|
|
1418
|
+
return
|
|
1339
1419
|
}
|
|
1340
1420
|
}
|
|
1341
1421
|
|
|
1342
|
-
|
|
1343
|
-
|
|
1422
|
+
const result = await Command.execute(command, {
|
|
1423
|
+
agent: !human,
|
|
1424
|
+
argv: rest,
|
|
1425
|
+
defaults,
|
|
1426
|
+
displayName,
|
|
1427
|
+
env: options.envSchema,
|
|
1428
|
+
envSource: options.env,
|
|
1429
|
+
format,
|
|
1430
|
+
formatExplicit,
|
|
1431
|
+
inputOptions: {},
|
|
1432
|
+
middlewares: allMiddleware,
|
|
1433
|
+
name,
|
|
1434
|
+
path,
|
|
1435
|
+
vars: options.vars,
|
|
1436
|
+
version: options.version,
|
|
1437
|
+
})
|
|
1344
1438
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1439
|
+
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1440
|
+
|
|
1441
|
+
// Streaming path — async generator
|
|
1442
|
+
if ('stream' in result) {
|
|
1443
|
+
await handleStreaming(result.stream, {
|
|
1444
|
+
name: displayName,
|
|
1445
|
+
path,
|
|
1446
|
+
start,
|
|
1447
|
+
format,
|
|
1448
|
+
formatExplicit,
|
|
1449
|
+
human,
|
|
1450
|
+
renderOutput,
|
|
1451
|
+
fullOutput,
|
|
1452
|
+
truncate,
|
|
1453
|
+
write,
|
|
1454
|
+
writeln,
|
|
1455
|
+
exit,
|
|
1456
|
+
})
|
|
1457
|
+
return
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
if (result.ok) {
|
|
1461
|
+
const cta = formatCtaBlock(displayName, result.cta as CtaBlock | undefined)
|
|
1462
|
+
write({
|
|
1463
|
+
ok: true,
|
|
1464
|
+
data: result.data,
|
|
1465
|
+
meta: {
|
|
1357
1466
|
command: path,
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
message: err.message,
|
|
1379
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
1380
|
-
},
|
|
1381
|
-
meta: {
|
|
1382
|
-
command: path,
|
|
1383
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1384
|
-
...(cta ? { cta } : undefined),
|
|
1385
|
-
},
|
|
1386
|
-
})
|
|
1387
|
-
exit(err.exitCode ?? 1)
|
|
1388
|
-
}
|
|
1389
|
-
const composed = allMiddleware.reduceRight(
|
|
1390
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1391
|
-
handleMwSentinel(await mw(mwCtx, next))
|
|
1392
|
-
},
|
|
1393
|
-
runCommand,
|
|
1467
|
+
duration,
|
|
1468
|
+
...(cta ? { cta } : undefined),
|
|
1469
|
+
},
|
|
1470
|
+
})
|
|
1471
|
+
} else {
|
|
1472
|
+
const cta = formatCtaBlock(displayName, result.cta as CtaBlock | undefined)
|
|
1473
|
+
|
|
1474
|
+
if (human && !formatExplicit && result.error.fieldErrors) {
|
|
1475
|
+
writeln(
|
|
1476
|
+
formatHumanValidationError(
|
|
1477
|
+
displayName,
|
|
1478
|
+
path,
|
|
1479
|
+
command,
|
|
1480
|
+
new ValidationError({
|
|
1481
|
+
message: result.error.message,
|
|
1482
|
+
fieldErrors: result.error.fieldErrors,
|
|
1483
|
+
}),
|
|
1484
|
+
options.env,
|
|
1485
|
+
configFlag,
|
|
1486
|
+
),
|
|
1394
1487
|
)
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
await runCommand()
|
|
1488
|
+
exit(1)
|
|
1489
|
+
return
|
|
1398
1490
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1491
|
+
|
|
1492
|
+
write({
|
|
1401
1493
|
ok: false,
|
|
1402
1494
|
error: {
|
|
1403
|
-
code:
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1410
|
-
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
1411
|
-
...(error instanceof ValidationError ? { fieldErrors: error.fieldErrors } : undefined),
|
|
1495
|
+
code: result.error.code,
|
|
1496
|
+
message: result.error.message,
|
|
1497
|
+
...(result.error.retryable !== undefined
|
|
1498
|
+
? { retryable: result.error.retryable }
|
|
1499
|
+
: undefined),
|
|
1500
|
+
...(result.error.fieldErrors ? { fieldErrors: result.error.fieldErrors } : undefined),
|
|
1412
1501
|
},
|
|
1413
1502
|
meta: {
|
|
1414
1503
|
command: path,
|
|
1415
|
-
duration
|
|
1504
|
+
duration,
|
|
1505
|
+
...(cta ? { cta } : undefined),
|
|
1416
1506
|
},
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
if (human && !formatExplicit && error instanceof ValidationError) {
|
|
1420
|
-
writeln(formatHumanValidationError(name, path, command, error, options.env))
|
|
1421
|
-
exit(1)
|
|
1422
|
-
return
|
|
1423
|
-
}
|
|
1424
|
-
|
|
1425
|
-
write(errorOutput)
|
|
1426
|
-
exit(error instanceof IncurError ? (error.exitCode ?? 1) : 1)
|
|
1507
|
+
})
|
|
1508
|
+
exit(result.exitCode ?? 1)
|
|
1427
1509
|
}
|
|
1428
1510
|
}
|
|
1429
1511
|
|
|
1430
1512
|
/** @internal Options for fetchImpl. */
|
|
1431
1513
|
declare namespace fetchImpl {
|
|
1432
1514
|
type Options = {
|
|
1515
|
+
/** CLI-level env schema. */
|
|
1516
|
+
envSchema?: z.ZodObject<any> | undefined
|
|
1517
|
+
/** Group-level middleware collected during command resolution. */
|
|
1518
|
+
groupMiddlewares?: MiddlewareHandler[] | undefined
|
|
1433
1519
|
mcpHandler?:
|
|
1434
|
-
| ((
|
|
1520
|
+
| ((
|
|
1521
|
+
req: Request,
|
|
1522
|
+
commands: Map<string, CommandEntry>,
|
|
1523
|
+
mcpOptions?: {
|
|
1524
|
+
middlewares?: MiddlewareHandler[] | undefined
|
|
1525
|
+
env?: z.ZodObject<any> | undefined
|
|
1526
|
+
vars?: z.ZodObject<any> | undefined
|
|
1527
|
+
},
|
|
1528
|
+
) => Promise<Response>)
|
|
1435
1529
|
| undefined
|
|
1436
1530
|
middlewares?: MiddlewareHandler[] | undefined
|
|
1531
|
+
/** CLI name. */
|
|
1532
|
+
name?: string | undefined
|
|
1437
1533
|
rootCommand?: CommandDefinition<any, any, any> | undefined
|
|
1438
1534
|
vars?: z.ZodObject<any> | undefined
|
|
1535
|
+
/** CLI version string. */
|
|
1536
|
+
version?: string | undefined
|
|
1439
1537
|
}
|
|
1440
1538
|
}
|
|
1441
1539
|
|
|
@@ -1443,11 +1541,18 @@ declare namespace fetchImpl {
|
|
|
1443
1541
|
function createMcpHttpHandler(name: string, version: string) {
|
|
1444
1542
|
let transport: any
|
|
1445
1543
|
|
|
1446
|
-
return async (
|
|
1544
|
+
return async (
|
|
1545
|
+
req: Request,
|
|
1546
|
+
commands: Map<string, CommandEntry>,
|
|
1547
|
+
mcpOptions?: {
|
|
1548
|
+
middlewares?: MiddlewareHandler[] | undefined
|
|
1549
|
+
env?: z.ZodObject<any> | undefined
|
|
1550
|
+
vars?: z.ZodObject<any> | undefined
|
|
1551
|
+
},
|
|
1552
|
+
): Promise<Response> => {
|
|
1447
1553
|
if (!transport) {
|
|
1448
|
-
const { McpServer } =
|
|
1449
|
-
|
|
1450
|
-
await import('@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js')
|
|
1554
|
+
const { McpServer, WebStandardStreamableHTTPServerTransport } =
|
|
1555
|
+
await import('@modelcontextprotocol/server')
|
|
1451
1556
|
|
|
1452
1557
|
const server = new McpServer({ name, version })
|
|
1453
1558
|
|
|
@@ -1462,11 +1567,17 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1462
1567
|
tool.name,
|
|
1463
1568
|
{
|
|
1464
1569
|
...(tool.description ? { description: tool.description } : undefined),
|
|
1465
|
-
...(hasInput ? { inputSchema: mergedShape } : undefined),
|
|
1570
|
+
...(hasInput ? { inputSchema: z.object(mergedShape) } : undefined),
|
|
1466
1571
|
},
|
|
1467
1572
|
async (...callArgs: any[]) => {
|
|
1468
1573
|
const params = hasInput ? (callArgs[0] as Record<string, unknown>) : {}
|
|
1469
|
-
return Mcp.callTool(tool, params
|
|
1574
|
+
return Mcp.callTool(tool, params, {
|
|
1575
|
+
name,
|
|
1576
|
+
version,
|
|
1577
|
+
middlewares: mcpOptions?.middlewares,
|
|
1578
|
+
env: mcpOptions?.env,
|
|
1579
|
+
vars: mcpOptions?.vars,
|
|
1580
|
+
})
|
|
1470
1581
|
},
|
|
1471
1582
|
)
|
|
1472
1583
|
}
|
|
@@ -1495,7 +1606,11 @@ async function fetchImpl(
|
|
|
1495
1606
|
|
|
1496
1607
|
// MCP over HTTP: route /mcp to the MCP transport
|
|
1497
1608
|
if (segments[0] === 'mcp' && segments.length === 1 && options.mcpHandler)
|
|
1498
|
-
return options.mcpHandler(req, commands
|
|
1609
|
+
return options.mcpHandler(req, commands, {
|
|
1610
|
+
middlewares: options.middlewares,
|
|
1611
|
+
env: options.envSchema,
|
|
1612
|
+
vars: options.vars,
|
|
1613
|
+
})
|
|
1499
1614
|
|
|
1500
1615
|
// .well-known/skills/ — Agent Skills Discovery (RFC)
|
|
1501
1616
|
if (
|
|
@@ -1505,7 +1620,7 @@ async function fetchImpl(
|
|
|
1505
1620
|
req.method === 'GET'
|
|
1506
1621
|
) {
|
|
1507
1622
|
const groups = new Map<string, string>()
|
|
1508
|
-
const cmds = collectSkillCommands(commands, [], groups)
|
|
1623
|
+
const cmds = collectSkillCommands(commands, [], groups, options.rootCommand)
|
|
1509
1624
|
|
|
1510
1625
|
// GET /.well-known/skills/index.json
|
|
1511
1626
|
if (segments[2] === 'index.json' && segments.length === 3) {
|
|
@@ -1575,18 +1690,22 @@ async function fetchImpl(
|
|
|
1575
1690
|
|
|
1576
1691
|
const resolved = resolveCommand(commands, segments)
|
|
1577
1692
|
|
|
1578
|
-
if ('error' in resolved)
|
|
1693
|
+
if ('error' in resolved) {
|
|
1694
|
+
const parent = resolved.path ? `${name} ${resolved.path}` : name
|
|
1695
|
+
const suggestion = suggest(resolved.error, resolved.commands.keys())
|
|
1696
|
+
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
1579
1697
|
return jsonResponse(
|
|
1580
1698
|
{
|
|
1581
1699
|
ok: false,
|
|
1582
1700
|
error: {
|
|
1583
1701
|
code: 'COMMAND_NOT_FOUND',
|
|
1584
|
-
message: `'${resolved.error}' is not a command for '${
|
|
1702
|
+
message: `'${resolved.error}' is not a command for '${parent}'.${didYouMean}`,
|
|
1585
1703
|
},
|
|
1586
1704
|
meta: { command: resolved.error, duration: `${Math.round(performance.now() - start)}ms` },
|
|
1587
1705
|
},
|
|
1588
1706
|
404,
|
|
1589
1707
|
)
|
|
1708
|
+
}
|
|
1590
1709
|
|
|
1591
1710
|
if ('help' in resolved)
|
|
1592
1711
|
return jsonResponse(
|
|
@@ -1604,7 +1723,11 @@ async function fetchImpl(
|
|
|
1604
1723
|
if ('fetchGateway' in resolved) return resolved.fetchGateway.fetch(req)
|
|
1605
1724
|
|
|
1606
1725
|
const { command, path, rest } = resolved
|
|
1607
|
-
|
|
1726
|
+
const groupMiddlewares = 'middlewares' in resolved ? resolved.middlewares : []
|
|
1727
|
+
return executeCommand(path, command, rest, inputOptions, start, {
|
|
1728
|
+
...options,
|
|
1729
|
+
groupMiddlewares,
|
|
1730
|
+
})
|
|
1608
1731
|
}
|
|
1609
1732
|
|
|
1610
1733
|
/** @internal Executes a resolved command for the fetch handler and returns a JSON Response. */
|
|
@@ -1623,201 +1746,107 @@ async function executeCommand(
|
|
|
1623
1746
|
})
|
|
1624
1747
|
}
|
|
1625
1748
|
|
|
1626
|
-
const
|
|
1627
|
-
|
|
1628
|
-
|
|
1749
|
+
const allMiddleware = [
|
|
1750
|
+
...(options.middlewares ?? []),
|
|
1751
|
+
...((options.groupMiddlewares as MiddlewareHandler[] | undefined) ?? []),
|
|
1752
|
+
...((command.middleware as MiddlewareHandler[] | undefined) ?? []),
|
|
1753
|
+
]
|
|
1629
1754
|
|
|
1630
|
-
const
|
|
1631
|
-
|
|
1632
|
-
|
|
1755
|
+
const result = await Command.execute(command, {
|
|
1756
|
+
agent: true,
|
|
1757
|
+
argv: rest,
|
|
1758
|
+
env: options.envSchema,
|
|
1759
|
+
format: 'json',
|
|
1760
|
+
formatExplicit: true,
|
|
1761
|
+
inputOptions,
|
|
1762
|
+
middlewares: allMiddleware,
|
|
1763
|
+
name: options.name ?? path,
|
|
1764
|
+
parseMode: 'split',
|
|
1765
|
+
path,
|
|
1766
|
+
vars: options.vars,
|
|
1767
|
+
version: options.version,
|
|
1768
|
+
})
|
|
1633
1769
|
|
|
1634
|
-
|
|
1635
|
-
const errorFn = (opts: {
|
|
1636
|
-
code: string
|
|
1637
|
-
message: string
|
|
1638
|
-
exitCode?: number | undefined
|
|
1639
|
-
}): never => ({ [sentinel_]: 'error', ...opts }) as never
|
|
1640
|
-
|
|
1641
|
-
const result = command.run({
|
|
1642
|
-
agent: true,
|
|
1643
|
-
args,
|
|
1644
|
-
env: {},
|
|
1645
|
-
error: errorFn,
|
|
1646
|
-
format: 'json',
|
|
1647
|
-
formatExplicit: true,
|
|
1648
|
-
name: path,
|
|
1649
|
-
ok: okFn,
|
|
1650
|
-
options: parsedOptions,
|
|
1651
|
-
var: varsMap,
|
|
1652
|
-
version: undefined,
|
|
1653
|
-
})
|
|
1770
|
+
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1654
1771
|
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
while (true) {
|
|
1663
|
-
const { value, done } = await result.next()
|
|
1664
|
-
if (done) {
|
|
1665
|
-
returnValue = value
|
|
1666
|
-
break
|
|
1667
|
-
}
|
|
1668
|
-
if (isSentinel(value) && (value as any)[sentinel] === 'error') {
|
|
1669
|
-
const tagged = value as any
|
|
1670
|
-
controller.enqueue(
|
|
1671
|
-
encoder.encode(
|
|
1672
|
-
JSON.stringify({
|
|
1673
|
-
type: 'error',
|
|
1674
|
-
ok: false,
|
|
1675
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1676
|
-
}) + '\n',
|
|
1677
|
-
),
|
|
1678
|
-
)
|
|
1679
|
-
controller.close()
|
|
1680
|
-
return
|
|
1681
|
-
}
|
|
1682
|
-
controller.enqueue(
|
|
1683
|
-
encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'),
|
|
1684
|
-
)
|
|
1685
|
-
}
|
|
1686
|
-
const meta: Record<string, unknown> = { command: path }
|
|
1687
|
-
if (isSentinel(returnValue) && (returnValue as any)[sentinel] === 'error') {
|
|
1688
|
-
const tagged = returnValue as any
|
|
1689
|
-
controller.enqueue(
|
|
1690
|
-
encoder.encode(
|
|
1691
|
-
JSON.stringify({
|
|
1692
|
-
type: 'error',
|
|
1693
|
-
ok: false,
|
|
1694
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1695
|
-
}) + '\n',
|
|
1696
|
-
),
|
|
1697
|
-
)
|
|
1698
|
-
} else {
|
|
1699
|
-
controller.enqueue(
|
|
1700
|
-
encoder.encode(JSON.stringify({ type: 'done', ok: true, meta }) + '\n'),
|
|
1701
|
-
)
|
|
1702
|
-
}
|
|
1703
|
-
} catch (error) {
|
|
1772
|
+
// Streaming path — async generator → NDJSON response
|
|
1773
|
+
if ('stream' in result) {
|
|
1774
|
+
const stream = new ReadableStream({
|
|
1775
|
+
async start(controller) {
|
|
1776
|
+
const encoder = new TextEncoder()
|
|
1777
|
+
try {
|
|
1778
|
+
for await (const value of result.stream) {
|
|
1704
1779
|
controller.enqueue(
|
|
1705
|
-
encoder.encode(
|
|
1706
|
-
JSON.stringify({
|
|
1707
|
-
type: 'error',
|
|
1708
|
-
ok: false,
|
|
1709
|
-
error: {
|
|
1710
|
-
code: 'UNKNOWN',
|
|
1711
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1712
|
-
},
|
|
1713
|
-
}) + '\n',
|
|
1714
|
-
),
|
|
1780
|
+
encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'),
|
|
1715
1781
|
)
|
|
1716
1782
|
}
|
|
1717
|
-
controller.
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
)
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
response = jsonResponse({ ok: true, data: awaited, meta: { command: path, duration } }, 200)
|
|
1783
|
+
controller.enqueue(
|
|
1784
|
+
encoder.encode(
|
|
1785
|
+
JSON.stringify({
|
|
1786
|
+
type: 'done',
|
|
1787
|
+
ok: true,
|
|
1788
|
+
meta: { command: path },
|
|
1789
|
+
}) + '\n',
|
|
1790
|
+
),
|
|
1791
|
+
)
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
controller.enqueue(
|
|
1794
|
+
encoder.encode(
|
|
1795
|
+
JSON.stringify({
|
|
1796
|
+
type: 'error',
|
|
1797
|
+
ok: false,
|
|
1798
|
+
error: {
|
|
1799
|
+
code: 'UNKNOWN',
|
|
1800
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1801
|
+
},
|
|
1802
|
+
}) + '\n',
|
|
1803
|
+
),
|
|
1804
|
+
)
|
|
1805
|
+
}
|
|
1806
|
+
controller.close()
|
|
1807
|
+
},
|
|
1808
|
+
})
|
|
1809
|
+
return new Response(stream, {
|
|
1810
|
+
status: 200,
|
|
1811
|
+
headers: { 'content-type': 'application/x-ndjson' },
|
|
1812
|
+
})
|
|
1750
1813
|
}
|
|
1751
1814
|
|
|
1752
|
-
|
|
1753
|
-
const
|
|
1754
|
-
if (allMiddleware.length > 0) {
|
|
1755
|
-
const errorFn = (opts: {
|
|
1756
|
-
code: string
|
|
1757
|
-
message: string
|
|
1758
|
-
exitCode?: number | undefined
|
|
1759
|
-
}): never => {
|
|
1760
|
-
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1761
|
-
response = jsonResponse(
|
|
1762
|
-
{
|
|
1763
|
-
ok: false,
|
|
1764
|
-
error: { code: opts.code, message: opts.message },
|
|
1765
|
-
meta: { command: path, duration },
|
|
1766
|
-
},
|
|
1767
|
-
500,
|
|
1768
|
-
)
|
|
1769
|
-
return undefined as never
|
|
1770
|
-
}
|
|
1771
|
-
const mwCtx: MiddlewareContext = {
|
|
1772
|
-
agent: true,
|
|
1773
|
-
command: path,
|
|
1774
|
-
env: {},
|
|
1775
|
-
error: errorFn,
|
|
1776
|
-
format: 'json',
|
|
1777
|
-
formatExplicit: true,
|
|
1778
|
-
name: path,
|
|
1779
|
-
options: {},
|
|
1780
|
-
set(key: string, value: unknown) {
|
|
1781
|
-
varsMap[key] = value
|
|
1782
|
-
},
|
|
1783
|
-
var: varsMap,
|
|
1784
|
-
version: undefined,
|
|
1785
|
-
}
|
|
1786
|
-
const composed = allMiddleware.reduceRight(
|
|
1787
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1788
|
-
await mw(mwCtx, next)
|
|
1789
|
-
},
|
|
1790
|
-
runCommand,
|
|
1791
|
-
)
|
|
1792
|
-
await composed()
|
|
1793
|
-
} else {
|
|
1794
|
-
await runCommand()
|
|
1795
|
-
}
|
|
1796
|
-
} catch (error) {
|
|
1797
|
-
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1798
|
-
if (error instanceof ValidationError)
|
|
1799
|
-
return jsonResponse(
|
|
1800
|
-
{
|
|
1801
|
-
ok: false,
|
|
1802
|
-
error: { code: 'VALIDATION_ERROR', message: error.message },
|
|
1803
|
-
meta: { command: path, duration },
|
|
1804
|
-
},
|
|
1805
|
-
400,
|
|
1806
|
-
)
|
|
1815
|
+
if (!result.ok) {
|
|
1816
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta as CtaBlock | undefined)
|
|
1807
1817
|
return jsonResponse(
|
|
1808
1818
|
{
|
|
1809
1819
|
ok: false,
|
|
1810
1820
|
error: {
|
|
1811
|
-
code: error
|
|
1812
|
-
message: error
|
|
1821
|
+
code: result.error.code,
|
|
1822
|
+
message: result.error.message,
|
|
1823
|
+
...(result.error.retryable !== undefined
|
|
1824
|
+
? { retryable: result.error.retryable }
|
|
1825
|
+
: undefined),
|
|
1826
|
+
},
|
|
1827
|
+
meta: {
|
|
1828
|
+
command: path,
|
|
1829
|
+
duration,
|
|
1830
|
+
...(cta ? { cta } : undefined),
|
|
1813
1831
|
},
|
|
1814
|
-
meta: { command: path, duration },
|
|
1815
1832
|
},
|
|
1816
|
-
500,
|
|
1833
|
+
result.error.code === 'VALIDATION_ERROR' ? 400 : 500,
|
|
1817
1834
|
)
|
|
1818
1835
|
}
|
|
1819
1836
|
|
|
1820
|
-
|
|
1837
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta as CtaBlock | undefined)
|
|
1838
|
+
return jsonResponse(
|
|
1839
|
+
{
|
|
1840
|
+
ok: true,
|
|
1841
|
+
data: result.data,
|
|
1842
|
+
meta: {
|
|
1843
|
+
command: path,
|
|
1844
|
+
duration,
|
|
1845
|
+
...(cta ? { cta } : undefined),
|
|
1846
|
+
},
|
|
1847
|
+
},
|
|
1848
|
+
200,
|
|
1849
|
+
)
|
|
1821
1850
|
}
|
|
1822
1851
|
|
|
1823
1852
|
/** @internal Formats a validation error for TTY with usage hint. */
|
|
@@ -1827,6 +1856,7 @@ function formatHumanValidationError(
|
|
|
1827
1856
|
command: CommandDefinition<any, any, any>,
|
|
1828
1857
|
error: ValidationError,
|
|
1829
1858
|
envSource?: Record<string, string | undefined>,
|
|
1859
|
+
configFlag?: string,
|
|
1830
1860
|
): string {
|
|
1831
1861
|
const lines: string[] = []
|
|
1832
1862
|
for (const fe of error.fieldErrors) lines.push(`Error: missing required argument <${fe.path}>`)
|
|
@@ -1835,6 +1865,7 @@ function formatHumanValidationError(
|
|
|
1835
1865
|
lines.push(
|
|
1836
1866
|
Help.formatCommand(path === cli ? cli : `${cli} ${path}`, {
|
|
1837
1867
|
alias: command.alias as Record<string, string> | undefined,
|
|
1868
|
+
configFlag,
|
|
1838
1869
|
description: command.description,
|
|
1839
1870
|
args: command.args,
|
|
1840
1871
|
env: command.env,
|
|
@@ -1873,12 +1904,12 @@ function resolveCommand(
|
|
|
1873
1904
|
description?: string | undefined
|
|
1874
1905
|
commands: Map<string, CommandEntry>
|
|
1875
1906
|
}
|
|
1876
|
-
| { error: string; path: string } {
|
|
1907
|
+
| { error: string; path: string; commands: Map<string, CommandEntry>; rest: string[] } {
|
|
1877
1908
|
const [first, ...rest] = tokens
|
|
1878
1909
|
|
|
1879
|
-
if (!first || !commands.has(first)) return { error: first ?? '(none)', path: '' }
|
|
1910
|
+
if (!first || !commands.has(first)) return { error: first ?? '(none)', path: '', commands, rest }
|
|
1880
1911
|
|
|
1881
|
-
let entry = commands.get(first)!
|
|
1912
|
+
let entry = resolveAlias(commands, commands.get(first)!)
|
|
1882
1913
|
const path = [first]
|
|
1883
1914
|
let remaining = rest
|
|
1884
1915
|
let inheritedOutputPolicy: OutputPolicy | undefined
|
|
@@ -1908,10 +1939,16 @@ function resolveCommand(
|
|
|
1908
1939
|
commands: entry.commands,
|
|
1909
1940
|
}
|
|
1910
1941
|
|
|
1911
|
-
const
|
|
1912
|
-
if (!
|
|
1913
|
-
return {
|
|
1942
|
+
const rawChild = entry.commands.get(next)
|
|
1943
|
+
if (!rawChild) {
|
|
1944
|
+
return {
|
|
1945
|
+
error: next,
|
|
1946
|
+
path: path.join(' '),
|
|
1947
|
+
commands: entry.commands,
|
|
1948
|
+
rest: remaining.slice(1),
|
|
1949
|
+
}
|
|
1914
1950
|
}
|
|
1951
|
+
let child = resolveAlias(entry.commands, rawChild)
|
|
1915
1952
|
|
|
1916
1953
|
path.push(next)
|
|
1917
1954
|
remaining = remaining.slice(1)
|
|
@@ -1944,6 +1981,20 @@ declare namespace serveImpl {
|
|
|
1944
1981
|
type Options = serve.Options & {
|
|
1945
1982
|
/** Alternative binary names for this CLI. */
|
|
1946
1983
|
aliases?: string[] | undefined
|
|
1984
|
+
config?:
|
|
1985
|
+
| {
|
|
1986
|
+
flag?: string | undefined
|
|
1987
|
+
files?: string[] | undefined
|
|
1988
|
+
loader?:
|
|
1989
|
+
| ((
|
|
1990
|
+
path: string | undefined,
|
|
1991
|
+
) =>
|
|
1992
|
+
| Record<string, unknown>
|
|
1993
|
+
| undefined
|
|
1994
|
+
| Promise<Record<string, unknown> | undefined>)
|
|
1995
|
+
| undefined
|
|
1996
|
+
}
|
|
1997
|
+
| undefined
|
|
1947
1998
|
description?: string | undefined
|
|
1948
1999
|
/** CLI-level env schema. Parsed before middleware runs. */
|
|
1949
2000
|
envSchema?: z.ZodObject<any> | undefined
|
|
@@ -1951,8 +2002,6 @@ declare namespace serveImpl {
|
|
|
1951
2002
|
format?: Formatter.Format | undefined
|
|
1952
2003
|
/** Middleware handlers registered on the root CLI. */
|
|
1953
2004
|
middlewares?: MiddlewareHandler[] | undefined
|
|
1954
|
-
/** CLI-level options schema. Parsed before middleware runs. */
|
|
1955
|
-
optionsSchema?: z.ZodObject<any> | undefined
|
|
1956
2005
|
/** CLI-level default output policy. */
|
|
1957
2006
|
outputPolicy?: OutputPolicy | undefined
|
|
1958
2007
|
mcp?:
|
|
@@ -1979,9 +2028,11 @@ declare namespace serveImpl {
|
|
|
1979
2028
|
}
|
|
1980
2029
|
}
|
|
1981
2030
|
|
|
1982
|
-
/** @internal Extracts built-in flags (--
|
|
1983
|
-
|
|
1984
|
-
|
|
2031
|
+
/** @internal Extracts built-in flags (--full-output, --format, --json, --llms, --help, --version) from argv. */
|
|
2032
|
+
const validFormats = new Set(['toon', 'json', 'yaml', 'md', 'jsonl'] as const)
|
|
2033
|
+
|
|
2034
|
+
function extractBuiltinFlags(argv: string[], options: extractBuiltinFlags.Options = {}) {
|
|
2035
|
+
let fullOutput = false
|
|
1985
2036
|
let llms = false
|
|
1986
2037
|
let llmsFull = false
|
|
1987
2038
|
let mcp = false
|
|
@@ -1990,15 +2041,21 @@ function extractBuiltinFlags(argv: string[]) {
|
|
|
1990
2041
|
let schema = false
|
|
1991
2042
|
let format: Formatter.Format = 'toon'
|
|
1992
2043
|
let formatExplicit = false
|
|
2044
|
+
let configPath: string | undefined
|
|
2045
|
+
let configDisabled = false
|
|
1993
2046
|
let filterOutput: string | undefined
|
|
1994
2047
|
let tokenLimit: number | undefined
|
|
1995
2048
|
let tokenOffset: number | undefined
|
|
1996
2049
|
let tokenCount = false
|
|
1997
2050
|
const rest: string[] = []
|
|
1998
2051
|
|
|
2052
|
+
const cfgFlag = options.configFlag ? `--${options.configFlag}` : undefined
|
|
2053
|
+
const cfgFlagEq = options.configFlag ? `--${options.configFlag}=` : undefined
|
|
2054
|
+
const noCfgFlag = options.configFlag ? `--no-${options.configFlag}` : undefined
|
|
2055
|
+
|
|
1999
2056
|
for (let i = 0; i < argv.length; i++) {
|
|
2000
2057
|
const token = argv[i]!
|
|
2001
|
-
if (token === '--
|
|
2058
|
+
if (token === '--full-output') fullOutput = true
|
|
2002
2059
|
else if (token === '--llms') llms = true
|
|
2003
2060
|
else if (token === '--llms-full') llmsFull = true
|
|
2004
2061
|
else if (token === '--mcp') mcp = true
|
|
@@ -2009,26 +2066,54 @@ function extractBuiltinFlags(argv: string[]) {
|
|
|
2009
2066
|
format = 'json'
|
|
2010
2067
|
formatExplicit = true
|
|
2011
2068
|
} else if (token === '--format' && argv[i + 1]) {
|
|
2069
|
+
if (!validFormats.has(argv[i + 1]! as any))
|
|
2070
|
+
throw new ParseError({
|
|
2071
|
+
message: `Invalid format: "${argv[i + 1]}". Expected one of: ${[...validFormats].join(', ')}`,
|
|
2072
|
+
})
|
|
2012
2073
|
format = argv[i + 1] as Formatter.Format
|
|
2013
2074
|
formatExplicit = true
|
|
2014
2075
|
i++
|
|
2076
|
+
} else if (cfgFlag && token === cfgFlag) {
|
|
2077
|
+
const value = argv[i + 1]
|
|
2078
|
+
if (value === undefined)
|
|
2079
|
+
throw new ParseError({ message: `Missing value for flag: ${cfgFlag}` })
|
|
2080
|
+
configPath = value
|
|
2081
|
+
configDisabled = false
|
|
2082
|
+
i++
|
|
2083
|
+
} else if (cfgFlagEq && token.startsWith(cfgFlagEq)) {
|
|
2084
|
+
const value = token.slice(cfgFlagEq.length)
|
|
2085
|
+
if (value.length === 0)
|
|
2086
|
+
throw new ParseError({ message: `Missing value for flag: ${cfgFlag}` })
|
|
2087
|
+
configPath = value
|
|
2088
|
+
configDisabled = false
|
|
2089
|
+
} else if (noCfgFlag && token === noCfgFlag) {
|
|
2090
|
+
configPath = undefined
|
|
2091
|
+
configDisabled = true
|
|
2015
2092
|
} else if (token === '--filter-output' && argv[i + 1]) {
|
|
2016
2093
|
filterOutput = argv[i + 1]!
|
|
2017
2094
|
i++
|
|
2018
2095
|
} else if (token === '--token-limit' && argv[i + 1]) {
|
|
2019
|
-
|
|
2096
|
+
const n = Number(argv[i + 1])
|
|
2097
|
+
if (!Number.isFinite(n) || argv[i + 1]!.trim() === '')
|
|
2098
|
+
throw new ParseError({ message: `Invalid value for --token-limit: "${argv[i + 1]}"` })
|
|
2099
|
+
tokenLimit = n
|
|
2020
2100
|
i++
|
|
2021
2101
|
} else if (token === '--token-offset' && argv[i + 1]) {
|
|
2022
|
-
|
|
2102
|
+
const n = Number(argv[i + 1])
|
|
2103
|
+
if (!Number.isFinite(n) || argv[i + 1]!.trim() === '')
|
|
2104
|
+
throw new ParseError({ message: `Invalid value for --token-offset: "${argv[i + 1]}"` })
|
|
2105
|
+
tokenOffset = n
|
|
2023
2106
|
i++
|
|
2024
2107
|
} else if (token === '--token-count') tokenCount = true
|
|
2025
2108
|
else rest.push(token)
|
|
2026
2109
|
}
|
|
2027
2110
|
|
|
2028
2111
|
return {
|
|
2029
|
-
|
|
2112
|
+
fullOutput,
|
|
2030
2113
|
format,
|
|
2031
2114
|
formatExplicit,
|
|
2115
|
+
configPath,
|
|
2116
|
+
configDisabled,
|
|
2032
2117
|
filterOutput,
|
|
2033
2118
|
tokenLimit,
|
|
2034
2119
|
tokenOffset,
|
|
@@ -2043,17 +2128,191 @@ function extractBuiltinFlags(argv: string[]) {
|
|
|
2043
2128
|
}
|
|
2044
2129
|
}
|
|
2045
2130
|
|
|
2131
|
+
declare namespace extractBuiltinFlags {
|
|
2132
|
+
type Options = {
|
|
2133
|
+
configFlag?: string | undefined
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
/** @internal Loads config-backed option defaults for the active command. */
|
|
2138
|
+
async function loadCommandOptionDefaults(
|
|
2139
|
+
cli: string,
|
|
2140
|
+
path: string,
|
|
2141
|
+
options: loadCommandOptionDefaults.Options = {},
|
|
2142
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
2143
|
+
if (options.configDisabled) return undefined
|
|
2144
|
+
|
|
2145
|
+
const { loader } = options
|
|
2146
|
+
|
|
2147
|
+
// Resolve the target file path
|
|
2148
|
+
let targetPath: string | undefined
|
|
2149
|
+
if (options.configPath) {
|
|
2150
|
+
targetPath = resolveConfigPath(options.configPath)
|
|
2151
|
+
} else {
|
|
2152
|
+
const searchPaths = options.files ?? [`${cli}.json`]
|
|
2153
|
+
targetPath = await findFirstExisting(searchPaths)
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// Load and parse the config
|
|
2157
|
+
let parsed: Record<string, unknown>
|
|
2158
|
+
if (loader) {
|
|
2159
|
+
const result = await loader(targetPath)
|
|
2160
|
+
if (result === undefined) return undefined
|
|
2161
|
+
if (!isRecord(result))
|
|
2162
|
+
throw new ParseError({ message: 'Config loader must return a plain object or undefined' })
|
|
2163
|
+
parsed = result
|
|
2164
|
+
} else {
|
|
2165
|
+
if (!targetPath) return undefined
|
|
2166
|
+
const result = await readJsonConfig(targetPath, !!options.configPath)
|
|
2167
|
+
if (!result) return undefined
|
|
2168
|
+
parsed = result
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
// Extract the command section from the config tree
|
|
2172
|
+
return extractCommandSection(parsed, cli, path)
|
|
2173
|
+
}
|
|
2174
|
+
|
|
2175
|
+
declare namespace loadCommandOptionDefaults {
|
|
2176
|
+
type Options = {
|
|
2177
|
+
configDisabled?: boolean | undefined
|
|
2178
|
+
configPath?: string | undefined
|
|
2179
|
+
files?: string[] | undefined
|
|
2180
|
+
loader?:
|
|
2181
|
+
| ((
|
|
2182
|
+
path: string | undefined,
|
|
2183
|
+
) => Record<string, unknown> | undefined | Promise<Record<string, unknown> | undefined>)
|
|
2184
|
+
| undefined
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
/** @internal Resolves a config file path, expanding `~` to home dir. */
|
|
2189
|
+
function resolveConfigPath(filePath: string): string {
|
|
2190
|
+
if (filePath.startsWith('~/') || filePath === '~') {
|
|
2191
|
+
return path.join(os.homedir(), filePath.slice(1))
|
|
2192
|
+
}
|
|
2193
|
+
return path.resolve(process.cwd(), filePath)
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
/** @internal Returns the first readable file from a list of paths, or `undefined`. */
|
|
2197
|
+
async function findFirstExisting(paths: string[]): Promise<string | undefined> {
|
|
2198
|
+
for (const p of paths) {
|
|
2199
|
+
const resolved = resolveConfigPath(p)
|
|
2200
|
+
try {
|
|
2201
|
+
await fs.access(resolved, fs.constants.R_OK)
|
|
2202
|
+
return resolved
|
|
2203
|
+
} catch {}
|
|
2204
|
+
}
|
|
2205
|
+
return undefined
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
/** @internal Reads and parses a JSON config file. */
|
|
2209
|
+
async function readJsonConfig(
|
|
2210
|
+
targetPath: string,
|
|
2211
|
+
explicit: boolean,
|
|
2212
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
2213
|
+
let raw: string
|
|
2214
|
+
try {
|
|
2215
|
+
raw = await fs.readFile(targetPath, 'utf8')
|
|
2216
|
+
} catch (error) {
|
|
2217
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
2218
|
+
if (explicit) throw new ParseError({ message: `Config file not found: ${targetPath}` })
|
|
2219
|
+
return undefined
|
|
2220
|
+
}
|
|
2221
|
+
throw error
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
let parsed: unknown
|
|
2225
|
+
try {
|
|
2226
|
+
parsed = JSON.parse(raw)
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
throw new ParseError({
|
|
2229
|
+
message: `Invalid JSON config file: ${targetPath}`,
|
|
2230
|
+
cause: error instanceof Error ? error : undefined,
|
|
2231
|
+
})
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
if (!isRecord(parsed))
|
|
2235
|
+
throw new ParseError({
|
|
2236
|
+
message: `Invalid config file: expected a top-level object in ${targetPath}`,
|
|
2237
|
+
})
|
|
2238
|
+
return parsed
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
/** @internal Walks the nested config tree to extract option defaults for a command path. */
|
|
2242
|
+
function extractCommandSection(
|
|
2243
|
+
parsed: Record<string, unknown>,
|
|
2244
|
+
cli: string,
|
|
2245
|
+
path: string,
|
|
2246
|
+
): Record<string, unknown> | undefined {
|
|
2247
|
+
const segments = path === cli ? [] : path.split(' ')
|
|
2248
|
+
let node: unknown = parsed
|
|
2249
|
+
for (const seg of segments) {
|
|
2250
|
+
if (!isRecord(node)) return undefined
|
|
2251
|
+
const commands = node.commands
|
|
2252
|
+
if (!isRecord(commands)) return undefined
|
|
2253
|
+
node = commands[seg]
|
|
2254
|
+
if (node === undefined) return undefined
|
|
2255
|
+
}
|
|
2256
|
+
if (!isRecord(node))
|
|
2257
|
+
throw new ParseError({
|
|
2258
|
+
message: `Invalid config section for '${path}': expected an object`,
|
|
2259
|
+
})
|
|
2260
|
+
|
|
2261
|
+
const options = node.options
|
|
2262
|
+
if (options === undefined) return undefined
|
|
2263
|
+
if (!isRecord(options))
|
|
2264
|
+
throw new ParseError({
|
|
2265
|
+
message: `Invalid config 'options' for '${path}': expected an object`,
|
|
2266
|
+
})
|
|
2267
|
+
return Object.keys(options).length > 0 ? options : undefined
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2046
2270
|
/** @internal Collects immediate child commands/groups for help output. */
|
|
2047
2271
|
function collectHelpCommands(
|
|
2048
2272
|
commands: Map<string, CommandEntry>,
|
|
2049
2273
|
): { name: string; description?: string | undefined }[] {
|
|
2050
2274
|
const result: { name: string; description?: string | undefined }[] = []
|
|
2051
2275
|
for (const [name, entry] of commands) {
|
|
2276
|
+
if (isAlias(entry)) continue
|
|
2052
2277
|
result.push({ name, description: entry.description })
|
|
2053
2278
|
}
|
|
2054
2279
|
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
2055
2280
|
}
|
|
2056
2281
|
|
|
2282
|
+
/** @internal Finds the index of a builtin command token in the filtered argv. Returns -1 if not found. */
|
|
2283
|
+
function builtinIdx(filtered: string[], cliName: string, builtin: string): number {
|
|
2284
|
+
// e.g. `skills add` or `skill add`
|
|
2285
|
+
if (findBuiltin(filtered[0]!)?.name === builtin) return 0
|
|
2286
|
+
// e.g. `my-cli skills add`
|
|
2287
|
+
if (filtered[0] === cliName && findBuiltin(filtered[1]!)?.name === builtin) return 1
|
|
2288
|
+
// not a match
|
|
2289
|
+
return -1
|
|
2290
|
+
}
|
|
2291
|
+
|
|
2292
|
+
/** @internal Formats group-level help for a built-in command (e.g. `cli skills`). */
|
|
2293
|
+
function formatBuiltinHelp(cli: string, builtin: (typeof builtinCommands)[number]): string {
|
|
2294
|
+
return Help.formatRoot(`${cli} ${builtin.name}`, {
|
|
2295
|
+
aliases: builtin.aliases,
|
|
2296
|
+
description: builtin.description,
|
|
2297
|
+
commands: builtin.subcommands?.map((s) => ({ name: s.name, description: s.description })),
|
|
2298
|
+
})
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
/** @internal Formats subcommand-level help for a built-in command (e.g. `cli skills add --help`). */
|
|
2302
|
+
function formatBuiltinSubcommandHelp(
|
|
2303
|
+
cli: string,
|
|
2304
|
+
builtin: (typeof builtinCommands)[number],
|
|
2305
|
+
subName: string,
|
|
2306
|
+
): string {
|
|
2307
|
+
const sub = builtin.subcommands?.find((s) => s.name === subName)
|
|
2308
|
+
return Help.formatCommand(`${cli} ${builtin.name} ${subName}`, {
|
|
2309
|
+
alias: sub?.alias,
|
|
2310
|
+
description: sub?.description,
|
|
2311
|
+
hideGlobalOptions: true,
|
|
2312
|
+
options: sub?.options,
|
|
2313
|
+
})
|
|
2314
|
+
}
|
|
2315
|
+
|
|
2057
2316
|
/** @internal Formats help text for a fetch gateway command. */
|
|
2058
2317
|
function formatFetchHelp(name: string, description?: string): string {
|
|
2059
2318
|
const lines: string[] = []
|
|
@@ -2080,7 +2339,11 @@ export type CommandsMap = Record<
|
|
|
2080
2339
|
>
|
|
2081
2340
|
|
|
2082
2341
|
/** @internal Entry stored in a command map — either a leaf definition, a group, or a fetch gateway. */
|
|
2083
|
-
type CommandEntry =
|
|
2342
|
+
type CommandEntry =
|
|
2343
|
+
| CommandDefinition<any, any, any>
|
|
2344
|
+
| InternalGroup
|
|
2345
|
+
| InternalFetchGateway
|
|
2346
|
+
| InternalAlias
|
|
2084
2347
|
|
|
2085
2348
|
/** Controls when output data is displayed. `'all'` displays to both humans and agents. `'agent-only'` suppresses data output in human/TTY mode. */
|
|
2086
2349
|
export type OutputPolicy = 'agent-only' | 'all'
|
|
@@ -2116,6 +2379,27 @@ function isFetchGateway(entry: CommandEntry): entry is InternalFetchGateway {
|
|
|
2116
2379
|
return '_fetch' in entry
|
|
2117
2380
|
}
|
|
2118
2381
|
|
|
2382
|
+
/** @internal An alias entry that points to another command by name. */
|
|
2383
|
+
type InternalAlias = {
|
|
2384
|
+
_alias: true
|
|
2385
|
+
/** The canonical command name this alias resolves to. */
|
|
2386
|
+
target: string
|
|
2387
|
+
}
|
|
2388
|
+
|
|
2389
|
+
/** @internal Type guard for alias entries. */
|
|
2390
|
+
function isAlias(entry: CommandEntry): entry is InternalAlias {
|
|
2391
|
+
return '_alias' in entry
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
/** @internal Follows an alias entry to its canonical target. Returns the entry unchanged if not an alias. */
|
|
2395
|
+
function resolveAlias(
|
|
2396
|
+
commands: Map<string, CommandEntry>,
|
|
2397
|
+
entry: CommandEntry,
|
|
2398
|
+
): Exclude<CommandEntry, InternalAlias> {
|
|
2399
|
+
if (isAlias(entry)) return commands.get(entry.target)! as Exclude<CommandEntry, InternalAlias>
|
|
2400
|
+
return entry
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2119
2403
|
/** @internal Maps CLI instances to their command maps. */
|
|
2120
2404
|
export const toCommands = new WeakMap<Cli, Map<string, CommandEntry>>()
|
|
2121
2405
|
|
|
@@ -2123,11 +2407,20 @@ export const toCommands = new WeakMap<Cli, Map<string, CommandEntry>>()
|
|
|
2123
2407
|
const toMiddlewares = new WeakMap<Cli, MiddlewareHandler[]>()
|
|
2124
2408
|
|
|
2125
2409
|
/** @internal Maps root CLI instances to their command definitions. */
|
|
2126
|
-
const toRootDefinition = new WeakMap<Root, CommandDefinition<any, any, any>>()
|
|
2410
|
+
export const toRootDefinition = new WeakMap<Root, CommandDefinition<any, any, any>>()
|
|
2411
|
+
|
|
2412
|
+
/** @internal Maps CLI instances to their root options schema. */
|
|
2413
|
+
export const toRootOptions = new WeakMap<Cli, z.ZodObject<any>>()
|
|
2414
|
+
|
|
2415
|
+
/** @internal Maps CLI instances to whether config file loading is enabled. */
|
|
2416
|
+
export const toConfigEnabled = new WeakMap<Cli, boolean>()
|
|
2127
2417
|
|
|
2128
2418
|
/** @internal Maps CLI instances to their output policy. */
|
|
2129
2419
|
const toOutputPolicy = new WeakMap<Cli, OutputPolicy>()
|
|
2130
2420
|
|
|
2421
|
+
/** @internal Maps root CLI instances to their command aliases. */
|
|
2422
|
+
const toRootAliases = new WeakMap<Root, string[]>()
|
|
2423
|
+
|
|
2131
2424
|
/** @internal Sentinel symbol for `ok()` and `error()` return values. */
|
|
2132
2425
|
const sentinel = Symbol.for('incur.sentinel')
|
|
2133
2426
|
|
|
@@ -2152,7 +2445,7 @@ type ErrorResult = {
|
|
|
2152
2445
|
type CtaBlock<commands extends CommandsMap = Commands> = {
|
|
2153
2446
|
/** Commands to suggest. */
|
|
2154
2447
|
commands: Cta<commands>[]
|
|
2155
|
-
/** Human-readable label. Defaults to `"Suggested commands:"
|
|
2448
|
+
/** Human-readable label. Defaults to `"Suggested command:"` or `"Suggested commands:"` based on count. */
|
|
2156
2449
|
description?: string | undefined
|
|
2157
2450
|
}
|
|
2158
2451
|
|
|
@@ -2174,8 +2467,9 @@ function formatHumanError(error: {
|
|
|
2174
2467
|
/** @internal Formats a CTA block for human-readable TTY output. */
|
|
2175
2468
|
function formatHumanCta(cta: FormattedCtaBlock): string {
|
|
2176
2469
|
const lines: string[] = ['', cta.description]
|
|
2470
|
+
const maxLen = Math.max(...cta.commands.map((c) => c.command.length))
|
|
2177
2471
|
for (const c of cta.commands) {
|
|
2178
|
-
const desc = c.description ? ` # ${c.description}` : ''
|
|
2472
|
+
const desc = c.description ? ` ${''.padEnd(maxLen - c.command.length)}# ${c.description}` : ''
|
|
2179
2473
|
lines.push(` ${c.command}${desc}`)
|
|
2180
2474
|
}
|
|
2181
2475
|
return lines.join('\n')
|
|
@@ -2190,16 +2484,6 @@ function isSentinel(value: unknown): value is OkResult | ErrorResult {
|
|
|
2190
2484
|
return typeof value === 'object' && value !== null && sentinel in value
|
|
2191
2485
|
}
|
|
2192
2486
|
|
|
2193
|
-
/** @internal Type guard for async generators returned by streaming `run` handlers. */
|
|
2194
|
-
function isAsyncGenerator(value: unknown): value is AsyncGenerator<unknown, unknown, unknown> {
|
|
2195
|
-
return (
|
|
2196
|
-
typeof value === 'object' &&
|
|
2197
|
-
value !== null &&
|
|
2198
|
-
Symbol.asyncIterator in value &&
|
|
2199
|
-
typeof (value as any).next === 'function'
|
|
2200
|
-
)
|
|
2201
|
-
}
|
|
2202
|
-
|
|
2203
2487
|
/** @internal Handles streaming output from an async generator `run` handler. */
|
|
2204
2488
|
async function handleStreaming(
|
|
2205
2489
|
generator: AsyncGenerator<unknown, unknown, unknown>,
|
|
@@ -2211,7 +2495,7 @@ async function handleStreaming(
|
|
|
2211
2495
|
formatExplicit: boolean
|
|
2212
2496
|
human: boolean
|
|
2213
2497
|
renderOutput: boolean
|
|
2214
|
-
|
|
2498
|
+
fullOutput: boolean
|
|
2215
2499
|
truncate: (s: string) => { text: string; truncated: boolean; nextOffset?: number | undefined }
|
|
2216
2500
|
write: (output: Output) => void
|
|
2217
2501
|
writeln: (s: string) => void
|
|
@@ -2405,7 +2689,9 @@ async function handleStreaming(
|
|
|
2405
2689
|
function formatCtaBlock(name: string, block: CtaBlock | undefined): FormattedCtaBlock | undefined {
|
|
2406
2690
|
if (!block || block.commands.length === 0) return undefined
|
|
2407
2691
|
return {
|
|
2408
|
-
description:
|
|
2692
|
+
description:
|
|
2693
|
+
block.description ??
|
|
2694
|
+
(block.commands.length === 1 ? 'Suggested command:' : 'Suggested commands:'),
|
|
2409
2695
|
commands: block.commands.map((c) => formatCta(name, c)),
|
|
2410
2696
|
}
|
|
2411
2697
|
}
|
|
@@ -2439,6 +2725,7 @@ function collectIndexCommands(
|
|
|
2439
2725
|
): { name: string; description?: string | undefined }[] {
|
|
2440
2726
|
const result: { name: string; description?: string | undefined }[] = []
|
|
2441
2727
|
for (const [name, entry] of commands) {
|
|
2728
|
+
if (isAlias(entry)) continue
|
|
2442
2729
|
const path = [...prefix, name]
|
|
2443
2730
|
if (isGroup(entry)) {
|
|
2444
2731
|
result.push(...collectIndexCommands(entry.commands, path))
|
|
@@ -2473,6 +2760,7 @@ function collectCommands(
|
|
|
2473
2760
|
}[] {
|
|
2474
2761
|
const result: ReturnType<typeof collectCommands> = []
|
|
2475
2762
|
for (const [name, entry] of commands) {
|
|
2763
|
+
if (isAlias(entry)) continue
|
|
2476
2764
|
const path = [...prefix, name]
|
|
2477
2765
|
if (isFetchGateway(entry)) {
|
|
2478
2766
|
const cmd: (typeof result)[number] = { name: path.join(' ') }
|
|
@@ -2513,9 +2801,23 @@ function collectSkillCommands(
|
|
|
2513
2801
|
commands: Map<string, CommandEntry>,
|
|
2514
2802
|
prefix: string[],
|
|
2515
2803
|
groups: Map<string, string>,
|
|
2804
|
+
rootCommand?: CommandDefinition<any, any, any> | undefined,
|
|
2516
2805
|
): Skill.CommandInfo[] {
|
|
2517
2806
|
const result: Skill.CommandInfo[] = []
|
|
2807
|
+
if (rootCommand) {
|
|
2808
|
+
const cmd: Skill.CommandInfo = {}
|
|
2809
|
+
if (rootCommand.description) cmd.description = rootCommand.description
|
|
2810
|
+
if (rootCommand.args) cmd.args = rootCommand.args
|
|
2811
|
+
if (rootCommand.env) cmd.env = rootCommand.env
|
|
2812
|
+
if (rootCommand.hint) cmd.hint = rootCommand.hint
|
|
2813
|
+
if (rootCommand.options) cmd.options = rootCommand.options
|
|
2814
|
+
if (rootCommand.output) cmd.output = rootCommand.output
|
|
2815
|
+
const examples = formatExamples(rootCommand.examples)
|
|
2816
|
+
if (examples) cmd.examples = examples
|
|
2817
|
+
result.push(cmd)
|
|
2818
|
+
}
|
|
2518
2819
|
for (const [name, entry] of commands) {
|
|
2820
|
+
if (isAlias(entry)) continue
|
|
2519
2821
|
const path = [...prefix, name]
|
|
2520
2822
|
if (isFetchGateway(entry)) {
|
|
2521
2823
|
const cmd: Skill.CommandInfo = { name: path.join(' ') }
|
|
@@ -2544,7 +2846,7 @@ function collectSkillCommands(
|
|
|
2544
2846
|
result.push(cmd)
|
|
2545
2847
|
}
|
|
2546
2848
|
}
|
|
2547
|
-
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
2849
|
+
return result.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
|
|
2548
2850
|
}
|
|
2549
2851
|
|
|
2550
2852
|
/** @internal Formats examples into `{ command, description }` objects. `command` is the args/options suffix only. */
|
|
@@ -2684,15 +2986,11 @@ type CommandDefinition<
|
|
|
2684
2986
|
output extends z.ZodType | undefined = undefined,
|
|
2685
2987
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
2686
2988
|
cliEnv extends z.ZodObject<any> | undefined = undefined,
|
|
2687
|
-
> = {
|
|
2688
|
-
/**
|
|
2689
|
-
|
|
2690
|
-
? Partial<Record<keyof z.output<options>, string>>
|
|
2691
|
-
: Record<string, string> | undefined
|
|
2989
|
+
> = CommandMeta<options> & {
|
|
2990
|
+
/** Alternative names for this command (e.g. `['extensions', 'ext']` for an `extension` command). */
|
|
2991
|
+
aliases?: string[] | undefined
|
|
2692
2992
|
/** Zod schema for positional arguments. */
|
|
2693
2993
|
args?: args | undefined
|
|
2694
|
-
/** A short description of what the command does. */
|
|
2695
|
-
description?: string | undefined
|
|
2696
2994
|
/** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
|
|
2697
2995
|
env?: env | undefined
|
|
2698
2996
|
/** Usage examples for this command. */
|
|
@@ -2701,8 +2999,6 @@ type CommandDefinition<
|
|
|
2701
2999
|
format?: Formatter.Format | undefined
|
|
2702
3000
|
/** Plain text hint displayed after examples and before global options. */
|
|
2703
3001
|
hint?: string | undefined
|
|
2704
|
-
/** Zod schema for named options/flags. */
|
|
2705
|
-
options?: options | undefined
|
|
2706
3002
|
/** Zod schema for the command's return value. */
|
|
2707
3003
|
output?: output | undefined
|
|
2708
3004
|
/**
|
|
@@ -2724,6 +3020,8 @@ type CommandDefinition<
|
|
|
2724
3020
|
agent: boolean
|
|
2725
3021
|
/** Positional arguments. */
|
|
2726
3022
|
args: InferOutput<args>
|
|
3023
|
+
/** The binary name the user invoked (e.g. an alias). Falls back to `name` when not resolvable. */
|
|
3024
|
+
displayName: string
|
|
2727
3025
|
/** Parsed environment variables. */
|
|
2728
3026
|
env: InferOutput<env>
|
|
2729
3027
|
/** Return an error result with optional CTAs. */
|
|
@@ -2801,3 +3099,13 @@ function emitDeprecationWarnings(
|
|
|
2801
3099
|
}
|
|
2802
3100
|
}
|
|
2803
3101
|
}
|
|
3102
|
+
|
|
3103
|
+
/** @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`. */
|
|
3104
|
+
function resolveDisplayName(name: string, aliases?: string[]): string {
|
|
3105
|
+
const bin = process.argv[1]
|
|
3106
|
+
if (!bin) return name
|
|
3107
|
+
const basename = path.basename(bin)
|
|
3108
|
+
if (basename === name) return name
|
|
3109
|
+
if (aliases?.includes(basename)) return basename
|
|
3110
|
+
return name
|
|
3111
|
+
}
|