incur 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/SKILL.md +664 -0
- package/dist/Cli.d.ts +255 -0
- package/dist/Cli.d.ts.map +1 -0
- package/dist/Cli.js +900 -0
- package/dist/Cli.js.map +1 -0
- package/dist/Errors.d.ts +92 -0
- package/dist/Errors.d.ts.map +1 -0
- package/dist/Errors.js +75 -0
- package/dist/Errors.js.map +1 -0
- package/dist/Formatter.d.ts +5 -0
- package/dist/Formatter.d.ts.map +1 -0
- package/dist/Formatter.js +91 -0
- package/dist/Formatter.js.map +1 -0
- package/dist/Help.d.ts +53 -0
- package/dist/Help.d.ts.map +1 -0
- package/dist/Help.js +231 -0
- package/dist/Help.js.map +1 -0
- package/dist/Mcp.d.ts +13 -0
- package/dist/Mcp.d.ts.map +1 -0
- package/dist/Mcp.js +140 -0
- package/dist/Mcp.js.map +1 -0
- package/dist/Parser.d.ts +24 -0
- package/dist/Parser.d.ts.map +1 -0
- package/dist/Parser.js +215 -0
- package/dist/Parser.js.map +1 -0
- package/dist/Register.d.ts +19 -0
- package/dist/Register.d.ts.map +1 -0
- package/dist/Register.js +2 -0
- package/dist/Register.js.map +1 -0
- package/dist/Schema.d.ts +4 -0
- package/dist/Schema.d.ts.map +1 -0
- package/dist/Schema.js +8 -0
- package/dist/Schema.js.map +1 -0
- package/dist/Skill.d.ts +29 -0
- package/dist/Skill.d.ts.map +1 -0
- package/dist/Skill.js +196 -0
- package/dist/Skill.js.map +1 -0
- package/dist/Skillgen.d.ts +3 -0
- package/dist/Skillgen.d.ts.map +1 -0
- package/dist/Skillgen.js +67 -0
- package/dist/Skillgen.js.map +1 -0
- package/dist/SyncMcp.d.ts +23 -0
- package/dist/SyncMcp.d.ts.map +1 -0
- package/dist/SyncMcp.js +100 -0
- package/dist/SyncMcp.js.map +1 -0
- package/dist/SyncSkills.d.ts +38 -0
- package/dist/SyncSkills.d.ts.map +1 -0
- package/dist/SyncSkills.js +163 -0
- package/dist/SyncSkills.js.map +1 -0
- package/dist/Typegen.d.ts +6 -0
- package/dist/Typegen.d.ts.map +1 -0
- package/dist/Typegen.js +92 -0
- package/dist/Typegen.js.map +1 -0
- package/dist/bin.d.ts +14 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +30 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/pm.d.ts +3 -0
- package/dist/internal/pm.d.ts.map +1 -0
- package/dist/internal/pm.js +11 -0
- package/dist/internal/pm.js.map +1 -0
- package/dist/internal/types.d.ts +11 -0
- package/dist/internal/types.d.ts.map +1 -0
- package/dist/internal/types.js +2 -0
- package/dist/internal/types.js.map +1 -0
- package/dist/internal/utils.d.ts +8 -0
- package/dist/internal/utils.d.ts.map +1 -0
- package/dist/internal/utils.js +51 -0
- package/dist/internal/utils.js.map +1 -0
- package/examples/npm/cli.ts +180 -0
- package/examples/npm/node_modules/.bin/incur.src +21 -0
- package/examples/npm/node_modules/.bin/tsx +21 -0
- package/examples/npm/package.json +14 -0
- package/examples/npm/tsconfig.json +9 -0
- package/examples/presto/cli.ts +246 -0
- package/examples/presto/node_modules/.bin/incur.src +21 -0
- package/examples/presto/node_modules/.bin/tsx +21 -0
- package/examples/presto/package.json +14 -0
- package/examples/presto/tsconfig.json +9 -0
- package/package.json +53 -2
- package/src/Cli.test-d.ts +135 -0
- package/src/Cli.test.ts +1373 -0
- package/src/Cli.ts +1470 -0
- package/src/Errors.test.ts +96 -0
- package/src/Errors.ts +139 -0
- package/src/Formatter.test.ts +245 -0
- package/src/Formatter.ts +106 -0
- package/src/Help.test.ts +124 -0
- package/src/Help.ts +302 -0
- package/src/Mcp.test.ts +254 -0
- package/src/Mcp.ts +195 -0
- package/src/Parser.test-d.ts +45 -0
- package/src/Parser.test.ts +118 -0
- package/src/Parser.ts +247 -0
- package/src/Register.ts +18 -0
- package/src/Schema.test.ts +125 -0
- package/src/Schema.ts +8 -0
- package/src/Skill.test.ts +293 -0
- package/src/Skill.ts +253 -0
- package/src/Skillgen.ts +66 -0
- package/src/SyncMcp.test.ts +75 -0
- package/src/SyncMcp.ts +132 -0
- package/src/SyncSkills.test.ts +92 -0
- package/src/SyncSkills.ts +205 -0
- package/src/Typegen.test.ts +150 -0
- package/src/Typegen.ts +107 -0
- package/src/bin.ts +33 -0
- package/src/e2e.test.ts +1710 -0
- package/src/index.ts +14 -0
- package/src/internal/pm.test.ts +38 -0
- package/src/internal/pm.ts +8 -0
- package/src/internal/types.ts +22 -0
- package/src/internal/utils.ts +50 -0
- package/src/tsconfig.json +8 -0
package/SKILL.md
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: incur
|
|
3
|
+
description: incur is a TypeScript framework for building CLIs that work for both AI agents and humans. Use when creating new CLIs.
|
|
4
|
+
command: incur
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# incur
|
|
8
|
+
|
|
9
|
+
TypeScript framework for building CLIs for agents and human consumption. Strictly typed schemas for arguments and options, structured output envelopes, auto-generated skill files, and agent discovery via Skills, MCP, and `--llms`.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm i incur
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
pnpm i incur
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
bun i incur
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick Start
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { Cli, z } from 'incur'
|
|
29
|
+
|
|
30
|
+
const cli = Cli.create('greet', {
|
|
31
|
+
description: 'A greeting CLI',
|
|
32
|
+
args: z.object({
|
|
33
|
+
name: z.string().describe('Name to greet'),
|
|
34
|
+
}),
|
|
35
|
+
run({ args }) {
|
|
36
|
+
return { message: `hello ${args.name}` }
|
|
37
|
+
},
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
cli.serve()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
greet world
|
|
45
|
+
# → message: hello world
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Creating a CLI
|
|
49
|
+
|
|
50
|
+
`Cli.create()` is the entry point. It has two modes:
|
|
51
|
+
|
|
52
|
+
### Single-command CLI
|
|
53
|
+
|
|
54
|
+
Pass `run` to create a CLI with no subcommands:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
const cli = Cli.create('tool', {
|
|
58
|
+
description: 'Does one thing',
|
|
59
|
+
args: z.object({ file: z.string() }),
|
|
60
|
+
run({ args, options }) {
|
|
61
|
+
return { processed: args.file }
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Router CLI (subcommands)
|
|
67
|
+
|
|
68
|
+
Omit `run` to create a CLI that registers subcommands via `.command()`:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const cli = Cli.create('gh', {
|
|
72
|
+
version: '1.0.0',
|
|
73
|
+
description: 'GitHub CLI',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
cli.command('status', {
|
|
77
|
+
description: 'Show repo status',
|
|
78
|
+
run() {
|
|
79
|
+
return { clean: true }
|
|
80
|
+
},
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
cli.serve()
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Commands
|
|
87
|
+
|
|
88
|
+
### Registering commands
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
cli.command('install', {
|
|
92
|
+
description: 'Install a package',
|
|
93
|
+
args: z.object({
|
|
94
|
+
package: z.string().optional().describe('Package name'),
|
|
95
|
+
}),
|
|
96
|
+
options: z.object({
|
|
97
|
+
saveDev: z.boolean().optional().describe('Save as dev dependency'),
|
|
98
|
+
global: z.boolean().optional().describe('Install globally'),
|
|
99
|
+
}),
|
|
100
|
+
alias: { saveDev: 'D', global: 'g' },
|
|
101
|
+
output: z.object({
|
|
102
|
+
added: z.number(),
|
|
103
|
+
packages: z.number(),
|
|
104
|
+
}),
|
|
105
|
+
examples: [
|
|
106
|
+
{ args: { package: 'express' }, description: 'Install a package' },
|
|
107
|
+
{
|
|
108
|
+
args: { package: 'vitest' },
|
|
109
|
+
options: { saveDev: true },
|
|
110
|
+
description: 'Install as dev dependency',
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
run({ args, options }) {
|
|
114
|
+
return { added: 1, packages: 451 }
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`.command()` is chainable — it returns the CLI instance:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
cli
|
|
123
|
+
.command('ping', { run: () => ({ pong: true }) })
|
|
124
|
+
.command('version', { run: () => ({ version: '1.0.0' }) })
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Subcommand groups
|
|
128
|
+
|
|
129
|
+
Create a sub-CLI and mount it as a command group:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const cli = Cli.create('gh', { description: 'GitHub CLI' })
|
|
133
|
+
|
|
134
|
+
const pr = Cli.create('pr', { description: 'Pull request commands' })
|
|
135
|
+
|
|
136
|
+
pr.command('list', {
|
|
137
|
+
description: 'List pull requests',
|
|
138
|
+
options: z.object({
|
|
139
|
+
state: z.enum(['open', 'closed', 'all']).default('open'),
|
|
140
|
+
}),
|
|
141
|
+
run({ options }) {
|
|
142
|
+
return { prs: [], state: options.state }
|
|
143
|
+
},
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
pr.command('view', {
|
|
147
|
+
description: 'View a pull request',
|
|
148
|
+
args: z.object({ number: z.number() }),
|
|
149
|
+
run({ args }) {
|
|
150
|
+
return { number: args.number, title: 'Fix bug' }
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// Mount onto the parent CLI
|
|
155
|
+
cli.command(pr)
|
|
156
|
+
|
|
157
|
+
cli.serve()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
gh pr list --state closed
|
|
162
|
+
gh pr view 42
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Groups nest arbitrarily:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const cli = Cli.create('gh', { description: 'GitHub CLI' })
|
|
169
|
+
const pr = Cli.create('pr', { description: 'Pull requests' })
|
|
170
|
+
const review = Cli.create('review', { description: 'Review commands' })
|
|
171
|
+
|
|
172
|
+
review.command('approve', { run: () => ({ approved: true }) })
|
|
173
|
+
pr.command(review)
|
|
174
|
+
cli.command(pr)
|
|
175
|
+
// → gh pr review approve
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Arguments & Options
|
|
179
|
+
|
|
180
|
+
All schemas use Zod. Arguments are positional (assigned by schema key order). Options are named flags.
|
|
181
|
+
|
|
182
|
+
### Arguments
|
|
183
|
+
|
|
184
|
+
```ts
|
|
185
|
+
args: z.object({
|
|
186
|
+
repo: z.string().describe('Repository in owner/repo format'),
|
|
187
|
+
branch: z.string().optional().describe('Branch name'),
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
tool clone owner/repo main
|
|
193
|
+
# ^^^^^^^^^^ ^^^^
|
|
194
|
+
# repo branch
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Options
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
options: z.object({
|
|
201
|
+
state: z.enum(['open', 'closed']).default('open').describe('Filter by state'),
|
|
202
|
+
limit: z.number().default(30).describe('Max results'),
|
|
203
|
+
label: z.array(z.string()).optional().describe('Filter by labels'),
|
|
204
|
+
verbose: z.boolean().optional().describe('Show details'),
|
|
205
|
+
})
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Supported parsing:
|
|
209
|
+
|
|
210
|
+
- `--flag value` and `--flag=value`
|
|
211
|
+
- `-f value` short aliases (via `alias` property)
|
|
212
|
+
- `--verbose` boolean flags (`true`), `--no-verbose` (`false`)
|
|
213
|
+
- `--label bug --label feature` array options
|
|
214
|
+
- Automatic type coercion (string → number, string → boolean)
|
|
215
|
+
- Defaults from `.default()`, optionality from `.optional()`
|
|
216
|
+
|
|
217
|
+
### Aliases
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
alias: { state: 's', limit: 'l' }
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```sh
|
|
224
|
+
tool list -s closed -l 10
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Environment variables
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
env: z.object({
|
|
231
|
+
NPM_TOKEN: z.string().optional().describe('Auth token'),
|
|
232
|
+
NPM_REGISTRY: z.string().default('https://registry.npmjs.org').describe('Registry URL'),
|
|
233
|
+
})
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Environment variables are parsed from `process.env` and validated against the Zod schema.
|
|
237
|
+
|
|
238
|
+
### Usage patterns
|
|
239
|
+
|
|
240
|
+
Define alternative usage patterns to show in `--help` instead of the auto-generated synopsis:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
Cli.create('curl.md', {
|
|
244
|
+
args: z.object({ url: z.string() }),
|
|
245
|
+
options: z.object({ objective: z.string().optional() }),
|
|
246
|
+
usage: [
|
|
247
|
+
{ args: { url: true } },
|
|
248
|
+
{ args: { url: true }, options: { objective: true } },
|
|
249
|
+
{ prefix: 'cat file.txt |', suffix: '| head' },
|
|
250
|
+
],
|
|
251
|
+
run({ args }) {
|
|
252
|
+
return { content: '...' }
|
|
253
|
+
},
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Renders in help as:
|
|
258
|
+
|
|
259
|
+
```
|
|
260
|
+
Usage: curl.md <url>
|
|
261
|
+
curl.md <url> --objective <objective>
|
|
262
|
+
cat file.txt | curl.md | head
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
Each usage entry supports:
|
|
266
|
+
|
|
267
|
+
| Property | Type | Description |
|
|
268
|
+
| --------- | ---------------------------- | ------------------------------------------------ |
|
|
269
|
+
| `args` | `Partial<Record<key, true>>` | Argument keys to include as `<key>` placeholders |
|
|
270
|
+
| `options` | `Partial<Record<key, true>>` | Option keys to include as `--key <key>` flags |
|
|
271
|
+
| `prefix` | `string` | Text prepended before the command (e.g. piping) |
|
|
272
|
+
| `suffix` | `string` | Text appended after the command |
|
|
273
|
+
|
|
274
|
+
Both `args` and `options` are strictly typed from the Zod schemas — only valid keys are allowed.
|
|
275
|
+
|
|
276
|
+
Usage patterns also work on subcommands via `.command()`.
|
|
277
|
+
|
|
278
|
+
## Output
|
|
279
|
+
|
|
280
|
+
Every command returns data. incur wraps it in a structured envelope and serializes to the requested format.
|
|
281
|
+
|
|
282
|
+
### Output schema
|
|
283
|
+
|
|
284
|
+
Define `output` to declare the return shape:
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
cli.command('info', {
|
|
288
|
+
output: z.object({
|
|
289
|
+
name: z.string(),
|
|
290
|
+
version: z.string(),
|
|
291
|
+
}),
|
|
292
|
+
run() {
|
|
293
|
+
return { name: 'express', version: '4.21.2' }
|
|
294
|
+
},
|
|
295
|
+
})
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
When `output` is provided, TypeScript enforces that `run()` returns the correct shape.
|
|
299
|
+
|
|
300
|
+
### Formats
|
|
301
|
+
|
|
302
|
+
Control with `--format <fmt>` or `--json`:
|
|
303
|
+
|
|
304
|
+
| Flag | Format | Description |
|
|
305
|
+
| --------------- | -------- | -------------------------------------------- |
|
|
306
|
+
| _(default)_ | TOON | Token-efficient, ~40% fewer tokens than JSON |
|
|
307
|
+
| `--format json` | JSON | `JSON.parse()`-safe |
|
|
308
|
+
| `--format yaml` | YAML | Human-readable |
|
|
309
|
+
| `--format md` | Markdown | Tables for docs/issues |
|
|
310
|
+
|
|
311
|
+
### Envelope
|
|
312
|
+
|
|
313
|
+
With `--verbose`, the full envelope is emitted:
|
|
314
|
+
|
|
315
|
+
```sh
|
|
316
|
+
tool info express --verbose
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
ok: true
|
|
321
|
+
data:
|
|
322
|
+
name: express
|
|
323
|
+
version: 4.21.2
|
|
324
|
+
meta:
|
|
325
|
+
command: info
|
|
326
|
+
duration: 12ms
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Without `--verbose`, only `data` is emitted. On errors, only the `error` block is emitted.
|
|
330
|
+
|
|
331
|
+
### TTY detection
|
|
332
|
+
|
|
333
|
+
incur adapts output based on whether stdout is a TTY:
|
|
334
|
+
|
|
335
|
+
| Scenario | TTY (human) | Non-TTY (agent/pipe) |
|
|
336
|
+
| --------------------- | ----------------------- | -------------------- |
|
|
337
|
+
| Command output | Formatted data only | TOON envelope |
|
|
338
|
+
| Errors | Human-readable message | Error envelope |
|
|
339
|
+
| `--help` | Pretty help text | Same |
|
|
340
|
+
| `--json` / `--format` | Overrides to structured | Same |
|
|
341
|
+
|
|
342
|
+
## Structured Errors
|
|
343
|
+
|
|
344
|
+
### `ok()` and `error()` context helpers
|
|
345
|
+
|
|
346
|
+
Use the context helpers for explicit result control:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
run({ args, ok, error }) {
|
|
350
|
+
const item = await db.find(args.id)
|
|
351
|
+
if (!item)
|
|
352
|
+
return error({
|
|
353
|
+
code: 'NOT_FOUND',
|
|
354
|
+
message: `Item ${args.id} not found`,
|
|
355
|
+
retryable: false,
|
|
356
|
+
})
|
|
357
|
+
return ok(item)
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### CTAs (Call to Action)
|
|
362
|
+
|
|
363
|
+
Suggest next commands to guide agents on success:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
run({ args, ok }) {
|
|
367
|
+
const result = { id: 42, name: args.name }
|
|
368
|
+
return ok(result, {
|
|
369
|
+
cta: {
|
|
370
|
+
description: 'Suggested commands:',
|
|
371
|
+
commands: [
|
|
372
|
+
{ command: 'get', args: { id: 42 }, description: 'View the item' },
|
|
373
|
+
'list',
|
|
374
|
+
],
|
|
375
|
+
},
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Or on errors, to help agents self-correct:
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
run({ args, error }) {
|
|
384
|
+
const token = process.env.GH_TOKEN
|
|
385
|
+
if (!token)
|
|
386
|
+
return error({
|
|
387
|
+
code: 'NOT_AUTHENTICATED',
|
|
388
|
+
message: 'GitHub token not found',
|
|
389
|
+
retryable: true,
|
|
390
|
+
cta: {
|
|
391
|
+
description: 'To authenticate:',
|
|
392
|
+
commands: [
|
|
393
|
+
{ command: 'auth login', description: 'Log in to GitHub' },
|
|
394
|
+
{ command: 'config set', options: { token: true }, description: 'Set token manually' },
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
})
|
|
398
|
+
// ...
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Agent Discovery
|
|
403
|
+
|
|
404
|
+
### MCP Server
|
|
405
|
+
|
|
406
|
+
Every incur CLI has built-in Model Context Protocol (MCP) support — exposing commands as MCP tools that agents can call directly.
|
|
407
|
+
|
|
408
|
+
#### `mcp add` built-in command
|
|
409
|
+
|
|
410
|
+
Register the CLI as an MCP server for your agents:
|
|
411
|
+
|
|
412
|
+
```sh
|
|
413
|
+
my-cli mcp add
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
This registers the CLI with your agent's MCP config. Works with Claude Code, Cursor, Amp, and others out of the box.
|
|
417
|
+
|
|
418
|
+
Options:
|
|
419
|
+
|
|
420
|
+
| Flag | Description |
|
|
421
|
+
| ----------------- | -------------------------------------------------------- |
|
|
422
|
+
| `-c`, `--command` | Override the command agents will run to start the server |
|
|
423
|
+
| `--agent <agent>` | Target a specific agent (e.g. `claude-code`, `cursor`) |
|
|
424
|
+
| `--no-global` | Install to project instead of globally |
|
|
425
|
+
|
|
426
|
+
#### `--mcp` flag
|
|
427
|
+
|
|
428
|
+
Start the CLI as an MCP stdio server:
|
|
429
|
+
|
|
430
|
+
```sh
|
|
431
|
+
my-cli --mcp
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
This exposes all commands as MCP tools over stdin/stdout. Command groups are flattened with underscores (e.g. `pr_list`, `pr_view`). Arguments and options are merged into a single flat input schema.
|
|
435
|
+
|
|
436
|
+
### Skills
|
|
437
|
+
|
|
438
|
+
All incur-based CLIs can auto-generate and install agent skill files with `skills add`:
|
|
439
|
+
|
|
440
|
+
```sh
|
|
441
|
+
my-cli skills add
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
This generates Markdown skill files from your command definitions and installs them so agents discover your CLI automatically.
|
|
445
|
+
|
|
446
|
+
#### Configuration
|
|
447
|
+
|
|
448
|
+
It is also possible to configure `skills add`:
|
|
449
|
+
|
|
450
|
+
```ts
|
|
451
|
+
const cli = Cli.create('my-cli', {
|
|
452
|
+
sync: {
|
|
453
|
+
depth: 1,
|
|
454
|
+
include: ['_root'],
|
|
455
|
+
suggestions: ['install react as a dependency', 'check for outdated packages'],
|
|
456
|
+
},
|
|
457
|
+
})
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
| Option | Type | Description |
|
|
461
|
+
| ------------- | ---------- | ---------------------------------------------------------------------------------------------------- |
|
|
462
|
+
| `depth` | `number` | Grouping depth for skill files. `0` = single file, `1` = one per top-level command. Default: `1` |
|
|
463
|
+
| `include` | `string[]` | Glob patterns for additional SKILL.md files to include. Use `'_root'` for the project-level SKILL.md |
|
|
464
|
+
| `suggestions` | `string[]` | Example prompts shown after sync to help users get started |
|
|
465
|
+
|
|
466
|
+
### `--llms` flag
|
|
467
|
+
|
|
468
|
+
Every incur CLI gets a built-in `--llms` flag that outputs a machine-readable manifest of all commands:
|
|
469
|
+
|
|
470
|
+
```sh
|
|
471
|
+
tool --llms
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
Outputs Markdown skill documentation by default.
|
|
475
|
+
|
|
476
|
+
```md
|
|
477
|
+
# tool install
|
|
478
|
+
|
|
479
|
+
Install a package
|
|
480
|
+
|
|
481
|
+
## Arguments
|
|
482
|
+
|
|
483
|
+
| Name | Type | Required | Description |
|
|
484
|
+
| --------- | -------- | -------- | ----------------------- |
|
|
485
|
+
| `package` | `string` | no | Package name to install |
|
|
486
|
+
|
|
487
|
+
## Options
|
|
488
|
+
|
|
489
|
+
| Flag | Type | Default | Description |
|
|
490
|
+
| ----------- | --------- | ------- | ---------------------- |
|
|
491
|
+
| `--saveDev` | `boolean` | | Save as dev dependency |
|
|
492
|
+
| `--global` | `boolean` | | Install globally |
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Use `--llms --format json` for JSON schema manifest:
|
|
496
|
+
|
|
497
|
+
```json
|
|
498
|
+
{
|
|
499
|
+
"version": "incur.v1",
|
|
500
|
+
"commands": [
|
|
501
|
+
{
|
|
502
|
+
"name": "install",
|
|
503
|
+
"description": "Install a package",
|
|
504
|
+
"schema": {
|
|
505
|
+
"args": { "type": "object", "properties": { "package": { "type": "string" } } },
|
|
506
|
+
"options": { "type": "object", "properties": { "saveDev": { "type": "boolean" } } },
|
|
507
|
+
"output": { "type": "object", "properties": { "added": { "type": "number" } } }
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
}
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Built-in Flags
|
|
515
|
+
|
|
516
|
+
| Flag | Description |
|
|
517
|
+
| ---------------- | -------------------------------------------- |
|
|
518
|
+
| `--help`, `-h` | Show help for the CLI or a specific command |
|
|
519
|
+
| `--version` | Print CLI version |
|
|
520
|
+
| `--llms` | Output agent-readable command manifest |
|
|
521
|
+
| `--mcp` | Start as an MCP stdio server |
|
|
522
|
+
| `--json` | Shorthand for `--format json` |
|
|
523
|
+
| `--format <fmt>` | Output format: `toon`, `json`, `yaml`, `md` |
|
|
524
|
+
| `--verbose` | Include full envelope (`ok`, `data`, `meta`) |
|
|
525
|
+
|
|
526
|
+
## Examples
|
|
527
|
+
|
|
528
|
+
### Typed examples on commands
|
|
529
|
+
|
|
530
|
+
```ts
|
|
531
|
+
cli.command('deploy', {
|
|
532
|
+
args: z.object({ env: z.enum(['staging', 'production']) }),
|
|
533
|
+
options: z.object({ force: z.boolean().optional() }),
|
|
534
|
+
examples: [
|
|
535
|
+
{ args: { env: 'staging' }, description: 'Deploy to staging' },
|
|
536
|
+
{ args: { env: 'production' }, options: { force: true }, description: 'Force deploy to prod' },
|
|
537
|
+
],
|
|
538
|
+
run({ args }) {
|
|
539
|
+
return { deployed: args.env }
|
|
540
|
+
},
|
|
541
|
+
})
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
Examples appear in `--help` output and generated skill files.
|
|
545
|
+
|
|
546
|
+
### Hints
|
|
547
|
+
|
|
548
|
+
```ts
|
|
549
|
+
cli.command('publish', {
|
|
550
|
+
hint: 'Requires NPM_TOKEN to be set in your environment.',
|
|
551
|
+
// ...
|
|
552
|
+
})
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Hints are displayed after examples in help output and included in skill files.
|
|
556
|
+
|
|
557
|
+
## Serving
|
|
558
|
+
|
|
559
|
+
Call `.serve()` to parse `process.argv` and run:
|
|
560
|
+
|
|
561
|
+
```ts
|
|
562
|
+
cli.serve()
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
For testing, pass custom argv and DI overrides:
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
let output = ''
|
|
569
|
+
await cli.serve(['install', 'express', '--json'], {
|
|
570
|
+
stdout(s) {
|
|
571
|
+
output += s
|
|
572
|
+
},
|
|
573
|
+
exit() {},
|
|
574
|
+
})
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### `serve()` options
|
|
578
|
+
|
|
579
|
+
| Option | Type | Description |
|
|
580
|
+
| -------- | ------------------------------------- | ------------------------------ |
|
|
581
|
+
| `stdout` | `(s: string) => void` | Override stdout writer |
|
|
582
|
+
| `exit` | `(code: number) => void` | Override exit handler |
|
|
583
|
+
| `env` | `Record<string, string \| undefined>` | Override environment variables |
|
|
584
|
+
|
|
585
|
+
## Type Generation
|
|
586
|
+
|
|
587
|
+
Generate type definitions for your CLI's command map to get typed CTAs:
|
|
588
|
+
|
|
589
|
+
```sh
|
|
590
|
+
incur gen
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
This creates a `incur.generated.ts` file that registers your commands on the `Cli.Commands` type, enabling autocomplete on CTA command names, args, and options.
|
|
594
|
+
|
|
595
|
+
## Full Example
|
|
596
|
+
|
|
597
|
+
```ts
|
|
598
|
+
import { Cli, z } from 'incur'
|
|
599
|
+
|
|
600
|
+
const cli = Cli.create('npm', {
|
|
601
|
+
version: '10.9.2',
|
|
602
|
+
description: 'The package manager for JavaScript.',
|
|
603
|
+
sync: {
|
|
604
|
+
suggestions: ['install react as a dependency', 'check for outdated packages'],
|
|
605
|
+
},
|
|
606
|
+
})
|
|
607
|
+
|
|
608
|
+
cli.command('install', {
|
|
609
|
+
description: 'Install a package',
|
|
610
|
+
args: z.object({
|
|
611
|
+
package: z.string().optional().describe('Package name to install'),
|
|
612
|
+
}),
|
|
613
|
+
options: z.object({
|
|
614
|
+
saveDev: z.boolean().optional().describe('Save as dev dependency'),
|
|
615
|
+
global: z.boolean().optional().describe('Install globally'),
|
|
616
|
+
}),
|
|
617
|
+
alias: { saveDev: 'D', global: 'g' },
|
|
618
|
+
output: z.object({
|
|
619
|
+
added: z.number().describe('Number of packages added'),
|
|
620
|
+
packages: z.number().describe('Total packages'),
|
|
621
|
+
}),
|
|
622
|
+
examples: [
|
|
623
|
+
{ args: { package: 'express' }, description: 'Install a package' },
|
|
624
|
+
{
|
|
625
|
+
args: { package: 'vitest' },
|
|
626
|
+
options: { saveDev: true },
|
|
627
|
+
description: 'Install as dev dependency',
|
|
628
|
+
},
|
|
629
|
+
],
|
|
630
|
+
run({ args }) {
|
|
631
|
+
if (!args.package) return { added: 120, packages: 450 }
|
|
632
|
+
return { added: 1, packages: 451 }
|
|
633
|
+
},
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
cli.command('outdated', {
|
|
637
|
+
description: 'Check for outdated packages',
|
|
638
|
+
options: z.object({
|
|
639
|
+
global: z.boolean().describe('Check global packages'),
|
|
640
|
+
}),
|
|
641
|
+
alias: { global: 'g' },
|
|
642
|
+
output: z.object({
|
|
643
|
+
packages: z.array(
|
|
644
|
+
z.object({
|
|
645
|
+
name: z.string(),
|
|
646
|
+
current: z.string(),
|
|
647
|
+
wanted: z.string(),
|
|
648
|
+
latest: z.string(),
|
|
649
|
+
}),
|
|
650
|
+
),
|
|
651
|
+
}),
|
|
652
|
+
run() {
|
|
653
|
+
return {
|
|
654
|
+
packages: [{ name: 'express', current: '4.18.0', wanted: '4.21.2', latest: '4.21.2' }],
|
|
655
|
+
}
|
|
656
|
+
},
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
cli.serve()
|
|
660
|
+
|
|
661
|
+
export default cli
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
> Always `export default cli` so that `incur gen` can import it and generate types.
|