incur 0.1.5 → 0.1.7
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 +175 -31
- package/SKILL.md +159 -11
- package/dist/Cli.d.ts +60 -17
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +128 -62
- package/dist/Cli.js.map +1 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js.map +1 -1
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js.map +1 -1
- package/dist/bin.d.ts +1 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +4 -4
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/agents.d.ts.map +1 -1
- package/dist/internal/agents.js +151 -27
- package/dist/internal/agents.js.map +1 -1
- package/dist/middleware.d.ts +20 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +5 -0
- package/dist/middleware.js.map +1 -0
- package/examples/npm/cli.ts +8 -8
- package/package.json +1 -1
- package/src/Cli.test-d.ts +68 -13
- package/src/Cli.test.ts +683 -29
- package/src/Cli.ts +237 -78
- package/src/Mcp.test.ts +6 -6
- package/src/Skill.test.ts +3 -1
- package/src/Skill.ts +2 -1
- package/src/SyncSkills.ts +1 -4
- package/src/bin.ts +4 -4
- package/src/e2e.test.ts +262 -39
- package/src/index.ts +2 -0
- package/src/internal/agents.ts +153 -33
- package/src/middleware.ts +30 -0
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
- [**Inferred types**](#inferred-types): generic type flow from schemas to `run` callbacks with zero manual annotations
|
|
22
22
|
- [**Global options**](#global-options): `--format`, `--json`, `--verbose`, `--help`, `--version` on every CLI for free
|
|
23
23
|
- [**Light API surface**](#light-api-surface): `Cli.create()`, `.command()`, `.serve()` – that's it
|
|
24
|
+
- [**Middleware**](#middleware): composable before/after hooks with typed dependency injection via `cli.use()`
|
|
24
25
|
|
|
25
26
|
## Quickprompt
|
|
26
27
|
|
|
@@ -66,8 +67,8 @@ Cli.create('greet', {
|
|
|
66
67
|
args: z.object({
|
|
67
68
|
name: z.string().describe('Name to greet'),
|
|
68
69
|
}),
|
|
69
|
-
run(
|
|
70
|
-
return { message: `hello ${args.name}` }
|
|
70
|
+
run(c) {
|
|
71
|
+
return { message: `hello ${c.args.name}` }
|
|
71
72
|
},
|
|
72
73
|
}).serve()
|
|
73
74
|
```
|
|
@@ -124,7 +125,7 @@ Cli.create('my-cli', {
|
|
|
124
125
|
saveDev: z.boolean().optional().describe('Save as dev dependency'),
|
|
125
126
|
}),
|
|
126
127
|
alias: { saveDev: 'D' },
|
|
127
|
-
run(
|
|
128
|
+
run(c) {
|
|
128
129
|
return { added: 1, packages: 451 }
|
|
129
130
|
},
|
|
130
131
|
})
|
|
@@ -176,8 +177,8 @@ const pr = Cli.create('pr', { description: 'Pull request commands' }).command('l
|
|
|
176
177
|
options: z.object({
|
|
177
178
|
state: z.enum(['open', 'closed', 'all']).default('open'),
|
|
178
179
|
}),
|
|
179
|
-
run(
|
|
180
|
-
return { prs: [], state: options.state }
|
|
180
|
+
run(c) {
|
|
181
|
+
return { prs: [], state: c.options.state }
|
|
181
182
|
},
|
|
182
183
|
})
|
|
183
184
|
|
|
@@ -264,16 +265,19 @@ Return CTAs from `ok()` or `error()` to suggest next steps. `cta` parameters are
|
|
|
264
265
|
```ts
|
|
265
266
|
cli.command('list', {
|
|
266
267
|
args: z.object({ state: z.enum(['open', 'closed']).default('open') }),
|
|
267
|
-
run(
|
|
268
|
+
run(c) {
|
|
268
269
|
const items = [{ id: 1, title: 'Fix bug' }]
|
|
269
|
-
return ok(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
270
|
+
return c.ok(
|
|
271
|
+
{ items },
|
|
272
|
+
{
|
|
273
|
+
cta: {
|
|
274
|
+
commands: [
|
|
275
|
+
{ command: 'get 1', description: 'View item' },
|
|
276
|
+
{ command: 'list', args: { state: 'closed' }, description: 'View closed' },
|
|
277
|
+
],
|
|
278
|
+
},
|
|
275
279
|
},
|
|
276
|
-
|
|
280
|
+
)
|
|
277
281
|
},
|
|
278
282
|
})
|
|
279
283
|
```
|
|
@@ -296,17 +300,19 @@ A small API means agents can build entire CLIs in a single pass without needing
|
|
|
296
300
|
import { Cli, z } from 'incur'
|
|
297
301
|
|
|
298
302
|
// Define sub-command groups
|
|
299
|
-
const db = Cli.create('db', { description: 'Database commands' })
|
|
300
|
-
|
|
303
|
+
const db = Cli.create('db', { description: 'Database commands' }).command('migrate', {
|
|
304
|
+
description: 'Run migrations',
|
|
305
|
+
run: () => ({ migrated: true }),
|
|
306
|
+
})
|
|
301
307
|
|
|
302
308
|
// Create the root CLI
|
|
303
309
|
Cli.create('tool', { description: 'A tool' })
|
|
304
310
|
// Register commands
|
|
305
311
|
.command('run', { description: 'Run a task', run: () => ({ ok: true }) })
|
|
306
312
|
// Mount sub-command groups
|
|
307
|
-
.command(db)
|
|
313
|
+
.command(db)
|
|
308
314
|
// Serve the CLI
|
|
309
|
-
.serve()
|
|
315
|
+
.serve()
|
|
310
316
|
```
|
|
311
317
|
|
|
312
318
|
```sh
|
|
@@ -364,8 +370,8 @@ cli.command('deploy', {
|
|
|
364
370
|
options: z.object({ force: z.boolean().optional() }),
|
|
365
371
|
env: z.object({ DEPLOY_TOKEN: z.string() }),
|
|
366
372
|
output: z.object({ url: z.string(), duration: z.number() }),
|
|
367
|
-
run(
|
|
368
|
-
return { url: `https://${args.env}.example.com`, duration: 3.2 }
|
|
373
|
+
run(c) {
|
|
374
|
+
return { url: `https://${c.args.env}.example.com`, duration: 3.2 }
|
|
369
375
|
},
|
|
370
376
|
})
|
|
371
377
|
```
|
|
@@ -404,10 +410,10 @@ async *run() {
|
|
|
404
410
|
Use `ok()` or `error()` as the return value to attach CTAs or signal failure:
|
|
405
411
|
|
|
406
412
|
```ts
|
|
407
|
-
async *run(
|
|
413
|
+
async *run(c) {
|
|
408
414
|
yield { step: 1 }
|
|
409
415
|
yield { step: 2 }
|
|
410
|
-
return ok(undefined, { cta: { commands: ['status'] } })
|
|
416
|
+
return c.ok(undefined, { cta: { commands: ['status'] } })
|
|
411
417
|
}
|
|
412
418
|
```
|
|
413
419
|
|
|
@@ -420,20 +426,158 @@ cli.command('greet', {
|
|
|
420
426
|
args: z.object({ name: z.string() }),
|
|
421
427
|
options: z.object({ loud: z.boolean().default(false) }),
|
|
422
428
|
output: z.object({ message: z.string() }),
|
|
423
|
-
run(
|
|
424
|
-
args.name
|
|
425
|
-
//
|
|
426
|
-
options.loud
|
|
427
|
-
//
|
|
428
|
-
return ok(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
429
|
+
run(c) {
|
|
430
|
+
c.args.name
|
|
431
|
+
// ^? (property) name: string
|
|
432
|
+
c.options.loud
|
|
433
|
+
// ^? (property) loud: boolean
|
|
434
|
+
return c.ok(
|
|
435
|
+
{ message: `hello ${c.args.name}` },
|
|
436
|
+
{
|
|
437
|
+
// ^? (property) message: string
|
|
438
|
+
cta: { commands: ['greet world'] },
|
|
439
|
+
// ^? 'greet' | 'other-cmd'
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
},
|
|
443
|
+
})
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Output policy
|
|
447
|
+
|
|
448
|
+
Control whether output data is displayed to humans. By default, output goes to everyone (`'all'`). Set `outputPolicy: 'agent-only'` to suppress data in TTY mode while still returning it to agents via `--json`, `--format`, or `--verbose`.
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
cli.command('deploy', {
|
|
452
|
+
outputPolicy: 'agent-only',
|
|
453
|
+
run() {
|
|
454
|
+
// Agents get the structured data; humans see nothing (or just CTAs/errors)
|
|
455
|
+
return { id: 'deploy-123', url: 'https://staging.example.com' }
|
|
456
|
+
},
|
|
457
|
+
})
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
Set it on a group or root CLI to inherit across all children:
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
const internal = Cli.create('internal', {
|
|
464
|
+
description: 'Internal commands',
|
|
465
|
+
outputPolicy: 'agent-only',
|
|
466
|
+
})
|
|
467
|
+
internal.command('sync', { run: () => ({ synced: true }) }) // inherits agent-only
|
|
468
|
+
internal.command('status', {
|
|
469
|
+
outputPolicy: 'all', // overrides to show output
|
|
470
|
+
run: () => ({ ok: true }),
|
|
471
|
+
})
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Agent detection
|
|
475
|
+
|
|
476
|
+
The `run` context includes an `agent` boolean — `true` when stdout is not a TTY (piped or consumed by an agent), `false` when running in a terminal. Use it to tailor behavior:
|
|
477
|
+
|
|
478
|
+
```ts
|
|
479
|
+
cli.command('deploy', {
|
|
480
|
+
args: z.object({ env: z.enum(['staging', 'production']) }),
|
|
481
|
+
run(c) {
|
|
482
|
+
if (!c.agent) console.log(`Deploying to ${c.args.env}...`)
|
|
483
|
+
return { url: `https://${c.args.env}.example.com` }
|
|
484
|
+
},
|
|
485
|
+
})
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Middleware
|
|
489
|
+
|
|
490
|
+
Register composable before/after hooks with `cli.use()`. Middleware executes in registration order, onion-style – each calls `await next()` to proceed to the next middleware or the command handler.
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
const cli = Cli.create('deploy-cli', { description: 'Deploy tools' })
|
|
494
|
+
.use(async (c, next) => {
|
|
495
|
+
const start = Date.now()
|
|
496
|
+
await next()
|
|
497
|
+
console.log(`took ${Date.now() - start}ms`)
|
|
498
|
+
})
|
|
499
|
+
.command('deploy', {
|
|
500
|
+
run() {
|
|
501
|
+
return { deployed: true }
|
|
502
|
+
},
|
|
503
|
+
})
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```sh
|
|
507
|
+
$ deploy-cli deploy
|
|
508
|
+
# → deployed: true
|
|
509
|
+
# took 12ms
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
Per-command middleware runs after root and group middleware, and only for that command:
|
|
513
|
+
|
|
514
|
+
```ts
|
|
515
|
+
import { Cli, middleware, z } from 'incur'
|
|
516
|
+
|
|
517
|
+
const cli = Cli.create('my-cli', {
|
|
518
|
+
description: 'My CLI',
|
|
519
|
+
vars: z.object({ user: z.custom<User>() }),
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
const requireAuth = middleware<typeof cli.vars>((c, next) => {
|
|
523
|
+
if (!c.var.user) throw new Error('must be logged in')
|
|
524
|
+
return next()
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
cli.command('deploy', {
|
|
528
|
+
middleware: [requireAuth],
|
|
529
|
+
run() {
|
|
530
|
+
return { deployed: true }
|
|
531
|
+
},
|
|
532
|
+
})
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
```sh
|
|
536
|
+
$ my-cli deploy
|
|
537
|
+
# Error: must be logged in
|
|
538
|
+
|
|
539
|
+
$ my-cli other-cmd
|
|
540
|
+
# per-command middleware does not run
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Variables
|
|
544
|
+
|
|
545
|
+
Declare a `vars` schema on `create()` to enable typed variables. Middleware sets them with `c.set()`, and both middleware and command handlers read them via `c.var`. Use `.default()` for vars that don't need middleware:
|
|
546
|
+
|
|
547
|
+
```ts
|
|
548
|
+
type User = { id: string; name: string }
|
|
549
|
+
|
|
550
|
+
const cli = Cli.create('my-cli', {
|
|
551
|
+
description: 'My CLI',
|
|
552
|
+
vars: z.object({
|
|
553
|
+
user: z.custom<User>(),
|
|
554
|
+
requestId: z.string(),
|
|
555
|
+
debug: z.boolean().default(true),
|
|
556
|
+
}),
|
|
557
|
+
})
|
|
558
|
+
|
|
559
|
+
cli.use(async (c, next) => {
|
|
560
|
+
c.set('user', await authenticate())
|
|
561
|
+
c.set('requestId', crypto.randomUUID())
|
|
562
|
+
await next()
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
cli.command('whoami', {
|
|
566
|
+
run(c) {
|
|
567
|
+
return { user: c.var.user, requestId: c.var.requestId, debug: c.var.debug }
|
|
433
568
|
},
|
|
434
569
|
})
|
|
435
570
|
```
|
|
436
571
|
|
|
572
|
+
```sh
|
|
573
|
+
$ my-cli whoami
|
|
574
|
+
# → user:
|
|
575
|
+
# → id: u_123
|
|
576
|
+
# → name: Alice
|
|
577
|
+
# → requestId: 550e8400-e29b-41d4-a716-446655440000
|
|
578
|
+
# → debug: true
|
|
579
|
+
```
|
|
580
|
+
|
|
437
581
|
### Global options
|
|
438
582
|
|
|
439
583
|
Every incur CLI includes these flags automatically:
|
package/SKILL.md
CHANGED
|
@@ -339,22 +339,35 @@ incur adapts output based on whether stdout is a TTY:
|
|
|
339
339
|
| `--help` | Pretty help text | Same |
|
|
340
340
|
| `--json` / `--format` | Overrides to structured | Same |
|
|
341
341
|
|
|
342
|
-
##
|
|
342
|
+
## Run Context
|
|
343
343
|
|
|
344
|
-
### `
|
|
344
|
+
### `agent` boolean
|
|
345
|
+
|
|
346
|
+
The `run` context includes `agent` — `true` when stdout is not a TTY (piped or consumed by an agent), `false` when running in a terminal:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
cli.command('deploy', {
|
|
350
|
+
run(c) {
|
|
351
|
+
if (!c.agent) console.log('Deploying...')
|
|
352
|
+
return { status: 'ok' }
|
|
353
|
+
},
|
|
354
|
+
})
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### `ok()` and `error()` helpers
|
|
345
358
|
|
|
346
359
|
Use the context helpers for explicit result control:
|
|
347
360
|
|
|
348
361
|
```ts
|
|
349
|
-
run(
|
|
350
|
-
const item = await db.find(args.id)
|
|
362
|
+
run(c) {
|
|
363
|
+
const item = await db.find(c.args.id)
|
|
351
364
|
if (!item)
|
|
352
365
|
return error({
|
|
353
366
|
code: 'NOT_FOUND',
|
|
354
|
-
message: `Item ${args.id} not found`,
|
|
367
|
+
message: `Item ${c.args.id} not found`,
|
|
355
368
|
retryable: false,
|
|
356
369
|
})
|
|
357
|
-
return ok(item)
|
|
370
|
+
return c.ok(item)
|
|
358
371
|
}
|
|
359
372
|
```
|
|
360
373
|
|
|
@@ -363,9 +376,9 @@ run({ args, ok, error }) {
|
|
|
363
376
|
Suggest next commands to guide agents on success:
|
|
364
377
|
|
|
365
378
|
```ts
|
|
366
|
-
run(
|
|
367
|
-
const result = { id: 42, name: args.name }
|
|
368
|
-
return ok(result, {
|
|
379
|
+
run(c) {
|
|
380
|
+
const result = { id: 42, name: c.args.name }
|
|
381
|
+
return c.ok(result, {
|
|
369
382
|
cta: {
|
|
370
383
|
description: 'Suggested commands:',
|
|
371
384
|
commands: [
|
|
@@ -380,10 +393,10 @@ run({ args, ok }) {
|
|
|
380
393
|
Or on errors, to help agents self-correct:
|
|
381
394
|
|
|
382
395
|
```ts
|
|
383
|
-
run(
|
|
396
|
+
run(c) {
|
|
384
397
|
const token = process.env.GH_TOKEN
|
|
385
398
|
if (!token)
|
|
386
|
-
return error({
|
|
399
|
+
return c.error({
|
|
387
400
|
code: 'NOT_AUTHENTICATED',
|
|
388
401
|
message: 'GitHub token not found',
|
|
389
402
|
retryable: true,
|
|
@@ -554,6 +567,141 @@ cli.command('publish', {
|
|
|
554
567
|
|
|
555
568
|
Hints are displayed after examples in help output and included in skill files.
|
|
556
569
|
|
|
570
|
+
### Output policy
|
|
571
|
+
|
|
572
|
+
Control whether output data is displayed to humans. `'all'` (default) shows output to everyone. `'agent-only'` suppresses data in human/TTY mode while still returning it via `--json`, `--format`, or `--verbose`.
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
cli.command('deploy', {
|
|
576
|
+
outputPolicy: 'agent-only',
|
|
577
|
+
run() {
|
|
578
|
+
return { id: 'deploy-123', url: 'https://staging.example.com' }
|
|
579
|
+
},
|
|
580
|
+
})
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
Set on a group or root CLI to inherit across children. Children can override:
|
|
584
|
+
|
|
585
|
+
```ts
|
|
586
|
+
const sub = Cli.create('internal', { outputPolicy: 'agent-only' })
|
|
587
|
+
sub.command('sync', { run: () => ({ synced: true }) }) // inherits agent-only
|
|
588
|
+
sub.command('status', { outputPolicy: 'all', run: () => ({}) }) // overrides
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Middleware
|
|
592
|
+
|
|
593
|
+
Register composable before/after hooks with `cli.use()`. Middleware executes in registration order, onion-style. Each calls `await next()` to proceed.
|
|
594
|
+
|
|
595
|
+
```ts
|
|
596
|
+
const cli = Cli.create('deploy-cli', { description: 'Deploy tools' })
|
|
597
|
+
.use(async (c, next) => {
|
|
598
|
+
const start = Date.now()
|
|
599
|
+
await next()
|
|
600
|
+
console.log(`took ${Date.now() - start}ms`)
|
|
601
|
+
})
|
|
602
|
+
.command('deploy', {
|
|
603
|
+
run() {
|
|
604
|
+
return { deployed: true }
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
```sh
|
|
610
|
+
$ deploy-cli deploy
|
|
611
|
+
# → deployed: true
|
|
612
|
+
# took 12ms
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
Middleware on a sub-CLI only applies to its commands:
|
|
616
|
+
|
|
617
|
+
```ts
|
|
618
|
+
const admin = Cli.create('admin', { description: 'Admin commands' })
|
|
619
|
+
.use(async (c, next) => {
|
|
620
|
+
if (!isAdmin()) throw new Error('forbidden')
|
|
621
|
+
await next()
|
|
622
|
+
})
|
|
623
|
+
.command('reset', { run: () => ({ reset: true }) })
|
|
624
|
+
|
|
625
|
+
cli.command(admin) // middleware only runs for `my-cli admin reset`
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
```sh
|
|
629
|
+
$ my-cli admin reset
|
|
630
|
+
# → reset: true
|
|
631
|
+
|
|
632
|
+
$ my-cli other-cmd
|
|
633
|
+
# middleware does not run
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
Per-command middleware runs after root and group middleware, and only for that command:
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
import { Cli, middleware, z } from 'incur'
|
|
640
|
+
|
|
641
|
+
const cli = Cli.create('my-cli', {
|
|
642
|
+
description: 'My CLI',
|
|
643
|
+
vars: z.object({ user: z.custom<{ id: string }>() }),
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
const requireAuth = middleware<typeof cli.vars>((c, next) => {
|
|
647
|
+
if (!c.var.user) throw new Error('must be logged in')
|
|
648
|
+
return next()
|
|
649
|
+
})
|
|
650
|
+
|
|
651
|
+
cli.command('deploy', {
|
|
652
|
+
middleware: [requireAuth],
|
|
653
|
+
run() {
|
|
654
|
+
return { deployed: true }
|
|
655
|
+
},
|
|
656
|
+
})
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
```sh
|
|
660
|
+
$ my-cli deploy
|
|
661
|
+
# Error: must be logged in
|
|
662
|
+
|
|
663
|
+
$ my-cli other-cmd
|
|
664
|
+
# per-command middleware does not run
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Vars — typed dependency injection
|
|
668
|
+
|
|
669
|
+
Declare a `vars` schema on `create()` to inject typed variables. Middleware sets them with `c.set()`, handlers read them via `c.var`. Use `.default()` for vars that don't need middleware:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
const cli = Cli.create('my-cli', {
|
|
673
|
+
description: 'My CLI',
|
|
674
|
+
vars: z.object({
|
|
675
|
+
user: z.custom<{ id: string; name: string }>(),
|
|
676
|
+
requestId: z.string(),
|
|
677
|
+
debug: z.boolean().default(false),
|
|
678
|
+
}),
|
|
679
|
+
})
|
|
680
|
+
|
|
681
|
+
cli.use(async (c, next) => {
|
|
682
|
+
c.set('user', await authenticate())
|
|
683
|
+
c.set('requestId', crypto.randomUUID())
|
|
684
|
+
await next()
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
cli.command('whoami', {
|
|
688
|
+
run(c) {
|
|
689
|
+
return { user: c.var.user, requestId: c.var.requestId, debug: c.var.debug }
|
|
690
|
+
},
|
|
691
|
+
})
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
```sh
|
|
695
|
+
$ my-cli whoami
|
|
696
|
+
# → user:
|
|
697
|
+
# → id: u_123
|
|
698
|
+
# → name: Alice
|
|
699
|
+
# → requestId: 550e8400-e29b-41d4-a716-446655440000
|
|
700
|
+
# → debug: false
|
|
701
|
+
```
|
|
702
|
+
|
|
703
|
+
Middleware does **not** run for built-in commands (`--help`, `--llms`, `--mcp`, `mcp add`, `skills add`).
|
|
704
|
+
|
|
557
705
|
## Serving
|
|
558
706
|
|
|
559
707
|
Call `.serve()` to parse `process.argv` and run:
|
package/dist/Cli.d.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
import * as Formatter from './Formatter.js';
|
|
3
|
+
import type { Handler as MiddlewareHandler } from './middleware.js';
|
|
4
|
+
export type { MiddlewareHandler };
|
|
3
5
|
import type { Register } from './Register.js';
|
|
4
6
|
/** A CLI application instance. Also used as a command group when mounted on a parent CLI. */
|
|
5
|
-
export type Cli<commands extends CommandsMap = {}> = {
|
|
7
|
+
export type Cli<commands extends CommandsMap = {}, vars extends z.ZodObject<any> | undefined = undefined> = {
|
|
6
8
|
/** Registers a root command or mounts a sub-CLI as a command group. */
|
|
7
9
|
command: {
|
|
8
10
|
/** Registers a command. Returns the CLI instance for chaining. */
|
|
9
|
-
<const name extends string, const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const options extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(name: name, definition: CommandDefinition<args, env, options, output>): Cli<commands & {
|
|
11
|
+
<const name extends string, const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const options extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(name: name, definition: CommandDefinition<args, env, options, output, vars>): Cli<commands & {
|
|
10
12
|
[key in name]: {
|
|
11
13
|
args: InferOutput<args>;
|
|
12
14
|
options: InferOutput<options>;
|
|
13
15
|
};
|
|
14
|
-
}>;
|
|
16
|
+
}, vars>;
|
|
15
17
|
/** Mounts a sub-CLI as a command group. */
|
|
16
|
-
<const name extends string, const sub extends CommandsMap>(cli: Cli<sub> & {
|
|
18
|
+
<const name extends string, const sub extends CommandsMap>(cli: Cli<sub, any> & {
|
|
17
19
|
name: name;
|
|
18
20
|
}): Cli<commands & {
|
|
19
21
|
[key in keyof sub & string as `${name} ${key}`]: sub[key];
|
|
20
|
-
}>;
|
|
22
|
+
}, vars>;
|
|
21
23
|
/** Mounts a root CLI as a single command. */
|
|
22
24
|
<const name extends string, const args extends z.ZodObject<any> | undefined, const opts extends z.ZodObject<any> | undefined>(cli: Root<args, opts> & {
|
|
23
25
|
name: name;
|
|
@@ -26,7 +28,7 @@ export type Cli<commands extends CommandsMap = {}> = {
|
|
|
26
28
|
args: InferOutput<args>;
|
|
27
29
|
options: InferOutput<opts>;
|
|
28
30
|
};
|
|
29
|
-
}>;
|
|
31
|
+
}, vars>;
|
|
30
32
|
};
|
|
31
33
|
/** A short description of the CLI. */
|
|
32
34
|
description?: string | undefined;
|
|
@@ -34,6 +36,10 @@ export type Cli<commands extends CommandsMap = {}> = {
|
|
|
34
36
|
name: string;
|
|
35
37
|
/** Parses argv, runs the matched command, and writes the output envelope to stdout. */
|
|
36
38
|
serve(argv?: string[], options?: serve.Options): Promise<void>;
|
|
39
|
+
/** Registers middleware that runs around every command. */
|
|
40
|
+
use(handler: MiddlewareHandler<vars>): Cli<commands, vars>;
|
|
41
|
+
/** The vars schema, if declared. Use `typeof cli.vars` with `Middleware.create` for typed middleware. */
|
|
42
|
+
vars: vars;
|
|
37
43
|
};
|
|
38
44
|
/** Root CLI — a single command with no subcommands. Carries phantom generics for mounting inference. */
|
|
39
45
|
export type Root<_args extends z.ZodObject<any> | undefined = undefined, _options extends z.ZodObject<any> | undefined = undefined> = Omit<Cli, 'command'>;
|
|
@@ -73,18 +79,18 @@ export type Cta<commands extends CommandsMap = Commands> = ([keyof commands] ext
|
|
|
73
79
|
description?: string | undefined;
|
|
74
80
|
});
|
|
75
81
|
/** Creates a CLI with a root handler. Can still register subcommands which take precedence. */
|
|
76
|
-
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(name: string, definition: create.Options<args, env, opts, output> & {
|
|
82
|
+
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined, const vars extends z.ZodObject<any> | undefined = undefined>(name: string, definition: create.Options<args, env, opts, output, vars> & {
|
|
77
83
|
run: Function;
|
|
78
84
|
}): Cli<{
|
|
79
85
|
[key in typeof name]: {
|
|
80
86
|
args: InferOutput<args>;
|
|
81
87
|
options: InferOutput<opts>;
|
|
82
88
|
};
|
|
83
|
-
}>;
|
|
89
|
+
}, vars>;
|
|
84
90
|
/** Creates a router CLI that registers subcommands. */
|
|
85
|
-
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(name: string, definition?: create.Options<args, env, opts, output>): Cli
|
|
91
|
+
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined, const vars extends z.ZodObject<any> | undefined = undefined>(name: string, definition?: create.Options<args, env, opts, output, vars>): Cli<{}, vars>;
|
|
86
92
|
/** Creates a CLI with a root handler from a single options object. Can still register subcommands. */
|
|
87
|
-
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(definition: create.Options<args, env, opts, output> & {
|
|
93
|
+
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined, const vars extends z.ZodObject<any> | undefined = undefined>(definition: create.Options<args, env, opts, output, vars> & {
|
|
88
94
|
name: string;
|
|
89
95
|
run: Function;
|
|
90
96
|
}): Cli<{
|
|
@@ -92,14 +98,14 @@ export declare function create<const args extends z.ZodObject<any> | undefined =
|
|
|
92
98
|
args: InferOutput<args>;
|
|
93
99
|
options: InferOutput<opts>;
|
|
94
100
|
};
|
|
95
|
-
}>;
|
|
101
|
+
}, vars>;
|
|
96
102
|
/** Creates a router CLI from a single options object (e.g. package.json). */
|
|
97
|
-
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined>(definition: create.Options<args, env, opts, output> & {
|
|
103
|
+
export declare function create<const args extends z.ZodObject<any> | undefined = undefined, const env extends z.ZodObject<any> | undefined = undefined, const opts extends z.ZodObject<any> | undefined = undefined, const output extends z.ZodType | undefined = undefined, const vars extends z.ZodObject<any> | undefined = undefined>(definition: create.Options<args, env, opts, output, vars> & {
|
|
98
104
|
name: string;
|
|
99
|
-
}): Cli
|
|
105
|
+
}): Cli<{}, vars>;
|
|
100
106
|
export declare namespace create {
|
|
101
107
|
/** Options for creating a CLI. Provide `run` for a leaf CLI, omit it for a router. */
|
|
102
|
-
type Options<args extends z.ZodObject<any> | undefined = undefined, env extends z.ZodObject<any> | undefined = undefined, options extends z.ZodObject<any> | undefined = undefined, output extends z.ZodType | undefined = undefined> = {
|
|
108
|
+
type Options<args extends z.ZodObject<any> | undefined = undefined, env extends z.ZodObject<any> | undefined = undefined, options extends z.ZodObject<any> | undefined = undefined, output extends z.ZodType | undefined = undefined, vars extends z.ZodObject<any> | undefined = undefined> = {
|
|
103
109
|
/** Map of option names to single-char aliases. */
|
|
104
110
|
alias?: options extends z.ZodObject<any> ? Partial<Record<keyof z.output<options>, string>> : Record<string, string> | undefined;
|
|
105
111
|
/** Zod schema for positional arguments. */
|
|
@@ -116,10 +122,24 @@ export declare namespace create {
|
|
|
116
122
|
options?: options | undefined;
|
|
117
123
|
/** Zod schema for the return value. */
|
|
118
124
|
output?: output | undefined;
|
|
125
|
+
/**
|
|
126
|
+
* Controls when output data is displayed. Inherited by child commands when set on a group or root CLI.
|
|
127
|
+
*
|
|
128
|
+
* - `'all'` — displays to both humans and agents.
|
|
129
|
+
* - `'agent-only'` — suppresses data output in human/TTY mode while still returning it to agents.
|
|
130
|
+
*
|
|
131
|
+
* @default 'all'
|
|
132
|
+
*/
|
|
133
|
+
outputPolicy?: OutputPolicy | undefined;
|
|
119
134
|
/** Alternative usage patterns shown in help output. */
|
|
120
135
|
usage?: Usage<args, options>[] | undefined;
|
|
136
|
+
/** Zod schema for middleware variables. Keys define variable names, schemas define types and defaults. */
|
|
137
|
+
vars?: vars | undefined;
|
|
121
138
|
/** The root command handler. When provided, creates a leaf CLI with no subcommands. */
|
|
122
139
|
run?: ((context: {
|
|
140
|
+
/** Whether the consumer is an agent (stdout is not a TTY). */
|
|
141
|
+
agent: boolean;
|
|
142
|
+
/** Positional arguments. */
|
|
123
143
|
args: InferOutput<args>;
|
|
124
144
|
/** Parsed environment variables. */
|
|
125
145
|
env: InferOutput<env>;
|
|
@@ -135,6 +155,8 @@ export declare namespace create {
|
|
|
135
155
|
cta?: CtaBlock | undefined;
|
|
136
156
|
}) => never;
|
|
137
157
|
options: InferOutput<options>;
|
|
158
|
+
/** Variables set by middleware. */
|
|
159
|
+
var: InferVars<vars>;
|
|
138
160
|
}) => InferReturn<output> | Promise<InferReturn<output>> | AsyncGenerator<InferReturn<output>, unknown, unknown>) | undefined;
|
|
139
161
|
/** Options for the built-in `mcp add` command. */
|
|
140
162
|
mcp?: {
|
|
@@ -176,14 +198,18 @@ export type CommandsMap = Record<string, {
|
|
|
176
198
|
}>;
|
|
177
199
|
/** @internal Entry stored in a command map — either a leaf definition or a group. */
|
|
178
200
|
type CommandEntry = CommandDefinition<any, any, any> | InternalGroup;
|
|
201
|
+
/** Controls when output data is displayed. `'all'` displays to both humans and agents. `'agent-only'` suppresses data output in human/TTY mode. */
|
|
202
|
+
export type OutputPolicy = 'agent-only' | 'all';
|
|
179
203
|
/** @internal A command group's internal storage. */
|
|
180
204
|
type InternalGroup = {
|
|
181
205
|
_group: true;
|
|
182
206
|
description?: string | undefined;
|
|
207
|
+
middlewares?: MiddlewareHandler[] | undefined;
|
|
208
|
+
outputPolicy?: OutputPolicy | undefined;
|
|
183
209
|
commands: Map<string, CommandEntry>;
|
|
184
210
|
};
|
|
185
211
|
/** @internal Maps CLI instances to their command maps. */
|
|
186
|
-
export declare const toCommands: WeakMap<Cli<{}>, Map<string, CommandEntry>>;
|
|
212
|
+
export declare const toCommands: WeakMap<Cli<{}, undefined>, Map<string, CommandEntry>>;
|
|
187
213
|
/** @internal A CTA block with a description and list of suggested commands. */
|
|
188
214
|
type CtaBlock<commands extends CommandsMap = Commands> = {
|
|
189
215
|
/** Commands to suggest. */
|
|
@@ -220,8 +246,10 @@ type Usage<args extends z.ZodObject<any> | undefined, options extends z.ZodObjec
|
|
|
220
246
|
type InferOutput<schema extends z.ZodObject<any> | undefined> = schema extends z.ZodObject<any> ? z.output<schema> : {};
|
|
221
247
|
/** @internal Inferred return type for a command handler. */
|
|
222
248
|
type InferReturn<output extends z.ZodType | undefined> = output extends z.ZodType ? z.output<output> : unknown;
|
|
249
|
+
/** @internal Inferred vars type from a Zod schema, or `{}` when no schema is provided. */
|
|
250
|
+
type InferVars<vars extends z.ZodObject<any> | undefined> = vars extends z.ZodObject<any> ? z.output<vars> : {};
|
|
223
251
|
/** @internal Defines a command's schema, handler, and metadata. */
|
|
224
|
-
type CommandDefinition<args extends z.ZodObject<any> | undefined = undefined, env extends z.ZodObject<any> | undefined = undefined, options extends z.ZodObject<any> | undefined = undefined, output extends z.ZodType | undefined = undefined> = {
|
|
252
|
+
type CommandDefinition<args extends z.ZodObject<any> | undefined = undefined, env extends z.ZodObject<any> | undefined = undefined, options extends z.ZodObject<any> | undefined = undefined, output extends z.ZodType | undefined = undefined, vars extends z.ZodObject<any> | undefined = undefined> = {
|
|
225
253
|
/** Map of option names to single-char aliases. */
|
|
226
254
|
alias?: options extends z.ZodObject<any> ? Partial<Record<keyof z.output<options>, string>> : Record<string, string> | undefined;
|
|
227
255
|
/** Zod schema for positional arguments. */
|
|
@@ -240,10 +268,24 @@ type CommandDefinition<args extends z.ZodObject<any> | undefined = undefined, en
|
|
|
240
268
|
options?: options | undefined;
|
|
241
269
|
/** Zod schema for the command's return value. */
|
|
242
270
|
output?: output | undefined;
|
|
271
|
+
/**
|
|
272
|
+
* Controls when output data is displayed. Inherited by child commands when set on a group.
|
|
273
|
+
*
|
|
274
|
+
* - `'all'` — displays to both humans and agents.
|
|
275
|
+
* - `'agent-only'` — suppresses data output in human/TTY mode while still returning it to agents.
|
|
276
|
+
*
|
|
277
|
+
* @default 'all'
|
|
278
|
+
*/
|
|
279
|
+
outputPolicy?: OutputPolicy | undefined;
|
|
280
|
+
/** Middleware that runs only for this command, after root and group middleware. */
|
|
281
|
+
middleware?: MiddlewareHandler<vars>[] | undefined;
|
|
243
282
|
/** Alternative usage patterns shown in help output. */
|
|
244
283
|
usage?: Usage<args, options>[] | undefined;
|
|
245
284
|
/** The command handler. Return a value for single-return, or use `async *run` to stream chunks. */
|
|
246
285
|
run(context: {
|
|
286
|
+
/** Whether the consumer is an agent (stdout is not a TTY). */
|
|
287
|
+
agent: boolean;
|
|
288
|
+
/** Positional arguments. */
|
|
247
289
|
args: InferOutput<args>;
|
|
248
290
|
/** Parsed environment variables. */
|
|
249
291
|
env: InferOutput<env>;
|
|
@@ -259,7 +301,8 @@ type CommandDefinition<args extends z.ZodObject<any> | undefined = undefined, en
|
|
|
259
301
|
cta?: CtaBlock | undefined;
|
|
260
302
|
}) => never;
|
|
261
303
|
options: InferOutput<options>;
|
|
304
|
+
/** Variables set by middleware. */
|
|
305
|
+
var: InferVars<vars>;
|
|
262
306
|
}): InferReturn<output> | Promise<InferReturn<output>> | AsyncGenerator<InferReturn<output>, unknown, unknown>;
|
|
263
307
|
};
|
|
264
|
-
export {};
|
|
265
308
|
//# sourceMappingURL=Cli.d.ts.map
|