incur 0.2.2 → 0.3.0

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/src/Help.test.ts CHANGED
@@ -28,7 +28,7 @@ describe('formatCommand', () => {
28
28
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
29
29
  --format <toon|json|yaml|md|jsonl> Output format
30
30
  --help Show help
31
- --llms Print LLM-readable manifest
31
+ --llms, --llms-full Print LLM-readable manifest
32
32
  --schema Show JSON Schema for a command
33
33
  --token-count Print token count of output (instead of output)
34
34
  --token-limit <n> Limit output to n tokens
@@ -50,7 +50,7 @@ describe('formatCommand', () => {
50
50
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
51
51
  --format <toon|json|yaml|md|jsonl> Output format
52
52
  --help Show help
53
- --llms Print LLM-readable manifest
53
+ --llms, --llms-full Print LLM-readable manifest
54
54
  --schema Show JSON Schema for a command
55
55
  --token-count Print token count of output (instead of output)
56
56
  --token-limit <n> Limit output to n tokens
@@ -79,7 +79,7 @@ describe('formatCommand', () => {
79
79
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
80
80
  --format <toon|json|yaml|md|jsonl> Output format
81
81
  --help Show help
82
- --llms Print LLM-readable manifest
82
+ --llms, --llms-full Print LLM-readable manifest
83
83
  --schema Show JSON Schema for a command
84
84
  --token-count Print token count of output (instead of output)
85
85
  --token-limit <n> Limit output to n tokens
@@ -125,7 +125,7 @@ describe('formatRoot', () => {
125
125
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
126
126
  --format <toon|json|yaml|md|jsonl> Output format
127
127
  --help Show help
128
- --llms Print LLM-readable manifest
128
+ --llms, --llms-full Print LLM-readable manifest
129
129
  --schema Show JSON Schema for a command
130
130
  --token-count Print token count of output (instead of output)
131
131
  --token-limit <n> Limit output to n tokens
@@ -150,7 +150,7 @@ describe('formatRoot', () => {
150
150
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
151
151
  --format <toon|json|yaml|md|jsonl> Output format
152
152
  --help Show help
153
- --llms Print LLM-readable manifest
153
+ --llms, --llms-full Print LLM-readable manifest
154
154
  --schema Show JSON Schema for a command
155
155
  --token-count Print token count of output (instead of output)
156
156
  --token-limit <n> Limit output to n tokens
@@ -179,7 +179,7 @@ describe('formatRoot', () => {
179
179
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
180
180
  --format <toon|json|yaml|md|jsonl> Output format
181
181
  --help Show help
182
- --llms Print LLM-readable manifest
182
+ --llms, --llms-full Print LLM-readable manifest
183
183
  --schema Show JSON Schema for a command
184
184
  --token-count Print token count of output (instead of output)
185
185
  --token-limit <n> Limit output to n tokens
@@ -208,7 +208,7 @@ describe('formatRoot', () => {
208
208
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
209
209
  --format <toon|json|yaml|md|jsonl> Output format
210
210
  --help Show help
211
- --llms Print LLM-readable manifest
211
+ --llms, --llms-full Print LLM-readable manifest
212
212
  --schema Show JSON Schema for a command
213
213
  --token-count Print token count of output (instead of output)
214
214
  --token-limit <n> Limit output to n tokens
package/src/Help.ts CHANGED
@@ -329,7 +329,7 @@ function globalOptionsLines(root = false): string[] {
329
329
  { flag: '--filter-output <keys>', desc: 'Filter output by key paths (e.g. foo,bar.baz,a[0,3])' },
330
330
  { flag: '--format <toon|json|yaml|md|jsonl>', desc: 'Output format' },
331
331
  { flag: '--help', desc: 'Show help' },
332
- { flag: '--llms', desc: 'Print LLM-readable manifest' },
332
+ { flag: '--llms, --llms-full', desc: 'Print LLM-readable manifest' },
333
333
  ...(root ? [{ flag: '--mcp', desc: 'Start as MCP stdio server' }] : []),
334
334
  { flag: '--schema', desc: 'Show JSON Schema for a command' },
335
335
  { flag: '--token-count', desc: 'Print token count of output (instead of output)' },
package/src/Skill.test.ts CHANGED
@@ -145,6 +145,41 @@ test('concatenates multiple commands', () => {
145
145
  `)
146
146
  })
147
147
 
148
+ describe('index', () => {
149
+ test('generates compact command index', () => {
150
+ const result = Skill.index('test', [
151
+ { name: 'ping', description: 'Health check' },
152
+ { name: 'greet', description: 'Greet someone', args: z.object({ name: z.string() }) },
153
+ ])
154
+ expect(result).toMatchInlineSnapshot(`
155
+ "# test
156
+
157
+ | Command | Description |
158
+ |---------|-------------|
159
+ | \`test ping\` | Health check |
160
+ | \`test greet <name>\` | Greet someone |
161
+
162
+ Run \`test --llms-full\` for full manifest. Run \`test <command> --schema\` for argument details."
163
+ `)
164
+ })
165
+
166
+ test('uses brackets for optional args', () => {
167
+ const result = Skill.index('test', [
168
+ {
169
+ name: 'install',
170
+ description: 'Install a package',
171
+ args: z.object({ package: z.string().optional() }),
172
+ },
173
+ ])
174
+ expect(result).toContain('`test install [package]`')
175
+ })
176
+
177
+ test('handles commands without descriptions', () => {
178
+ const result = Skill.index('test', [{ name: 'ping' }])
179
+ expect(result).toContain('| `test ping` | |')
180
+ })
181
+ })
182
+
148
183
  describe('hash', () => {
149
184
  test('returns consistent hash for same commands', () => {
150
185
  const commands: Skill.CommandInfo[] = [
@@ -218,6 +253,7 @@ describe('split', () => {
218
253
  "---
219
254
  name: gh-auth
220
255
  description: Authenticate with GitHub. Log in, Check status. Run \`gh auth --help\` for usage details.
256
+ requires_bin: gh
221
257
  command: gh auth
222
258
  ---
223
259
 
@@ -235,6 +271,7 @@ describe('split', () => {
235
271
  "---
236
272
  name: gh-pr
237
273
  description: Manage pull requests. List PRs, Create PR. Run \`gh pr --help\` for usage details.
274
+ requires_bin: gh
238
275
  command: gh pr
239
276
  ---
240
277
 
@@ -294,9 +331,16 @@ describe('split', () => {
294
331
  expect(files[0]!.content).toContain('Run `gh auth login --help` for usage details.')
295
332
  })
296
333
 
297
- test('omits --help hint when no descriptions exist', () => {
334
+ test('emits fallback description when no explicit descriptions exist', () => {
298
335
  const files = Skill.split('test', [{ name: 'ping' }], 1)
299
- expect(files[0]!.content).not.toContain('--help')
336
+ expect(files[0]!.content).toContain(
337
+ 'description: Run `test ping --help` for usage details.',
338
+ )
339
+ })
340
+
341
+ test('includes requires_bin in frontmatter', () => {
342
+ const files = Skill.split('gh', [{ name: 'auth login', description: 'Log in' }], 1)
343
+ expect(files[0]!.content).toContain('requires_bin: gh')
300
344
  })
301
345
 
302
346
  test('no per-command frontmatter in split files', () => {
package/src/Skill.ts CHANGED
@@ -23,6 +23,33 @@ export type File = {
23
23
  content: string
24
24
  }
25
25
 
26
+ /** Generates a compact Markdown command index for `--llms`. */
27
+ export function index(name: string, commands: CommandInfo[], description?: string | undefined): string {
28
+ const lines: string[] = [`# ${name}`]
29
+ if (description) lines.push('', description)
30
+ lines.push('')
31
+ lines.push('| Command | Description |')
32
+ lines.push('|---------|-------------|')
33
+ for (const cmd of commands) {
34
+ const signature = buildSignature(name, cmd)
35
+ const desc = cmd.description ?? ''
36
+ lines.push(`| \`${signature}\` | ${desc} |`)
37
+ }
38
+ lines.push('', `Run \`${name} --llms-full\` for full manifest. Run \`${name} <command> --schema\` for argument details.`)
39
+ return lines.join('\n')
40
+ }
41
+
42
+ /** @internal Builds a command signature with arg placeholders. */
43
+ function buildSignature(cli: string, cmd: CommandInfo): string {
44
+ const base = `${cli} ${cmd.name}`
45
+ if (!cmd.args) return base
46
+ const shape = cmd.args.shape as Record<string, z.ZodType>
47
+ const json = Schema.toJsonSchema(cmd.args)
48
+ const required = new Set((json.required as string[] | undefined) ?? [])
49
+ const argNames = Object.keys(shape).map((k) => (required.has(k) ? `<${k}>` : `[${k}]`))
50
+ return `${base} ${argNames.join(' ')}`
51
+ }
52
+
26
53
  /** Generates a Markdown skill file from a CLI name and collected command data. */
27
54
  export function generate(
28
55
  name: string,
@@ -88,12 +115,15 @@ function renderGroup(
88
115
  const descParts: string[] = []
89
116
  if (groupDesc) descParts.push(groupDesc.replace(/\.$/, ''))
90
117
  if (childDescs.length > 0) descParts.push(childDescs.join(', '))
91
- const description = descParts.join('. ') || undefined
118
+ const description =
119
+ descParts.length > 0
120
+ ? `${descParts.join('. ')}. Run \`${title} --help\` for usage details.`
121
+ : `Run \`${title} --help\` for usage details.`
92
122
 
93
123
  const slug = title.replace(/\s+/g, '-')
94
124
  const fm = ['---', `name: ${slug}`]
95
- if (description)
96
- fm.push(`description: ${description}. Run \`${title} --help\` for usage details.`)
125
+ fm.push(`description: ${description}`)
126
+ fm.push(`requires_bin: ${cli}`)
97
127
  fm.push(`command: ${title}`, '---')
98
128
 
99
129
  const body = cmds.map((cmd) => renderCommandBody(cli, cmd)).join('\n\n---\n\n')
package/src/SyncSkills.ts CHANGED
@@ -13,8 +13,8 @@ export async function sync(
13
13
  commands: Map<string, any>,
14
14
  options: sync.Options = {},
15
15
  ): Promise<sync.Result> {
16
- const cwd = options.cwd ?? resolvePackageRoot()
17
16
  const { depth = 1, description, global = true } = options
17
+ const cwd = options.cwd ?? (global ? resolvePackageRoot() : process.cwd())
18
18
 
19
19
  const groups = new Map<string, string>()
20
20
  if (description) groups.set(name, description)
package/src/e2e.test.ts CHANGED
@@ -939,7 +939,7 @@ describe('help', () => {
939
939
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
940
940
  --format <toon|json|yaml|md|jsonl> Output format
941
941
  --help Show help
942
- --llms Print LLM-readable manifest
942
+ --llms, --llms-full Print LLM-readable manifest
943
943
  --mcp Start as MCP stdio server
944
944
  --schema Show JSON Schema for a command
945
945
  --token-count Print token count of output (instead of output)
@@ -973,7 +973,7 @@ describe('help', () => {
973
973
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
974
974
  --format <toon|json|yaml|md|jsonl> Output format
975
975
  --help Show help
976
- --llms Print LLM-readable manifest
976
+ --llms, --llms-full Print LLM-readable manifest
977
977
  --schema Show JSON Schema for a command
978
978
  --token-count Print token count of output (instead of output)
979
979
  --token-limit <n> Limit output to n tokens
@@ -1000,7 +1000,7 @@ describe('help', () => {
1000
1000
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1001
1001
  --format <toon|json|yaml|md|jsonl> Output format
1002
1002
  --help Show help
1003
- --llms Print LLM-readable manifest
1003
+ --llms, --llms-full Print LLM-readable manifest
1004
1004
  --schema Show JSON Schema for a command
1005
1005
  --token-count Print token count of output (instead of output)
1006
1006
  --token-limit <n> Limit output to n tokens
@@ -1026,7 +1026,7 @@ describe('help', () => {
1026
1026
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1027
1027
  --format <toon|json|yaml|md|jsonl> Output format
1028
1028
  --help Show help
1029
- --llms Print LLM-readable manifest
1029
+ --llms, --llms-full Print LLM-readable manifest
1030
1030
  --schema Show JSON Schema for a command
1031
1031
  --token-count Print token count of output (instead of output)
1032
1032
  --token-limit <n> Limit output to n tokens
@@ -1058,7 +1058,7 @@ describe('help', () => {
1058
1058
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1059
1059
  --format <toon|json|yaml|md|jsonl> Output format
1060
1060
  --help Show help
1061
- --llms Print LLM-readable manifest
1061
+ --llms, --llms-full Print LLM-readable manifest
1062
1062
  --schema Show JSON Schema for a command
1063
1063
  --token-count Print token count of output (instead of output)
1064
1064
  --token-limit <n> Limit output to n tokens
@@ -1090,9 +1090,9 @@ describe('help', () => {
1090
1090
  })
1091
1091
  })
1092
1092
 
1093
- describe('--llms', () => {
1093
+ describe('--llms-full', () => {
1094
1094
  test('json manifest lists all leaf commands sorted', async () => {
1095
- const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1095
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'json'])
1096
1096
  const manifest = json(output)
1097
1097
  expect(manifest.version).toBe('incur.v1')
1098
1098
  const names = manifest.commands.map((c: any) => c.name)
@@ -1127,7 +1127,7 @@ describe('--llms', () => {
1127
1127
  })
1128
1128
 
1129
1129
  test('manifest includes schema.args and schema.options separately', async () => {
1130
- const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1130
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'json'])
1131
1131
  const projectList = json(output).commands.find((c: any) => c.name === 'project list')
1132
1132
  expect(projectList.schema.options.properties).toMatchInlineSnapshot(`
1133
1133
  {
@@ -1157,7 +1157,7 @@ describe('--llms', () => {
1157
1157
  })
1158
1158
 
1159
1159
  test('manifest includes schema.output', async () => {
1160
- const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1160
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'json'])
1161
1161
  const projectGet = json(output).commands.find((c: any) => c.name === 'project get')
1162
1162
  expect(projectGet.schema.output).toMatchInlineSnapshot(`
1163
1163
  {
@@ -1204,13 +1204,13 @@ describe('--llms', () => {
1204
1204
  })
1205
1205
 
1206
1206
  test('manifest omits schema when no schemas defined', async () => {
1207
- const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1207
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'json'])
1208
1208
  const ping = json(output).commands.find((c: any) => c.name === 'ping')
1209
1209
  expect(ping.schema).toBeUndefined()
1210
1210
  })
1211
1211
 
1212
- test('scoped --llms to group', async () => {
1213
- const { output } = await serve(createApp(), ['auth', '--llms', '--format', 'json'])
1212
+ test('scoped --llms-full to group', async () => {
1213
+ const { output } = await serve(createApp(), ['auth', '--llms-full', '--format', 'json'])
1214
1214
  const names = json(output).commands.map((c: any) => c.name)
1215
1215
  expect(names).toMatchInlineSnapshot(`
1216
1216
  [
@@ -1221,8 +1221,8 @@ describe('--llms', () => {
1221
1221
  `)
1222
1222
  })
1223
1223
 
1224
- test('scoped --llms to nested group', async () => {
1225
- const { output } = await serve(createApp(), ['project', 'deploy', '--llms', '--format', 'json'])
1224
+ test('scoped --llms-full to nested group', async () => {
1225
+ const { output } = await serve(createApp(), ['project', 'deploy', '--llms-full', '--format', 'json'])
1226
1226
  const names = json(output).commands.map((c: any) => c.name)
1227
1227
  expect(names).toMatchInlineSnapshot(`
1228
1228
  [
@@ -1233,27 +1233,27 @@ describe('--llms', () => {
1233
1233
  `)
1234
1234
  })
1235
1235
 
1236
- test('default --llms outputs markdown', async () => {
1237
- const { output } = await serve(createApp(), ['--llms'])
1236
+ test('default --llms-full outputs markdown', async () => {
1237
+ const { output } = await serve(createApp(), ['--llms-full'])
1238
1238
  expect(output).toContain('# app')
1239
1239
  expect(output).toContain('auth login')
1240
1240
  expect(output).toContain('project list')
1241
1241
  })
1242
1242
 
1243
- test('--llms markdown includes argument tables', async () => {
1244
- const { output } = await serve(createApp(), ['project', '--llms'])
1243
+ test('--llms-full markdown includes argument tables', async () => {
1244
+ const { output } = await serve(createApp(), ['project', '--llms-full'])
1245
1245
  expect(output).toContain('Arguments')
1246
1246
  expect(output).toContain('`id`')
1247
1247
  })
1248
1248
 
1249
- test('--llms markdown includes options tables', async () => {
1250
- const { output } = await serve(createApp(), ['project', '--llms'])
1249
+ test('--llms-full markdown includes options tables', async () => {
1250
+ const { output } = await serve(createApp(), ['project', '--llms-full'])
1251
1251
  expect(output).toContain('Options')
1252
1252
  expect(output).toContain('`--limit`')
1253
1253
  })
1254
1254
 
1255
- test('--llms json includes examples on commands', async () => {
1256
- const { output } = await serve(createApp(), ['project', 'deploy', '--llms', '--format', 'json'])
1255
+ test('--llms-full json includes examples on commands', async () => {
1256
+ const { output } = await serve(createApp(), ['project', 'deploy', '--llms-full', '--format', 'json'])
1257
1257
  const deployCreate = json(output).commands.find((c: any) => c.name === 'project deploy create')
1258
1258
  expect(deployCreate.examples).toMatchInlineSnapshot(`
1259
1259
  [
@@ -1269,27 +1269,268 @@ describe('--llms', () => {
1269
1269
  `)
1270
1270
  })
1271
1271
 
1272
- test('--llms json omits examples when not defined', async () => {
1273
- const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1272
+ test('--llms-full json omits examples when not defined', async () => {
1273
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'json'])
1274
1274
  const ping = json(output).commands.find((c: any) => c.name === 'ping')
1275
1275
  expect(ping.examples).toBeUndefined()
1276
1276
  })
1277
1277
 
1278
- test('--llms markdown includes examples section', async () => {
1279
- const { output } = await serve(createApp(), ['--llms'])
1278
+ test('--llms-full markdown includes examples section', async () => {
1279
+ const { output } = await serve(createApp(), ['--llms-full'])
1280
1280
  expect(output).toContain('Examples')
1281
1281
  expect(output).toContain('Deploy staging from main')
1282
1282
  expect(output).toContain('app project deploy create staging')
1283
1283
  })
1284
1284
 
1285
- test('--llms markdown includes output tables', async () => {
1286
- const { output } = await serve(createApp(), ['project', '--llms'])
1285
+ test('--llms-full markdown includes output tables', async () => {
1286
+ const { output } = await serve(createApp(), ['project', '--llms-full'])
1287
1287
  expect(output).toContain('Output')
1288
1288
  })
1289
1289
 
1290
+ test('--llms-full --format yaml', async () => {
1291
+ const { output } = await serve(createApp(), ['--llms-full', '--format', 'yaml'])
1292
+ expect(output).toContain('version: incur.v1')
1293
+ })
1294
+ })
1295
+
1296
+ describe('--llms', () => {
1297
+ test('outputs compact markdown table with all commands', async () => {
1298
+ const { output } = await serve(createApp(), ['--llms'])
1299
+ expect(output).toMatchInlineSnapshot(`
1300
+ "# app
1301
+
1302
+ A comprehensive CLI application for testing.
1303
+
1304
+ | Command | Description |
1305
+ |---------|-------------|
1306
+ | \`app api\` | Proxy to HTTP API |
1307
+ | \`app auth login\` | Log in to the service |
1308
+ | \`app auth logout\` | Log out of the service |
1309
+ | \`app auth status\` | Show authentication status |
1310
+ | \`app config [key]\` | Show current configuration |
1311
+ | \`app echo <message> [repeat]\` | Echo back arguments |
1312
+ | \`app explode\` | Always fails |
1313
+ | \`app explode-clac\` | Fails with IncurError |
1314
+ | \`app noop\` | Returns nothing |
1315
+ | \`app ping\` | Health check |
1316
+ | \`app project create <name>\` | Create a new project |
1317
+ | \`app project delete <id>\` | Delete a project |
1318
+ | \`app project deploy create <env>\` | Create a deployment |
1319
+ | \`app project deploy rollback <deployId>\` | Rollback a deployment |
1320
+ | \`app project deploy status <deployId>\` | Check deployment status |
1321
+ | \`app project get <id>\` | Get a project by ID |
1322
+ | \`app project list\` | List projects |
1323
+ | \`app slow\` | Async command |
1324
+ | \`app stream\` | Stream chunks |
1325
+ | \`app stream-error\` | Stream with mid-stream error |
1326
+ | \`app stream-ok\` | Stream with ok() return |
1327
+ | \`app stream-text\` | Stream plain text |
1328
+ | \`app stream-throw\` | Stream that throws |
1329
+ | \`app validate-fail <email> <age>\` | Fails validation |
1330
+
1331
+ Run \`app --llms-full\` for full manifest. Run \`app <command> --schema\` for argument details.
1332
+ "
1333
+ `)
1334
+ })
1335
+
1336
+ test('json manifest has name + description only, no schema/examples', async () => {
1337
+ const { output } = await serve(createApp(), ['--llms', '--format', 'json'])
1338
+ expect(json(output)).toMatchInlineSnapshot(`
1339
+ {
1340
+ "commands": [
1341
+ {
1342
+ "description": "Proxy to HTTP API",
1343
+ "name": "api",
1344
+ },
1345
+ {
1346
+ "description": "Log in to the service",
1347
+ "name": "auth login",
1348
+ },
1349
+ {
1350
+ "description": "Log out of the service",
1351
+ "name": "auth logout",
1352
+ },
1353
+ {
1354
+ "description": "Show authentication status",
1355
+ "name": "auth status",
1356
+ },
1357
+ {
1358
+ "description": "Show current configuration",
1359
+ "name": "config",
1360
+ },
1361
+ {
1362
+ "description": "Echo back arguments",
1363
+ "name": "echo",
1364
+ },
1365
+ {
1366
+ "description": "Always fails",
1367
+ "name": "explode",
1368
+ },
1369
+ {
1370
+ "description": "Fails with IncurError",
1371
+ "name": "explode-clac",
1372
+ },
1373
+ {
1374
+ "description": "Returns nothing",
1375
+ "name": "noop",
1376
+ },
1377
+ {
1378
+ "description": "Health check",
1379
+ "name": "ping",
1380
+ },
1381
+ {
1382
+ "description": "Create a new project",
1383
+ "name": "project create",
1384
+ },
1385
+ {
1386
+ "description": "Delete a project",
1387
+ "name": "project delete",
1388
+ },
1389
+ {
1390
+ "description": "Create a deployment",
1391
+ "name": "project deploy create",
1392
+ },
1393
+ {
1394
+ "description": "Rollback a deployment",
1395
+ "name": "project deploy rollback",
1396
+ },
1397
+ {
1398
+ "description": "Check deployment status",
1399
+ "name": "project deploy status",
1400
+ },
1401
+ {
1402
+ "description": "Get a project by ID",
1403
+ "name": "project get",
1404
+ },
1405
+ {
1406
+ "description": "List projects",
1407
+ "name": "project list",
1408
+ },
1409
+ {
1410
+ "description": "Async command",
1411
+ "name": "slow",
1412
+ },
1413
+ {
1414
+ "description": "Stream chunks",
1415
+ "name": "stream",
1416
+ },
1417
+ {
1418
+ "description": "Stream with mid-stream error",
1419
+ "name": "stream-error",
1420
+ },
1421
+ {
1422
+ "description": "Stream with ok() return",
1423
+ "name": "stream-ok",
1424
+ },
1425
+ {
1426
+ "description": "Stream plain text",
1427
+ "name": "stream-text",
1428
+ },
1429
+ {
1430
+ "description": "Stream that throws",
1431
+ "name": "stream-throw",
1432
+ },
1433
+ {
1434
+ "description": "Fails validation",
1435
+ "name": "validate-fail",
1436
+ },
1437
+ ],
1438
+ "version": "incur.v1",
1439
+ }
1440
+ `)
1441
+ })
1442
+
1443
+ test('scoped to group', async () => {
1444
+ const { output } = await serve(createApp(), ['auth', '--llms'])
1445
+ expect(output).toMatchInlineSnapshot(`
1446
+ "# app auth
1447
+
1448
+ Authentication commands
1449
+
1450
+ | Command | Description |
1451
+ |---------|-------------|
1452
+ | \`app auth auth login\` | Log in to the service |
1453
+ | \`app auth auth logout\` | Log out of the service |
1454
+ | \`app auth auth status\` | Show authentication status |
1455
+
1456
+ Run \`app auth --llms-full\` for full manifest. Run \`app auth <command> --schema\` for argument details.
1457
+ "
1458
+ `)
1459
+ })
1460
+
1461
+ test('scoped to nested group', async () => {
1462
+ const { output } = await serve(createApp(), ['project', 'deploy', '--llms'])
1463
+ expect(output).toMatchInlineSnapshot(`
1464
+ "# app project deploy
1465
+
1466
+ Deployment commands
1467
+
1468
+ | Command | Description |
1469
+ |---------|-------------|
1470
+ | \`app project deploy project deploy create <env>\` | Create a deployment |
1471
+ | \`app project deploy project deploy rollback <deployId>\` | Rollback a deployment |
1472
+ | \`app project deploy project deploy status <deployId>\` | Check deployment status |
1473
+
1474
+ Run \`app project deploy --llms-full\` for full manifest. Run \`app project deploy <command> --schema\` for argument details.
1475
+ "
1476
+ `)
1477
+ })
1478
+
1290
1479
  test('--llms --format yaml', async () => {
1291
1480
  const { output } = await serve(createApp(), ['--llms', '--format', 'yaml'])
1292
- expect(output).toContain('version: incur.v1')
1481
+ expect(output).toMatchInlineSnapshot(`
1482
+ "version: incur.v1
1483
+ commands:
1484
+ - name: api
1485
+ description: Proxy to HTTP API
1486
+ - name: auth login
1487
+ description: Log in to the service
1488
+ - name: auth logout
1489
+ description: Log out of the service
1490
+ - name: auth status
1491
+ description: Show authentication status
1492
+ - name: config
1493
+ description: Show current configuration
1494
+ - name: echo
1495
+ description: Echo back arguments
1496
+ - name: explode
1497
+ description: Always fails
1498
+ - name: explode-clac
1499
+ description: Fails with IncurError
1500
+ - name: noop
1501
+ description: Returns nothing
1502
+ - name: ping
1503
+ description: Health check
1504
+ - name: project create
1505
+ description: Create a new project
1506
+ - name: project delete
1507
+ description: Delete a project
1508
+ - name: project deploy create
1509
+ description: Create a deployment
1510
+ - name: project deploy rollback
1511
+ description: Rollback a deployment
1512
+ - name: project deploy status
1513
+ description: Check deployment status
1514
+ - name: project get
1515
+ description: Get a project by ID
1516
+ - name: project list
1517
+ description: List projects
1518
+ - name: slow
1519
+ description: Async command
1520
+ - name: stream
1521
+ description: Stream chunks
1522
+ - name: stream-error
1523
+ description: Stream with mid-stream error
1524
+ - name: stream-ok
1525
+ description: Stream with ok() return
1526
+ - name: stream-text
1527
+ description: Stream plain text
1528
+ - name: stream-throw
1529
+ description: Stream that throws
1530
+ - name: validate-fail
1531
+ description: Fails validation
1532
+ "
1533
+ `)
1293
1534
  })
1294
1535
  })
1295
1536
 
@@ -1461,7 +1702,7 @@ describe('root command with subcommands', () => {
1461
1702
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1462
1703
  --format <toon|json|yaml|md|jsonl> Output format
1463
1704
  --help Show help
1464
- --llms Print LLM-readable manifest
1705
+ --llms, --llms-full Print LLM-readable manifest
1465
1706
  --mcp Start as MCP stdio server
1466
1707
  --schema Show JSON Schema for a command
1467
1708
  --token-count Print token count of output (instead of output)
@@ -1638,7 +1879,7 @@ describe('env', () => {
1638
1879
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1639
1880
  --format <toon|json|yaml|md|jsonl> Output format
1640
1881
  --help Show help
1641
- --llms Print LLM-readable manifest
1882
+ --llms, --llms-full Print LLM-readable manifest
1642
1883
  --schema Show JSON Schema for a command
1643
1884
  --token-count Print token count of output (instead of output)
1644
1885
  --token-limit <n> Limit output to n tokens
@@ -1652,8 +1893,8 @@ describe('env', () => {
1652
1893
  `)
1653
1894
  })
1654
1895
 
1655
- test('--llms json includes schema.env', async () => {
1656
- const { output } = await serve(createApp(), ['auth', '--llms', '--format', 'json'])
1896
+ test('--llms-full json includes schema.env', async () => {
1897
+ const { output } = await serve(createApp(), ['auth', '--llms-full', '--format', 'json'])
1657
1898
  const login = json(output).commands.find((c: any) => c.name === 'auth login')
1658
1899
  expect(login.schema.env.properties).toMatchInlineSnapshot(`
1659
1900
  {
@@ -1670,8 +1911,8 @@ describe('env', () => {
1670
1911
  `)
1671
1912
  })
1672
1913
 
1673
- test('--llms markdown includes env vars table', async () => {
1674
- const { output } = await serve(createApp(), ['auth', '--llms'])
1914
+ test('--llms-full markdown includes env vars table', async () => {
1915
+ const { output } = await serve(createApp(), ['auth', '--llms-full'])
1675
1916
  expect(output).toContain('Environment Variables')
1676
1917
  expect(output).toContain('`AUTH_TOKEN`')
1677
1918
  expect(output).toContain('`AUTH_HOST`')
@@ -2631,6 +2872,7 @@ describe('.well-known/skills', () => {
2631
2872
  "---
2632
2873
  name: app-ping
2633
2874
  description: Health check. Run \`app ping --help\` for usage details.
2875
+ requires_bin: app
2634
2876
  command: app ping
2635
2877
  ---
2636
2878