incur 0.4.8 → 0.4.10
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/dist/Cli.d.ts +69 -19
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +419 -73
- package/dist/Cli.js.map +1 -1
- package/dist/Completions.d.ts +2 -1
- package/dist/Completions.d.ts.map +1 -1
- package/dist/Completions.js +52 -13
- package/dist/Completions.js.map +1 -1
- package/dist/Formatter.d.ts.map +1 -1
- package/dist/Formatter.js +6 -5
- package/dist/Formatter.js.map +1 -1
- package/dist/Help.d.ts +5 -0
- package/dist/Help.d.ts.map +1 -1
- package/dist/Help.js +19 -5
- package/dist/Help.js.map +1 -1
- package/dist/Mcp.d.ts +21 -0
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +44 -13
- package/dist/Mcp.js.map +1 -1
- package/dist/Parser.d.ts +14 -0
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +127 -1
- package/dist/Parser.js.map +1 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +5 -2
- package/dist/Skill.js.map +1 -1
- package/dist/Skillgen.d.ts.map +1 -1
- package/dist/Skillgen.js +4 -38
- package/dist/Skillgen.js.map +1 -1
- package/dist/SyncMcp.d.ts.map +1 -1
- package/dist/SyncMcp.js +75 -8
- package/dist/SyncMcp.js.map +1 -1
- package/dist/SyncSkills.d.ts +9 -0
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +13 -74
- package/dist/SyncSkills.js.map +1 -1
- package/dist/bin.d.ts +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/internal/command.d.ts +31 -19
- package/dist/internal/command.d.ts.map +1 -1
- package/dist/internal/command.js +10 -5
- package/dist/internal/command.js.map +1 -1
- package/dist/internal/cta.d.ts +22 -0
- package/dist/internal/cta.d.ts.map +1 -0
- package/dist/internal/cta.js +25 -0
- package/dist/internal/cta.js.map +1 -0
- package/dist/internal/json.d.ts +5 -0
- package/dist/internal/json.d.ts.map +1 -0
- package/dist/internal/json.js +13 -0
- package/dist/internal/json.js.map +1 -0
- package/dist/internal/yaml.d.ts +12 -0
- package/dist/internal/yaml.d.ts.map +1 -0
- package/dist/internal/yaml.js +20 -0
- package/dist/internal/yaml.js.map +1 -0
- package/dist/middleware.d.ts +8 -4
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/package.json +1 -1
- package/src/Cli.test-d.ts +73 -0
- package/src/Cli.test.ts +1536 -48
- package/src/Cli.ts +606 -108
- package/src/Completions.test.ts +85 -0
- package/src/Completions.ts +48 -9
- package/src/Formatter.test.ts +17 -0
- package/src/Formatter.ts +7 -5
- package/src/Help.test.ts +32 -1
- package/src/Help.ts +42 -4
- package/src/Mcp.test.ts +289 -0
- package/src/Mcp.ts +68 -13
- package/src/Parser.test.ts +173 -0
- package/src/Parser.ts +132 -1
- package/src/Skill.test.ts +6 -0
- package/src/Skill.ts +8 -5
- package/src/Skillgen.test.ts +55 -6
- package/src/Skillgen.ts +9 -35
- package/src/SyncMcp.test.ts +89 -0
- package/src/SyncMcp.ts +72 -8
- package/src/SyncSkills.test.ts +78 -1
- package/src/SyncSkills.ts +20 -78
- package/src/bun-compile.test.ts +5 -0
- package/src/e2e.test.ts +166 -12
- package/src/internal/command.ts +12 -4
- package/src/internal/cta.ts +58 -0
- package/src/internal/json.ts +16 -0
- package/src/internal/yaml.ts +23 -0
- package/src/lazy-imports.test.ts +94 -0
- package/src/middleware.ts +12 -3
package/src/Cli.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs from 'node:fs/promises'
|
|
2
2
|
import * as os from 'node:os'
|
|
3
3
|
import * as path from 'node:path'
|
|
4
|
+
import { PassThrough } from 'node:stream'
|
|
4
5
|
import { estimateTokenCount, sliceByTokens } from 'tokenx'
|
|
5
|
-
import { parse as yamlParse, stringify as yamlStringify } from 'yaml'
|
|
6
6
|
import { z } from 'zod'
|
|
7
7
|
|
|
8
8
|
import * as Completions from './Completions.js'
|
|
@@ -21,9 +21,12 @@ import {
|
|
|
21
21
|
shells,
|
|
22
22
|
} from './internal/command.js'
|
|
23
23
|
import * as Command from './internal/command.js'
|
|
24
|
+
import { formatCtaBlock, type FormattedCta, type FormattedCtaBlock } from './internal/cta.js'
|
|
24
25
|
import { isRecord, suggest, toKebab } from './internal/helpers.js'
|
|
26
|
+
import * as Json from './internal/json.js'
|
|
25
27
|
import { detectRunner } from './internal/pm.js'
|
|
26
28
|
import type { OneOf } from './internal/types.js'
|
|
29
|
+
import * as Yaml from './internal/yaml.js'
|
|
27
30
|
import * as Mcp from './Mcp.js'
|
|
28
31
|
import type { Context as MiddlewareContext, Handler as MiddlewareHandler } from './middleware.js'
|
|
29
32
|
import * as Openapi from './Openapi.js'
|
|
@@ -35,11 +38,14 @@ import * as Skill from './Skill.js'
|
|
|
35
38
|
import * as SyncMcp from './SyncMcp.js'
|
|
36
39
|
import * as SyncSkills from './SyncSkills.js'
|
|
37
40
|
|
|
41
|
+
const destructiveCommandHint = 'Confirm with the user before executing this destructive command.'
|
|
42
|
+
|
|
38
43
|
/** A CLI application instance. Also used as a command group when mounted on a parent CLI. */
|
|
39
44
|
export type Cli<
|
|
40
45
|
commands extends CommandsMap = {},
|
|
41
46
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
42
47
|
env extends z.ZodObject<any> | undefined = undefined,
|
|
48
|
+
globals extends z.ZodObject<any> | undefined = undefined,
|
|
43
49
|
> = {
|
|
44
50
|
/** Registers a root command or mounts a sub-CLI as a command group. */
|
|
45
51
|
command: {
|
|
@@ -56,12 +62,18 @@ export type Cli<
|
|
|
56
62
|
): Cli<
|
|
57
63
|
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<options> } },
|
|
58
64
|
vars,
|
|
59
|
-
env
|
|
65
|
+
env,
|
|
66
|
+
globals
|
|
60
67
|
>
|
|
61
68
|
/** Mounts a sub-CLI as a command group. */
|
|
62
69
|
<const name extends string, const sub extends CommandsMap>(
|
|
63
|
-
cli: Cli<sub, any, any> & { name: name },
|
|
64
|
-
): Cli<
|
|
70
|
+
cli: Cli<sub, any, any, any> & { name: name },
|
|
71
|
+
): Cli<
|
|
72
|
+
commands & { [key in keyof sub & string as `${name} ${key}`]: sub[key] },
|
|
73
|
+
vars,
|
|
74
|
+
env,
|
|
75
|
+
globals
|
|
76
|
+
>
|
|
65
77
|
/** Mounts a root CLI as a single command. */
|
|
66
78
|
<
|
|
67
79
|
const name extends string,
|
|
@@ -72,7 +84,8 @@ export type Cli<
|
|
|
72
84
|
): Cli<
|
|
73
85
|
commands & { [key in name]: { args: InferOutput<args>; options: InferOutput<opts> } },
|
|
74
86
|
vars,
|
|
75
|
-
env
|
|
87
|
+
env,
|
|
88
|
+
globals
|
|
76
89
|
>
|
|
77
90
|
/** Mounts a fetch handler as a command, optionally with OpenAPI spec for typed subcommands. */
|
|
78
91
|
<const name extends string>(
|
|
@@ -85,7 +98,7 @@ export type Cli<
|
|
|
85
98
|
openapiConfig?: Openapi.Config | undefined
|
|
86
99
|
outputPolicy?: OutputPolicy | undefined
|
|
87
100
|
},
|
|
88
|
-
): Cli<commands, vars, env>
|
|
101
|
+
): Cli<commands, vars, env, globals>
|
|
89
102
|
}
|
|
90
103
|
/** A short description of the CLI. */
|
|
91
104
|
description?: string | undefined
|
|
@@ -98,7 +111,7 @@ export type Cli<
|
|
|
98
111
|
/** Parses argv, runs the matched command, and writes the output envelope to stdout. */
|
|
99
112
|
serve(argv?: string[], options?: serve.Options): Promise<void>
|
|
100
113
|
/** Registers middleware that runs around every command. */
|
|
101
|
-
use(handler: MiddlewareHandler<vars, env>): Cli<commands, vars, env>
|
|
114
|
+
use(handler: MiddlewareHandler<vars, env, globals>): Cli<commands, vars, env, globals>
|
|
102
115
|
/** The vars schema, if declared. Use `typeof cli.vars` with `middleware<vars, env>()` for typed middleware. */
|
|
103
116
|
vars: vars
|
|
104
117
|
}
|
|
@@ -163,10 +176,16 @@ export function create<
|
|
|
163
176
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
164
177
|
const output extends z.ZodType | undefined = undefined,
|
|
165
178
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
179
|
+
const globals extends z.ZodObject<any> | undefined = undefined,
|
|
166
180
|
>(
|
|
167
181
|
name: string,
|
|
168
|
-
definition: create.Options<args, env, opts, output, vars> & { run: Function },
|
|
169
|
-
): Cli<
|
|
182
|
+
definition: create.Options<args, env, opts, output, vars, globals> & { run: Function },
|
|
183
|
+
): Cli<
|
|
184
|
+
{ [key in typeof name]: { args: InferOutput<args>; options: InferOutput<opts> } },
|
|
185
|
+
vars,
|
|
186
|
+
env,
|
|
187
|
+
globals
|
|
188
|
+
>
|
|
170
189
|
/** Creates a router CLI that registers subcommands. */
|
|
171
190
|
export function create<
|
|
172
191
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -174,7 +193,11 @@ export function create<
|
|
|
174
193
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
175
194
|
const output extends z.ZodType | undefined = undefined,
|
|
176
195
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
177
|
-
|
|
196
|
+
const globals extends z.ZodObject<any> | undefined = undefined,
|
|
197
|
+
>(
|
|
198
|
+
name: string,
|
|
199
|
+
definition?: create.Options<args, env, opts, output, vars, globals>,
|
|
200
|
+
): Cli<{}, vars, env, globals>
|
|
178
201
|
/** Creates a CLI with a root handler from a single options object. Can still register subcommands. */
|
|
179
202
|
export function create<
|
|
180
203
|
const args extends z.ZodObject<any> | undefined = undefined,
|
|
@@ -182,14 +205,19 @@ export function create<
|
|
|
182
205
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
183
206
|
const output extends z.ZodType | undefined = undefined,
|
|
184
207
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
208
|
+
const globals extends z.ZodObject<any> | undefined = undefined,
|
|
185
209
|
>(
|
|
186
|
-
definition: create.Options<args, env, opts, output, vars> & {
|
|
210
|
+
definition: create.Options<args, env, opts, output, vars, globals> & {
|
|
211
|
+
name: string
|
|
212
|
+
run: Function
|
|
213
|
+
},
|
|
187
214
|
): Cli<
|
|
188
215
|
{
|
|
189
216
|
[key in (typeof definition)['name']]: { args: InferOutput<args>; options: InferOutput<opts> }
|
|
190
217
|
},
|
|
191
218
|
vars,
|
|
192
|
-
env
|
|
219
|
+
env,
|
|
220
|
+
globals
|
|
193
221
|
>
|
|
194
222
|
/** Creates a router CLI from a single options object (e.g. package.json). */
|
|
195
223
|
export function create<
|
|
@@ -198,7 +226,10 @@ export function create<
|
|
|
198
226
|
const opts extends z.ZodObject<any> | undefined = undefined,
|
|
199
227
|
const output extends z.ZodType | undefined = undefined,
|
|
200
228
|
const vars extends z.ZodObject<any> | undefined = undefined,
|
|
201
|
-
|
|
229
|
+
const globals extends z.ZodObject<any> | undefined = undefined,
|
|
230
|
+
>(
|
|
231
|
+
definition: create.Options<args, env, opts, output, vars, globals> & { name: string },
|
|
232
|
+
): Cli<{}, vars, env, globals>
|
|
202
233
|
export function create(
|
|
203
234
|
nameOrDefinition: string | (any & { name: string }),
|
|
204
235
|
definition?: any,
|
|
@@ -214,7 +245,9 @@ export function create(
|
|
|
214
245
|
const commands = new Map<string, CommandEntry>()
|
|
215
246
|
const middlewares: MiddlewareHandler[] = []
|
|
216
247
|
const pending: Promise<void>[] = []
|
|
217
|
-
const mcpHandler = createMcpHttpHandler(name, def.version ?? '0.0.0'
|
|
248
|
+
const mcpHandler = createMcpHttpHandler(name, def.version ?? '0.0.0', {
|
|
249
|
+
stateless: def.mcp?.stateless,
|
|
250
|
+
})
|
|
218
251
|
|
|
219
252
|
if (def.openapi && rootFetch) {
|
|
220
253
|
pending.push(
|
|
@@ -249,12 +282,14 @@ export function create(
|
|
|
249
282
|
basePath: def.basePath,
|
|
250
283
|
config: def.openapiConfig,
|
|
251
284
|
})
|
|
252
|
-
|
|
285
|
+
const entry = {
|
|
253
286
|
_group: true,
|
|
254
287
|
description: def.description,
|
|
255
288
|
commands: generated as Map<string, CommandEntry>,
|
|
256
289
|
...(def.outputPolicy ? { outputPolicy: def.outputPolicy } : undefined),
|
|
257
|
-
} as InternalGroup
|
|
290
|
+
} as InternalGroup
|
|
291
|
+
assertNoGlobalOptionConflicts(nameOrCli, entry, toGlobals.get(cli))
|
|
292
|
+
commands.set(nameOrCli, entry)
|
|
258
293
|
})(),
|
|
259
294
|
)
|
|
260
295
|
return cli
|
|
@@ -268,6 +303,7 @@ export function create(
|
|
|
268
303
|
} as InternalFetchGateway)
|
|
269
304
|
return cli
|
|
270
305
|
}
|
|
306
|
+
assertNoGlobalOptionConflicts(nameOrCli, def, toGlobals.get(cli))
|
|
271
307
|
commands.set(nameOrCli, def)
|
|
272
308
|
if (def.aliases)
|
|
273
309
|
for (const a of def.aliases) commands.set(a, { _alias: true, target: nameOrCli })
|
|
@@ -275,6 +311,7 @@ export function create(
|
|
|
275
311
|
}
|
|
276
312
|
const mountedRootDef = toRootDefinition.get(nameOrCli)
|
|
277
313
|
if (mountedRootDef) {
|
|
314
|
+
assertNoGlobalOptionConflicts(nameOrCli.name, mountedRootDef, toGlobals.get(cli))
|
|
278
315
|
commands.set(nameOrCli.name, mountedRootDef)
|
|
279
316
|
const rootAliases = toRootAliases.get(nameOrCli)
|
|
280
317
|
if (rootAliases)
|
|
@@ -285,21 +322,25 @@ export function create(
|
|
|
285
322
|
const subCommands = toCommands.get(sub)!
|
|
286
323
|
const subOutputPolicy = toOutputPolicy.get(sub)
|
|
287
324
|
const subMiddlewares = toMiddlewares.get(sub)
|
|
288
|
-
|
|
325
|
+
const entry = {
|
|
289
326
|
_group: true,
|
|
290
327
|
description: sub.description,
|
|
291
328
|
commands: subCommands,
|
|
292
329
|
...(subOutputPolicy ? { outputPolicy: subOutputPolicy } : undefined),
|
|
293
330
|
...(subMiddlewares?.length ? { middlewares: subMiddlewares } : undefined),
|
|
294
|
-
}
|
|
331
|
+
} as InternalGroup
|
|
332
|
+
assertNoGlobalOptionConflicts(sub.name, entry, toGlobals.get(cli))
|
|
333
|
+
commands.set(sub.name, entry)
|
|
295
334
|
return cli
|
|
296
335
|
},
|
|
297
336
|
|
|
298
337
|
async fetch(req: Request) {
|
|
299
338
|
if (pending.length > 0) await Promise.all(pending)
|
|
339
|
+
const globalsDesc = toGlobals.get(cli)
|
|
300
340
|
return fetchImpl(name, commands, req, {
|
|
301
341
|
description: def.description,
|
|
302
342
|
envSchema: def.env,
|
|
343
|
+
globals: globalsDesc,
|
|
303
344
|
mcpHandler,
|
|
304
345
|
middlewares,
|
|
305
346
|
name,
|
|
@@ -311,13 +352,16 @@ export function create(
|
|
|
311
352
|
|
|
312
353
|
async serve(argv = process.argv.slice(2), serveOptions: serve.Options = {}) {
|
|
313
354
|
if (pending.length > 0) await Promise.all(pending)
|
|
355
|
+
const globalsDesc = toGlobals.get(cli)
|
|
314
356
|
return serveImpl(name, commands, argv, {
|
|
315
357
|
...serveOptions,
|
|
316
358
|
aliases: def.aliases,
|
|
359
|
+
banner: def.banner,
|
|
317
360
|
config: def.config,
|
|
318
361
|
description: def.description,
|
|
319
362
|
envSchema: def.env,
|
|
320
363
|
format: def.format,
|
|
364
|
+
globals: globalsDesc,
|
|
321
365
|
mcp: def.mcp,
|
|
322
366
|
middlewares,
|
|
323
367
|
outputPolicy: def.outputPolicy,
|
|
@@ -340,6 +384,44 @@ export function create(
|
|
|
340
384
|
if (def.options) toRootOptions.set(cli, def.options)
|
|
341
385
|
if (def.config !== undefined) toConfigEnabled.set(cli, true)
|
|
342
386
|
if (def.outputPolicy) toOutputPolicy.set(cli, def.outputPolicy)
|
|
387
|
+
if (def.globals) {
|
|
388
|
+
toGlobals.set(cli, { schema: def.globals, alias: def.globalAlias as any })
|
|
389
|
+
const builtinNames = [
|
|
390
|
+
'verbose',
|
|
391
|
+
'format',
|
|
392
|
+
'json',
|
|
393
|
+
'llms',
|
|
394
|
+
'llmsFull',
|
|
395
|
+
'mcp',
|
|
396
|
+
'help',
|
|
397
|
+
'version',
|
|
398
|
+
'schema',
|
|
399
|
+
'filterOutput',
|
|
400
|
+
'tokenLimit',
|
|
401
|
+
'tokenOffset',
|
|
402
|
+
'tokenCount',
|
|
403
|
+
...(def.config?.flag
|
|
404
|
+
? [def.config.flag, `no${def.config.flag[0].toUpperCase()}${def.config.flag.slice(1)}`]
|
|
405
|
+
: []),
|
|
406
|
+
]
|
|
407
|
+
const globalKeys = Object.keys(def.globals.shape)
|
|
408
|
+
for (const key of globalKeys) {
|
|
409
|
+
if (builtinNames.includes(key))
|
|
410
|
+
throw new Error(
|
|
411
|
+
`Global option '${key}' conflicts with a built-in flag. Choose a different name.`,
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
// Check globalAlias values against reserved short aliases
|
|
415
|
+
const reservedShorts = new Set(['h'])
|
|
416
|
+
if (def.globalAlias) {
|
|
417
|
+
for (const [name, short] of Object.entries(def.globalAlias as Record<string, string>)) {
|
|
418
|
+
if (reservedShorts.has(short))
|
|
419
|
+
throw new Error(
|
|
420
|
+
`Global alias '-${short}' for '${name}' conflicts with a built-in short flag. Choose a different alias.`,
|
|
421
|
+
)
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
343
425
|
toMiddlewares.set(cli, middlewares)
|
|
344
426
|
toCommands.set(cli, commands)
|
|
345
427
|
return cli
|
|
@@ -353,6 +435,7 @@ export declare namespace create {
|
|
|
353
435
|
options extends z.ZodObject<any> | undefined = undefined,
|
|
354
436
|
output extends z.ZodType | undefined = undefined,
|
|
355
437
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
438
|
+
globals extends z.ZodObject<any> | undefined = undefined,
|
|
356
439
|
> = {
|
|
357
440
|
/** Map of option names to single-char aliases. */
|
|
358
441
|
alias?: options extends z.ZodObject<any>
|
|
@@ -360,6 +443,19 @@ export declare namespace create {
|
|
|
360
443
|
: Record<string, string> | undefined
|
|
361
444
|
/** Alternative binary names for this CLI (e.g. shorter aliases in package.json `bin`). Shell completions are registered for all names. */
|
|
362
445
|
aliases?: string[] | undefined
|
|
446
|
+
/**
|
|
447
|
+
* Text to display above root help output (e.g. branding, live status). Only called when the CLI is invoked with no subcommand. Errors are silently swallowed.
|
|
448
|
+
*
|
|
449
|
+
* Pass a function for all consumers, or an object with `mode` to target `'human'`, `'agent'`, or `'all'` (default).
|
|
450
|
+
*/
|
|
451
|
+
banner?:
|
|
452
|
+
| (() => string | undefined | Promise<string | undefined>)
|
|
453
|
+
| {
|
|
454
|
+
render: () => string | undefined | Promise<string | undefined>
|
|
455
|
+
/** @default 'all' */
|
|
456
|
+
mode?: 'all' | 'human' | 'agent' | undefined
|
|
457
|
+
}
|
|
458
|
+
| undefined
|
|
363
459
|
/** Zod schema for positional arguments. */
|
|
364
460
|
args?: args | undefined
|
|
365
461
|
/** Enable config-file defaults for command options. */
|
|
@@ -382,6 +478,8 @@ export declare namespace create {
|
|
|
382
478
|
| undefined
|
|
383
479
|
/** A short description of what the CLI does. */
|
|
384
480
|
description?: string | undefined
|
|
481
|
+
/** Marks the root command as destructive when generating agent skills. */
|
|
482
|
+
destructive?: boolean | undefined
|
|
385
483
|
/** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
|
|
386
484
|
env?: env | undefined
|
|
387
485
|
/** Usage examples for this command. */
|
|
@@ -394,6 +492,14 @@ export declare namespace create {
|
|
|
394
492
|
openapiConfig?: Openapi.Config | undefined
|
|
395
493
|
/** Default output format. Overridden by `--format` or `--json`. */
|
|
396
494
|
format?: Formatter.Format | undefined
|
|
495
|
+
/** Plain text hint displayed after examples and before global options. */
|
|
496
|
+
hint?: string | undefined
|
|
497
|
+
/** Map of global option names to single-char aliases. */
|
|
498
|
+
globalAlias?: globals extends z.ZodObject<any>
|
|
499
|
+
? Partial<Record<keyof z.output<globals>, string>>
|
|
500
|
+
: Record<string, string> | undefined
|
|
501
|
+
/** Zod schema for global options available to all commands. Parsed before command resolution and passed to middleware and command handlers. */
|
|
502
|
+
globals?: globals | undefined
|
|
397
503
|
/** Zod schema for named options/flags. */
|
|
398
504
|
options?: options | undefined
|
|
399
505
|
/** Zod schema for the return value. */
|
|
@@ -446,13 +552,17 @@ export declare namespace create {
|
|
|
446
552
|
| Promise<InferReturn<output>>
|
|
447
553
|
| AsyncGenerator<InferReturn<output>, unknown, unknown>)
|
|
448
554
|
| undefined
|
|
449
|
-
/** Options for
|
|
555
|
+
/** Options for MCP integration. */
|
|
450
556
|
mcp?:
|
|
451
557
|
| {
|
|
452
558
|
/** Target specific agents by default (e.g. `['claude-code', 'cursor']`). */
|
|
453
559
|
agents?: string[] | undefined
|
|
454
560
|
/** Override the command agents will run to start the MCP server. Auto-detected if omitted. */
|
|
455
561
|
command?: string | undefined
|
|
562
|
+
/** Instructions describing how to use the server and its features. */
|
|
563
|
+
instructions?: string | undefined
|
|
564
|
+
/** Disable HTTP MCP session management. Defaults to `true`. */
|
|
565
|
+
stateless?: boolean | undefined
|
|
456
566
|
}
|
|
457
567
|
| undefined
|
|
458
568
|
/** Options for the built-in `skills add` command. */
|
|
@@ -504,6 +614,20 @@ async function serveImpl(
|
|
|
504
614
|
stdout(s.endsWith('\n') ? s : `${s}\n`)
|
|
505
615
|
}
|
|
506
616
|
|
|
617
|
+
async function writeBanner() {
|
|
618
|
+
if (!options.banner || help) return
|
|
619
|
+
const banner =
|
|
620
|
+
typeof options.banner === 'function'
|
|
621
|
+
? { render: options.banner, mode: 'all' as const }
|
|
622
|
+
: options.banner
|
|
623
|
+
const mode = banner.mode ?? 'all'
|
|
624
|
+
if (mode !== 'all' && mode !== (human ? 'human' : 'agent')) return
|
|
625
|
+
try {
|
|
626
|
+
const text = await banner.render()
|
|
627
|
+
if (text) writeln(text)
|
|
628
|
+
} catch {}
|
|
629
|
+
}
|
|
630
|
+
|
|
507
631
|
let builtinFlags: ReturnType<typeof extractBuiltinFlags>
|
|
508
632
|
try {
|
|
509
633
|
builtinFlags = extractBuiltinFlags(argv, { configFlag })
|
|
@@ -531,9 +655,35 @@ async function serveImpl(
|
|
|
531
655
|
schema,
|
|
532
656
|
configPath,
|
|
533
657
|
configDisabled,
|
|
534
|
-
rest
|
|
658
|
+
rest,
|
|
535
659
|
} = builtinFlags
|
|
536
660
|
|
|
661
|
+
let globals: Record<string, unknown> = {}
|
|
662
|
+
let filtered = rest
|
|
663
|
+
|
|
664
|
+
function parseGlobalOptions(validate: boolean) {
|
|
665
|
+
if (!options.globals) return true
|
|
666
|
+
try {
|
|
667
|
+
const result = Parser.parseGlobals(rest, options.globals.schema, options.globals.alias, {
|
|
668
|
+
validate,
|
|
669
|
+
})
|
|
670
|
+
if (validate) globals = result.parsed
|
|
671
|
+
filtered = result.rest
|
|
672
|
+
return true
|
|
673
|
+
} catch (error) {
|
|
674
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
675
|
+
if (human) writeln(formatHumanError({ code: 'UNKNOWN', message }))
|
|
676
|
+
else writeln(Formatter.format({ code: 'UNKNOWN', message }, 'toon'))
|
|
677
|
+
exit(1)
|
|
678
|
+
return false
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (!parseGlobalOptions(false)) return
|
|
683
|
+
|
|
684
|
+
// Pre-load yaml for the sync formatting paths below (yaml is loaded lazily -- see internal/yaml.ts).
|
|
685
|
+
if (formatFlag === 'yaml') await Yaml.load()
|
|
686
|
+
|
|
537
687
|
// --mcp: start as MCP stdio server
|
|
538
688
|
if (mcpFlag) {
|
|
539
689
|
await Mcp.serve(name, options.version ?? '0.0.0', commands, {
|
|
@@ -541,6 +691,7 @@ async function serveImpl(
|
|
|
541
691
|
env: options.envSchema,
|
|
542
692
|
vars: options.vars,
|
|
543
693
|
version: options.version,
|
|
694
|
+
...(options.mcp?.instructions ? { instructions: options.mcp.instructions } : undefined),
|
|
544
695
|
})
|
|
545
696
|
return
|
|
546
697
|
}
|
|
@@ -557,7 +708,15 @@ async function serveImpl(
|
|
|
557
708
|
stdout(names.map((n) => Completions.register(completeShell, n)).join('\n'))
|
|
558
709
|
} else {
|
|
559
710
|
const index = Number(process.env._COMPLETE_INDEX ?? words.length - 1)
|
|
560
|
-
const candidates = Completions.complete(
|
|
711
|
+
const candidates = Completions.complete(
|
|
712
|
+
commands,
|
|
713
|
+
options.rootCommand,
|
|
714
|
+
words,
|
|
715
|
+
index,
|
|
716
|
+
options.globals
|
|
717
|
+
? { schema: options.globals.schema, alias: options.globals.alias }
|
|
718
|
+
: undefined,
|
|
719
|
+
)
|
|
561
720
|
// Add built-in commands (completions, mcp, skills) to completions
|
|
562
721
|
const current = words[index] ?? ''
|
|
563
722
|
const nonFlags = words.slice(0, index).filter((w) => !w.startsWith('-'))
|
|
@@ -630,27 +789,41 @@ async function serveImpl(
|
|
|
630
789
|
}
|
|
631
790
|
|
|
632
791
|
const scopedRoot = prefix.length === 0 ? options.rootCommand : undefined
|
|
792
|
+
// Markdown skill output renders scopedName separately. Passing prefix again
|
|
793
|
+
// to those collect helpers would double the group segment in command names
|
|
794
|
+
// (e.g. "cli auth auth login" instead of "cli auth login").
|
|
795
|
+
const collectPrefix = prefix.length > 0 ? ([] as string[]) : prefix
|
|
633
796
|
|
|
634
797
|
if (llmsFull) {
|
|
635
798
|
if (!formatExplicit || formatFlag === 'md') {
|
|
636
799
|
const groups = new Map<string, string>()
|
|
637
|
-
const cmds = collectSkillCommands(scopedCommands,
|
|
800
|
+
const cmds = collectSkillCommands(scopedCommands, collectPrefix, groups, scopedRoot)
|
|
638
801
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name
|
|
639
802
|
writeln(Skill.generate(scopedName, cmds, groups))
|
|
640
803
|
return
|
|
641
804
|
}
|
|
642
|
-
writeln(
|
|
805
|
+
writeln(
|
|
806
|
+
Formatter.format(
|
|
807
|
+
buildManifest(scopedCommands, prefix, options.globals?.schema),
|
|
808
|
+
formatFlag,
|
|
809
|
+
),
|
|
810
|
+
)
|
|
643
811
|
return
|
|
644
812
|
}
|
|
645
813
|
|
|
646
814
|
if (!formatExplicit || formatFlag === 'md') {
|
|
647
815
|
const groups = new Map<string, string>()
|
|
648
|
-
const cmds = collectSkillCommands(scopedCommands,
|
|
816
|
+
const cmds = collectSkillCommands(scopedCommands, collectPrefix, groups, scopedRoot)
|
|
649
817
|
const scopedName = prefix.length > 0 ? `${name} ${prefix.join(' ')}` : name
|
|
650
818
|
writeln(Skill.index(scopedName, cmds, scopedDescription))
|
|
651
819
|
return
|
|
652
820
|
}
|
|
653
|
-
writeln(
|
|
821
|
+
writeln(
|
|
822
|
+
Formatter.format(
|
|
823
|
+
buildIndexManifest(scopedCommands, prefix, options.globals?.schema),
|
|
824
|
+
formatFlag,
|
|
825
|
+
),
|
|
826
|
+
)
|
|
654
827
|
return
|
|
655
828
|
}
|
|
656
829
|
|
|
@@ -830,12 +1003,16 @@ async function serveImpl(
|
|
|
830
1003
|
return
|
|
831
1004
|
}
|
|
832
1005
|
|
|
833
|
-
// mcp add: register CLI
|
|
1006
|
+
// mcp add/doctor: register or smoke-test CLI MCP server integration.
|
|
834
1007
|
const mcpIdx = builtinIdx(filtered, name, 'mcp')
|
|
835
1008
|
if (mcpIdx !== -1) {
|
|
1009
|
+
const builtin = findBuiltin('mcp')!
|
|
836
1010
|
const mcpSub = filtered[mcpIdx + 1]
|
|
837
|
-
|
|
838
|
-
|
|
1011
|
+
const sub = mcpSub ? findBuiltinSubcommand(builtin, mcpSub) : undefined
|
|
1012
|
+
if (mcpSub && !sub) {
|
|
1013
|
+
const candidates =
|
|
1014
|
+
builtin.subcommands?.flatMap((sub) => [sub.name, ...(sub.aliases ?? [])]) ?? []
|
|
1015
|
+
const suggestion = suggest(mcpSub, candidates)
|
|
839
1016
|
const didYouMean = suggestion ? ` Did you mean '${suggestion}'?` : ''
|
|
840
1017
|
const message = `'${mcpSub}' is not a command for '${name} mcp'.${didYouMean}`
|
|
841
1018
|
const ctaCommands: FormattedCta[] = []
|
|
@@ -856,13 +1033,17 @@ async function serveImpl(
|
|
|
856
1033
|
return
|
|
857
1034
|
}
|
|
858
1035
|
if (!mcpSub) {
|
|
859
|
-
|
|
860
|
-
writeln(formatBuiltinHelp(name, b))
|
|
1036
|
+
writeln(formatBuiltinHelp(name, builtin))
|
|
861
1037
|
return
|
|
862
1038
|
}
|
|
863
1039
|
if (help) {
|
|
864
|
-
|
|
865
|
-
|
|
1040
|
+
writeln(formatBuiltinSubcommandHelp(name, builtin, sub!.name))
|
|
1041
|
+
return
|
|
1042
|
+
}
|
|
1043
|
+
if (sub!.name === 'doctor') {
|
|
1044
|
+
const result = await runMcpDoctor(name, commands, options)
|
|
1045
|
+
writeln(Formatter.format(result, formatExplicit ? formatFlag : 'toon'))
|
|
1046
|
+
if (!result.ok) exit(1)
|
|
866
1047
|
return
|
|
867
1048
|
}
|
|
868
1049
|
const rest = filtered.slice(mcpIdx + 2)
|
|
@@ -930,12 +1111,14 @@ async function serveImpl(
|
|
|
930
1111
|
) {
|
|
931
1112
|
// Root command with args but none provided (human mode) — show help
|
|
932
1113
|
const cmd = options.rootCommand
|
|
1114
|
+
await writeBanner()
|
|
933
1115
|
writeln(
|
|
934
1116
|
Help.formatCommand(name, {
|
|
935
1117
|
alias: cmd.alias as Record<string, string> | undefined,
|
|
936
1118
|
aliases: options.aliases,
|
|
937
1119
|
configFlag,
|
|
938
1120
|
description: cmd.description ?? options.description,
|
|
1121
|
+
globals: options.globals,
|
|
939
1122
|
version: options.version,
|
|
940
1123
|
args: cmd.args,
|
|
941
1124
|
env: cmd.env,
|
|
@@ -953,11 +1136,13 @@ async function serveImpl(
|
|
|
953
1136
|
if (options.rootCommand || options.rootFetch) {
|
|
954
1137
|
// Root command/fetch with no args — treat as root invocation
|
|
955
1138
|
} else {
|
|
1139
|
+
await writeBanner()
|
|
956
1140
|
writeln(
|
|
957
1141
|
Help.formatRoot(name, {
|
|
958
1142
|
aliases: options.aliases,
|
|
959
1143
|
configFlag,
|
|
960
1144
|
description: options.description,
|
|
1145
|
+
globals: options.globals,
|
|
961
1146
|
version: options.version,
|
|
962
1147
|
commands: collectHelpCommands(commands),
|
|
963
1148
|
root: true,
|
|
@@ -1018,6 +1203,7 @@ async function serveImpl(
|
|
|
1018
1203
|
aliases: options.aliases,
|
|
1019
1204
|
configFlag,
|
|
1020
1205
|
description: cmd.description ?? options.description,
|
|
1206
|
+
globals: options.globals,
|
|
1021
1207
|
version: options.version,
|
|
1022
1208
|
args: cmd.args,
|
|
1023
1209
|
env: cmd.env,
|
|
@@ -1036,6 +1222,7 @@ async function serveImpl(
|
|
|
1036
1222
|
aliases: isRoot ? options.aliases : undefined,
|
|
1037
1223
|
configFlag,
|
|
1038
1224
|
description: helpDesc,
|
|
1225
|
+
globals: options.globals,
|
|
1039
1226
|
version: isRoot ? options.version : undefined,
|
|
1040
1227
|
commands: collectHelpCommands(helpCmds),
|
|
1041
1228
|
root: isRoot,
|
|
@@ -1056,6 +1243,7 @@ async function serveImpl(
|
|
|
1056
1243
|
aliases: isRootCmd ? options.aliases : cmd.aliases,
|
|
1057
1244
|
configFlag,
|
|
1058
1245
|
description: cmd.description,
|
|
1246
|
+
globals: options.globals,
|
|
1059
1247
|
version: isRootCmd ? options.version : undefined,
|
|
1060
1248
|
args: cmd.args,
|
|
1061
1249
|
env: cmd.env,
|
|
@@ -1079,6 +1267,7 @@ async function serveImpl(
|
|
|
1079
1267
|
Help.formatRoot(`${name} ${resolved.path}`, {
|
|
1080
1268
|
configFlag,
|
|
1081
1269
|
description: resolved.description,
|
|
1270
|
+
globals: options.globals,
|
|
1082
1271
|
commands: collectHelpCommands(resolved.commands),
|
|
1083
1272
|
}),
|
|
1084
1273
|
)
|
|
@@ -1104,6 +1293,7 @@ async function serveImpl(
|
|
|
1104
1293
|
if (cmd.env) result.env = Schema.toJsonSchema(cmd.env)
|
|
1105
1294
|
if (cmd.options) result.options = Schema.toJsonSchema(cmd.options)
|
|
1106
1295
|
if (cmd.output) result.output = Schema.toJsonSchema(cmd.output)
|
|
1296
|
+
if (options.globals?.schema) result.globals = Schema.toJsonSchema(options.globals.schema)
|
|
1107
1297
|
writeln(Formatter.format(result, format))
|
|
1108
1298
|
return
|
|
1109
1299
|
}
|
|
@@ -1113,6 +1303,7 @@ async function serveImpl(
|
|
|
1113
1303
|
Help.formatRoot(`${name} ${resolved.path}`, {
|
|
1114
1304
|
configFlag,
|
|
1115
1305
|
description: resolved.description,
|
|
1306
|
+
globals: options.globals,
|
|
1116
1307
|
commands: collectHelpCommands(resolved.commands),
|
|
1117
1308
|
}),
|
|
1118
1309
|
)
|
|
@@ -1124,6 +1315,7 @@ async function serveImpl(
|
|
|
1124
1315
|
// Resolve effective format: explicit --format/--json → command default → CLI default → toon
|
|
1125
1316
|
const resolvedFormat = 'command' in resolved && (resolved as any).command.format
|
|
1126
1317
|
const format = formatExplicit ? formatFlag : resolvedFormat || options.format || 'toon'
|
|
1318
|
+
if (format === 'yaml') await Yaml.load()
|
|
1127
1319
|
|
|
1128
1320
|
// Fall back to root fetch/command when no subcommand matches,
|
|
1129
1321
|
// but only if the token doesn't look like a typo of a known command.
|
|
@@ -1285,6 +1477,7 @@ async function serveImpl(
|
|
|
1285
1477
|
|
|
1286
1478
|
// Fetch gateway execution path
|
|
1287
1479
|
if ('fetchGateway' in effective) {
|
|
1480
|
+
if (!parseGlobalOptions(true)) return
|
|
1288
1481
|
const { fetchGateway, path, rest: fetchRest } = effective
|
|
1289
1482
|
const fetchMiddleware = [
|
|
1290
1483
|
...(options.middlewares ?? []),
|
|
@@ -1370,6 +1563,7 @@ async function serveImpl(
|
|
|
1370
1563
|
error: errorFn,
|
|
1371
1564
|
format,
|
|
1372
1565
|
formatExplicit,
|
|
1566
|
+
globals,
|
|
1373
1567
|
name,
|
|
1374
1568
|
set(key: string, value: unknown) {
|
|
1375
1569
|
varsMap[key] = value
|
|
@@ -1420,7 +1614,9 @@ async function serveImpl(
|
|
|
1420
1614
|
return
|
|
1421
1615
|
}
|
|
1422
1616
|
|
|
1423
|
-
const { command, path, rest } = effective
|
|
1617
|
+
const { command, path, rest: commandRest } = effective
|
|
1618
|
+
|
|
1619
|
+
if (!parseGlobalOptions(true)) return
|
|
1424
1620
|
|
|
1425
1621
|
// Collect middleware: root CLI + groups traversed + per-command
|
|
1426
1622
|
const allMiddleware = [
|
|
@@ -1433,7 +1629,7 @@ async function serveImpl(
|
|
|
1433
1629
|
|
|
1434
1630
|
if (human)
|
|
1435
1631
|
emitDeprecationWarnings(
|
|
1436
|
-
|
|
1632
|
+
commandRest,
|
|
1437
1633
|
command.options,
|
|
1438
1634
|
command.alias as Record<string, string> | undefined,
|
|
1439
1635
|
)
|
|
@@ -1463,13 +1659,14 @@ async function serveImpl(
|
|
|
1463
1659
|
|
|
1464
1660
|
const result = await Command.execute(command, {
|
|
1465
1661
|
agent: !human,
|
|
1466
|
-
argv:
|
|
1662
|
+
argv: commandRest,
|
|
1467
1663
|
defaults,
|
|
1468
1664
|
displayName,
|
|
1469
1665
|
env: options.envSchema,
|
|
1470
1666
|
envSource: options.env,
|
|
1471
1667
|
format,
|
|
1472
1668
|
formatExplicit,
|
|
1669
|
+
globals,
|
|
1473
1670
|
inputOptions: {},
|
|
1474
1671
|
middlewares: allMiddleware,
|
|
1475
1672
|
name,
|
|
@@ -1558,6 +1755,8 @@ declare namespace fetchImpl {
|
|
|
1558
1755
|
description?: string | undefined
|
|
1559
1756
|
/** CLI-level env schema. */
|
|
1560
1757
|
envSchema?: z.ZodObject<any> | undefined
|
|
1758
|
+
/** Global options schema and alias map. */
|
|
1759
|
+
globals?: GlobalsDescriptor | undefined
|
|
1561
1760
|
/** Group-level middleware collected during command resolution. */
|
|
1562
1761
|
groupMiddlewares?: MiddlewareHandler[] | undefined
|
|
1563
1762
|
mcpHandler?:
|
|
@@ -1582,7 +1781,11 @@ declare namespace fetchImpl {
|
|
|
1582
1781
|
}
|
|
1583
1782
|
|
|
1584
1783
|
/** @internal Creates a lazy MCP HTTP handler scoped to a CLI instance. */
|
|
1585
|
-
function createMcpHttpHandler(
|
|
1784
|
+
function createMcpHttpHandler(
|
|
1785
|
+
name: string,
|
|
1786
|
+
version: string,
|
|
1787
|
+
options: createMcpHttpHandler.Options = {},
|
|
1788
|
+
) {
|
|
1586
1789
|
let transport: any
|
|
1587
1790
|
|
|
1588
1791
|
return async (
|
|
@@ -1594,8 +1797,12 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1594
1797
|
vars?: z.ZodObject<any> | undefined
|
|
1595
1798
|
},
|
|
1596
1799
|
): Promise<Response> => {
|
|
1800
|
+
const stateless = options.stateless ?? true
|
|
1801
|
+
if (stateless && req.method !== 'POST')
|
|
1802
|
+
return new Response(null, { status: 405, headers: { Allow: 'POST' } })
|
|
1803
|
+
|
|
1597
1804
|
if (!transport) {
|
|
1598
|
-
const { McpServer, WebStandardStreamableHTTPServerTransport } =
|
|
1805
|
+
const { fromJsonSchema, McpServer, WebStandardStreamableHTTPServerTransport } =
|
|
1599
1806
|
await import('@modelcontextprotocol/server')
|
|
1600
1807
|
|
|
1601
1808
|
const server = new McpServer({ name, version })
|
|
@@ -1612,6 +1819,9 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1612
1819
|
{
|
|
1613
1820
|
...(tool.description ? { description: tool.description } : undefined),
|
|
1614
1821
|
...(hasInput ? { inputSchema: z.object(mergedShape) } : undefined),
|
|
1822
|
+
...(tool.outputSchema
|
|
1823
|
+
? { outputSchema: fromJsonSchema(tool.outputSchema) }
|
|
1824
|
+
: undefined),
|
|
1615
1825
|
},
|
|
1616
1826
|
async (...callArgs: any[]) => {
|
|
1617
1827
|
const params = hasInput ? (callArgs[0] as Record<string, unknown>) : {}
|
|
@@ -1626,16 +1836,26 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1626
1836
|
)
|
|
1627
1837
|
}
|
|
1628
1838
|
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1839
|
+
const transportOptions = stateless
|
|
1840
|
+
? { enableJsonResponse: true }
|
|
1841
|
+
: {
|
|
1842
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
1843
|
+
enableJsonResponse: true,
|
|
1844
|
+
}
|
|
1845
|
+
transport = new WebStandardStreamableHTTPServerTransport(transportOptions)
|
|
1633
1846
|
await server.connect(transport)
|
|
1634
1847
|
}
|
|
1635
1848
|
return transport.handleRequest(req)
|
|
1636
1849
|
}
|
|
1637
1850
|
}
|
|
1638
1851
|
|
|
1852
|
+
declare namespace createMcpHttpHandler {
|
|
1853
|
+
type Options = {
|
|
1854
|
+
/** Disable HTTP MCP session management. Defaults to `true`. */
|
|
1855
|
+
stateless?: boolean | undefined
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1639
1859
|
function isOpenapiRoute(segments: string[]) {
|
|
1640
1860
|
if (segments.length === 1)
|
|
1641
1861
|
return (
|
|
@@ -1677,7 +1897,7 @@ async function fetchImpl(
|
|
|
1677
1897
|
if (req.method === 'GET' && isOpenapiRoute(segments)) {
|
|
1678
1898
|
const spec = generatedOpenapi(name, commands, options)
|
|
1679
1899
|
const yaml = segments[0] === 'openapi.yml' || segments[0] === 'openapi.yaml'
|
|
1680
|
-
return new Response(yaml ?
|
|
1900
|
+
return new Response(yaml ? (await Yaml.load()).stringify(spec) : JSON.stringify(spec), {
|
|
1681
1901
|
status: 200,
|
|
1682
1902
|
headers: {
|
|
1683
1903
|
'content-type': yaml ? 'application/yaml' : 'application/json',
|
|
@@ -1701,6 +1921,8 @@ async function fetchImpl(
|
|
|
1701
1921
|
segments.length >= 3 &&
|
|
1702
1922
|
req.method === 'GET'
|
|
1703
1923
|
) {
|
|
1924
|
+
// Pre-load yaml for the sync call paths below (`Skill.split`, frontmatter parsing).
|
|
1925
|
+
await Yaml.load()
|
|
1704
1926
|
const groups = new Map<string, string>()
|
|
1705
1927
|
const cmds = collectSkillCommands(commands, [], groups, options.rootCommand)
|
|
1706
1928
|
|
|
@@ -1709,7 +1931,7 @@ async function fetchImpl(
|
|
|
1709
1931
|
const files = Skill.split(name, cmds, 1, groups)
|
|
1710
1932
|
const skills = files.map((f) => {
|
|
1711
1933
|
const fmMatch = f.content.match(/^---\n([\s\S]*?)\n---/)
|
|
1712
|
-
const meta = fmMatch ? (
|
|
1934
|
+
const meta = fmMatch ? (Yaml.loadSync().parse(fmMatch[1]!) as Record<string, string>) : {}
|
|
1713
1935
|
return {
|
|
1714
1936
|
name: f.dir || name,
|
|
1715
1937
|
description: meta.description ?? '',
|
|
@@ -1750,7 +1972,7 @@ async function fetchImpl(
|
|
|
1750
1972
|
}
|
|
1751
1973
|
|
|
1752
1974
|
function jsonResponse(body: unknown, status: number) {
|
|
1753
|
-
return new Response(
|
|
1975
|
+
return new Response(Json.stringify(body), {
|
|
1754
1976
|
status,
|
|
1755
1977
|
headers: { 'content-type': 'application/json' },
|
|
1756
1978
|
})
|
|
@@ -1823,7 +2045,7 @@ async function executeCommand(
|
|
|
1823
2045
|
options: fetchImpl.Options,
|
|
1824
2046
|
): Promise<Response> {
|
|
1825
2047
|
function jsonResponse(body: unknown, status: number) {
|
|
1826
|
-
return new Response(
|
|
2048
|
+
return new Response(Json.stringify(body), {
|
|
1827
2049
|
status,
|
|
1828
2050
|
headers: { 'content-type': 'application/json' },
|
|
1829
2051
|
})
|
|
@@ -1835,13 +2057,40 @@ async function executeCommand(
|
|
|
1835
2057
|
...((command.middleware as MiddlewareHandler[] | undefined) ?? []),
|
|
1836
2058
|
]
|
|
1837
2059
|
|
|
2060
|
+
let globals: Record<string, unknown> = {}
|
|
2061
|
+
let commandInputOptions = inputOptions
|
|
2062
|
+
if (options.globals) {
|
|
2063
|
+
const globalKeys = new Set(Object.keys(options.globals.schema.shape))
|
|
2064
|
+
const rawGlobals: Record<string, unknown> = {}
|
|
2065
|
+
commandInputOptions = {}
|
|
2066
|
+
for (const [key, value] of Object.entries(inputOptions)) {
|
|
2067
|
+
if (globalKeys.has(key)) rawGlobals[key] = value
|
|
2068
|
+
else commandInputOptions[key] = value
|
|
2069
|
+
}
|
|
2070
|
+
try {
|
|
2071
|
+
globals = options.globals.schema.parse(rawGlobals)
|
|
2072
|
+
} catch (error: any) {
|
|
2073
|
+
const issues: any[] = error?.issues ?? error?.error?.issues ?? []
|
|
2074
|
+
const message = issues.map((i: any) => i.message).join('; ') || 'Validation failed'
|
|
2075
|
+
return jsonResponse(
|
|
2076
|
+
{
|
|
2077
|
+
ok: false,
|
|
2078
|
+
error: { code: 'VALIDATION_ERROR', message },
|
|
2079
|
+
meta: { command: path, duration: `${Math.round(performance.now() - start)}ms` },
|
|
2080
|
+
},
|
|
2081
|
+
400,
|
|
2082
|
+
)
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
|
|
1838
2086
|
const result = await Command.execute(command, {
|
|
1839
2087
|
agent: true,
|
|
1840
2088
|
argv: rest,
|
|
1841
2089
|
env: options.envSchema,
|
|
1842
2090
|
format: 'json',
|
|
1843
2091
|
formatExplicit: true,
|
|
1844
|
-
|
|
2092
|
+
globals,
|
|
2093
|
+
inputOptions: commandInputOptions,
|
|
1845
2094
|
middlewares: allMiddleware,
|
|
1846
2095
|
name: options.name ?? path,
|
|
1847
2096
|
parseMode: 'split',
|
|
@@ -1854,39 +2103,78 @@ async function executeCommand(
|
|
|
1854
2103
|
|
|
1855
2104
|
// Streaming path — async generator → NDJSON response
|
|
1856
2105
|
if ('stream' in result) {
|
|
2106
|
+
const iterator = result.stream
|
|
2107
|
+
const encoder = new TextEncoder()
|
|
2108
|
+
const meta = (cta?: FormattedCtaBlock | undefined) => ({
|
|
2109
|
+
command: path,
|
|
2110
|
+
duration: `${Math.round(performance.now() - start)}ms`,
|
|
2111
|
+
...(cta ? { cta } : undefined),
|
|
2112
|
+
})
|
|
2113
|
+
const errorRecord = (err: ErrorResult) => ({
|
|
2114
|
+
type: 'error',
|
|
2115
|
+
ok: false,
|
|
2116
|
+
error: {
|
|
2117
|
+
code: err.code,
|
|
2118
|
+
message: err.message,
|
|
2119
|
+
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
2120
|
+
},
|
|
2121
|
+
meta: meta(formatCtaBlock(options.name ?? path, err.cta)),
|
|
2122
|
+
})
|
|
1857
2123
|
const stream = new ReadableStream({
|
|
1858
|
-
async
|
|
1859
|
-
|
|
2124
|
+
async cancel() {
|
|
2125
|
+
await iterator.return(undefined)
|
|
2126
|
+
},
|
|
2127
|
+
async pull(controller) {
|
|
1860
2128
|
try {
|
|
1861
|
-
|
|
2129
|
+
const { value, done } = await iterator.next()
|
|
2130
|
+
if (done) {
|
|
2131
|
+
if (isSentinel(value) && value[sentinel] === 'error') {
|
|
2132
|
+
controller.enqueue(encoder.encode(Json.stringify(errorRecord(value)) + '\n'))
|
|
2133
|
+
controller.close()
|
|
2134
|
+
return
|
|
2135
|
+
}
|
|
2136
|
+
const cta =
|
|
2137
|
+
isSentinel(value) && value[sentinel] === 'ok'
|
|
2138
|
+
? formatCtaBlock(options.name ?? path, value.cta)
|
|
2139
|
+
: undefined
|
|
1862
2140
|
controller.enqueue(
|
|
1863
|
-
encoder.encode(
|
|
2141
|
+
encoder.encode(
|
|
2142
|
+
Json.stringify({
|
|
2143
|
+
type: 'done',
|
|
2144
|
+
ok: true,
|
|
2145
|
+
meta: meta(cta),
|
|
2146
|
+
}) + '\n',
|
|
2147
|
+
),
|
|
1864
2148
|
)
|
|
2149
|
+
controller.close()
|
|
2150
|
+
return
|
|
1865
2151
|
}
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
)
|
|
2152
|
+
|
|
2153
|
+
if (isSentinel(value) && value[sentinel] === 'error') {
|
|
2154
|
+
controller.enqueue(encoder.encode(Json.stringify(errorRecord(value)) + '\n'))
|
|
2155
|
+
await iterator.return(undefined)
|
|
2156
|
+
controller.close()
|
|
2157
|
+
return
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
controller.enqueue(encoder.encode(Json.stringify({ type: 'chunk', data: value }) + '\n'))
|
|
1875
2161
|
} catch (error) {
|
|
1876
2162
|
controller.enqueue(
|
|
1877
2163
|
encoder.encode(
|
|
1878
|
-
|
|
2164
|
+
Json.stringify({
|
|
1879
2165
|
type: 'error',
|
|
1880
2166
|
ok: false,
|
|
1881
2167
|
error: {
|
|
1882
|
-
code: 'UNKNOWN',
|
|
2168
|
+
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
1883
2169
|
message: error instanceof Error ? error.message : String(error),
|
|
2170
|
+
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
1884
2171
|
},
|
|
2172
|
+
meta: meta(),
|
|
1885
2173
|
}) + '\n',
|
|
1886
2174
|
),
|
|
1887
2175
|
)
|
|
2176
|
+
controller.close()
|
|
1888
2177
|
}
|
|
1889
|
-
controller.close()
|
|
1890
2178
|
},
|
|
1891
2179
|
})
|
|
1892
2180
|
return new Response(stream, {
|
|
@@ -1906,6 +2194,7 @@ async function executeCommand(
|
|
|
1906
2194
|
...(result.error.retryable !== undefined
|
|
1907
2195
|
? { retryable: result.error.retryable }
|
|
1908
2196
|
: undefined),
|
|
2197
|
+
...(result.error.fieldErrors ? { fieldErrors: result.error.fieldErrors } : undefined),
|
|
1909
2198
|
},
|
|
1910
2199
|
meta: {
|
|
1911
2200
|
command: path,
|
|
@@ -2107,6 +2396,8 @@ declare namespace serveImpl {
|
|
|
2107
2396
|
envSchema?: z.ZodObject<any> | undefined
|
|
2108
2397
|
/** CLI-level default output format. */
|
|
2109
2398
|
format?: Formatter.Format | undefined
|
|
2399
|
+
/** Global options schema and alias map. */
|
|
2400
|
+
globals?: GlobalsDescriptor | undefined
|
|
2110
2401
|
/** Middleware handlers registered on the root CLI. */
|
|
2111
2402
|
middlewares?: MiddlewareHandler[] | undefined
|
|
2112
2403
|
/** CLI-level default output policy. */
|
|
@@ -2115,6 +2406,16 @@ declare namespace serveImpl {
|
|
|
2115
2406
|
| {
|
|
2116
2407
|
agents?: string[] | undefined
|
|
2117
2408
|
command?: string | undefined
|
|
2409
|
+
instructions?: string | undefined
|
|
2410
|
+
stateless?: boolean | undefined
|
|
2411
|
+
}
|
|
2412
|
+
| undefined
|
|
2413
|
+
/** Banner config, called before root help. */
|
|
2414
|
+
banner?:
|
|
2415
|
+
| (() => string | undefined | Promise<string | undefined>)
|
|
2416
|
+
| {
|
|
2417
|
+
render: () => string | undefined | Promise<string | undefined>
|
|
2418
|
+
mode?: 'all' | 'human' | 'agent' | undefined
|
|
2118
2419
|
}
|
|
2119
2420
|
| undefined
|
|
2120
2421
|
/** Root command handler, invoked when no subcommand matches. */
|
|
@@ -2421,6 +2722,136 @@ function formatBuiltinSubcommandHelp(
|
|
|
2421
2722
|
})
|
|
2422
2723
|
}
|
|
2423
2724
|
|
|
2725
|
+
type McpDoctorResult = {
|
|
2726
|
+
ok: boolean
|
|
2727
|
+
toolCount: number
|
|
2728
|
+
tools: { name: string; description?: string | undefined }[]
|
|
2729
|
+
warnings: string[]
|
|
2730
|
+
errors: { code: string; message: string }[]
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2733
|
+
async function runMcpDoctor(
|
|
2734
|
+
name: string,
|
|
2735
|
+
commands: Map<string, CommandEntry>,
|
|
2736
|
+
options: serveImpl.Options,
|
|
2737
|
+
): Promise<McpDoctorResult> {
|
|
2738
|
+
const warnings: string[] = []
|
|
2739
|
+
const errors: McpDoctorResult['errors'] = []
|
|
2740
|
+
const input = new PassThrough()
|
|
2741
|
+
const output = new PassThrough()
|
|
2742
|
+
const chunks: string[] = []
|
|
2743
|
+
output.on('data', (chunk) => chunks.push(chunk.toString()))
|
|
2744
|
+
|
|
2745
|
+
let serveError: unknown
|
|
2746
|
+
const done = Mcp.serve(name, options.version ?? '0.0.0', commands, {
|
|
2747
|
+
input,
|
|
2748
|
+
output,
|
|
2749
|
+
middlewares: options.middlewares,
|
|
2750
|
+
env: options.envSchema,
|
|
2751
|
+
vars: options.vars,
|
|
2752
|
+
version: options.version,
|
|
2753
|
+
...(options.mcp?.instructions ? { instructions: options.mcp.instructions } : undefined),
|
|
2754
|
+
}).catch((error) => {
|
|
2755
|
+
serveError = error
|
|
2756
|
+
})
|
|
2757
|
+
|
|
2758
|
+
input.write(
|
|
2759
|
+
`${Json.stringify({
|
|
2760
|
+
jsonrpc: '2.0',
|
|
2761
|
+
id: 1,
|
|
2762
|
+
method: 'initialize',
|
|
2763
|
+
params: {
|
|
2764
|
+
protocolVersion: '2024-11-05',
|
|
2765
|
+
capabilities: {},
|
|
2766
|
+
clientInfo: { name: 'incur-doctor', version: '1.0.0' },
|
|
2767
|
+
},
|
|
2768
|
+
})}\n`,
|
|
2769
|
+
)
|
|
2770
|
+
input.write(`${Json.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/list', params: {} })}\n`)
|
|
2771
|
+
await new Promise((resolve) => setTimeout(resolve, 20))
|
|
2772
|
+
input.end()
|
|
2773
|
+
await done
|
|
2774
|
+
|
|
2775
|
+
if (serveError)
|
|
2776
|
+
return {
|
|
2777
|
+
ok: false,
|
|
2778
|
+
toolCount: 0,
|
|
2779
|
+
tools: [],
|
|
2780
|
+
warnings,
|
|
2781
|
+
errors: [{ code: 'MCP_SERVER_FAILED', message: errorMessage(serveError) }],
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
let responses: Record<string, unknown>[]
|
|
2785
|
+
try {
|
|
2786
|
+
responses = parseMcpDoctorResponses(chunks)
|
|
2787
|
+
} catch (error) {
|
|
2788
|
+
return {
|
|
2789
|
+
ok: false,
|
|
2790
|
+
toolCount: 0,
|
|
2791
|
+
tools: [],
|
|
2792
|
+
warnings,
|
|
2793
|
+
errors: [{ code: 'MCP_RESPONSE_PARSE_FAILED', message: errorMessage(error) }],
|
|
2794
|
+
}
|
|
2795
|
+
}
|
|
2796
|
+
|
|
2797
|
+
const initialize = responses.find((response) => response.id === 1)
|
|
2798
|
+
if (!initialize)
|
|
2799
|
+
errors.push({ code: 'MCP_INITIALIZE_MISSING', message: 'Missing initialize response.' })
|
|
2800
|
+
else if (initialize.error)
|
|
2801
|
+
errors.push({ code: 'MCP_INITIALIZE_FAILED', message: mcpErrorMessage(initialize.error) })
|
|
2802
|
+
|
|
2803
|
+
const toolsList = responses.find((response) => response.id === 2)
|
|
2804
|
+
let tools: McpDoctorResult['tools'] = []
|
|
2805
|
+
if (!toolsList)
|
|
2806
|
+
errors.push({ code: 'MCP_TOOLS_LIST_MISSING', message: 'Missing tools/list response.' })
|
|
2807
|
+
else if (toolsList.error)
|
|
2808
|
+
errors.push({ code: 'MCP_TOOLS_LIST_FAILED', message: mcpErrorMessage(toolsList.error) })
|
|
2809
|
+
else if (!isRecord(toolsList.result) || !Array.isArray(toolsList.result.tools))
|
|
2810
|
+
errors.push({
|
|
2811
|
+
code: 'MCP_TOOLS_LIST_INVALID',
|
|
2812
|
+
message: 'tools/list did not return a tools array.',
|
|
2813
|
+
})
|
|
2814
|
+
else
|
|
2815
|
+
tools = toolsList.result.tools
|
|
2816
|
+
.filter(isRecord)
|
|
2817
|
+
.map((tool) => ({
|
|
2818
|
+
name: typeof tool.name === 'string' ? tool.name : '',
|
|
2819
|
+
...(typeof tool.description === 'string' ? { description: tool.description } : undefined),
|
|
2820
|
+
}))
|
|
2821
|
+
.filter((tool) => tool.name)
|
|
2822
|
+
|
|
2823
|
+
if (errors.length === 0 && tools.length === 0) warnings.push('No MCP tools exposed.')
|
|
2824
|
+
|
|
2825
|
+
return {
|
|
2826
|
+
ok: errors.length === 0,
|
|
2827
|
+
toolCount: tools.length,
|
|
2828
|
+
tools,
|
|
2829
|
+
warnings,
|
|
2830
|
+
errors,
|
|
2831
|
+
}
|
|
2832
|
+
}
|
|
2833
|
+
|
|
2834
|
+
function parseMcpDoctorResponses(chunks: string[]): Record<string, unknown>[] {
|
|
2835
|
+
const responses: Record<string, unknown>[] = []
|
|
2836
|
+
for (const line of chunks.join('').split(/\r?\n/)) {
|
|
2837
|
+
const trimmed = line.trim()
|
|
2838
|
+
if (!trimmed) continue
|
|
2839
|
+
const parsed = JSON.parse(trimmed)
|
|
2840
|
+
if (!isRecord(parsed)) throw new Error('Expected JSON-RPC response object.')
|
|
2841
|
+
responses.push(parsed)
|
|
2842
|
+
}
|
|
2843
|
+
return responses
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
function errorMessage(error: unknown): string {
|
|
2847
|
+
return error instanceof Error ? error.message : String(error)
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
function mcpErrorMessage(error: unknown): string {
|
|
2851
|
+
if (isRecord(error) && typeof error.message === 'string') return error.message
|
|
2852
|
+
return Json.stringify(error)
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2424
2855
|
/** @internal Formats help text for a fetch gateway command. */
|
|
2425
2856
|
function formatFetchHelp(name: string, description?: string): string {
|
|
2426
2857
|
const lines: string[] = []
|
|
@@ -2528,6 +2959,41 @@ function resolveAlias(
|
|
|
2528
2959
|
return entry
|
|
2529
2960
|
}
|
|
2530
2961
|
|
|
2962
|
+
/** @internal Validates command options against CLI-level global options. */
|
|
2963
|
+
function assertNoGlobalOptionConflicts(
|
|
2964
|
+
path: string,
|
|
2965
|
+
entry: CommandEntry,
|
|
2966
|
+
globals: GlobalsDescriptor | undefined,
|
|
2967
|
+
) {
|
|
2968
|
+
if (!globals || isFetchGateway(entry) || isAlias(entry)) return
|
|
2969
|
+
if (isGroup(entry)) {
|
|
2970
|
+
for (const [name, child] of entry.commands)
|
|
2971
|
+
assertNoGlobalOptionConflicts(`${path} ${name}`, child, globals)
|
|
2972
|
+
return
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
if (entry.options) {
|
|
2976
|
+
const globalKeys = Object.keys(globals.schema.shape)
|
|
2977
|
+
const optionKeys = Object.keys(entry.options.shape)
|
|
2978
|
+
for (const key of optionKeys) {
|
|
2979
|
+
if (globalKeys.includes(key))
|
|
2980
|
+
throw new Error(
|
|
2981
|
+
`Command '${path}' option '${key}' conflicts with a global option. Choose a different name.`,
|
|
2982
|
+
)
|
|
2983
|
+
}
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
if (globals.alias && entry.alias) {
|
|
2987
|
+
const globalAliasValues = new Set(Object.values(globals.alias))
|
|
2988
|
+
for (const [name, short] of Object.entries(entry.alias)) {
|
|
2989
|
+
if (short && globalAliasValues.has(short))
|
|
2990
|
+
throw new Error(
|
|
2991
|
+
`Command '${path}' alias '-${short}' for '${name}' conflicts with a global alias. Choose a different alias.`,
|
|
2992
|
+
)
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
|
|
2531
2997
|
/** @internal Maps CLI instances to their command maps. */
|
|
2532
2998
|
export const toCommands = new WeakMap<Cli, Map<string, CommandEntry>>()
|
|
2533
2999
|
|
|
@@ -2546,6 +3012,15 @@ export const toConfigEnabled = new WeakMap<Cli, boolean>()
|
|
|
2546
3012
|
/** @internal Maps CLI instances to their output policy. */
|
|
2547
3013
|
const toOutputPolicy = new WeakMap<Cli, OutputPolicy>()
|
|
2548
3014
|
|
|
3015
|
+
/** Descriptor for a CLI's custom global options schema and aliases. */
|
|
3016
|
+
export type GlobalsDescriptor = {
|
|
3017
|
+
schema: z.ZodObject<any>
|
|
3018
|
+
alias?: Record<string, string> | undefined
|
|
3019
|
+
}
|
|
3020
|
+
|
|
3021
|
+
/** @internal Maps CLI instances to their globals schema and alias map. */
|
|
3022
|
+
const toGlobals = new WeakMap<Cli, GlobalsDescriptor>()
|
|
3023
|
+
|
|
2549
3024
|
/** @internal Maps root CLI instances to their command aliases. */
|
|
2550
3025
|
const toRootAliases = new WeakMap<Root, string[]>()
|
|
2551
3026
|
|
|
@@ -2667,7 +3142,7 @@ async function handleStreaming(
|
|
|
2667
3142
|
return
|
|
2668
3143
|
}
|
|
2669
3144
|
}
|
|
2670
|
-
if (useJsonl) ctx.writeln(
|
|
3145
|
+
if (useJsonl) ctx.writeln(Json.stringify({ type: 'chunk', data: value }))
|
|
2671
3146
|
else if (ctx.renderOutput)
|
|
2672
3147
|
ctx.writeln(ctx.truncate(Formatter.format(value, ctx.format)).text)
|
|
2673
3148
|
}
|
|
@@ -2719,6 +3194,7 @@ async function handleStreaming(
|
|
|
2719
3194
|
error: {
|
|
2720
3195
|
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
2721
3196
|
message: error instanceof Error ? error.message : String(error),
|
|
3197
|
+
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
2722
3198
|
},
|
|
2723
3199
|
}),
|
|
2724
3200
|
)
|
|
@@ -2802,6 +3278,7 @@ async function handleStreaming(
|
|
|
2802
3278
|
error: {
|
|
2803
3279
|
code: error instanceof IncurError ? error.code : 'UNKNOWN',
|
|
2804
3280
|
message: error instanceof Error ? error.message : String(error),
|
|
3281
|
+
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
2805
3282
|
},
|
|
2806
3283
|
meta: {
|
|
2807
3284
|
command: ctx.path,
|
|
@@ -2813,36 +3290,16 @@ async function handleStreaming(
|
|
|
2813
3290
|
}
|
|
2814
3291
|
}
|
|
2815
3292
|
|
|
2816
|
-
/** @internal Formats a CTA block into the output envelope shape. */
|
|
2817
|
-
function formatCtaBlock(name: string, block: CtaBlock | undefined): FormattedCtaBlock | undefined {
|
|
2818
|
-
if (!block || block.commands.length === 0) return undefined
|
|
2819
|
-
return {
|
|
2820
|
-
description:
|
|
2821
|
-
block.description ??
|
|
2822
|
-
(block.commands.length === 1 ? 'Suggested command:' : 'Suggested commands:'),
|
|
2823
|
-
commands: block.commands.map((c) => formatCta(name, c)),
|
|
2824
|
-
}
|
|
2825
|
-
}
|
|
2826
|
-
|
|
2827
|
-
/** @internal Formats a CTA by prefixing the CLI name. Handles string and object forms. */
|
|
2828
|
-
function formatCta(name: string, cta: Cta): FormattedCta {
|
|
2829
|
-
if (typeof cta === 'string') return { command: `${name} ${cta}` }
|
|
2830
|
-
const prefix = cta.command === name || cta.command.startsWith(`${name} `) ? '' : `${name} `
|
|
2831
|
-
let cmd = `${prefix}${cta.command}`
|
|
2832
|
-
if (cta.args)
|
|
2833
|
-
for (const [key, value] of Object.entries(cta.args))
|
|
2834
|
-
cmd += value === true ? ` <${key}>` : ` ${value}`
|
|
2835
|
-
if (cta.options)
|
|
2836
|
-
for (const [key, value] of Object.entries(cta.options))
|
|
2837
|
-
cmd += value === true ? ` --${key} <${key}>` : ` --${key} ${value}`
|
|
2838
|
-
return { command: cmd, ...(cta.description ? { description: cta.description } : undefined) }
|
|
2839
|
-
}
|
|
2840
|
-
|
|
2841
3293
|
/** @internal Builds the `--llms` index manifest (name + description only) from the command tree. */
|
|
2842
|
-
function buildIndexManifest(
|
|
3294
|
+
function buildIndexManifest(
|
|
3295
|
+
commands: Map<string, CommandEntry>,
|
|
3296
|
+
prefix: string[] = [],
|
|
3297
|
+
globalsSchema?: z.ZodObject<any>,
|
|
3298
|
+
) {
|
|
2843
3299
|
return {
|
|
2844
3300
|
version: 'incur.v1',
|
|
2845
3301
|
commands: collectIndexCommands(commands, prefix).sort((a, b) => a.name.localeCompare(b.name)),
|
|
3302
|
+
...(globalsSchema ? { globals: Schema.toJsonSchema(globalsSchema) } : undefined),
|
|
2846
3303
|
}
|
|
2847
3304
|
}
|
|
2848
3305
|
|
|
@@ -2869,10 +3326,15 @@ function collectIndexCommands(
|
|
|
2869
3326
|
}
|
|
2870
3327
|
|
|
2871
3328
|
/** @internal Builds the `--llms` manifest from the command tree. */
|
|
2872
|
-
function buildManifest(
|
|
3329
|
+
function buildManifest(
|
|
3330
|
+
commands: Map<string, CommandEntry>,
|
|
3331
|
+
prefix: string[] = [],
|
|
3332
|
+
globalsSchema?: z.ZodObject<any>,
|
|
3333
|
+
) {
|
|
2873
3334
|
return {
|
|
2874
3335
|
version: 'incur.v1',
|
|
2875
3336
|
commands: collectCommands(commands, prefix).sort((a, b) => a.name.localeCompare(b.name)),
|
|
3337
|
+
...(globalsSchema ? { globals: Schema.toJsonSchema(globalsSchema) } : undefined),
|
|
2876
3338
|
}
|
|
2877
3339
|
}
|
|
2878
3340
|
|
|
@@ -2925,11 +3387,11 @@ function collectCommands(
|
|
|
2925
3387
|
}
|
|
2926
3388
|
|
|
2927
3389
|
/** @internal Recursively collects leaf commands as `Skill.CommandInfo` for `--llms --format md`. */
|
|
2928
|
-
function collectSkillCommands(
|
|
3390
|
+
export function collectSkillCommands(
|
|
2929
3391
|
commands: Map<string, CommandEntry>,
|
|
2930
3392
|
prefix: string[],
|
|
2931
3393
|
groups: Map<string, string>,
|
|
2932
|
-
rootCommand?:
|
|
3394
|
+
rootCommand?: SkillCommandSource | undefined,
|
|
2933
3395
|
): Skill.CommandInfo[] {
|
|
2934
3396
|
const result: Skill.CommandInfo[] = []
|
|
2935
3397
|
if (rootCommand) {
|
|
@@ -2938,6 +3400,7 @@ function collectSkillCommands(
|
|
|
2938
3400
|
if (rootCommand.args) cmd.args = rootCommand.args
|
|
2939
3401
|
if (rootCommand.env) cmd.env = rootCommand.env
|
|
2940
3402
|
if (rootCommand.hint) cmd.hint = rootCommand.hint
|
|
3403
|
+
if (isDestructive(rootCommand)) cmd.hint = appendDestructiveHint(cmd.hint)
|
|
2941
3404
|
if (rootCommand.options) cmd.options = rootCommand.options
|
|
2942
3405
|
if (rootCommand.output) cmd.output = rootCommand.output
|
|
2943
3406
|
const examples = formatExamples(rootCommand.examples)
|
|
@@ -2961,6 +3424,7 @@ function collectSkillCommands(
|
|
|
2961
3424
|
if (entry.args) cmd.args = entry.args
|
|
2962
3425
|
if (entry.env) cmd.env = entry.env
|
|
2963
3426
|
if (entry.hint) cmd.hint = entry.hint
|
|
3427
|
+
if (isDestructive(entry)) cmd.hint = appendDestructiveHint(cmd.hint)
|
|
2964
3428
|
if (entry.options) cmd.options = entry.options
|
|
2965
3429
|
if (entry.output) cmd.output = entry.output
|
|
2966
3430
|
const examples = formatExamples(entry.examples)
|
|
@@ -2977,6 +3441,29 @@ function collectSkillCommands(
|
|
|
2977
3441
|
return result.sort((a, b) => (a.name ?? '').localeCompare(b.name ?? ''))
|
|
2978
3442
|
}
|
|
2979
3443
|
|
|
3444
|
+
type SkillCommandSource = Pick<
|
|
3445
|
+
CommandDefinition<any, any, any, any, any, any>,
|
|
3446
|
+
| 'args'
|
|
3447
|
+
| 'description'
|
|
3448
|
+
| 'destructive'
|
|
3449
|
+
| 'env'
|
|
3450
|
+
| 'examples'
|
|
3451
|
+
| 'hint'
|
|
3452
|
+
| 'mcp'
|
|
3453
|
+
| 'options'
|
|
3454
|
+
| 'output'
|
|
3455
|
+
>
|
|
3456
|
+
|
|
3457
|
+
function isDestructive(command: SkillCommandSource): boolean {
|
|
3458
|
+
return command.destructive === true || command.mcp?.annotations?.destructiveHint === true
|
|
3459
|
+
}
|
|
3460
|
+
|
|
3461
|
+
function appendDestructiveHint(hint: string | undefined): string {
|
|
3462
|
+
if (!hint) return destructiveCommandHint
|
|
3463
|
+
if (hint.includes(destructiveCommandHint)) return hint
|
|
3464
|
+
return `${hint} ${destructiveCommandHint}`
|
|
3465
|
+
}
|
|
3466
|
+
|
|
2980
3467
|
/** @internal Formats examples into `{ command, description }` objects. `command` is the args/options suffix only. */
|
|
2981
3468
|
export function formatExamples(
|
|
2982
3469
|
examples: Example<any, any>[] | undefined,
|
|
@@ -2993,6 +3480,18 @@ export function formatExamples(
|
|
|
2993
3480
|
})
|
|
2994
3481
|
}
|
|
2995
3482
|
|
|
3483
|
+
/** @internal Parses YAML frontmatter from generated skill Markdown. */
|
|
3484
|
+
export function parseSkillFrontmatter(content: string): {
|
|
3485
|
+
description?: string | undefined
|
|
3486
|
+
name?: string | undefined
|
|
3487
|
+
} {
|
|
3488
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
3489
|
+
if (!match) return {}
|
|
3490
|
+
const meta = Yaml.loadSync().parse(match[1]!)
|
|
3491
|
+
if (!meta || typeof meta !== 'object') return {}
|
|
3492
|
+
return meta as { description?: string | undefined; name?: string | undefined }
|
|
3493
|
+
}
|
|
3494
|
+
|
|
2996
3495
|
/** @internal Builds separate args, env, and options JSON Schemas. */
|
|
2997
3496
|
function buildInputSchema(
|
|
2998
3497
|
args: z.ZodObject<any> | undefined,
|
|
@@ -3121,12 +3620,27 @@ type CommandDefinition<
|
|
|
3121
3620
|
args?: args | undefined
|
|
3122
3621
|
/** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
|
|
3123
3622
|
env?: env | undefined
|
|
3623
|
+
/** Marks this command as destructive when generating agent skills. */
|
|
3624
|
+
destructive?: boolean | undefined
|
|
3124
3625
|
/** Usage examples for this command. */
|
|
3125
3626
|
examples?: Example<args, options>[] | undefined
|
|
3126
3627
|
/** Default output format. Overridden by `--format` or `--json`. */
|
|
3127
3628
|
format?: Formatter.Format | undefined
|
|
3128
3629
|
/** Plain text hint displayed after examples and before global options. */
|
|
3129
3630
|
hint?: string | undefined
|
|
3631
|
+
/** MCP-specific metadata exposed when this command is served as a tool. */
|
|
3632
|
+
mcp?:
|
|
3633
|
+
| {
|
|
3634
|
+
/** Override the command name exposed to MCP clients. */
|
|
3635
|
+
name?: string | undefined
|
|
3636
|
+
/** Override the command description exposed to MCP clients. */
|
|
3637
|
+
description?: string | undefined
|
|
3638
|
+
/** MCP tool annotations that describe tool behavior to clients. */
|
|
3639
|
+
annotations?: Mcp.ToolAnnotations | undefined
|
|
3640
|
+
/** Tool-specific instructions surfaced to MCP clients. */
|
|
3641
|
+
instructions?: string | undefined
|
|
3642
|
+
}
|
|
3643
|
+
| undefined
|
|
3130
3644
|
/** Zod schema for the command's return value. */
|
|
3131
3645
|
output?: output | undefined
|
|
3132
3646
|
/**
|
|
@@ -3179,22 +3693,6 @@ type CommandDefinition<
|
|
|
3179
3693
|
| AsyncGenerator<InferReturn<output>, unknown, unknown>
|
|
3180
3694
|
}
|
|
3181
3695
|
|
|
3182
|
-
/** @internal A formatted CTA block as it appears in the output envelope. */
|
|
3183
|
-
type FormattedCtaBlock = {
|
|
3184
|
-
/** Formatted command suggestions. */
|
|
3185
|
-
commands: FormattedCta[]
|
|
3186
|
-
/** Human-readable label for the CTA block. */
|
|
3187
|
-
description: string
|
|
3188
|
-
}
|
|
3189
|
-
|
|
3190
|
-
/** @internal A formatted CTA as it appears in the output envelope. */
|
|
3191
|
-
type FormattedCta = {
|
|
3192
|
-
/** The full command string with args and options folded in. */
|
|
3193
|
-
command: string
|
|
3194
|
-
/** A short description of what the command does. */
|
|
3195
|
-
description?: string | undefined
|
|
3196
|
-
}
|
|
3197
|
-
|
|
3198
3696
|
/** @internal Scans argv for deprecated flags and writes warnings to stderr. */
|
|
3199
3697
|
function emitDeprecationWarnings(
|
|
3200
3698
|
argv: string[],
|