incur 0.4.0 → 0.4.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/README.md +83 -22
- package/SKILL.md +6 -6
- package/dist/Cli.d.ts +46 -26
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +728 -441
- package/dist/Cli.js.map +1 -1
- package/dist/Completions.d.ts +4 -3
- package/dist/Completions.d.ts.map +1 -1
- package/dist/Completions.js +17 -10
- package/dist/Completions.js.map +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js +10 -9
- package/dist/Fetch.js.map +1 -1
- package/dist/Filter.js +0 -18
- package/dist/Filter.js.map +1 -1
- package/dist/Formatter.d.ts.map +1 -1
- package/dist/Formatter.js +6 -1
- package/dist/Formatter.js.map +1 -1
- package/dist/Help.d.ts +7 -1
- package/dist/Help.d.ts.map +1 -1
- package/dist/Help.js +44 -27
- package/dist/Help.js.map +1 -1
- package/dist/Mcp.d.ts +37 -5
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +71 -72
- package/dist/Mcp.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +22 -14
- package/dist/Openapi.js.map +1 -1
- package/dist/Parser.d.ts +4 -0
- package/dist/Parser.d.ts.map +1 -1
- package/dist/Parser.js +70 -38
- package/dist/Parser.js.map +1 -1
- package/dist/Schema.d.ts +5 -1
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +13 -2
- package/dist/Schema.js.map +1 -1
- package/dist/Skill.d.ts +2 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js +33 -19
- package/dist/Skill.js.map +1 -1
- package/dist/Skillgen.js +1 -1
- package/dist/Skillgen.js.map +1 -1
- package/dist/SyncSkills.d.ts +48 -0
- package/dist/SyncSkills.d.ts.map +1 -1
- package/dist/SyncSkills.js +108 -10
- package/dist/SyncSkills.js.map +1 -1
- package/dist/Typegen.js +4 -2
- package/dist/Typegen.js.map +1 -1
- package/dist/bin.d.ts +2 -1
- package/dist/bin.d.ts.map +1 -1
- package/dist/bin.js +17 -2
- package/dist/bin.js.map +1 -1
- package/dist/internal/command.d.ts +170 -0
- package/dist/internal/command.d.ts.map +1 -0
- package/dist/internal/command.js +292 -0
- package/dist/internal/command.js.map +1 -0
- package/dist/internal/configSchema.d.ts +8 -0
- package/dist/internal/configSchema.d.ts.map +1 -0
- package/dist/internal/configSchema.js +57 -0
- package/dist/internal/configSchema.js.map +1 -0
- package/dist/internal/dereference.d.ts +12 -0
- package/dist/internal/dereference.d.ts.map +1 -0
- package/dist/internal/dereference.js +71 -0
- package/dist/internal/dereference.js.map +1 -0
- package/dist/internal/helpers.d.ts +9 -0
- package/dist/internal/helpers.d.ts.map +1 -0
- package/dist/internal/helpers.js +54 -0
- package/dist/internal/helpers.js.map +1 -0
- package/dist/middleware.d.ts +6 -8
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/examples/npm/.npmrc.json +21 -0
- package/examples/npm/config.schema.json +134 -0
- package/package.json +6 -29
- package/src/Cli.test-d.ts +44 -33
- package/src/Cli.test.ts +1231 -101
- package/src/Cli.ts +877 -569
- package/src/Completions.test.ts +136 -12
- package/src/Completions.ts +18 -13
- package/src/Fetch.test.ts +21 -0
- package/src/Fetch.ts +8 -10
- package/src/Filter.ts +0 -17
- package/src/Formatter.test.ts +15 -2
- package/src/Formatter.ts +5 -1
- package/src/Help.test.ts +184 -20
- package/src/Help.ts +52 -28
- package/src/Mcp.test.ts +159 -0
- package/src/Mcp.ts +108 -86
- package/src/Openapi.test.ts +17 -5
- package/src/Openapi.ts +21 -15
- package/src/Parser.test-d.ts +22 -0
- package/src/Parser.test.ts +89 -0
- package/src/Parser.ts +87 -36
- package/src/Schema.test.ts +29 -0
- package/src/Schema.ts +12 -2
- package/src/Skill.test.ts +87 -6
- package/src/Skill.ts +38 -21
- package/src/Skillgen.ts +1 -1
- package/src/SyncMcp.test.ts +6 -8
- package/src/SyncSkills.test.ts +146 -3
- package/src/SyncSkills.ts +191 -10
- package/src/Typegen.test.ts +15 -0
- package/src/Typegen.ts +4 -2
- package/src/bin.ts +21 -2
- package/src/e2e.test.ts +188 -98
- package/src/internal/command.ts +449 -0
- package/src/internal/configSchema.test.ts +193 -0
- package/src/internal/configSchema.ts +66 -0
- package/src/internal/dereference.test.ts +695 -0
- package/src/internal/dereference.ts +75 -0
- package/src/internal/helpers.test.ts +75 -0
- package/src/internal/helpers.ts +59 -0
- package/src/middleware.ts +5 -12
package/src/Completions.test.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { Cli, Completions, z } from 'incur'
|
|
2
|
+
import { execFile, spawnSync } from 'node:child_process'
|
|
3
|
+
import { chmod, mkdtemp, rm, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { tmpdir } from 'node:os'
|
|
5
|
+
import { join } from 'node:path'
|
|
2
6
|
|
|
3
7
|
const originalIsTTY = process.stdout.isTTY
|
|
4
8
|
const originalEnv = { ...process.env }
|
|
@@ -15,6 +19,45 @@ vi.mock('./SyncSkills.js', async (importOriginal) => {
|
|
|
15
19
|
return { ...actual, readHash: () => undefined }
|
|
16
20
|
})
|
|
17
21
|
|
|
22
|
+
function hasShell(shell: string): boolean {
|
|
23
|
+
return spawnSync(shell, ['-c', ':'], { stdio: 'ignore' }).status === 0
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const bash = hasShell('bash')
|
|
27
|
+
const zsh = hasShell('zsh')
|
|
28
|
+
const fish = hasShell('fish')
|
|
29
|
+
|
|
30
|
+
function exec(cmd: string, args: string[], env: NodeJS.ProcessEnv): Promise<string> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
execFile(cmd, args, { env, timeout: 30_000 }, (error, stdout, stderr) => {
|
|
33
|
+
if (error) reject(new Error(stderr?.trim() || stdout?.trim() || error.message))
|
|
34
|
+
else resolve(stdout)
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function withFakeCli(run: (dir: string) => Promise<void>) {
|
|
40
|
+
const dir = await mkdtemp(join(tmpdir(), 'incur-completions-'))
|
|
41
|
+
const bin = join(dir, 'fake-cli')
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
await writeFile(
|
|
45
|
+
bin,
|
|
46
|
+
`#!/bin/sh
|
|
47
|
+
if [ -n "$COMPLETE" ]; then
|
|
48
|
+
printf '%s' "$COMPLETE:\${_COMPLETE_INDEX:-missing}"
|
|
49
|
+
else
|
|
50
|
+
printf 'missing'
|
|
51
|
+
fi
|
|
52
|
+
`,
|
|
53
|
+
)
|
|
54
|
+
await chmod(bin, 0o755)
|
|
55
|
+
await run(dir)
|
|
56
|
+
} finally {
|
|
57
|
+
await rm(dir, { recursive: true, force: true })
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
18
61
|
async function serve(
|
|
19
62
|
cli: { serve: Cli.Cli['serve'] },
|
|
20
63
|
argv: string[],
|
|
@@ -231,20 +274,59 @@ describe('register', () => {
|
|
|
231
274
|
test('bash: generates complete -F script with nospace support', () => {
|
|
232
275
|
const script = Completions.register('bash', 'mycli')
|
|
233
276
|
expect(script).toContain('_incur_complete_mycli()')
|
|
234
|
-
expect(script).toContain('COMPLETE="bash"')
|
|
277
|
+
expect(script).toContain('export COMPLETE="bash"')
|
|
235
278
|
expect(script).toContain('complete -o default -o bashdefault -o nosort -F')
|
|
236
279
|
expect(script).toContain('"mycli" -- "${COMP_WORDS[@]}"')
|
|
237
280
|
expect(script).toContain('compopt -o nospace')
|
|
238
281
|
})
|
|
239
282
|
|
|
283
|
+
test.skipIf(!bash)('bash: exports completion env vars to the CLI subprocess', async () => {
|
|
284
|
+
await withFakeCli(async (dir) => {
|
|
285
|
+
const output = await exec(
|
|
286
|
+
'bash',
|
|
287
|
+
[
|
|
288
|
+
'-lc',
|
|
289
|
+
`${Completions.register('bash', 'fake-cli')}
|
|
290
|
+
COMP_WORDS=('fake-cli' 'build' '')
|
|
291
|
+
COMP_CWORD=2
|
|
292
|
+
_incur_complete_fake_cli
|
|
293
|
+
printf '%s' "\${COMPREPLY[*]}"`,
|
|
294
|
+
],
|
|
295
|
+
{ ...process.env, PATH: `${dir}:${process.env.PATH ?? ''}` },
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
expect(output).toBe('bash:2')
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
|
+
|
|
240
302
|
test('zsh: generates compdef script', () => {
|
|
241
303
|
const script = Completions.register('zsh', 'mycli')
|
|
242
304
|
expect(script).toContain('#compdef mycli')
|
|
243
|
-
expect(script).toContain('COMPLETE="zsh"')
|
|
305
|
+
expect(script).toContain('export COMPLETE="zsh"')
|
|
244
306
|
expect(script).toContain('compdef _incur_complete_mycli mycli')
|
|
245
307
|
expect(script).toContain('_describe')
|
|
246
308
|
})
|
|
247
309
|
|
|
310
|
+
test.skipIf(!zsh)('zsh: exports completion env vars to the CLI subprocess', async () => {
|
|
311
|
+
await withFakeCli(async (dir) => {
|
|
312
|
+
const output = await exec(
|
|
313
|
+
'zsh',
|
|
314
|
+
[
|
|
315
|
+
'-lc',
|
|
316
|
+
`compdef() { : }
|
|
317
|
+
_describe() { print -r -- "\${(j:|:)\${(@P)2}}" }
|
|
318
|
+
${Completions.register('zsh', 'fake-cli')}
|
|
319
|
+
words=('fake-cli' 'build' '')
|
|
320
|
+
CURRENT=3
|
|
321
|
+
_incur_complete_fake_cli`,
|
|
322
|
+
],
|
|
323
|
+
{ ...process.env, PATH: `${dir}:${process.env.PATH ?? ''}` },
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
expect(output.trim()).toBe('zsh:2')
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
248
330
|
test('fish: generates complete command', () => {
|
|
249
331
|
const script = Completions.register('fish', 'mycli')
|
|
250
332
|
expect(script).toContain('complete --keep-order --exclusive --command mycli')
|
|
@@ -252,6 +334,22 @@ describe('register', () => {
|
|
|
252
334
|
expect(script).toContain('commandline --current-token')
|
|
253
335
|
})
|
|
254
336
|
|
|
337
|
+
test.skipIf(!fish)('fish: passes completion env vars to the CLI subprocess', async () => {
|
|
338
|
+
await withFakeCli(async (dir) => {
|
|
339
|
+
const output = await exec(
|
|
340
|
+
'fish',
|
|
341
|
+
[
|
|
342
|
+
'-c',
|
|
343
|
+
`${Completions.register('fish', 'fake-cli')}
|
|
344
|
+
complete --do-complete 'fake-cli '`,
|
|
345
|
+
],
|
|
346
|
+
{ ...process.env, PATH: `${dir}:${process.env.PATH ?? ''}` },
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
expect(output.trim()).toBe('fish:missing')
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
|
|
255
353
|
test('nushell: generates external completer closure', () => {
|
|
256
354
|
const script = Completions.register('nushell', 'mycli')
|
|
257
355
|
expect(script).toContain('COMPLETE=nushell')
|
|
@@ -294,27 +392,24 @@ describe('completions built-in command', () => {
|
|
|
294
392
|
expect(output).toMatchInlineSnapshot(`
|
|
295
393
|
"mycli completions — Generate shell completion script
|
|
296
394
|
|
|
297
|
-
Usage: mycli completions <
|
|
395
|
+
Usage: mycli completions <bash|fish|nushell|zsh>
|
|
298
396
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
fish
|
|
302
|
-
nushell
|
|
303
|
-
zsh
|
|
397
|
+
Arguments:
|
|
398
|
+
shell Shell to generate completions for
|
|
304
399
|
|
|
305
400
|
Setup:
|
|
306
401
|
bash eval "$(mycli completions bash)" # add to ~/.bashrc
|
|
307
|
-
zsh eval "$(mycli completions zsh)" # add to ~/.zshrc
|
|
308
402
|
fish mycli completions fish | source # add to ~/.config/fish/config.fish
|
|
309
403
|
nushell see \`mycli completions nushell\` # add to config.nu
|
|
404
|
+
zsh eval "$(mycli completions zsh)" # add to ~/.zshrc
|
|
310
405
|
"
|
|
311
406
|
`)
|
|
312
407
|
})
|
|
313
408
|
|
|
314
|
-
test('
|
|
409
|
+
test('shows help on missing shell argument', async () => {
|
|
315
410
|
const cli = makeCli()
|
|
316
411
|
const output = await serve(cli, ['completions'])
|
|
317
|
-
expect(output).toContain('
|
|
412
|
+
expect(output).toContain('Generate shell completion script')
|
|
318
413
|
})
|
|
319
414
|
|
|
320
415
|
test('errors on unknown shell', async () => {
|
|
@@ -391,9 +486,38 @@ describe('serve integration', () => {
|
|
|
391
486
|
expect(output).toContain('db')
|
|
392
487
|
})
|
|
393
488
|
|
|
489
|
+
test('COMPLETE=bash includes built-in commands at root', async () => {
|
|
490
|
+
const cli = makeCli()
|
|
491
|
+
const output = await serve(cli, ['--', 'mycli', ''], {
|
|
492
|
+
COMPLETE: 'bash',
|
|
493
|
+
_COMPLETE_INDEX: '1',
|
|
494
|
+
})
|
|
495
|
+
expect(output).toContain('completions')
|
|
496
|
+
expect(output).toContain('mcp')
|
|
497
|
+
expect(output).toContain('skills')
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
test('COMPLETE=bash suggests add for skills subcommand', async () => {
|
|
501
|
+
const cli = makeCli()
|
|
502
|
+
const output = await serve(cli, ['--', 'mycli', 'skills', ''], {
|
|
503
|
+
COMPLETE: 'bash',
|
|
504
|
+
_COMPLETE_INDEX: '2',
|
|
505
|
+
})
|
|
506
|
+
expect(output).toContain('add')
|
|
507
|
+
})
|
|
508
|
+
|
|
509
|
+
test('COMPLETE=bash suggests add for mcp subcommand', async () => {
|
|
510
|
+
const cli = makeCli()
|
|
511
|
+
const output = await serve(cli, ['--', 'mycli', 'mcp', ''], {
|
|
512
|
+
COMPLETE: 'bash',
|
|
513
|
+
_COMPLETE_INDEX: '2',
|
|
514
|
+
})
|
|
515
|
+
expect(output).toContain('add')
|
|
516
|
+
})
|
|
517
|
+
|
|
394
518
|
test('COMPLETE=zsh with words outputs candidates in zsh format', async () => {
|
|
395
519
|
const cli = makeCli()
|
|
396
|
-
|
|
520
|
+
await serve(cli, ['--', 'mycli', '--'], {
|
|
397
521
|
COMPLETE: 'zsh',
|
|
398
522
|
_COMPLETE_INDEX: '1',
|
|
399
523
|
})
|
package/src/Completions.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { z } from 'zod'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
export type Shell = 'bash' | 'fish' | 'nushell' | 'zsh'
|
|
3
|
+
import type { Shell } from './internal/command.js'
|
|
5
4
|
|
|
6
5
|
/** A completion candidate with an optional description. */
|
|
7
6
|
export type Candidate = {
|
|
@@ -13,14 +12,16 @@ export type Candidate = {
|
|
|
13
12
|
value: string
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
/** @internal Entry stored in a command map — either a leaf definition or
|
|
15
|
+
/** @internal Entry stored in a command map — either a leaf definition, a group, or an alias. */
|
|
17
16
|
type CommandEntry = {
|
|
17
|
+
_alias?: true | undefined
|
|
18
18
|
_group?: true | undefined
|
|
19
19
|
alias?: Record<string, string | undefined> | undefined
|
|
20
20
|
args?: z.ZodObject<any> | undefined
|
|
21
21
|
commands?: Map<string, CommandEntry> | undefined
|
|
22
22
|
description?: string | undefined
|
|
23
23
|
options?: z.ZodObject<any> | undefined
|
|
24
|
+
target?: string | undefined
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -62,7 +63,10 @@ export function complete(
|
|
|
62
63
|
for (let i = 0; i < index; i++) {
|
|
63
64
|
const token = argv[i]!
|
|
64
65
|
if (token.startsWith('-')) continue
|
|
65
|
-
|
|
66
|
+
let entry = scope.commands.get(token)
|
|
67
|
+
if (!entry) continue
|
|
68
|
+
// Follow alias to canonical entry
|
|
69
|
+
if (entry._alias && entry.target) entry = scope.commands.get(entry.target)
|
|
66
70
|
if (!entry) continue
|
|
67
71
|
if (entry._group && entry.commands) {
|
|
68
72
|
scope = { commands: entry.commands }
|
|
@@ -117,6 +121,7 @@ export function complete(
|
|
|
117
121
|
|
|
118
122
|
// Suggest subcommands (groups get noSpace so user can keep typing subcommand)
|
|
119
123
|
for (const [name, entry] of scope.commands) {
|
|
124
|
+
if (entry._alias) continue
|
|
120
125
|
if (name.startsWith(current))
|
|
121
126
|
candidates.push({
|
|
122
127
|
value: name,
|
|
@@ -233,25 +238,25 @@ function bashRegister(name: string): string {
|
|
|
233
238
|
local _COMPLETE_INDEX=\${COMP_CWORD}
|
|
234
239
|
local _completions
|
|
235
240
|
_completions=( $(
|
|
236
|
-
COMPLETE="bash"
|
|
237
|
-
_COMPLETE_INDEX="
|
|
241
|
+
export COMPLETE="bash"
|
|
242
|
+
export _COMPLETE_INDEX="$_COMPLETE_INDEX"
|
|
238
243
|
"${name}" -- "\${COMP_WORDS[@]}"
|
|
239
244
|
) )
|
|
240
|
-
if [[
|
|
245
|
+
if [[ $? != 0 ]]; then
|
|
241
246
|
unset COMPREPLY
|
|
242
247
|
return
|
|
243
248
|
fi
|
|
244
249
|
local _nospace=false
|
|
245
250
|
COMPREPLY=()
|
|
246
251
|
for _c in "\${_completions[@]}"; do
|
|
247
|
-
if [[ "
|
|
252
|
+
if [[ "$_c" == *$'\\001' ]]; then
|
|
248
253
|
_nospace=true
|
|
249
254
|
COMPREPLY+=("\${_c%$'\\001'}")
|
|
250
255
|
else
|
|
251
|
-
COMPREPLY+=("
|
|
256
|
+
COMPREPLY+=("$_c")
|
|
252
257
|
fi
|
|
253
258
|
done
|
|
254
|
-
if [[
|
|
259
|
+
if [[ $_nospace == true ]]; then
|
|
255
260
|
compopt -o nospace
|
|
256
261
|
fi
|
|
257
262
|
}
|
|
@@ -263,11 +268,11 @@ function zshRegister(name: string): string {
|
|
|
263
268
|
return `#compdef ${name}
|
|
264
269
|
_incur_complete_${id}() {
|
|
265
270
|
local completions=("\${(@f)$(
|
|
266
|
-
_COMPLETE_INDEX=$(( CURRENT - 1 ))
|
|
267
|
-
COMPLETE="zsh"
|
|
271
|
+
export _COMPLETE_INDEX=$(( CURRENT - 1 ))
|
|
272
|
+
export COMPLETE="zsh"
|
|
268
273
|
"${name}" -- "\${words[@]}" 2>/dev/null
|
|
269
274
|
)}")
|
|
270
|
-
if [[ -n
|
|
275
|
+
if [[ -n $completions ]]; then
|
|
271
276
|
_describe 'values' completions -S ''
|
|
272
277
|
fi
|
|
273
278
|
}
|
package/src/Fetch.test.ts
CHANGED
|
@@ -82,6 +82,27 @@ describe('parseArgv', () => {
|
|
|
82
82
|
expect(input.query.get('limit')).toBe('5')
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
+
test('--method as last token throws missing value error', () => {
|
|
86
|
+
expect(() => Fetch.parseArgv(['users', '--method'])).toThrow('Missing value for --method')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('-X as last token throws missing value error', () => {
|
|
90
|
+
expect(() => Fetch.parseArgv(['users', '-X'])).toThrow('Missing value for -X')
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test('--header as last token throws missing value error', () => {
|
|
94
|
+
expect(() => Fetch.parseArgv(['users', '--header'])).toThrow('Missing value for --header')
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
test('--limit as last token throws missing value error', () => {
|
|
98
|
+
expect(() => Fetch.parseArgv(['users', '--limit'])).toThrow('Missing value for --limit')
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
test('unknown short flag does not swallow the next token', () => {
|
|
102
|
+
const input = Fetch.parseArgv(['users', '-z', 'create'])
|
|
103
|
+
expect(input.path).toBe('/users/create')
|
|
104
|
+
})
|
|
105
|
+
|
|
85
106
|
test('mixed tokens, flags, and query params', () => {
|
|
86
107
|
const input = Fetch.parseArgv([
|
|
87
108
|
'users',
|
package/src/Fetch.ts
CHANGED
|
@@ -43,24 +43,22 @@ export function parseArgv(argv: string[]): FetchInput {
|
|
|
43
43
|
} else {
|
|
44
44
|
const key = token.slice(2)
|
|
45
45
|
const value = argv[i + 1]
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
query.set(key, value!)
|
|
51
|
-
i += 2
|
|
52
|
-
}
|
|
46
|
+
if (value === undefined) throw new Error(`Missing value for --${key}`)
|
|
47
|
+
if (reservedFlags.has(key)) handleReserved(key, value)
|
|
48
|
+
else query.set(key, value)
|
|
49
|
+
i += 2
|
|
53
50
|
}
|
|
54
51
|
} else if (token.startsWith('-') && token.length === 2) {
|
|
55
52
|
const short = token[1]!
|
|
56
53
|
const mapped = reservedShort[short]
|
|
57
|
-
const value = argv[i + 1]!
|
|
58
54
|
if (mapped) {
|
|
55
|
+
const value = argv[i + 1]
|
|
56
|
+
if (value === undefined) throw new Error(`Missing value for -${short}`)
|
|
59
57
|
handleReserved(mapped, value)
|
|
60
58
|
i += 2
|
|
61
59
|
} else {
|
|
62
|
-
// Unknown short flag —
|
|
63
|
-
i
|
|
60
|
+
// Unknown short flag — treat as single token, don't consume next
|
|
61
|
+
i++
|
|
64
62
|
}
|
|
65
63
|
} else {
|
|
66
64
|
segments.push(token)
|
package/src/Filter.ts
CHANGED
|
@@ -80,23 +80,6 @@ export function apply(data: unknown, paths: FilterPath[]): unknown {
|
|
|
80
80
|
return result
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function resolve(data: unknown, segments: Segment[], index: number): unknown {
|
|
84
|
-
if (index >= segments.length) return data
|
|
85
|
-
const segment = segments[index]!
|
|
86
|
-
|
|
87
|
-
if ('key' in segment) {
|
|
88
|
-
if (typeof data !== 'object' || data === null) return undefined
|
|
89
|
-
const val = (data as Record<string, unknown>)[segment.key]
|
|
90
|
-
return resolve(val, segments, index + 1)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// slice segment
|
|
94
|
-
if (!Array.isArray(data)) return undefined
|
|
95
|
-
const sliced = data.slice(segment.start, segment.end)
|
|
96
|
-
if (index + 1 >= segments.length) return sliced
|
|
97
|
-
return sliced.map((item) => resolve(item, segments, index + 1))
|
|
98
|
-
}
|
|
99
|
-
|
|
100
83
|
function merge(
|
|
101
84
|
target: Record<string, unknown>,
|
|
102
85
|
data: unknown,
|
package/src/Formatter.test.ts
CHANGED
|
@@ -296,8 +296,21 @@ describe('format md', () => {
|
|
|
296
296
|
})
|
|
297
297
|
|
|
298
298
|
describe('format jsonl', () => {
|
|
299
|
-
test('
|
|
299
|
+
test('single object outputs one JSON line', () => {
|
|
300
300
|
const result = Formatter.format({ message: 'hello' }, 'jsonl')
|
|
301
|
-
expect(result).toMatchInlineSnapshot(`"message:
|
|
301
|
+
expect(result).toMatchInlineSnapshot(`"{"message":"hello"}"`)
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
test('array outputs one JSON line per element', () => {
|
|
305
|
+
const result = Formatter.format([{ id: 1 }, { id: 2 }], 'jsonl')
|
|
306
|
+
expect(result).toMatchInlineSnapshot(`
|
|
307
|
+
"{"id":1}
|
|
308
|
+
{"id":2}"
|
|
309
|
+
`)
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
test('scalar value outputs JSON', () => {
|
|
313
|
+
expect(Formatter.format(42, 'jsonl')).toMatchInlineSnapshot(`"42"`)
|
|
314
|
+
expect(Formatter.format('hi', 'jsonl')).toMatchInlineSnapshot(`""hi""`)
|
|
302
315
|
})
|
|
303
316
|
})
|
package/src/Formatter.ts
CHANGED
|
@@ -10,7 +10,11 @@ export function format(value: unknown, fmt: Format = 'toon'): string {
|
|
|
10
10
|
if (fmt === 'json') return JSON.stringify(value, null, 2)
|
|
11
11
|
if (fmt === 'yaml') return yamlStringify(value)
|
|
12
12
|
if (fmt === 'md') return formatMarkdown(value)
|
|
13
|
-
|
|
13
|
+
if (fmt === 'jsonl') {
|
|
14
|
+
if (Array.isArray(value)) return value.map((v) => JSON.stringify(v)).join('\n')
|
|
15
|
+
return JSON.stringify(value)
|
|
16
|
+
}
|
|
17
|
+
// toon (default)
|
|
14
18
|
if (isScalar(value)) return String(value)
|
|
15
19
|
return encode(value as Record<string, unknown>)
|
|
16
20
|
}
|