incur 0.1.17 → 0.2.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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAEvD,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AAErC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA;AACvC,OAAO,KAAK,UAAU,MAAM,iBAAiB,CAAA;AAC7C,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "devDependencies": {
3
3
  "@changesets/cli": "latest",
4
+ "@hono/zod-openapi": "^1.2.2",
4
5
  "@types/node": "latest",
5
6
  "@vitest/coverage-v8": "^4.0.18",
6
7
  "bun": "^1.3.10",
8
+ "hono": "^4.12.5",
7
9
  "oxfmt": "^0.35.0",
8
10
  "oxlint": "^1.50.0",
9
11
  "tsx": "^4.21.0",
@@ -14,7 +16,7 @@
14
16
  "[!start-pkg]": "",
15
17
  "name": "incur",
16
18
  "type": "module",
17
- "version": "0.1.17",
19
+ "version": "0.2.0",
18
20
  "license": "MIT",
19
21
  "repository": {
20
22
  "type": "git",
@@ -28,6 +30,7 @@
28
30
  ],
29
31
  "dependencies": {
30
32
  "@modelcontextprotocol/sdk": "^1.27.1",
33
+ "@readme/openapi-parser": "^6.0.0",
31
34
  "@toon-format/toon": "^2.1.0",
32
35
  "yaml": "^2.8.2",
33
36
  "zod": "^4.3.6"
package/src/Cli.test-d.ts CHANGED
@@ -47,9 +47,9 @@ test('output constrains run return type', () => {
47
47
  },
48
48
  })
49
49
 
50
+ // @ts-expect-error — return doesn't match output schema
50
51
  cli.command('greet', {
51
52
  output: z.object({ message: z.string() }),
52
- // @ts-expect-error — return doesn't match output schema
53
53
  run() {
54
54
  return { wrong: 123 }
55
55
  },
@@ -64,9 +64,9 @@ test('alias keys are constrained to option keys', () => {
64
64
  run: () => ({}),
65
65
  })
66
66
 
67
+ // @ts-expect-error — 'foo' is not an option key
67
68
  cli.command('list', {
68
69
  options: z.object({ state: z.string() }),
69
- // @ts-expect-error — 'foo' is not an option key
70
70
  alias: { foo: 'f' },
71
71
  run: () => ({}),
72
72
  })
package/src/Cli.test.ts CHANGED
@@ -2435,3 +2435,208 @@ test('--llms includes hint in skill output', async () => {
2435
2435
  const { output } = await serve(cli, ['--llms'])
2436
2436
  expect(output).toContain('Always confirm before deploying to production')
2437
2437
  })
2438
+
2439
+ describe('fetch', async () => {
2440
+ const { app } = await import('../test/fixtures/hono-api.js')
2441
+
2442
+ test('command with fetch: GET /users', async () => {
2443
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2444
+ description: 'Hono API',
2445
+ fetch: app.fetch,
2446
+ })
2447
+ const { output } = await serve(cli, ['api', 'users'])
2448
+ expect(output).toMatchInlineSnapshot(`
2449
+ "users[1]{id,name}:
2450
+ 1,Alice
2451
+ limit: 10
2452
+ "
2453
+ `)
2454
+ })
2455
+
2456
+ test('GET with query params', async () => {
2457
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2458
+ fetch: app.fetch,
2459
+ })
2460
+ const { output } = await serve(cli, ['api', 'users', '--limit', '5'])
2461
+ expect(output).toMatchInlineSnapshot(`
2462
+ "users[1]{id,name}:
2463
+ 1,Alice
2464
+ limit: 5
2465
+ "
2466
+ `)
2467
+ })
2468
+
2469
+ test('GET /users/:id via path segments', async () => {
2470
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2471
+ fetch: app.fetch,
2472
+ })
2473
+ const { output } = await serve(cli, ['api', 'users', '42'])
2474
+ expect(output).toMatchInlineSnapshot(`
2475
+ "id: 42
2476
+ name: Alice
2477
+ "
2478
+ `)
2479
+ })
2480
+
2481
+ test('POST with -X and -d', async () => {
2482
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2483
+ fetch: app.fetch,
2484
+ })
2485
+ const { output } = await serve(cli, [
2486
+ 'api',
2487
+ 'users',
2488
+ '-X',
2489
+ 'POST',
2490
+ '-d',
2491
+ '{"name":"Bob"}',
2492
+ ])
2493
+ expect(output).toMatchInlineSnapshot(`
2494
+ "created: true
2495
+ name: Bob
2496
+ "
2497
+ `)
2498
+ })
2499
+
2500
+ test('implicit POST with --body', async () => {
2501
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2502
+ fetch: app.fetch,
2503
+ })
2504
+ const { output } = await serve(cli, [
2505
+ 'api',
2506
+ 'users',
2507
+ '--body',
2508
+ '{"name":"Eve"}',
2509
+ ])
2510
+ expect(output).toMatchInlineSnapshot(`
2511
+ "created: true
2512
+ name: Eve
2513
+ "
2514
+ `)
2515
+ })
2516
+
2517
+ test('DELETE with --method', async () => {
2518
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2519
+ fetch: app.fetch,
2520
+ })
2521
+ const { output } = await serve(cli, [
2522
+ 'api',
2523
+ 'users',
2524
+ '1',
2525
+ '--method',
2526
+ 'DELETE',
2527
+ ])
2528
+ expect(output).toMatchInlineSnapshot(`
2529
+ "deleted: true
2530
+ id: 1
2531
+ "
2532
+ `)
2533
+ })
2534
+
2535
+ test('error response → exit code 1', async () => {
2536
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2537
+ fetch: app.fetch,
2538
+ })
2539
+ const { exitCode, output } = await serve(cli, ['api', 'error'])
2540
+ expect(exitCode).toBe(1)
2541
+ expect(output).toContain('HTTP_404')
2542
+ })
2543
+
2544
+ test('--format json', async () => {
2545
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2546
+ fetch: app.fetch,
2547
+ })
2548
+ const { output } = await serve(cli, ['api', 'health', '--format', 'json'])
2549
+ expect(JSON.parse(output)).toEqual({ ok: true })
2550
+ })
2551
+
2552
+ test('--verbose includes request/response meta', async () => {
2553
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2554
+ fetch: app.fetch,
2555
+ })
2556
+ const { output } = await serve(cli, ['api', 'health', '--verbose', '--format', 'json'])
2557
+ const parsed = JSON.parse(output)
2558
+ expect(parsed.ok).toBe(true)
2559
+ expect(parsed.data).toEqual({ ok: true })
2560
+ expect(parsed.meta.command).toBe('api')
2561
+ })
2562
+
2563
+ test('native + fetch commands coexist', async () => {
2564
+ const cli = Cli.create('test', { description: 'test' })
2565
+ .command('api', { fetch: app.fetch })
2566
+ .command('ping', { run: () => ({ pong: true }) })
2567
+ const { output: fetchOut } = await serve(cli, ['api', 'health'])
2568
+ expect(fetchOut).toContain('ok: true')
2569
+ const { output: nativeOut } = await serve(cli, ['ping'])
2570
+ expect(nativeOut).toContain('pong: true')
2571
+ })
2572
+
2573
+ test('root-level fetch', async () => {
2574
+ const cli = Cli.create('api', { description: 'API', fetch: app.fetch })
2575
+ const { output } = await serve(cli, ['users'])
2576
+ expect(output).toMatchInlineSnapshot(`
2577
+ "users[1]{id,name}:
2578
+ 1,Alice
2579
+ limit: 10
2580
+ "
2581
+ `)
2582
+ })
2583
+
2584
+ test('root-level fetch with no args → root path', async () => {
2585
+ const cli = Cli.create('api', { description: 'API', fetch: app.fetch })
2586
+ // Hono returns 404 for / since we don't have a root route
2587
+ const { exitCode } = await serve(cli, [])
2588
+ expect(exitCode).toBe(1)
2589
+ })
2590
+
2591
+ test('--help on fetch command', async () => {
2592
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2593
+ description: 'Proxy to Hono API',
2594
+ fetch: app.fetch,
2595
+ })
2596
+ const { output } = await serve(cli, ['api', '--help'])
2597
+ expect(output).toContain('Proxy to Hono API')
2598
+ expect(output).toContain('--method')
2599
+ expect(output).toContain('--header')
2600
+ expect(output).toContain('--body')
2601
+ })
2602
+
2603
+ test('text response', async () => {
2604
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2605
+ fetch: app.fetch,
2606
+ })
2607
+ const { output } = await serve(cli, ['api', 'text'])
2608
+ expect(output).toContain('hello world')
2609
+ })
2610
+
2611
+ test('middleware runs before fetch handler', async () => {
2612
+ let middlewareRan = false
2613
+ const cli = Cli.create('test', { description: 'test' })
2614
+ .use(async (_c, next) => {
2615
+ middlewareRan = true
2616
+ await next()
2617
+ })
2618
+ .command('api', { fetch: app.fetch })
2619
+ await serve(cli, ['api', 'health'])
2620
+ expect(middlewareRan).toBe(true)
2621
+ })
2622
+
2623
+ test('fetch command appears in --llms', async () => {
2624
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2625
+ description: 'Proxy to API',
2626
+ fetch: app.fetch,
2627
+ })
2628
+ const { output } = await serve(cli, ['--llms'])
2629
+ expect(output).toContain('api')
2630
+ expect(output).toContain('Proxy to API')
2631
+ })
2632
+
2633
+ test('fetch command appears in --help root', async () => {
2634
+ const cli = Cli.create('test', { description: 'test' }).command('api', {
2635
+ description: 'Proxy to API',
2636
+ fetch: app.fetch,
2637
+ })
2638
+ const { output } = await serve(cli, ['--help'])
2639
+ expect(output).toContain('api')
2640
+ expect(output).toContain('Proxy to API')
2641
+ })
2642
+ })