incur 0.3.2 → 0.3.3
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 +22 -26
- package/SKILL.md +17 -20
- package/dist/Cli.d.ts +1 -1
- package/dist/Cli.d.ts.map +1 -1
- package/dist/Cli.js +132 -24
- package/dist/Cli.js.map +1 -1
- package/dist/Fetch.d.ts.map +1 -1
- package/dist/Fetch.js.map +1 -1
- package/dist/Filter.js.map +1 -1
- package/dist/Help.js +4 -4
- package/dist/Help.js.map +1 -1
- package/dist/Openapi.d.ts.map +1 -1
- package/dist/Openapi.js +10 -2
- package/dist/Openapi.js.map +1 -1
- package/dist/Skill.d.ts.map +1 -1
- package/dist/Skill.js.map +1 -1
- package/package.json +1 -1
- package/src/Cli.test.ts +13 -24
- package/src/Cli.ts +211 -63
- package/src/Fetch.test.ts +3 -13
- package/src/Fetch.ts +1 -2
- package/src/Filter.ts +7 -3
- package/src/Help.test.ts +2 -2
- package/src/Help.ts +2 -2
- package/src/Openapi.test.ts +89 -32
- package/src/Openapi.ts +15 -6
- package/src/Skill.test.ts +1 -3
- package/src/Skill.ts +9 -2
- package/src/e2e.test.ts +74 -34
package/src/Fetch.ts
CHANGED
|
@@ -102,8 +102,7 @@ export function buildRequest(input: FetchInput): Request {
|
|
|
102
102
|
|
|
103
103
|
if (input.body !== undefined) {
|
|
104
104
|
init.body = input.body
|
|
105
|
-
if (!input.headers.has('content-type'))
|
|
106
|
-
input.headers.set('content-type', 'application/json')
|
|
105
|
+
if (!input.headers.has('content-type')) input.headers.set('content-type', 'application/json')
|
|
107
106
|
}
|
|
108
107
|
|
|
109
108
|
return new Request(url.toString(), init)
|
package/src/Filter.ts
CHANGED
|
@@ -97,7 +97,12 @@ function resolve(data: unknown, segments: Segment[], index: number): unknown {
|
|
|
97
97
|
return sliced.map((item) => resolve(item, segments, index + 1))
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
function merge(
|
|
100
|
+
function merge(
|
|
101
|
+
target: Record<string, unknown>,
|
|
102
|
+
data: unknown,
|
|
103
|
+
segments: Segment[],
|
|
104
|
+
index: number,
|
|
105
|
+
): void {
|
|
101
106
|
if (index >= segments.length || typeof data !== 'object' || data === null) return
|
|
102
107
|
const segment = segments[index]!
|
|
103
108
|
|
|
@@ -129,8 +134,7 @@ function merge(target: Record<string, unknown>, data: unknown, segments: Segment
|
|
|
129
134
|
|
|
130
135
|
// Next segment is a key — recurse into nested object
|
|
131
136
|
if (typeof val !== 'object' || val === null) return
|
|
132
|
-
if (!target[segment.key] || typeof target[segment.key] !== 'object')
|
|
133
|
-
target[segment.key] = {}
|
|
137
|
+
if (!target[segment.key] || typeof target[segment.key] !== 'object') target[segment.key] = {}
|
|
134
138
|
merge(target[segment.key] as Record<string, unknown>, val, segments, index + 1)
|
|
135
139
|
return
|
|
136
140
|
}
|
package/src/Help.test.ts
CHANGED
|
@@ -199,9 +199,9 @@ describe('formatRoot', () => {
|
|
|
199
199
|
})
|
|
200
200
|
expect(result).toMatchInlineSnapshot(`
|
|
201
201
|
"my-tool@1.0.0 — A test CLI
|
|
202
|
-
Aliases: mt, myt
|
|
203
202
|
|
|
204
203
|
Usage: my-tool <command>
|
|
204
|
+
Aliases: mt, myt
|
|
205
205
|
|
|
206
206
|
Commands:
|
|
207
207
|
fetch Fetch a URL
|
|
@@ -228,9 +228,9 @@ describe('formatRoot', () => {
|
|
|
228
228
|
})
|
|
229
229
|
expect(result).toMatchInlineSnapshot(`
|
|
230
230
|
"my-tool@1.0.0 — A test CLI
|
|
231
|
-
Aliases: mt, myt
|
|
232
231
|
|
|
233
232
|
Usage: my-tool <url>
|
|
233
|
+
Aliases: mt, myt
|
|
234
234
|
|
|
235
235
|
Arguments:
|
|
236
236
|
url URL to fetch
|
package/src/Help.ts
CHANGED
|
@@ -8,11 +8,11 @@ export function formatRoot(name: string, options: formatRoot.Options = {}): stri
|
|
|
8
8
|
// Header
|
|
9
9
|
const title = version ? `${name}@${version}` : name
|
|
10
10
|
lines.push(description ? `${title} \u2014 ${description}` : title)
|
|
11
|
-
if (aliases?.length) lines.push(`Aliases: ${aliases.join(', ')}`)
|
|
12
11
|
lines.push('')
|
|
13
12
|
|
|
14
13
|
// Synopsis
|
|
15
14
|
lines.push(`Usage: ${name} <command>`)
|
|
15
|
+
if (aliases?.length) lines.push(`Aliases: ${aliases.join(', ')}`)
|
|
16
16
|
|
|
17
17
|
// Commands
|
|
18
18
|
if (commands.length > 0) {
|
|
@@ -105,7 +105,6 @@ export function formatCommand(name: string, options: formatCommand.Options = {})
|
|
|
105
105
|
// Header
|
|
106
106
|
const title = version ? `${name}@${version}` : name
|
|
107
107
|
lines.push(description ? `${title} \u2014 ${description}` : title)
|
|
108
|
-
if (aliases?.length) lines.push(`Aliases: ${aliases.join(', ')}`)
|
|
109
108
|
lines.push('')
|
|
110
109
|
|
|
111
110
|
// Synopsis
|
|
@@ -128,6 +127,7 @@ export function formatCommand(name: string, options: formatCommand.Options = {})
|
|
|
128
127
|
const commandSuffix = options.commands && options.commands.length > 0 ? ' | <command>' : ''
|
|
129
128
|
lines.push(`Usage: ${synopsis}${opts ? ' [options]' : ''}${commandSuffix}`)
|
|
130
129
|
}
|
|
130
|
+
if (aliases?.length) lines.push(`Aliases: ${aliases.join(', ')}`)
|
|
131
131
|
|
|
132
132
|
// Arguments
|
|
133
133
|
if (args) {
|
package/src/Openapi.test.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { describe, expect, test } from 'vitest'
|
|
2
2
|
|
|
3
|
-
import * as Cli from './Cli.js'
|
|
4
|
-
import * as Openapi from './Openapi.js'
|
|
5
|
-
import { app } from '../test/fixtures/hono-api.js'
|
|
6
3
|
import { app as prefixedApp } from '../test/fixtures/hono-api-prefixed.js'
|
|
7
|
-
import {
|
|
4
|
+
import { app } from '../test/fixtures/hono-api.js'
|
|
8
5
|
import { app as openapiApp, spec as openapiSpec } from '../test/fixtures/hono-openapi-app.js'
|
|
6
|
+
import { spec } from '../test/fixtures/openapi-spec.js'
|
|
7
|
+
import * as Cli from './Cli.js'
|
|
8
|
+
import * as Openapi from './Openapi.js'
|
|
9
9
|
|
|
10
10
|
function serve(cli: { serve: Cli.Cli['serve'] }, argv: string[]) {
|
|
11
11
|
let output = ''
|
|
@@ -13,7 +13,9 @@ function serve(cli: { serve: Cli.Cli['serve'] }, argv: string[]) {
|
|
|
13
13
|
return cli
|
|
14
14
|
.serve(argv, {
|
|
15
15
|
stdout: (s) => (output += s),
|
|
16
|
-
exit: (c) => {
|
|
16
|
+
exit: (c) => {
|
|
17
|
+
exitCode = c
|
|
18
|
+
},
|
|
17
19
|
})
|
|
18
20
|
.then(() => ({
|
|
19
21
|
output,
|
|
@@ -22,9 +24,7 @@ function serve(cli: { serve: Cli.Cli['serve'] }, argv: string[]) {
|
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
function json(output: string) {
|
|
25
|
-
return JSON.parse(
|
|
26
|
-
output.replace(/"duration": "[^"]+"/g, '"duration": "<stripped>"'),
|
|
27
|
-
)
|
|
27
|
+
return JSON.parse(output.replace(/"duration": "[^"]+"/g, '"duration": "<stripped>"'))
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
describe('generateCommands', () => {
|
|
@@ -46,8 +46,10 @@ describe('generateCommands', () => {
|
|
|
46
46
|
|
|
47
47
|
describe('cli integration', () => {
|
|
48
48
|
function createCli() {
|
|
49
|
-
return Cli.create('test', { description: 'test' })
|
|
50
|
-
|
|
49
|
+
return Cli.create('test', { description: 'test' }).command('api', {
|
|
50
|
+
fetch: app.fetch,
|
|
51
|
+
openapi: spec,
|
|
52
|
+
})
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
test('GET /users via operationId', async () => {
|
|
@@ -57,7 +59,12 @@ describe('cli integration', () => {
|
|
|
57
59
|
|
|
58
60
|
test('GET /users?limit=5 via options', async () => {
|
|
59
61
|
const { output } = await serve(createCli(), [
|
|
60
|
-
'api',
|
|
62
|
+
'api',
|
|
63
|
+
'listUsers',
|
|
64
|
+
'--limit',
|
|
65
|
+
'5',
|
|
66
|
+
'--format',
|
|
67
|
+
'json',
|
|
61
68
|
])
|
|
62
69
|
expect(json(output).limit).toBe(5)
|
|
63
70
|
})
|
|
@@ -125,7 +132,11 @@ describe('cli integration', () => {
|
|
|
125
132
|
|
|
126
133
|
test('--verbose wraps in envelope', async () => {
|
|
127
134
|
const { output } = await serve(createCli(), [
|
|
128
|
-
'api',
|
|
135
|
+
'api',
|
|
136
|
+
'healthCheck',
|
|
137
|
+
'--verbose',
|
|
138
|
+
'--format',
|
|
139
|
+
'json',
|
|
129
140
|
])
|
|
130
141
|
const parsed = json(output)
|
|
131
142
|
expect(parsed.ok).toBe(true)
|
|
@@ -141,8 +152,10 @@ describe('cli integration', () => {
|
|
|
141
152
|
|
|
142
153
|
describe('@hono/zod-openapi integration', () => {
|
|
143
154
|
function createCli() {
|
|
144
|
-
return Cli.create('test', { description: 'test' })
|
|
145
|
-
|
|
155
|
+
return Cli.create('test', { description: 'test' }).command('api', {
|
|
156
|
+
fetch: openapiApp.fetch,
|
|
157
|
+
openapi: openapiSpec,
|
|
158
|
+
})
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
test('GET /users via listUsers', async () => {
|
|
@@ -151,7 +164,14 @@ describe('@hono/zod-openapi integration', () => {
|
|
|
151
164
|
})
|
|
152
165
|
|
|
153
166
|
test('GET /users?limit=5', async () => {
|
|
154
|
-
const { output } = await serve(createCli(), [
|
|
167
|
+
const { output } = await serve(createCli(), [
|
|
168
|
+
'api',
|
|
169
|
+
'listUsers',
|
|
170
|
+
'--limit',
|
|
171
|
+
'5',
|
|
172
|
+
'--format',
|
|
173
|
+
'json',
|
|
174
|
+
])
|
|
155
175
|
expect(json(output).limit).toBe(5)
|
|
156
176
|
})
|
|
157
177
|
|
|
@@ -224,7 +244,11 @@ describe('@hono/zod-openapi integration', () => {
|
|
|
224
244
|
|
|
225
245
|
test('--verbose wraps in envelope', async () => {
|
|
226
246
|
const { output } = await serve(createCli(), [
|
|
227
|
-
'api',
|
|
247
|
+
'api',
|
|
248
|
+
'healthCheck',
|
|
249
|
+
'--verbose',
|
|
250
|
+
'--format',
|
|
251
|
+
'json',
|
|
228
252
|
])
|
|
229
253
|
const parsed = json(output)
|
|
230
254
|
expect(parsed.ok).toBe(true)
|
|
@@ -248,7 +272,15 @@ describe('@hono/zod-openapi integration', () => {
|
|
|
248
272
|
|
|
249
273
|
test('PUT /users/:id with optional boolean body option', async () => {
|
|
250
274
|
const { output } = await serve(createCli(), [
|
|
251
|
-
'api',
|
|
275
|
+
'api',
|
|
276
|
+
'updateUser',
|
|
277
|
+
'1',
|
|
278
|
+
'--name',
|
|
279
|
+
'Updated',
|
|
280
|
+
'--active',
|
|
281
|
+
'true',
|
|
282
|
+
'--format',
|
|
283
|
+
'json',
|
|
252
284
|
])
|
|
253
285
|
const parsed = json(output)
|
|
254
286
|
expect(parsed.id).toBe(1)
|
|
@@ -257,44 +289,63 @@ describe('@hono/zod-openapi integration', () => {
|
|
|
257
289
|
})
|
|
258
290
|
|
|
259
291
|
test('query param coercion with zod-openapi generated spec', async () => {
|
|
260
|
-
const { output } = await serve(createCli(), [
|
|
292
|
+
const { output } = await serve(createCli(), [
|
|
293
|
+
'api',
|
|
294
|
+
'listUsers',
|
|
295
|
+
'--limit',
|
|
296
|
+
'3',
|
|
297
|
+
'--format',
|
|
298
|
+
'json',
|
|
299
|
+
])
|
|
261
300
|
expect(json(output).limit).toBe(3)
|
|
262
301
|
})
|
|
263
302
|
})
|
|
264
303
|
|
|
265
304
|
describe('basePath', () => {
|
|
266
305
|
test('fetch gateway prepends basePath to request path', async () => {
|
|
267
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
268
|
-
|
|
306
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
307
|
+
fetch: prefixedApp.fetch,
|
|
308
|
+
basePath: '/api',
|
|
309
|
+
})
|
|
269
310
|
const { output } = await serve(cli, ['api', 'users'])
|
|
270
311
|
expect(output).toContain('Alice')
|
|
271
312
|
})
|
|
272
313
|
|
|
273
314
|
test('fetch gateway basePath with query params', async () => {
|
|
274
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
275
|
-
|
|
315
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
316
|
+
fetch: prefixedApp.fetch,
|
|
317
|
+
basePath: '/api',
|
|
318
|
+
})
|
|
276
319
|
const { output } = await serve(cli, ['api', 'users', '--limit', '5', '--format', 'json'])
|
|
277
320
|
expect(json(output).limit).toBe(5)
|
|
278
321
|
})
|
|
279
322
|
|
|
280
323
|
test('fetch gateway basePath with POST', async () => {
|
|
281
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
282
|
-
|
|
324
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
325
|
+
fetch: prefixedApp.fetch,
|
|
326
|
+
basePath: '/api',
|
|
327
|
+
})
|
|
283
328
|
const { output } = await serve(cli, ['api', 'users', '-X', 'POST', '-d', '{"name":"Bob"}'])
|
|
284
329
|
expect(output).toContain('Bob')
|
|
285
330
|
expect(output).toContain('created')
|
|
286
331
|
})
|
|
287
332
|
|
|
288
333
|
test('openapi with basePath prepends to spec paths', async () => {
|
|
289
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
290
|
-
|
|
334
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
335
|
+
fetch: prefixedApp.fetch,
|
|
336
|
+
openapi: spec,
|
|
337
|
+
basePath: '/api',
|
|
338
|
+
})
|
|
291
339
|
const { output } = await serve(cli, ['api', 'listUsers'])
|
|
292
340
|
expect(output).toContain('Alice')
|
|
293
341
|
})
|
|
294
342
|
|
|
295
343
|
test('openapi basePath with path params', async () => {
|
|
296
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
297
|
-
|
|
344
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
345
|
+
fetch: prefixedApp.fetch,
|
|
346
|
+
openapi: spec,
|
|
347
|
+
basePath: '/api',
|
|
348
|
+
})
|
|
298
349
|
const { output } = await serve(cli, ['api', 'getUser', '42'])
|
|
299
350
|
expect(output).toMatchInlineSnapshot(`
|
|
300
351
|
"id: 42
|
|
@@ -304,16 +355,22 @@ describe('basePath', () => {
|
|
|
304
355
|
})
|
|
305
356
|
|
|
306
357
|
test('openapi basePath with body options', async () => {
|
|
307
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
308
|
-
|
|
358
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
359
|
+
fetch: prefixedApp.fetch,
|
|
360
|
+
openapi: spec,
|
|
361
|
+
basePath: '/api',
|
|
362
|
+
})
|
|
309
363
|
const { output } = await serve(cli, ['api', 'createUser', '--name', 'Bob'])
|
|
310
364
|
expect(output).toContain('created')
|
|
311
365
|
expect(output).toContain('Bob')
|
|
312
366
|
})
|
|
313
367
|
|
|
314
368
|
test('openapi basePath with health check', async () => {
|
|
315
|
-
const cli = Cli.create('test', { description: 'test' })
|
|
316
|
-
|
|
369
|
+
const cli = Cli.create('test', { description: 'test' }).command('api', {
|
|
370
|
+
fetch: prefixedApp.fetch,
|
|
371
|
+
openapi: spec,
|
|
372
|
+
basePath: '/api',
|
|
373
|
+
})
|
|
317
374
|
const { output } = await serve(cli, ['api', 'healthCheck', '--format', 'json'])
|
|
318
375
|
expect(json(output)).toEqual({ ok: true })
|
|
319
376
|
})
|
package/src/Openapi.ts
CHANGED
|
@@ -46,7 +46,7 @@ export async function generateCommands(
|
|
|
46
46
|
fetch: FetchHandler,
|
|
47
47
|
options: { basePath?: string | undefined } = {},
|
|
48
48
|
): Promise<Map<string, GeneratedCommand>> {
|
|
49
|
-
const resolved = await dereference(structuredClone(spec) as any) as unknown as OpenAPISpec
|
|
49
|
+
const resolved = (await dereference(structuredClone(spec) as any)) as unknown as OpenAPISpec
|
|
50
50
|
const commands = new Map<string, GeneratedCommand>()
|
|
51
51
|
const paths = (resolved.paths ?? {}) as Record<string, Record<string, unknown>>
|
|
52
52
|
|
|
@@ -96,7 +96,15 @@ export async function generateCommands(
|
|
|
96
96
|
description: op.summary ?? op.description,
|
|
97
97
|
args: argsSchema,
|
|
98
98
|
options: optionsSchema,
|
|
99
|
-
run: createHandler({
|
|
99
|
+
run: createHandler({
|
|
100
|
+
basePath: options.basePath,
|
|
101
|
+
fetch,
|
|
102
|
+
httpMethod,
|
|
103
|
+
path,
|
|
104
|
+
pathParams,
|
|
105
|
+
queryParams,
|
|
106
|
+
bodyProps,
|
|
107
|
+
}),
|
|
100
108
|
})
|
|
101
109
|
}
|
|
102
110
|
}
|
|
@@ -135,8 +143,7 @@ function createHandler(config: {
|
|
|
135
143
|
const bodyKeys = Object.keys(config.bodyProps)
|
|
136
144
|
if (bodyKeys.length > 0) {
|
|
137
145
|
const bodyObj: Record<string, unknown> = {}
|
|
138
|
-
for (const key of bodyKeys)
|
|
139
|
-
if (options[key] !== undefined) bodyObj[key] = options[key]
|
|
146
|
+
for (const key of bodyKeys) if (options[key] !== undefined) bodyObj[key] = options[key]
|
|
140
147
|
if (Object.keys(bodyObj).length > 0) body = JSON.stringify(bodyObj)
|
|
141
148
|
}
|
|
142
149
|
|
|
@@ -180,8 +187,10 @@ function coerceIfNeeded(schema: z.ZodType): z.ZodType {
|
|
|
180
187
|
const inner = isOptional ? schema.unwrap() : schema
|
|
181
188
|
|
|
182
189
|
// Direct number/boolean
|
|
183
|
-
if (inner instanceof z.ZodNumber)
|
|
184
|
-
|
|
190
|
+
if (inner instanceof z.ZodNumber)
|
|
191
|
+
return isOptional ? z.coerce.number().optional() : z.coerce.number()
|
|
192
|
+
if (inner instanceof z.ZodBoolean)
|
|
193
|
+
return isOptional ? z.coerce.boolean().optional() : z.coerce.boolean()
|
|
185
194
|
|
|
186
195
|
// Union containing number (e.g. type: ["number", "null"] from OpenAPI 3.1)
|
|
187
196
|
if (inner instanceof z.ZodUnion) {
|
package/src/Skill.test.ts
CHANGED
|
@@ -333,9 +333,7 @@ describe('split', () => {
|
|
|
333
333
|
|
|
334
334
|
test('emits fallback description when no explicit descriptions exist', () => {
|
|
335
335
|
const files = Skill.split('test', [{ name: 'ping' }], 1)
|
|
336
|
-
expect(files[0]!.content).toContain(
|
|
337
|
-
'description: Run `test ping --help` for usage details.',
|
|
338
|
-
)
|
|
336
|
+
expect(files[0]!.content).toContain('description: Run `test ping --help` for usage details.')
|
|
339
337
|
})
|
|
340
338
|
|
|
341
339
|
test('includes requires_bin in frontmatter', () => {
|
package/src/Skill.ts
CHANGED
|
@@ -24,7 +24,11 @@ export type File = {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/** Generates a compact Markdown command index for `--llms`. */
|
|
27
|
-
export function index(
|
|
27
|
+
export function index(
|
|
28
|
+
name: string,
|
|
29
|
+
commands: CommandInfo[],
|
|
30
|
+
description?: string | undefined,
|
|
31
|
+
): string {
|
|
28
32
|
const lines: string[] = [`# ${name}`]
|
|
29
33
|
if (description) lines.push('', description)
|
|
30
34
|
lines.push('')
|
|
@@ -35,7 +39,10 @@ export function index(name: string, commands: CommandInfo[], description?: strin
|
|
|
35
39
|
const desc = cmd.description ?? ''
|
|
36
40
|
lines.push(`| \`${signature}\` | ${desc} |`)
|
|
37
41
|
}
|
|
38
|
-
lines.push(
|
|
42
|
+
lines.push(
|
|
43
|
+
'',
|
|
44
|
+
`Run \`${name} --llms-full\` for full manifest. Run \`${name} <command> --schema\` for argument details.`,
|
|
45
|
+
)
|
|
39
46
|
return lines.join('\n')
|
|
40
47
|
}
|
|
41
48
|
|
package/src/e2e.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Cli, Errors, Skill, Typegen, z } from 'incur'
|
|
2
|
+
|
|
2
3
|
import { app as honoApp } from '../test/fixtures/hono-api.js'
|
|
3
4
|
|
|
4
5
|
let __mockSkillsHash: string | undefined
|
|
@@ -432,7 +433,13 @@ describe('--token-limit and --token-offset', () => {
|
|
|
432
433
|
})
|
|
433
434
|
|
|
434
435
|
test('--token-limit and --token-offset together for pagination', async () => {
|
|
435
|
-
const { output } = await serve(createApp(), [
|
|
436
|
+
const { output } = await serve(createApp(), [
|
|
437
|
+
'ping',
|
|
438
|
+
'--token-offset',
|
|
439
|
+
'2',
|
|
440
|
+
'--token-limit',
|
|
441
|
+
'4',
|
|
442
|
+
])
|
|
436
443
|
expect(output).toMatchInlineSnapshot(`
|
|
437
444
|
" true
|
|
438
445
|
[truncated: showing tokens 2–3 of 3]
|
|
@@ -449,7 +456,14 @@ describe('--token-limit and --token-offset', () => {
|
|
|
449
456
|
})
|
|
450
457
|
|
|
451
458
|
test('works with --verbose', async () => {
|
|
452
|
-
const { output } = await serve(createApp(), [
|
|
459
|
+
const { output } = await serve(createApp(), [
|
|
460
|
+
'ping',
|
|
461
|
+
'--verbose',
|
|
462
|
+
'--format',
|
|
463
|
+
'json',
|
|
464
|
+
'--token-limit',
|
|
465
|
+
'20',
|
|
466
|
+
])
|
|
453
467
|
expect(output).toMatchInlineSnapshot(`
|
|
454
468
|
"{
|
|
455
469
|
"ok": true,
|
|
@@ -466,13 +480,27 @@ describe('--token-limit and --token-offset', () => {
|
|
|
466
480
|
})
|
|
467
481
|
|
|
468
482
|
test('--verbose includes meta.nextOffset when truncated', async () => {
|
|
469
|
-
const { output } = await serve(createApp(), [
|
|
483
|
+
const { output } = await serve(createApp(), [
|
|
484
|
+
'ping',
|
|
485
|
+
'--verbose',
|
|
486
|
+
'--format',
|
|
487
|
+
'json',
|
|
488
|
+
'--token-limit',
|
|
489
|
+
'2',
|
|
490
|
+
])
|
|
470
491
|
expect(output).toContain('"nextOffset"')
|
|
471
492
|
expect(output).toContain('[truncated:')
|
|
472
493
|
})
|
|
473
494
|
|
|
474
495
|
test('--verbose omits meta.nextOffset when not truncated', async () => {
|
|
475
|
-
const { output } = await serve(createApp(), [
|
|
496
|
+
const { output } = await serve(createApp(), [
|
|
497
|
+
'ping',
|
|
498
|
+
'--verbose',
|
|
499
|
+
'--format',
|
|
500
|
+
'json',
|
|
501
|
+
'--token-limit',
|
|
502
|
+
'10000',
|
|
503
|
+
])
|
|
476
504
|
const parsed = json(output)
|
|
477
505
|
expect(parsed.meta.nextOffset).toBeUndefined()
|
|
478
506
|
})
|
|
@@ -499,7 +527,12 @@ describe('--token-count', () => {
|
|
|
499
527
|
})
|
|
500
528
|
|
|
501
529
|
test('works with --filter-output', async () => {
|
|
502
|
-
const { output } = await serve(createApp(), [
|
|
530
|
+
const { output } = await serve(createApp(), [
|
|
531
|
+
'ping',
|
|
532
|
+
'--filter-output',
|
|
533
|
+
'pong',
|
|
534
|
+
'--token-count',
|
|
535
|
+
])
|
|
503
536
|
expect(output.trim()).toBe('1')
|
|
504
537
|
})
|
|
505
538
|
})
|
|
@@ -1222,7 +1255,13 @@ describe('--llms-full', () => {
|
|
|
1222
1255
|
})
|
|
1223
1256
|
|
|
1224
1257
|
test('scoped --llms-full to nested group', async () => {
|
|
1225
|
-
const { output } = await serve(createApp(), [
|
|
1258
|
+
const { output } = await serve(createApp(), [
|
|
1259
|
+
'project',
|
|
1260
|
+
'deploy',
|
|
1261
|
+
'--llms-full',
|
|
1262
|
+
'--format',
|
|
1263
|
+
'json',
|
|
1264
|
+
])
|
|
1226
1265
|
const names = json(output).commands.map((c: any) => c.name)
|
|
1227
1266
|
expect(names).toMatchInlineSnapshot(`
|
|
1228
1267
|
[
|
|
@@ -1253,7 +1292,13 @@ describe('--llms-full', () => {
|
|
|
1253
1292
|
})
|
|
1254
1293
|
|
|
1255
1294
|
test('--llms-full json includes examples on commands', async () => {
|
|
1256
|
-
const { output } = await serve(createApp(), [
|
|
1295
|
+
const { output } = await serve(createApp(), [
|
|
1296
|
+
'project',
|
|
1297
|
+
'deploy',
|
|
1298
|
+
'--llms-full',
|
|
1299
|
+
'--format',
|
|
1300
|
+
'json',
|
|
1301
|
+
])
|
|
1257
1302
|
const deployCreate = json(output).commands.find((c: any) => c.name === 'project deploy create')
|
|
1258
1303
|
expect(deployCreate.examples).toMatchInlineSnapshot(`
|
|
1259
1304
|
[
|
|
@@ -2188,12 +2233,7 @@ describe('fetch gateway', () => {
|
|
|
2188
2233
|
})
|
|
2189
2234
|
|
|
2190
2235
|
test('implicit POST with --body', async () => {
|
|
2191
|
-
const { output } = await serve(createApp(), [
|
|
2192
|
-
'api',
|
|
2193
|
-
'users',
|
|
2194
|
-
'--body',
|
|
2195
|
-
'{"name":"Eve"}',
|
|
2196
|
-
])
|
|
2236
|
+
const { output } = await serve(createApp(), ['api', 'users', '--body', '{"name":"Eve"}'])
|
|
2197
2237
|
expect(output).toMatchInlineSnapshot(`
|
|
2198
2238
|
"created: true
|
|
2199
2239
|
name: Eve
|
|
@@ -2202,13 +2242,7 @@ describe('fetch gateway', () => {
|
|
|
2202
2242
|
})
|
|
2203
2243
|
|
|
2204
2244
|
test('DELETE with --method', async () => {
|
|
2205
|
-
const { output } = await serve(createApp(), [
|
|
2206
|
-
'api',
|
|
2207
|
-
'users',
|
|
2208
|
-
'1',
|
|
2209
|
-
'--method',
|
|
2210
|
-
'DELETE',
|
|
2211
|
-
])
|
|
2245
|
+
const { output } = await serve(createApp(), ['api', 'users', '1', '--method', 'DELETE'])
|
|
2212
2246
|
expect(output).toMatchInlineSnapshot(`
|
|
2213
2247
|
"deleted: true
|
|
2214
2248
|
id: 1
|
|
@@ -2240,13 +2274,7 @@ describe('fetch gateway', () => {
|
|
|
2240
2274
|
})
|
|
2241
2275
|
|
|
2242
2276
|
test('--verbose wraps in envelope', async () => {
|
|
2243
|
-
const { output } = await serve(createApp(), [
|
|
2244
|
-
'api',
|
|
2245
|
-
'health',
|
|
2246
|
-
'--verbose',
|
|
2247
|
-
'--format',
|
|
2248
|
-
'json',
|
|
2249
|
-
])
|
|
2277
|
+
const { output } = await serve(createApp(), ['api', 'health', '--verbose', '--format', 'json'])
|
|
2250
2278
|
const parsed = json(output)
|
|
2251
2279
|
expect(parsed.ok).toBe(true)
|
|
2252
2280
|
expect(parsed.data).toEqual({ ok: true })
|
|
@@ -2298,7 +2326,10 @@ describe('fetch gateway', () => {
|
|
|
2298
2326
|
|
|
2299
2327
|
test('streaming NDJSON --format jsonl', async () => {
|
|
2300
2328
|
const { output } = await serve(createApp(), ['api', 'stream', '--format', 'jsonl'])
|
|
2301
|
-
const lines = output
|
|
2329
|
+
const lines = output
|
|
2330
|
+
.trim()
|
|
2331
|
+
.split('\n')
|
|
2332
|
+
.map((l) => JSON.parse(l))
|
|
2302
2333
|
expect(lines[0]).toEqual({ type: 'chunk', data: { progress: 1 } })
|
|
2303
2334
|
expect(lines[1]).toEqual({ type: 'chunk', data: { progress: 2 } })
|
|
2304
2335
|
expect(lines[2].type).toBe('done')
|
|
@@ -2374,7 +2405,8 @@ describe('fetch api', () => {
|
|
|
2374
2405
|
|
|
2375
2406
|
test('GET with query params → options', async () => {
|
|
2376
2407
|
const cli = createApp()
|
|
2377
|
-
expect(await fetchJson(cli, new Request('http://localhost/echo/hi?prefix=yo')))
|
|
2408
|
+
expect(await fetchJson(cli, new Request('http://localhost/echo/hi?prefix=yo')))
|
|
2409
|
+
.toMatchInlineSnapshot(`
|
|
2378
2410
|
{
|
|
2379
2411
|
"body": {
|
|
2380
2412
|
"data": {
|
|
@@ -2420,7 +2452,8 @@ describe('fetch api', () => {
|
|
|
2420
2452
|
|
|
2421
2453
|
test('trailing path segments → positional args', async () => {
|
|
2422
2454
|
const cli = createApp()
|
|
2423
|
-
expect(await fetchJson(cli, new Request('http://localhost/project/get/p1')))
|
|
2455
|
+
expect(await fetchJson(cli, new Request('http://localhost/project/get/p1')))
|
|
2456
|
+
.toMatchInlineSnapshot(`
|
|
2424
2457
|
{
|
|
2425
2458
|
"body": {
|
|
2426
2459
|
"data": {
|
|
@@ -2447,7 +2480,8 @@ describe('fetch api', () => {
|
|
|
2447
2480
|
|
|
2448
2481
|
test('nested command (3 levels deep)', async () => {
|
|
2449
2482
|
const cli = createApp()
|
|
2450
|
-
expect(await fetchJson(cli, new Request('http://localhost/project/deploy/status/d-456')))
|
|
2483
|
+
expect(await fetchJson(cli, new Request('http://localhost/project/deploy/status/d-456')))
|
|
2484
|
+
.toMatchInlineSnapshot(`
|
|
2451
2485
|
{
|
|
2452
2486
|
"body": {
|
|
2453
2487
|
"data": {
|
|
@@ -2488,7 +2522,8 @@ describe('fetch api', () => {
|
|
|
2488
2522
|
|
|
2489
2523
|
test('IncurError → 500 with code', async () => {
|
|
2490
2524
|
const cli = createApp()
|
|
2491
|
-
expect(await fetchJson(cli, new Request('http://localhost/explode-clac')))
|
|
2525
|
+
expect(await fetchJson(cli, new Request('http://localhost/explode-clac')))
|
|
2526
|
+
.toMatchInlineSnapshot(`
|
|
2492
2527
|
{
|
|
2493
2528
|
"body": {
|
|
2494
2529
|
"error": {
|
|
@@ -2519,7 +2554,10 @@ describe('fetch api', () => {
|
|
|
2519
2554
|
const res = await cli.fetch(new Request('http://localhost/stream'))
|
|
2520
2555
|
expect(res.status).toBe(200)
|
|
2521
2556
|
expect(res.headers.get('content-type')).toBe('application/x-ndjson')
|
|
2522
|
-
const lines = (await res.text())
|
|
2557
|
+
const lines = (await res.text())
|
|
2558
|
+
.trim()
|
|
2559
|
+
.split('\n')
|
|
2560
|
+
.map((l) => JSON.parse(l))
|
|
2523
2561
|
expect(lines).toMatchInlineSnapshot(`
|
|
2524
2562
|
[
|
|
2525
2563
|
{
|
|
@@ -2585,7 +2623,9 @@ describe('fetch api', () => {
|
|
|
2585
2623
|
|
|
2586
2624
|
test('middleware error → error response', async () => {
|
|
2587
2625
|
const cli = Cli.create('test')
|
|
2588
|
-
cli.use((c) => {
|
|
2626
|
+
cli.use((c) => {
|
|
2627
|
+
c.error({ code: 'FORBIDDEN', message: 'nope' })
|
|
2628
|
+
})
|
|
2589
2629
|
cli.command('secret', { run: () => ({ secret: true }) })
|
|
2590
2630
|
expect(await fetchJson(cli, new Request('http://localhost/secret'))).toMatchInlineSnapshot(`
|
|
2591
2631
|
{
|