incur 0.3.3 → 0.3.5
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 +11 -11
- package/dist/Cli.d.ts +2 -7
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +234 -359
- package/dist/Cli.js.map +1 -1
- package/dist/Completions.d.ts +1 -2
- package/dist/Completions.d.ts.map +1 -1
- package/dist/Completions.js.map +1 -1
- package/dist/Help.d.ts +2 -0
- package/dist/Help.d.ts.map +1 -1
- package/dist/Help.js +20 -10
- package/dist/Help.js.map +1 -1
- package/dist/Mcp.d.ts +25 -5
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +61 -69
- package/dist/Mcp.js.map +1 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +5 -1
- package/dist/Skill.js.map +1 -1
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +10 -1
- package/dist/SyncSkills.js.map +1 -1
- package/dist/internal/command.d.ts +116 -0
- package/dist/internal/command.d.ts.map +1 -0
- package/dist/internal/command.js +275 -0
- package/dist/internal/command.js.map +1 -0
- package/package.json +1 -1
- package/src/Cli.test.ts +165 -18
- package/src/Cli.ts +288 -439
- package/src/Completions.test.ts +35 -9
- package/src/Completions.ts +1 -2
- package/src/Help.test.ts +18 -7
- package/src/Help.ts +21 -10
- package/src/Mcp.test.ts +143 -0
- package/src/Mcp.ts +92 -84
- package/src/Skill.ts +5 -1
- package/src/SyncSkills.ts +11 -1
- package/src/e2e.test.ts +40 -27
- package/src/internal/command.ts +425 -0
package/src/Cli.ts
CHANGED
|
@@ -8,6 +8,8 @@ import * as Fetch from './Fetch.js'
|
|
|
8
8
|
import * as Filter from './Filter.js'
|
|
9
9
|
import * as Formatter from './Formatter.js'
|
|
10
10
|
import * as Help from './Help.js'
|
|
11
|
+
import { builtinCommands, type CommandMeta, type Shell, shells } from './internal/command.js'
|
|
12
|
+
import * as Command from './internal/command.js'
|
|
11
13
|
import { detectRunner } from './internal/pm.js'
|
|
12
14
|
import type { OneOf } from './internal/types.js'
|
|
13
15
|
import * as Mcp from './Mcp.js'
|
|
@@ -257,10 +259,13 @@ export function create(
|
|
|
257
259
|
async fetch(req: Request) {
|
|
258
260
|
if (pending.length > 0) await Promise.all(pending)
|
|
259
261
|
return fetchImpl(name, commands, req, {
|
|
262
|
+
envSchema: def.env,
|
|
260
263
|
mcpHandler,
|
|
261
264
|
middlewares,
|
|
265
|
+
name,
|
|
262
266
|
rootCommand: rootDef,
|
|
263
267
|
vars: def.vars,
|
|
268
|
+
version: def.version,
|
|
264
269
|
})
|
|
265
270
|
},
|
|
266
271
|
|
|
@@ -442,12 +447,17 @@ async function serveImpl(
|
|
|
442
447
|
|
|
443
448
|
// --mcp: start as MCP stdio server
|
|
444
449
|
if (mcpFlag) {
|
|
445
|
-
await Mcp.serve(name, options.version ?? '0.0.0', commands
|
|
450
|
+
await Mcp.serve(name, options.version ?? '0.0.0', commands, {
|
|
451
|
+
middlewares: options.middlewares,
|
|
452
|
+
env: options.envSchema,
|
|
453
|
+
vars: options.vars,
|
|
454
|
+
version: options.version,
|
|
455
|
+
})
|
|
446
456
|
return
|
|
447
457
|
}
|
|
448
458
|
|
|
449
459
|
// COMPLETE: dynamic shell completions (called by shell hook at tab-press)
|
|
450
|
-
const completeShell = process.env.COMPLETE as
|
|
460
|
+
const completeShell = process.env.COMPLETE as Shell | undefined
|
|
451
461
|
if (completeShell) {
|
|
452
462
|
// Remove separator `--` from argv
|
|
453
463
|
const sepIdx = argv.indexOf('--')
|
|
@@ -459,6 +469,26 @@ async function serveImpl(
|
|
|
459
469
|
} else {
|
|
460
470
|
const index = Number(process.env._COMPLETE_INDEX ?? words.length - 1)
|
|
461
471
|
const candidates = Completions.complete(commands, options.rootCommand, words, index)
|
|
472
|
+
// Add built-in commands (completions, mcp, skills) to completions
|
|
473
|
+
const current = words[index] ?? ''
|
|
474
|
+
const nonFlags = words.slice(0, index).filter((w) => !w.startsWith('-'))
|
|
475
|
+
if (nonFlags.length <= 1) {
|
|
476
|
+
for (const b of builtinCommands) {
|
|
477
|
+
if (b.name.startsWith(current) && !candidates.some((c) => c.value === b.name))
|
|
478
|
+
candidates.push({
|
|
479
|
+
value: b.name,
|
|
480
|
+
description: b.description,
|
|
481
|
+
...(b.subcommands ? { noSpace: true } : undefined),
|
|
482
|
+
})
|
|
483
|
+
}
|
|
484
|
+
} else if (nonFlags.length === 2) {
|
|
485
|
+
const parent = nonFlags[nonFlags.length - 1]
|
|
486
|
+
const builtin = builtinCommands.find((b) => b.name === parent && b.subcommands)
|
|
487
|
+
if (builtin?.subcommands)
|
|
488
|
+
for (const sub of builtin.subcommands)
|
|
489
|
+
if (sub.name.startsWith(current))
|
|
490
|
+
candidates.push({ value: sub.name, description: sub.description })
|
|
491
|
+
}
|
|
462
492
|
const out = Completions.format(completeShell, candidates)
|
|
463
493
|
if (out) stdout(out)
|
|
464
494
|
}
|
|
@@ -544,73 +574,48 @@ async function serveImpl(
|
|
|
544
574
|
// not a completions invocation
|
|
545
575
|
return -1
|
|
546
576
|
})()
|
|
577
|
+
// TODO: refactor built-in command handlers (completions, skills, mcp) into a generic dispatch loop on `builtinCommands`
|
|
547
578
|
if (completionsIdx !== -1 && filtered[completionsIdx] === 'completions') {
|
|
548
|
-
|
|
579
|
+
const shell = filtered[completionsIdx + 1]
|
|
580
|
+
if (help || !shell) {
|
|
581
|
+
const b = builtinCommands.find((c) => c.name === 'completions')!
|
|
549
582
|
writeln(
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
' bash',
|
|
557
|
-
' fish',
|
|
558
|
-
' nushell',
|
|
559
|
-
' zsh',
|
|
560
|
-
'',
|
|
561
|
-
'Setup:',
|
|
562
|
-
...(() => {
|
|
563
|
-
const rows = [
|
|
564
|
-
['bash', `eval "$(${name} completions bash)"`, '# add to ~/.bashrc'],
|
|
565
|
-
['zsh', `eval "$(${name} completions zsh)"`, '# add to ~/.zshrc'],
|
|
566
|
-
['fish', `${name} completions fish | source`, '# add to ~/.config/fish/config.fish'],
|
|
567
|
-
['nushell', `see \`${name} completions nushell\``, '# add to config.nu'],
|
|
568
|
-
]
|
|
569
|
-
const shellW = Math.max(...rows.map((r) => r[0]!.length))
|
|
570
|
-
const cmdW = Math.max(...rows.map((r) => r[1]!.length))
|
|
571
|
-
return rows.map(
|
|
572
|
-
([shell, cmd, comment]) =>
|
|
573
|
-
` ${shell!.padEnd(shellW)} ${cmd!.padEnd(cmdW)} ${comment}`,
|
|
574
|
-
)
|
|
575
|
-
})(),
|
|
576
|
-
].join('\n'),
|
|
583
|
+
Help.formatCommand(`${name} completions`, {
|
|
584
|
+
args: b.args,
|
|
585
|
+
description: b.description,
|
|
586
|
+
hideGlobalOptions: true,
|
|
587
|
+
hint: b.hint?.(name),
|
|
588
|
+
}),
|
|
577
589
|
)
|
|
578
590
|
return
|
|
579
591
|
}
|
|
580
|
-
|
|
581
|
-
if (!shell || !['bash', 'fish', 'nushell', 'zsh'].includes(shell)) {
|
|
592
|
+
if (!shells.includes(shell as any)) {
|
|
582
593
|
writeln(
|
|
583
594
|
formatHumanError({
|
|
584
595
|
code: 'INVALID_SHELL',
|
|
585
|
-
message: shell
|
|
586
|
-
? `Unknown shell '${shell}'. Supported: bash, fish, nushell, zsh`
|
|
587
|
-
: `Missing shell argument. Usage: ${name} completions <bash|fish|nushell|zsh>`,
|
|
596
|
+
message: `Unknown shell '${shell}'. Supported: ${shells.join(', ')}`,
|
|
588
597
|
}),
|
|
589
598
|
)
|
|
590
599
|
exit(1)
|
|
591
600
|
return
|
|
592
601
|
}
|
|
593
602
|
const names = [name, ...(options.aliases ?? [])]
|
|
594
|
-
writeln(names.map((n) => Completions.register(shell as
|
|
603
|
+
writeln(names.map((n) => Completions.register(shell as Shell, n)).join('\n'))
|
|
595
604
|
return
|
|
596
605
|
}
|
|
597
606
|
|
|
598
607
|
// skills add: generate skill files and install via `<pm>x skills add` (only when sync is configured)
|
|
599
608
|
const skillsIdx =
|
|
600
609
|
filtered[0] === 'skills' ? 0 : filtered[0] === name && filtered[1] === 'skills' ? 1 : -1
|
|
601
|
-
if (skillsIdx !== -1 && filtered[skillsIdx] === 'skills'
|
|
610
|
+
if (skillsIdx !== -1 && filtered[skillsIdx] === 'skills') {
|
|
611
|
+
if (filtered[skillsIdx + 1] !== 'add') {
|
|
612
|
+
const b = builtinCommands.find((c) => c.name === 'skills')!
|
|
613
|
+
writeln(formatBuiltinHelp(name, b))
|
|
614
|
+
return
|
|
615
|
+
}
|
|
602
616
|
if (help) {
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
`${name} skills add — Sync skill files to your agent`,
|
|
606
|
-
'',
|
|
607
|
-
`Usage: ${name} skills add [options]`,
|
|
608
|
-
'',
|
|
609
|
-
'Options:',
|
|
610
|
-
' --depth <number> Grouping depth for skill files (default: 1)',
|
|
611
|
-
' --no-global Install to project instead of globally',
|
|
612
|
-
].join('\n'),
|
|
613
|
-
)
|
|
617
|
+
const b = builtinCommands.find((c) => c.name === 'skills')!
|
|
618
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'))
|
|
614
619
|
return
|
|
615
620
|
}
|
|
616
621
|
const rest = filtered.slice(skillsIdx + 2)
|
|
@@ -674,20 +679,15 @@ async function serveImpl(
|
|
|
674
679
|
|
|
675
680
|
// mcp add: register CLI as MCP server via `npx add-mcp`
|
|
676
681
|
const mcpIdx = filtered[0] === 'mcp' ? 0 : filtered[0] === name && filtered[1] === 'mcp' ? 1 : -1
|
|
677
|
-
if (mcpIdx !== -1 && filtered[mcpIdx] === 'mcp'
|
|
682
|
+
if (mcpIdx !== -1 && filtered[mcpIdx] === 'mcp') {
|
|
683
|
+
if (filtered[mcpIdx + 1] !== 'add') {
|
|
684
|
+
const b = builtinCommands.find((c) => c.name === 'mcp')!
|
|
685
|
+
writeln(formatBuiltinHelp(name, b))
|
|
686
|
+
return
|
|
687
|
+
}
|
|
678
688
|
if (help) {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
`${name} mcp add — Register as an MCP server for your agent`,
|
|
682
|
-
'',
|
|
683
|
-
`Usage: ${name} mcp add [options]`,
|
|
684
|
-
'',
|
|
685
|
-
'Options:',
|
|
686
|
-
' -c, --command <cmd> Override the command agents will run (e.g. "pnpm my-cli --mcp")',
|
|
687
|
-
' --no-global Install to project instead of globally',
|
|
688
|
-
' --agent <agent> Target a specific agent (e.g. claude-code, cursor)',
|
|
689
|
-
].join('\n'),
|
|
690
|
-
)
|
|
689
|
+
const b = builtinCommands.find((c) => c.name === 'mcp')!
|
|
690
|
+
writeln(formatBuiltinSubcommandHelp(name, b, 'add'))
|
|
691
691
|
return
|
|
692
692
|
}
|
|
693
693
|
const rest = filtered.slice(mcpIdx + 2)
|
|
@@ -1198,211 +1198,125 @@ async function serveImpl(
|
|
|
1198
1198
|
...((command.middleware as MiddlewareHandler[] | undefined) ?? []),
|
|
1199
1199
|
]
|
|
1200
1200
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
alias: command.alias as Record<string, string> | undefined,
|
|
1208
|
-
args: command.args,
|
|
1209
|
-
options: command.options,
|
|
1210
|
-
})
|
|
1211
|
-
|
|
1212
|
-
if (human)
|
|
1213
|
-
emitDeprecationWarnings(
|
|
1214
|
-
rest,
|
|
1215
|
-
command.options,
|
|
1216
|
-
command.alias as Record<string, string> | undefined,
|
|
1217
|
-
)
|
|
1201
|
+
if (human)
|
|
1202
|
+
emitDeprecationWarnings(
|
|
1203
|
+
rest,
|
|
1204
|
+
command.options,
|
|
1205
|
+
command.alias as Record<string, string> | undefined,
|
|
1206
|
+
)
|
|
1218
1207
|
|
|
1219
|
-
|
|
1208
|
+
const result = await Command.execute(command, {
|
|
1209
|
+
agent: !human,
|
|
1210
|
+
argv: rest,
|
|
1211
|
+
env: options.envSchema,
|
|
1212
|
+
envSource: options.env,
|
|
1213
|
+
format,
|
|
1214
|
+
formatExplicit,
|
|
1215
|
+
inputOptions: {},
|
|
1216
|
+
middlewares: allMiddleware,
|
|
1217
|
+
name,
|
|
1218
|
+
path,
|
|
1219
|
+
vars: options.vars,
|
|
1220
|
+
version: options.version,
|
|
1221
|
+
})
|
|
1220
1222
|
|
|
1221
|
-
|
|
1222
|
-
return { [sentinel]: 'ok', data, cta: meta.cta } as never
|
|
1223
|
-
}
|
|
1224
|
-
const errorFn = (opts: {
|
|
1225
|
-
code: string
|
|
1226
|
-
exitCode?: number | undefined
|
|
1227
|
-
message: string
|
|
1228
|
-
retryable?: boolean | undefined
|
|
1229
|
-
cta?: CtaBlock | undefined
|
|
1230
|
-
}): never => {
|
|
1231
|
-
return { [sentinel]: 'error', ...opts } as never
|
|
1232
|
-
}
|
|
1223
|
+
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1233
1224
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1225
|
+
// Streaming path — async generator
|
|
1226
|
+
if ('stream' in result) {
|
|
1227
|
+
await handleStreaming(result.stream, {
|
|
1228
|
+
name,
|
|
1229
|
+
path,
|
|
1230
|
+
start,
|
|
1239
1231
|
format,
|
|
1240
1232
|
formatExplicit,
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1233
|
+
human,
|
|
1234
|
+
renderOutput,
|
|
1235
|
+
verbose,
|
|
1236
|
+
truncate,
|
|
1237
|
+
write,
|
|
1238
|
+
writeln,
|
|
1239
|
+
exit,
|
|
1246
1240
|
})
|
|
1247
|
-
|
|
1248
|
-
// Streaming path — async generator
|
|
1249
|
-
if (isAsyncGenerator(result)) {
|
|
1250
|
-
await handleStreaming(result, {
|
|
1251
|
-
name,
|
|
1252
|
-
path,
|
|
1253
|
-
start,
|
|
1254
|
-
format,
|
|
1255
|
-
formatExplicit,
|
|
1256
|
-
human,
|
|
1257
|
-
renderOutput,
|
|
1258
|
-
verbose,
|
|
1259
|
-
truncate,
|
|
1260
|
-
write,
|
|
1261
|
-
writeln,
|
|
1262
|
-
exit,
|
|
1263
|
-
})
|
|
1264
|
-
return
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
const awaited = await result
|
|
1268
|
-
|
|
1269
|
-
if (isSentinel(awaited)) {
|
|
1270
|
-
const cta = formatCtaBlock(name, awaited.cta)
|
|
1271
|
-
if (awaited[sentinel] === 'ok') {
|
|
1272
|
-
write({
|
|
1273
|
-
ok: true,
|
|
1274
|
-
data: awaited.data,
|
|
1275
|
-
meta: {
|
|
1276
|
-
command: path,
|
|
1277
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1278
|
-
...(cta ? { cta } : undefined),
|
|
1279
|
-
},
|
|
1280
|
-
})
|
|
1281
|
-
} else {
|
|
1282
|
-
const err = awaited as ErrorResult
|
|
1283
|
-
write({
|
|
1284
|
-
ok: false,
|
|
1285
|
-
error: {
|
|
1286
|
-
code: err.code,
|
|
1287
|
-
message: err.message,
|
|
1288
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
1289
|
-
},
|
|
1290
|
-
meta: {
|
|
1291
|
-
command: path,
|
|
1292
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1293
|
-
...(cta ? { cta } : undefined),
|
|
1294
|
-
},
|
|
1295
|
-
})
|
|
1296
|
-
exit(err.exitCode ?? 1)
|
|
1297
|
-
}
|
|
1298
|
-
} else {
|
|
1299
|
-
write({
|
|
1300
|
-
ok: true,
|
|
1301
|
-
data: awaited,
|
|
1302
|
-
meta: {
|
|
1303
|
-
command: path,
|
|
1304
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1305
|
-
},
|
|
1306
|
-
})
|
|
1307
|
-
}
|
|
1241
|
+
return
|
|
1308
1242
|
}
|
|
1309
1243
|
|
|
1310
|
-
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
exitCode?: number | undefined
|
|
1317
|
-
message: string
|
|
1318
|
-
retryable?: boolean | undefined
|
|
1319
|
-
cta?: CtaBlock | undefined
|
|
1320
|
-
}): never => {
|
|
1321
|
-
return { [sentinel]: 'error', ...opts } as never
|
|
1322
|
-
}
|
|
1323
|
-
const mwCtx: MiddlewareContext = {
|
|
1324
|
-
agent: !human,
|
|
1244
|
+
if (result.ok) {
|
|
1245
|
+
const cta = formatCtaBlock(name, result.cta as CtaBlock | undefined)
|
|
1246
|
+
write({
|
|
1247
|
+
ok: true,
|
|
1248
|
+
data: result.data,
|
|
1249
|
+
meta: {
|
|
1325
1250
|
command: path,
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
message: err.message,
|
|
1346
|
-
...(err.retryable !== undefined ? { retryable: err.retryable } : undefined),
|
|
1347
|
-
},
|
|
1348
|
-
meta: {
|
|
1349
|
-
command: path,
|
|
1350
|
-
duration: `${Math.round(performance.now() - start)}ms`,
|
|
1351
|
-
...(cta ? { cta } : undefined),
|
|
1352
|
-
},
|
|
1353
|
-
})
|
|
1354
|
-
exit(err.exitCode ?? 1)
|
|
1355
|
-
}
|
|
1356
|
-
const composed = allMiddleware.reduceRight(
|
|
1357
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1358
|
-
handleMwSentinel(await mw(mwCtx, next))
|
|
1359
|
-
},
|
|
1360
|
-
runCommand,
|
|
1251
|
+
duration,
|
|
1252
|
+
...(cta ? { cta } : undefined),
|
|
1253
|
+
},
|
|
1254
|
+
})
|
|
1255
|
+
} else {
|
|
1256
|
+
const cta = formatCtaBlock(name, result.cta as CtaBlock | undefined)
|
|
1257
|
+
|
|
1258
|
+
if (human && !formatExplicit && result.error.fieldErrors) {
|
|
1259
|
+
writeln(
|
|
1260
|
+
formatHumanValidationError(
|
|
1261
|
+
name,
|
|
1262
|
+
path,
|
|
1263
|
+
command,
|
|
1264
|
+
new ValidationError({
|
|
1265
|
+
message: result.error.message,
|
|
1266
|
+
fieldErrors: result.error.fieldErrors,
|
|
1267
|
+
}),
|
|
1268
|
+
options.env,
|
|
1269
|
+
),
|
|
1361
1270
|
)
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
await runCommand()
|
|
1271
|
+
exit(1)
|
|
1272
|
+
return
|
|
1365
1273
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1274
|
+
|
|
1275
|
+
write({
|
|
1368
1276
|
ok: false,
|
|
1369
1277
|
error: {
|
|
1370
|
-
code:
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1377
|
-
...(error instanceof IncurError ? { retryable: error.retryable } : undefined),
|
|
1378
|
-
...(error instanceof ValidationError ? { fieldErrors: error.fieldErrors } : undefined),
|
|
1278
|
+
code: result.error.code,
|
|
1279
|
+
message: result.error.message,
|
|
1280
|
+
...(result.error.retryable !== undefined
|
|
1281
|
+
? { retryable: result.error.retryable }
|
|
1282
|
+
: undefined),
|
|
1283
|
+
...(result.error.fieldErrors ? { fieldErrors: result.error.fieldErrors } : undefined),
|
|
1379
1284
|
},
|
|
1380
1285
|
meta: {
|
|
1381
1286
|
command: path,
|
|
1382
|
-
duration
|
|
1287
|
+
duration,
|
|
1288
|
+
...(cta ? { cta } : undefined),
|
|
1383
1289
|
},
|
|
1384
|
-
}
|
|
1385
|
-
|
|
1386
|
-
if (human && !formatExplicit && error instanceof ValidationError) {
|
|
1387
|
-
writeln(formatHumanValidationError(name, path, command, error, options.env))
|
|
1388
|
-
exit(1)
|
|
1389
|
-
return
|
|
1390
|
-
}
|
|
1391
|
-
|
|
1392
|
-
write(errorOutput)
|
|
1393
|
-
exit(error instanceof IncurError ? (error.exitCode ?? 1) : 1)
|
|
1290
|
+
})
|
|
1291
|
+
exit(result.exitCode ?? 1)
|
|
1394
1292
|
}
|
|
1395
1293
|
}
|
|
1396
1294
|
|
|
1397
1295
|
/** @internal Options for fetchImpl. */
|
|
1398
1296
|
declare namespace fetchImpl {
|
|
1399
1297
|
type Options = {
|
|
1298
|
+
/** CLI-level env schema. */
|
|
1299
|
+
envSchema?: z.ZodObject<any> | undefined
|
|
1300
|
+
/** Group-level middleware collected during command resolution. */
|
|
1301
|
+
groupMiddlewares?: MiddlewareHandler[] | undefined
|
|
1400
1302
|
mcpHandler?:
|
|
1401
|
-
| ((
|
|
1303
|
+
| ((
|
|
1304
|
+
req: Request,
|
|
1305
|
+
commands: Map<string, CommandEntry>,
|
|
1306
|
+
mcpOptions?: {
|
|
1307
|
+
middlewares?: MiddlewareHandler[] | undefined
|
|
1308
|
+
env?: z.ZodObject<any> | undefined
|
|
1309
|
+
vars?: z.ZodObject<any> | undefined
|
|
1310
|
+
},
|
|
1311
|
+
) => Promise<Response>)
|
|
1402
1312
|
| undefined
|
|
1403
1313
|
middlewares?: MiddlewareHandler[] | undefined
|
|
1314
|
+
/** CLI name. */
|
|
1315
|
+
name?: string | undefined
|
|
1404
1316
|
rootCommand?: CommandDefinition<any, any, any> | undefined
|
|
1405
1317
|
vars?: z.ZodObject<any> | undefined
|
|
1318
|
+
/** CLI version string. */
|
|
1319
|
+
version?: string | undefined
|
|
1406
1320
|
}
|
|
1407
1321
|
}
|
|
1408
1322
|
|
|
@@ -1410,7 +1324,15 @@ declare namespace fetchImpl {
|
|
|
1410
1324
|
function createMcpHttpHandler(name: string, version: string) {
|
|
1411
1325
|
let transport: any
|
|
1412
1326
|
|
|
1413
|
-
return async (
|
|
1327
|
+
return async (
|
|
1328
|
+
req: Request,
|
|
1329
|
+
commands: Map<string, CommandEntry>,
|
|
1330
|
+
mcpOptions?: {
|
|
1331
|
+
middlewares?: MiddlewareHandler[] | undefined
|
|
1332
|
+
env?: z.ZodObject<any> | undefined
|
|
1333
|
+
vars?: z.ZodObject<any> | undefined
|
|
1334
|
+
},
|
|
1335
|
+
): Promise<Response> => {
|
|
1414
1336
|
if (!transport) {
|
|
1415
1337
|
const { McpServer } = await import('@modelcontextprotocol/sdk/server/mcp.js')
|
|
1416
1338
|
const { WebStandardStreamableHTTPServerTransport } =
|
|
@@ -1433,7 +1355,13 @@ function createMcpHttpHandler(name: string, version: string) {
|
|
|
1433
1355
|
},
|
|
1434
1356
|
async (...callArgs: any[]) => {
|
|
1435
1357
|
const params = hasInput ? (callArgs[0] as Record<string, unknown>) : {}
|
|
1436
|
-
return Mcp.callTool(tool, params
|
|
1358
|
+
return Mcp.callTool(tool, params, {
|
|
1359
|
+
name,
|
|
1360
|
+
version,
|
|
1361
|
+
middlewares: mcpOptions?.middlewares,
|
|
1362
|
+
env: mcpOptions?.env,
|
|
1363
|
+
vars: mcpOptions?.vars,
|
|
1364
|
+
})
|
|
1437
1365
|
},
|
|
1438
1366
|
)
|
|
1439
1367
|
}
|
|
@@ -1462,7 +1390,11 @@ async function fetchImpl(
|
|
|
1462
1390
|
|
|
1463
1391
|
// MCP over HTTP: route /mcp to the MCP transport
|
|
1464
1392
|
if (segments[0] === 'mcp' && segments.length === 1 && options.mcpHandler)
|
|
1465
|
-
return options.mcpHandler(req, commands
|
|
1393
|
+
return options.mcpHandler(req, commands, {
|
|
1394
|
+
middlewares: options.middlewares,
|
|
1395
|
+
env: options.envSchema,
|
|
1396
|
+
vars: options.vars,
|
|
1397
|
+
})
|
|
1466
1398
|
|
|
1467
1399
|
// .well-known/skills/ — Agent Skills Discovery (RFC)
|
|
1468
1400
|
if (
|
|
@@ -1571,7 +1503,11 @@ async function fetchImpl(
|
|
|
1571
1503
|
if ('fetchGateway' in resolved) return resolved.fetchGateway.fetch(req)
|
|
1572
1504
|
|
|
1573
1505
|
const { command, path, rest } = resolved
|
|
1574
|
-
|
|
1506
|
+
const groupMiddlewares = 'middlewares' in resolved ? resolved.middlewares : []
|
|
1507
|
+
return executeCommand(path, command, rest, inputOptions, start, {
|
|
1508
|
+
...options,
|
|
1509
|
+
groupMiddlewares,
|
|
1510
|
+
})
|
|
1575
1511
|
}
|
|
1576
1512
|
|
|
1577
1513
|
/** @internal Executes a resolved command for the fetch handler and returns a JSON Response. */
|
|
@@ -1590,200 +1526,107 @@ async function executeCommand(
|
|
|
1590
1526
|
})
|
|
1591
1527
|
}
|
|
1592
1528
|
|
|
1593
|
-
const
|
|
1594
|
-
|
|
1595
|
-
|
|
1529
|
+
const allMiddleware = [
|
|
1530
|
+
...(options.middlewares ?? []),
|
|
1531
|
+
...((options.groupMiddlewares as MiddlewareHandler[] | undefined) ?? []),
|
|
1532
|
+
...((command.middleware as MiddlewareHandler[] | undefined) ?? []),
|
|
1533
|
+
]
|
|
1596
1534
|
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1535
|
+
const result = await Command.execute(command, {
|
|
1536
|
+
agent: true,
|
|
1537
|
+
argv: rest,
|
|
1538
|
+
env: options.envSchema,
|
|
1539
|
+
format: 'json',
|
|
1540
|
+
formatExplicit: true,
|
|
1541
|
+
inputOptions,
|
|
1542
|
+
middlewares: allMiddleware,
|
|
1543
|
+
name: options.name ?? path,
|
|
1544
|
+
parseMode: 'split',
|
|
1545
|
+
path,
|
|
1546
|
+
vars: options.vars,
|
|
1547
|
+
version: options.version,
|
|
1548
|
+
})
|
|
1600
1549
|
|
|
1601
|
-
|
|
1602
|
-
const errorFn = (opts: {
|
|
1603
|
-
code: string
|
|
1604
|
-
message: string
|
|
1605
|
-
exitCode?: number | undefined
|
|
1606
|
-
}): never => ({ [sentinel_]: 'error', ...opts }) as never
|
|
1607
|
-
|
|
1608
|
-
const result = command.run({
|
|
1609
|
-
agent: true,
|
|
1610
|
-
args,
|
|
1611
|
-
env: {},
|
|
1612
|
-
error: errorFn,
|
|
1613
|
-
format: 'json',
|
|
1614
|
-
formatExplicit: true,
|
|
1615
|
-
name: path,
|
|
1616
|
-
ok: okFn,
|
|
1617
|
-
options: parsedOptions,
|
|
1618
|
-
var: varsMap,
|
|
1619
|
-
version: undefined,
|
|
1620
|
-
})
|
|
1550
|
+
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1621
1551
|
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
while (true) {
|
|
1630
|
-
const { value, done } = await result.next()
|
|
1631
|
-
if (done) {
|
|
1632
|
-
returnValue = value
|
|
1633
|
-
break
|
|
1634
|
-
}
|
|
1635
|
-
if (isSentinel(value) && (value as any)[sentinel] === 'error') {
|
|
1636
|
-
const tagged = value as any
|
|
1637
|
-
controller.enqueue(
|
|
1638
|
-
encoder.encode(
|
|
1639
|
-
JSON.stringify({
|
|
1640
|
-
type: 'error',
|
|
1641
|
-
ok: false,
|
|
1642
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1643
|
-
}) + '\n',
|
|
1644
|
-
),
|
|
1645
|
-
)
|
|
1646
|
-
controller.close()
|
|
1647
|
-
return
|
|
1648
|
-
}
|
|
1649
|
-
controller.enqueue(
|
|
1650
|
-
encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'),
|
|
1651
|
-
)
|
|
1652
|
-
}
|
|
1653
|
-
const meta: Record<string, unknown> = { command: path }
|
|
1654
|
-
if (isSentinel(returnValue) && (returnValue as any)[sentinel] === 'error') {
|
|
1655
|
-
const tagged = returnValue as any
|
|
1656
|
-
controller.enqueue(
|
|
1657
|
-
encoder.encode(
|
|
1658
|
-
JSON.stringify({
|
|
1659
|
-
type: 'error',
|
|
1660
|
-
ok: false,
|
|
1661
|
-
error: { code: tagged.code, message: tagged.message },
|
|
1662
|
-
}) + '\n',
|
|
1663
|
-
),
|
|
1664
|
-
)
|
|
1665
|
-
} else {
|
|
1666
|
-
controller.enqueue(
|
|
1667
|
-
encoder.encode(JSON.stringify({ type: 'done', ok: true, meta }) + '\n'),
|
|
1668
|
-
)
|
|
1669
|
-
}
|
|
1670
|
-
} catch (error) {
|
|
1552
|
+
// Streaming path — async generator → NDJSON response
|
|
1553
|
+
if ('stream' in result) {
|
|
1554
|
+
const stream = new ReadableStream({
|
|
1555
|
+
async start(controller) {
|
|
1556
|
+
const encoder = new TextEncoder()
|
|
1557
|
+
try {
|
|
1558
|
+
for await (const value of result.stream) {
|
|
1671
1559
|
controller.enqueue(
|
|
1672
|
-
encoder.encode(
|
|
1673
|
-
JSON.stringify({
|
|
1674
|
-
type: 'error',
|
|
1675
|
-
ok: false,
|
|
1676
|
-
error: {
|
|
1677
|
-
code: 'UNKNOWN',
|
|
1678
|
-
message: error instanceof Error ? error.message : String(error),
|
|
1679
|
-
},
|
|
1680
|
-
}) + '\n',
|
|
1681
|
-
),
|
|
1560
|
+
encoder.encode(JSON.stringify({ type: 'chunk', data: value }) + '\n'),
|
|
1682
1561
|
)
|
|
1683
1562
|
}
|
|
1684
|
-
controller.
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
)
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
}
|
|
1715
|
-
|
|
1716
|
-
response = jsonResponse({ ok: true, data: awaited, meta: { command: path, duration } }, 200)
|
|
1563
|
+
controller.enqueue(
|
|
1564
|
+
encoder.encode(
|
|
1565
|
+
JSON.stringify({
|
|
1566
|
+
type: 'done',
|
|
1567
|
+
ok: true,
|
|
1568
|
+
meta: { command: path },
|
|
1569
|
+
}) + '\n',
|
|
1570
|
+
),
|
|
1571
|
+
)
|
|
1572
|
+
} catch (error) {
|
|
1573
|
+
controller.enqueue(
|
|
1574
|
+
encoder.encode(
|
|
1575
|
+
JSON.stringify({
|
|
1576
|
+
type: 'error',
|
|
1577
|
+
ok: false,
|
|
1578
|
+
error: {
|
|
1579
|
+
code: 'UNKNOWN',
|
|
1580
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1581
|
+
},
|
|
1582
|
+
}) + '\n',
|
|
1583
|
+
),
|
|
1584
|
+
)
|
|
1585
|
+
}
|
|
1586
|
+
controller.close()
|
|
1587
|
+
},
|
|
1588
|
+
})
|
|
1589
|
+
return new Response(stream, {
|
|
1590
|
+
status: 200,
|
|
1591
|
+
headers: { 'content-type': 'application/x-ndjson' },
|
|
1592
|
+
})
|
|
1717
1593
|
}
|
|
1718
1594
|
|
|
1719
|
-
|
|
1720
|
-
const
|
|
1721
|
-
if (allMiddleware.length > 0) {
|
|
1722
|
-
const errorFn = (opts: {
|
|
1723
|
-
code: string
|
|
1724
|
-
message: string
|
|
1725
|
-
exitCode?: number | undefined
|
|
1726
|
-
}): never => {
|
|
1727
|
-
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1728
|
-
response = jsonResponse(
|
|
1729
|
-
{
|
|
1730
|
-
ok: false,
|
|
1731
|
-
error: { code: opts.code, message: opts.message },
|
|
1732
|
-
meta: { command: path, duration },
|
|
1733
|
-
},
|
|
1734
|
-
500,
|
|
1735
|
-
)
|
|
1736
|
-
return undefined as never
|
|
1737
|
-
}
|
|
1738
|
-
const mwCtx: MiddlewareContext = {
|
|
1739
|
-
agent: true,
|
|
1740
|
-
command: path,
|
|
1741
|
-
env: {},
|
|
1742
|
-
error: errorFn,
|
|
1743
|
-
format: 'json',
|
|
1744
|
-
formatExplicit: true,
|
|
1745
|
-
name: path,
|
|
1746
|
-
set(key: string, value: unknown) {
|
|
1747
|
-
varsMap[key] = value
|
|
1748
|
-
},
|
|
1749
|
-
var: varsMap,
|
|
1750
|
-
version: undefined,
|
|
1751
|
-
}
|
|
1752
|
-
const composed = allMiddleware.reduceRight(
|
|
1753
|
-
(next: () => Promise<void>, mw) => async () => {
|
|
1754
|
-
await mw(mwCtx, next)
|
|
1755
|
-
},
|
|
1756
|
-
runCommand,
|
|
1757
|
-
)
|
|
1758
|
-
await composed()
|
|
1759
|
-
} else {
|
|
1760
|
-
await runCommand()
|
|
1761
|
-
}
|
|
1762
|
-
} catch (error) {
|
|
1763
|
-
const duration = `${Math.round(performance.now() - start)}ms`
|
|
1764
|
-
if (error instanceof ValidationError)
|
|
1765
|
-
return jsonResponse(
|
|
1766
|
-
{
|
|
1767
|
-
ok: false,
|
|
1768
|
-
error: { code: 'VALIDATION_ERROR', message: error.message },
|
|
1769
|
-
meta: { command: path, duration },
|
|
1770
|
-
},
|
|
1771
|
-
400,
|
|
1772
|
-
)
|
|
1595
|
+
if (!result.ok) {
|
|
1596
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta as CtaBlock | undefined)
|
|
1773
1597
|
return jsonResponse(
|
|
1774
1598
|
{
|
|
1775
1599
|
ok: false,
|
|
1776
1600
|
error: {
|
|
1777
|
-
code: error
|
|
1778
|
-
message: error
|
|
1601
|
+
code: result.error.code,
|
|
1602
|
+
message: result.error.message,
|
|
1603
|
+
...(result.error.retryable !== undefined
|
|
1604
|
+
? { retryable: result.error.retryable }
|
|
1605
|
+
: undefined),
|
|
1606
|
+
},
|
|
1607
|
+
meta: {
|
|
1608
|
+
command: path,
|
|
1609
|
+
duration,
|
|
1610
|
+
...(cta ? { cta } : undefined),
|
|
1779
1611
|
},
|
|
1780
|
-
meta: { command: path, duration },
|
|
1781
1612
|
},
|
|
1782
|
-
500,
|
|
1613
|
+
result.error.code === 'VALIDATION_ERROR' ? 400 : 500,
|
|
1783
1614
|
)
|
|
1784
1615
|
}
|
|
1785
1616
|
|
|
1786
|
-
|
|
1617
|
+
const cta = formatCtaBlock(options.name ?? path, result.cta as CtaBlock | undefined)
|
|
1618
|
+
return jsonResponse(
|
|
1619
|
+
{
|
|
1620
|
+
ok: true,
|
|
1621
|
+
data: result.data,
|
|
1622
|
+
meta: {
|
|
1623
|
+
command: path,
|
|
1624
|
+
duration,
|
|
1625
|
+
...(cta ? { cta } : undefined),
|
|
1626
|
+
},
|
|
1627
|
+
},
|
|
1628
|
+
200,
|
|
1629
|
+
)
|
|
1787
1630
|
}
|
|
1788
1631
|
|
|
1789
1632
|
/** @internal Formats a validation error for TTY with usage hint. */
|
|
@@ -2018,6 +1861,29 @@ function collectHelpCommands(
|
|
|
2018
1861
|
return result.sort((a, b) => a.name.localeCompare(b.name))
|
|
2019
1862
|
}
|
|
2020
1863
|
|
|
1864
|
+
/** @internal Formats group-level help for a built-in command (e.g. `cli skills`). */
|
|
1865
|
+
function formatBuiltinHelp(cli: string, builtin: (typeof builtinCommands)[number]): string {
|
|
1866
|
+
return Help.formatRoot(`${cli} ${builtin.name}`, {
|
|
1867
|
+
description: builtin.description,
|
|
1868
|
+
commands: builtin.subcommands?.map((s) => ({ name: s.name, description: s.description })),
|
|
1869
|
+
})
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
/** @internal Formats subcommand-level help for a built-in command (e.g. `cli skills add --help`). */
|
|
1873
|
+
function formatBuiltinSubcommandHelp(
|
|
1874
|
+
cli: string,
|
|
1875
|
+
builtin: (typeof builtinCommands)[number],
|
|
1876
|
+
subName: string,
|
|
1877
|
+
): string {
|
|
1878
|
+
const sub = builtin.subcommands?.find((s) => s.name === subName)
|
|
1879
|
+
return Help.formatCommand(`${cli} ${builtin.name} ${subName}`, {
|
|
1880
|
+
alias: sub?.alias,
|
|
1881
|
+
description: sub?.description,
|
|
1882
|
+
hideGlobalOptions: true,
|
|
1883
|
+
options: sub?.options,
|
|
1884
|
+
})
|
|
1885
|
+
}
|
|
1886
|
+
|
|
2021
1887
|
/** @internal Formats help text for a fetch gateway command. */
|
|
2022
1888
|
function formatFetchHelp(name: string, description?: string): string {
|
|
2023
1889
|
const lines: string[] = []
|
|
@@ -2138,8 +2004,9 @@ function formatHumanError(error: {
|
|
|
2138
2004
|
/** @internal Formats a CTA block for human-readable TTY output. */
|
|
2139
2005
|
function formatHumanCta(cta: FormattedCtaBlock): string {
|
|
2140
2006
|
const lines: string[] = ['', cta.description]
|
|
2007
|
+
const maxLen = Math.max(...cta.commands.map((c) => c.command.length))
|
|
2141
2008
|
for (const c of cta.commands) {
|
|
2142
|
-
const desc = c.description ? ` # ${c.description}` : ''
|
|
2009
|
+
const desc = c.description ? ` ${''.padEnd(maxLen - c.command.length)}# ${c.description}` : ''
|
|
2143
2010
|
lines.push(` ${c.command}${desc}`)
|
|
2144
2011
|
}
|
|
2145
2012
|
return lines.join('\n')
|
|
@@ -2154,16 +2021,6 @@ function isSentinel(value: unknown): value is OkResult | ErrorResult {
|
|
|
2154
2021
|
return typeof value === 'object' && value !== null && sentinel in value
|
|
2155
2022
|
}
|
|
2156
2023
|
|
|
2157
|
-
/** @internal Type guard for async generators returned by streaming `run` handlers. */
|
|
2158
|
-
function isAsyncGenerator(value: unknown): value is AsyncGenerator<unknown, unknown, unknown> {
|
|
2159
|
-
return (
|
|
2160
|
-
typeof value === 'object' &&
|
|
2161
|
-
value !== null &&
|
|
2162
|
-
Symbol.asyncIterator in value &&
|
|
2163
|
-
typeof (value as any).next === 'function'
|
|
2164
|
-
)
|
|
2165
|
-
}
|
|
2166
|
-
|
|
2167
2024
|
/** @internal Handles streaming output from an async generator `run` handler. */
|
|
2168
2025
|
async function handleStreaming(
|
|
2169
2026
|
generator: AsyncGenerator<unknown, unknown, unknown>,
|
|
@@ -2648,15 +2505,9 @@ type CommandDefinition<
|
|
|
2648
2505
|
output extends z.ZodType | undefined = undefined,
|
|
2649
2506
|
vars extends z.ZodObject<any> | undefined = undefined,
|
|
2650
2507
|
cliEnv extends z.ZodObject<any> | undefined = undefined,
|
|
2651
|
-
> = {
|
|
2652
|
-
/** Map of option names to single-char aliases. */
|
|
2653
|
-
alias?: options extends z.ZodObject<any>
|
|
2654
|
-
? Partial<Record<keyof z.output<options>, string>>
|
|
2655
|
-
: Record<string, string> | undefined
|
|
2508
|
+
> = CommandMeta<options> & {
|
|
2656
2509
|
/** Zod schema for positional arguments. */
|
|
2657
2510
|
args?: args | undefined
|
|
2658
|
-
/** A short description of what the command does. */
|
|
2659
|
-
description?: string | undefined
|
|
2660
2511
|
/** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
|
|
2661
2512
|
env?: env | undefined
|
|
2662
2513
|
/** Usage examples for this command. */
|
|
@@ -2665,8 +2516,6 @@ type CommandDefinition<
|
|
|
2665
2516
|
format?: Formatter.Format | undefined
|
|
2666
2517
|
/** Plain text hint displayed after examples and before global options. */
|
|
2667
2518
|
hint?: string | undefined
|
|
2668
|
-
/** Zod schema for named options/flags. */
|
|
2669
|
-
options?: options | undefined
|
|
2670
2519
|
/** Zod schema for the command's return value. */
|
|
2671
2520
|
output?: output | undefined
|
|
2672
2521
|
/**
|