incur 0.4.0 → 0.4.1

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.
Files changed (115) hide show
  1. package/README.md +83 -22
  2. package/SKILL.md +6 -6
  3. package/dist/Cli.d.ts +46 -26
  4. package/dist/Cli.d.ts.map +1 -1
  5. package/dist/Cli.js +727 -440
  6. package/dist/Cli.js.map +1 -1
  7. package/dist/Completions.d.ts +4 -3
  8. package/dist/Completions.d.ts.map +1 -1
  9. package/dist/Completions.js +17 -10
  10. package/dist/Completions.js.map +1 -1
  11. package/dist/Fetch.d.ts.map +1 -1
  12. package/dist/Fetch.js +10 -9
  13. package/dist/Fetch.js.map +1 -1
  14. package/dist/Filter.js +0 -18
  15. package/dist/Filter.js.map +1 -1
  16. package/dist/Formatter.d.ts.map +1 -1
  17. package/dist/Formatter.js +6 -1
  18. package/dist/Formatter.js.map +1 -1
  19. package/dist/Help.d.ts +7 -1
  20. package/dist/Help.d.ts.map +1 -1
  21. package/dist/Help.js +44 -27
  22. package/dist/Help.js.map +1 -1
  23. package/dist/Mcp.d.ts +37 -5
  24. package/dist/Mcp.d.ts.map +1 -1
  25. package/dist/Mcp.js +71 -72
  26. package/dist/Mcp.js.map +1 -1
  27. package/dist/Openapi.d.ts.map +1 -1
  28. package/dist/Openapi.js +22 -14
  29. package/dist/Openapi.js.map +1 -1
  30. package/dist/Parser.d.ts +4 -0
  31. package/dist/Parser.d.ts.map +1 -1
  32. package/dist/Parser.js +70 -38
  33. package/dist/Parser.js.map +1 -1
  34. package/dist/Schema.d.ts +5 -1
  35. package/dist/Schema.d.ts.map +1 -1
  36. package/dist/Schema.js +13 -2
  37. package/dist/Schema.js.map +1 -1
  38. package/dist/Skill.d.ts +2 -1
  39. package/dist/Skill.d.ts.map +1 -1
  40. package/dist/Skill.js +33 -19
  41. package/dist/Skill.js.map +1 -1
  42. package/dist/Skillgen.js +1 -1
  43. package/dist/Skillgen.js.map +1 -1
  44. package/dist/SyncSkills.d.ts +44 -0
  45. package/dist/SyncSkills.d.ts.map +1 -1
  46. package/dist/SyncSkills.js +82 -7
  47. package/dist/SyncSkills.js.map +1 -1
  48. package/dist/Typegen.js +4 -2
  49. package/dist/Typegen.js.map +1 -1
  50. package/dist/bin.d.ts +2 -1
  51. package/dist/bin.d.ts.map +1 -1
  52. package/dist/bin.js +17 -2
  53. package/dist/bin.js.map +1 -1
  54. package/dist/internal/command.d.ts +170 -0
  55. package/dist/internal/command.d.ts.map +1 -0
  56. package/dist/internal/command.js +292 -0
  57. package/dist/internal/command.js.map +1 -0
  58. package/dist/internal/configSchema.d.ts +8 -0
  59. package/dist/internal/configSchema.d.ts.map +1 -0
  60. package/dist/internal/configSchema.js +57 -0
  61. package/dist/internal/configSchema.js.map +1 -0
  62. package/dist/internal/dereference.d.ts +12 -0
  63. package/dist/internal/dereference.d.ts.map +1 -0
  64. package/dist/internal/dereference.js +71 -0
  65. package/dist/internal/dereference.js.map +1 -0
  66. package/dist/internal/helpers.d.ts +9 -0
  67. package/dist/internal/helpers.d.ts.map +1 -0
  68. package/dist/internal/helpers.js +54 -0
  69. package/dist/internal/helpers.js.map +1 -0
  70. package/dist/middleware.d.ts +6 -8
  71. package/dist/middleware.d.ts.map +1 -1
  72. package/dist/middleware.js +1 -1
  73. package/dist/middleware.js.map +1 -1
  74. package/examples/npm/.npmrc.json +21 -0
  75. package/examples/npm/config.schema.json +134 -0
  76. package/package.json +6 -29
  77. package/src/Cli.test-d.ts +44 -33
  78. package/src/Cli.test.ts +1213 -100
  79. package/src/Cli.ts +876 -568
  80. package/src/Completions.test.ts +136 -12
  81. package/src/Completions.ts +18 -13
  82. package/src/Fetch.test.ts +21 -0
  83. package/src/Fetch.ts +8 -10
  84. package/src/Filter.ts +0 -17
  85. package/src/Formatter.test.ts +15 -2
  86. package/src/Formatter.ts +5 -1
  87. package/src/Help.test.ts +184 -20
  88. package/src/Help.ts +52 -28
  89. package/src/Mcp.test.ts +159 -0
  90. package/src/Mcp.ts +108 -86
  91. package/src/Openapi.test.ts +17 -5
  92. package/src/Openapi.ts +21 -15
  93. package/src/Parser.test-d.ts +22 -0
  94. package/src/Parser.test.ts +89 -0
  95. package/src/Parser.ts +87 -36
  96. package/src/Schema.test.ts +29 -0
  97. package/src/Schema.ts +12 -2
  98. package/src/Skill.test.ts +87 -6
  99. package/src/Skill.ts +38 -21
  100. package/src/Skillgen.ts +1 -1
  101. package/src/SyncMcp.test.ts +6 -8
  102. package/src/SyncSkills.test.ts +120 -3
  103. package/src/SyncSkills.ts +142 -6
  104. package/src/Typegen.test.ts +15 -0
  105. package/src/Typegen.ts +4 -2
  106. package/src/bin.ts +21 -2
  107. package/src/e2e.test.ts +172 -97
  108. package/src/internal/command.ts +449 -0
  109. package/src/internal/configSchema.test.ts +193 -0
  110. package/src/internal/configSchema.ts +66 -0
  111. package/src/internal/dereference.test.ts +695 -0
  112. package/src/internal/dereference.ts +75 -0
  113. package/src/internal/helpers.test.ts +75 -0
  114. package/src/internal/helpers.ts +59 -0
  115. package/src/middleware.ts +5 -12
package/src/e2e.test.ts CHANGED
@@ -70,9 +70,9 @@ describe('routing', () => {
70
70
  "code: COMMAND_NOT_FOUND
71
71
  message: 'nonexistent' is not a command for 'app'.
72
72
  cta:
73
- description: "See available commands:"
74
- commands[1]{command}:
75
- app --help
73
+ description: "Suggested command:"
74
+ commands[1]{command,description}:
75
+ app --help,see all available commands
76
76
  "
77
77
  `)
78
78
  })
@@ -85,8 +85,8 @@ describe('routing', () => {
85
85
  expect(output).toMatchInlineSnapshot(`
86
86
  "Error: 'nonexistent' is not a command for 'app'.
87
87
 
88
- See available commands:
89
- app --help
88
+ Suggested command:
89
+ app --help # see all available commands
90
90
  "
91
91
  `)
92
92
  })
@@ -98,9 +98,9 @@ describe('routing', () => {
98
98
  "code: COMMAND_NOT_FOUND
99
99
  message: 'whoami' is not a command for 'app auth'.
100
100
  cta:
101
- description: "See available commands:"
102
- commands[1]{command}:
103
- app auth --help
101
+ description: "Suggested command:"
102
+ commands[1]{command,description}:
103
+ app auth --help,see all available commands
104
104
  "
105
105
  `)
106
106
  })
@@ -112,9 +112,9 @@ describe('routing', () => {
112
112
  "code: COMMAND_NOT_FOUND
113
113
  message: 'nope' is not a command for 'app project deploy'.
114
114
  cta:
115
- description: "See available commands:"
116
- commands[1]{command}:
117
- app project deploy --help
115
+ description: "Suggested command:"
116
+ commands[1]{command,description}:
117
+ app project deploy --help,see all available commands
118
118
  "
119
119
  `)
120
120
  })
@@ -173,7 +173,7 @@ describe('args and options', () => {
173
173
  'read',
174
174
  '--scopes',
175
175
  'write',
176
- '--verbose',
176
+ '--full-output',
177
177
  '--format',
178
178
  'json',
179
179
  ])
@@ -192,7 +192,7 @@ describe('args and options', () => {
192
192
  'list',
193
193
  '--limit',
194
194
  '5',
195
- '--verbose',
195
+ '--full-output',
196
196
  '--format',
197
197
  'json',
198
198
  ])
@@ -327,8 +327,8 @@ describe('output formats', () => {
327
327
  `)
328
328
  })
329
329
 
330
- test('--verbose full envelope', async () => {
331
- const { output } = await serve(createApp(), ['ping', '--verbose'])
330
+ test('--full-output full envelope', async () => {
331
+ const { output } = await serve(createApp(), ['ping', '--full-output'])
332
332
  expect(output).toMatchInlineSnapshot(`
333
333
  "ok: true
334
334
  data:
@@ -340,8 +340,8 @@ describe('output formats', () => {
340
340
  `)
341
341
  })
342
342
 
343
- test('--verbose --format json full envelope', async () => {
344
- const { output } = await serve(createApp(), ['ping', '--verbose', '--format', 'json'])
343
+ test('--full-output --format json full envelope', async () => {
344
+ const { output } = await serve(createApp(), ['ping', '--full-output', '--format', 'json'])
345
345
  expect(json(output)).toMatchInlineSnapshot(`
346
346
  {
347
347
  "data": {
@@ -356,13 +356,13 @@ describe('output formats', () => {
356
356
  `)
357
357
  })
358
358
 
359
- test('nested command path in verbose meta', async () => {
359
+ test('nested command path in full-output meta', async () => {
360
360
  const { output } = await serve(createApp(), [
361
361
  'project',
362
362
  'deploy',
363
363
  'status',
364
364
  'd-1',
365
- '--verbose',
365
+ '--full-output',
366
366
  '--format',
367
367
  'json',
368
368
  ])
@@ -400,8 +400,8 @@ describe('undefined output', () => {
400
400
  expect(output).toBe('')
401
401
  })
402
402
 
403
- test('void command shows envelope with --verbose', async () => {
404
- const { output } = await serve(createApp(), ['noop', '--verbose', '--format', 'json'])
403
+ test('void command shows envelope with --full-output', async () => {
404
+ const { output } = await serve(createApp(), ['noop', '--full-output', '--format', 'json'])
405
405
  expect(json(output)).toMatchInlineSnapshot(`
406
406
  {
407
407
  "meta": {
@@ -455,10 +455,10 @@ describe('--token-limit and --token-offset', () => {
455
455
  `)
456
456
  })
457
457
 
458
- test('works with --verbose', async () => {
458
+ test('works with --full-output', async () => {
459
459
  const { output } = await serve(createApp(), [
460
460
  'ping',
461
- '--verbose',
461
+ '--full-output',
462
462
  '--format',
463
463
  'json',
464
464
  '--token-limit',
@@ -479,10 +479,10 @@ describe('--token-limit and --token-offset', () => {
479
479
  `)
480
480
  })
481
481
 
482
- test('--verbose includes meta.nextOffset when truncated', async () => {
482
+ test('--full-output includes meta.nextOffset when truncated', async () => {
483
483
  const { output } = await serve(createApp(), [
484
484
  'ping',
485
- '--verbose',
485
+ '--full-output',
486
486
  '--format',
487
487
  'json',
488
488
  '--token-limit',
@@ -492,10 +492,10 @@ describe('--token-limit and --token-offset', () => {
492
492
  expect(output).toContain('[truncated:')
493
493
  })
494
494
 
495
- test('--verbose omits meta.nextOffset when not truncated', async () => {
495
+ test('--full-output omits meta.nextOffset when not truncated', async () => {
496
496
  const { output } = await serve(createApp(), [
497
497
  'ping',
498
- '--verbose',
498
+ '--full-output',
499
499
  '--format',
500
500
  'json',
501
501
  '--token-limit',
@@ -575,7 +575,7 @@ describe('error handling', () => {
575
575
  const { output, exitCode } = await serve(createApp(), [
576
576
  'auth',
577
577
  'status',
578
- '--verbose',
578
+ '--full-output',
579
579
  '--format',
580
580
  'json',
581
581
  ])
@@ -595,7 +595,7 @@ describe('error handling', () => {
595
595
  "command": "app auth login",
596
596
  },
597
597
  ],
598
- "description": "Suggested commands:",
598
+ "description": "Suggested command:",
599
599
  },
600
600
  "duration": "<stripped>",
601
601
  },
@@ -633,7 +633,7 @@ describe('error handling', () => {
633
633
  test('command not found returns error envelope', async () => {
634
634
  const { output, exitCode } = await serve(createApp(), [
635
635
  'nonexistent',
636
- '--verbose',
636
+ '--full-output',
637
637
  '--format',
638
638
  'json',
639
639
  ])
@@ -650,9 +650,10 @@ describe('error handling', () => {
650
650
  "commands": [
651
651
  {
652
652
  "command": "app --help",
653
+ "description": "see all available commands",
653
654
  },
654
655
  ],
655
- "description": "See available commands:",
656
+ "description": "Suggested command:",
656
657
  },
657
658
  "duration": "<stripped>",
658
659
  },
@@ -675,7 +676,13 @@ describe('error handling', () => {
675
676
 
676
677
  describe('cta', () => {
677
678
  test('ok() with string CTAs', async () => {
678
- const { output } = await serve(createApp(), ['auth', 'login', '--verbose', '--format', 'json'])
679
+ const { output } = await serve(createApp(), [
680
+ 'auth',
681
+ 'login',
682
+ '--full-output',
683
+ '--format',
684
+ 'json',
685
+ ])
679
686
  expect(json(output).meta.cta).toMatchInlineSnapshot(`
680
687
  {
681
688
  "commands": [
@@ -693,7 +700,7 @@ describe('cta', () => {
693
700
  'project',
694
701
  'create',
695
702
  'MyProject',
696
- '--verbose',
703
+ '--full-output',
697
704
  '--format',
698
705
  'json',
699
706
  ])
@@ -714,7 +721,13 @@ describe('cta', () => {
714
721
  })
715
722
 
716
723
  test('error() with CTA', async () => {
717
- const { output } = await serve(createApp(), ['auth', 'status', '--verbose', '--format', 'json'])
724
+ const { output } = await serve(createApp(), [
725
+ 'auth',
726
+ 'status',
727
+ '--full-output',
728
+ '--format',
729
+ 'json',
730
+ ])
718
731
  expect(json(output).meta.cta).toMatchInlineSnapshot(`
719
732
  {
720
733
  "commands": [
@@ -722,13 +735,13 @@ describe('cta', () => {
722
735
  "command": "app auth login",
723
736
  },
724
737
  ],
725
- "description": "Suggested commands:",
738
+ "description": "Suggested command:",
726
739
  }
727
740
  `)
728
741
  })
729
742
 
730
743
  test('plain return omits CTA', async () => {
731
- const { output } = await serve(createApp(), ['ping', '--verbose', '--format', 'json'])
744
+ const { output } = await serve(createApp(), ['ping', '--full-output', '--format', 'json'])
732
745
  expect(json(output).meta.cta).toBeUndefined()
733
746
  })
734
747
 
@@ -737,7 +750,7 @@ describe('cta', () => {
737
750
  'project',
738
751
  'list',
739
752
  '--archived',
740
- '--verbose',
753
+ '--full-output',
741
754
  '--format',
742
755
  'json',
743
756
  ])
@@ -779,8 +792,8 @@ describe('streaming', () => {
779
792
  `)
780
793
  })
781
794
 
782
- test('default streams toon per chunk (--verbose)', async () => {
783
- const { output } = await serve(createApp(), ['stream', '--verbose'])
795
+ test('default streams toon per chunk (--full-output)', async () => {
796
+ const { output } = await serve(createApp(), ['stream', '--full-output'])
784
797
  expect(output).toMatchInlineSnapshot(`
785
798
  "content: hello
786
799
  content: world
@@ -802,8 +815,8 @@ describe('streaming', () => {
802
815
  `)
803
816
  })
804
817
 
805
- test('--format json --verbose buffers with envelope', async () => {
806
- const { output } = await serve(createApp(), ['stream', '--verbose', '--format', 'json'])
818
+ test('--format json --full-output buffers with envelope', async () => {
819
+ const { output } = await serve(createApp(), ['stream', '--full-output', '--format', 'json'])
807
820
  expect(json(output)).toMatchInlineSnapshot(`
808
821
  {
809
822
  "data": [
@@ -868,7 +881,7 @@ describe('streaming', () => {
868
881
  "command": "app ping",
869
882
  },
870
883
  ],
871
- "description": "Suggested commands:",
884
+ "description": "Suggested command:",
872
885
  }
873
886
  `)
874
887
  })
@@ -877,7 +890,7 @@ describe('streaming', () => {
877
890
  const { output } = await serve(createApp(), ['stream-ok'])
878
891
  expect(output).toContain('n: 1')
879
892
  expect(output).toContain('n: 2')
880
- expect(output).toContain('Suggested commands:')
893
+ expect(output).toContain('Suggested command:')
881
894
  expect(output).toContain('app ping')
882
895
  })
883
896
 
@@ -963,22 +976,22 @@ describe('help', () => {
963
976
  stream-throw Stream that throws
964
977
  validate-fail Fails validation
965
978
 
966
- Built-in Commands:
979
+ Integrations:
967
980
  completions Generate shell completion script
968
- mcp add Register as an MCP server
969
- skills add Sync skill files to your agent
981
+ mcp add Register as MCP server
982
+ skills Sync skill files to agents (add, list)
970
983
 
971
984
  Global Options:
972
985
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
973
986
  --format <toon|json|yaml|md|jsonl> Output format
987
+ --full-output Show full output envelope
974
988
  --help Show help
975
989
  --llms, --llms-full Print LLM-readable manifest
976
990
  --mcp Start as MCP stdio server
977
- --schema Show JSON Schema for a command
991
+ --schema Show JSON Schema for command
978
992
  --token-count Print token count of output (instead of output)
979
993
  --token-limit <n> Limit output to n tokens
980
994
  --token-offset <n> Skip first n tokens of output
981
- --verbose Show full output envelope
982
995
  --version Show version
983
996
  "
984
997
  `)
@@ -1005,13 +1018,13 @@ describe('help', () => {
1005
1018
  Global Options:
1006
1019
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1007
1020
  --format <toon|json|yaml|md|jsonl> Output format
1021
+ --full-output Show full output envelope
1008
1022
  --help Show help
1009
1023
  --llms, --llms-full Print LLM-readable manifest
1010
- --schema Show JSON Schema for a command
1024
+ --schema Show JSON Schema for command
1011
1025
  --token-count Print token count of output (instead of output)
1012
1026
  --token-limit <n> Limit output to n tokens
1013
1027
  --token-offset <n> Skip first n tokens of output
1014
- --verbose Show full output envelope
1015
1028
  "
1016
1029
  `)
1017
1030
  })
@@ -1032,13 +1045,13 @@ describe('help', () => {
1032
1045
  Global Options:
1033
1046
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1034
1047
  --format <toon|json|yaml|md|jsonl> Output format
1048
+ --full-output Show full output envelope
1035
1049
  --help Show help
1036
1050
  --llms, --llms-full Print LLM-readable manifest
1037
- --schema Show JSON Schema for a command
1051
+ --schema Show JSON Schema for command
1038
1052
  --token-count Print token count of output (instead of output)
1039
1053
  --token-limit <n> Limit output to n tokens
1040
1054
  --token-offset <n> Skip first n tokens of output
1041
- --verbose Show full output envelope
1042
1055
  "
1043
1056
  `)
1044
1057
  })
@@ -1053,18 +1066,18 @@ describe('help', () => {
1053
1066
  Options:
1054
1067
  --limit, -l <number> Max results (default: 20)
1055
1068
  --sort, -s <name|created|updated> Sort field (default: name)
1056
- --archived <boolean> Include archived (default: false)
1069
+ --archived Include archived
1057
1070
 
1058
1071
  Global Options:
1059
1072
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1060
1073
  --format <toon|json|yaml|md|jsonl> Output format
1074
+ --full-output Show full output envelope
1061
1075
  --help Show help
1062
1076
  --llms, --llms-full Print LLM-readable manifest
1063
- --schema Show JSON Schema for a command
1077
+ --schema Show JSON Schema for command
1064
1078
  --token-count Print token count of output (instead of output)
1065
1079
  --token-limit <n> Limit output to n tokens
1066
1080
  --token-offset <n> Skip first n tokens of output
1067
- --verbose Show full output envelope
1068
1081
  "
1069
1082
  `)
1070
1083
  })
@@ -1081,7 +1094,7 @@ describe('help', () => {
1081
1094
 
1082
1095
  Options:
1083
1096
  --branch, -b <string> Branch to deploy (default: main)
1084
- --dry-run <boolean> Dry run mode (default: false)
1097
+ --dry-run Dry run mode
1085
1098
 
1086
1099
  Examples:
1087
1100
  app project deploy create staging # Deploy staging from main
@@ -1090,13 +1103,13 @@ describe('help', () => {
1090
1103
  Global Options:
1091
1104
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1092
1105
  --format <toon|json|yaml|md|jsonl> Output format
1106
+ --full-output Show full output envelope
1093
1107
  --help Show help
1094
1108
  --llms, --llms-full Print LLM-readable manifest
1095
- --schema Show JSON Schema for a command
1109
+ --schema Show JSON Schema for command
1096
1110
  --token-count Print token count of output (instead of output)
1097
1111
  --token-limit <n> Limit output to n tokens
1098
1112
  --token-offset <n> Skip first n tokens of output
1099
- --verbose Show full output envelope
1100
1113
  "
1101
1114
  `)
1102
1115
  })
@@ -1589,8 +1602,8 @@ describe('typegen', () => {
1589
1602
  'auth login': { args: {}; options: { hostname: string; web: boolean; scopes: string[] } }
1590
1603
  'auth logout': { args: {}; options: {} }
1591
1604
  'auth status': { args: {}; options: {} }
1592
- 'config': { args: { key: string }; options: {} }
1593
- 'echo': { args: { message: string; repeat: number }; options: { upper: boolean; prefix: string } }
1605
+ 'config': { args: { key?: string }; options: {} }
1606
+ 'echo': { args: { message: string; repeat?: number }; options: { upper: boolean; prefix: string } }
1594
1607
  'explode': { args: {}; options: {} }
1595
1608
  'explode-clac': { args: {}; options: {} }
1596
1609
  'noop': { args: {}; options: {} }
@@ -1738,22 +1751,22 @@ describe('root command with subcommands', () => {
1738
1751
  info Show info
1739
1752
  version Show version
1740
1753
 
1741
- Built-in Commands:
1754
+ Integrations:
1742
1755
  completions Generate shell completion script
1743
- mcp add Register as an MCP server
1744
- skills add Sync skill files to your agent
1756
+ mcp add Register as MCP server
1757
+ skills Sync skill files to agents (add, list)
1745
1758
 
1746
1759
  Global Options:
1747
1760
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1748
1761
  --format <toon|json|yaml|md|jsonl> Output format
1762
+ --full-output Show full output envelope
1749
1763
  --help Show help
1750
1764
  --llms, --llms-full Print LLM-readable manifest
1751
1765
  --mcp Start as MCP stdio server
1752
- --schema Show JSON Schema for a command
1766
+ --schema Show JSON Schema for command
1753
1767
  --token-count Print token count of output (instead of output)
1754
1768
  --token-limit <n> Limit output to n tokens
1755
1769
  --token-offset <n> Skip first n tokens of output
1756
- --verbose Show full output envelope
1757
1770
  --version Show version
1758
1771
  "
1759
1772
  `)
@@ -1786,7 +1799,7 @@ describe('edge cases', () => {
1786
1799
  "description": "View "Alpha"",
1787
1800
  },
1788
1801
  ],
1789
- "description": "Suggested commands:",
1802
+ "description": "Suggested command:",
1790
1803
  },
1791
1804
  "items": [
1792
1805
  {
@@ -1864,7 +1877,7 @@ describe('edge cases', () => {
1864
1877
  'prod',
1865
1878
  '--branch',
1866
1879
  'release',
1867
- '--verbose',
1880
+ '--full-output',
1868
1881
  ])
1869
1882
  expect(json(output)).toMatchInlineSnapshot(`
1870
1883
  {
@@ -1893,7 +1906,7 @@ describe('env', () => {
1893
1906
  test('env vars passed to handler', async () => {
1894
1907
  const { output } = await serve(
1895
1908
  createApp(),
1896
- ['auth', 'login', '--verbose', '--format', 'json'],
1909
+ ['auth', 'login', '--full-output', '--format', 'json'],
1897
1910
  { env: { AUTH_HOST: 'custom.example.com' } },
1898
1911
  )
1899
1912
  expect(json(output).data.hostname).toBe('custom.example.com')
@@ -1902,7 +1915,7 @@ describe('env', () => {
1902
1915
  test('env defaults applied when var is unset', async () => {
1903
1916
  const { output } = await serve(
1904
1917
  createApp(),
1905
- ['auth', 'login', '--verbose', '--format', 'json'],
1918
+ ['auth', 'login', '--full-output', '--format', 'json'],
1906
1919
  { env: {} },
1907
1920
  )
1908
1921
  expect(json(output).data.hostname).toBe('api.example.com')
@@ -1917,19 +1930,19 @@ describe('env', () => {
1917
1930
 
1918
1931
  Options:
1919
1932
  --hostname, -h <string> API hostname (default: api.example.com)
1920
- --web, -w <boolean> Open browser (default: false)
1933
+ --web, -w Open browser
1921
1934
  --scopes <array> OAuth scopes
1922
1935
 
1923
1936
  Global Options:
1924
1937
  --filter-output <keys> Filter output by key paths (e.g. foo,bar.baz,a[0,3])
1925
1938
  --format <toon|json|yaml|md|jsonl> Output format
1939
+ --full-output Show full output envelope
1926
1940
  --help Show help
1927
1941
  --llms, --llms-full Print LLM-readable manifest
1928
- --schema Show JSON Schema for a command
1942
+ --schema Show JSON Schema for command
1929
1943
  --token-count Print token count of output (instead of output)
1930
1944
  --token-limit <n> Limit output to n tokens
1931
1945
  --token-offset <n> Skip first n tokens of output
1932
- --verbose Show full output envelope
1933
1946
 
1934
1947
  Environment Variables:
1935
1948
  AUTH_TOKEN Pre-existing auth token
@@ -1974,13 +1987,15 @@ describe('skills staleness', () => {
1974
1987
 
1975
1988
  afterEach(() => {
1976
1989
  stderrSpy.mockRestore()
1990
+ __mockSkillsHash = undefined
1977
1991
  })
1978
1992
 
1979
- test('warns when running a command with stale skills', async () => {
1993
+ test('includes skills CTA when stale', async () => {
1980
1994
  __mockSkillsHash = '0000000000000000'
1981
1995
  const { output } = await serve(createApp(), ['ping'])
1982
1996
  expect(output).toContain('pong: true')
1983
- expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('Skills are out of date.'))
1997
+ expect(output).toContain('Skills are out of date:')
1998
+ expect(output).toContain('skills add')
1984
1999
  })
1985
2000
 
1986
2001
  test('no warning when skills hash matches', async () => {
@@ -1991,20 +2006,20 @@ describe('skills staleness', () => {
1991
2006
 
1992
2007
  const { output } = await serve(cli, ['ping'])
1993
2008
  expect(output).toContain('pong: true')
1994
- expect(stderrSpy).not.toHaveBeenCalled()
2009
+ expect(output).not.toContain('Skills are out of date')
1995
2010
  })
1996
2011
 
1997
2012
  test('no warning on first use (no hash stored)', async () => {
1998
2013
  __mockSkillsHash = undefined
1999
2014
  const { output } = await serve(createApp(), ['ping'])
2000
2015
  expect(output).toContain('pong: true')
2001
- expect(stderrSpy).not.toHaveBeenCalled()
2016
+ expect(output).not.toContain('Skills are out of date')
2002
2017
  })
2003
2018
 
2004
2019
  test('no warning for --llms', async () => {
2005
2020
  __mockSkillsHash = '0000000000000000'
2006
- await serve(createApp(), ['--llms'])
2007
- expect(stderrSpy).not.toHaveBeenCalled()
2021
+ const { output } = await serve(createApp(), ['--llms'])
2022
+ expect(output).not.toContain('Skills are out of date')
2008
2023
  })
2009
2024
 
2010
2025
  test('no warning for --mcp', async () => {
@@ -2034,9 +2049,9 @@ describe('middleware', () => {
2034
2049
  `)
2035
2050
  })
2036
2051
 
2037
- test('vars: verbose envelope includes var data', async () => {
2052
+ test('vars: full-output envelope includes var data', async () => {
2038
2053
  const { cli } = createMiddlewareApp()
2039
- const { output } = await serve(cli, ['whoami', '--verbose', '--format', 'json'])
2054
+ const { output } = await serve(cli, ['whoami', '--full-output', '--format', 'json'])
2040
2055
  const parsed = json(output)
2041
2056
  expect(parsed.data.user).toBe('alice')
2042
2057
  expect(parsed.data.requestId).toBe('req-default')
@@ -2204,7 +2219,7 @@ describe('fetch gateway', () => {
2204
2219
  })
2205
2220
 
2206
2221
  test('query params from --key value', async () => {
2207
- const { output } = await serve(createApp(), ['api', 'users', '--limit', '5'])
2222
+ await serve(createApp(), ['api', 'users', '--limit', '5'])
2208
2223
  const { output: jsonOut } = await serve(createApp(), [
2209
2224
  'api',
2210
2225
  'users',
@@ -2273,8 +2288,14 @@ describe('fetch gateway', () => {
2273
2288
  expect(json(output)).toEqual({ ok: true })
2274
2289
  })
2275
2290
 
2276
- test('--verbose wraps in envelope', async () => {
2277
- const { output } = await serve(createApp(), ['api', 'health', '--verbose', '--format', 'json'])
2291
+ test('--full-output wraps in envelope', async () => {
2292
+ const { output } = await serve(createApp(), [
2293
+ 'api',
2294
+ 'health',
2295
+ '--full-output',
2296
+ '--format',
2297
+ 'json',
2298
+ ])
2278
2299
  const parsed = json(output)
2279
2300
  expect(parsed.ok).toBe(true)
2280
2301
  expect(parsed.data).toEqual({ ok: true })
@@ -2441,6 +2462,18 @@ describe('fetch api', () => {
2441
2462
  },
2442
2463
  "meta": {
2443
2464
  "command": "project create",
2465
+ "cta": {
2466
+ "commands": [
2467
+ {
2468
+ "command": "app project get p-new",
2469
+ "description": "View "MyProject"",
2470
+ },
2471
+ {
2472
+ "command": "app project list",
2473
+ },
2474
+ ],
2475
+ "description": "Suggested commands:",
2476
+ },
2444
2477
  "duration": "<stripped>",
2445
2478
  },
2446
2479
  "ok": true,
@@ -2524,21 +2557,22 @@ describe('fetch api', () => {
2524
2557
  const cli = createApp()
2525
2558
  expect(await fetchJson(cli, new Request('http://localhost/explode-clac')))
2526
2559
  .toMatchInlineSnapshot(`
2527
- {
2528
- "body": {
2529
- "error": {
2530
- "code": "QUOTA_EXCEEDED",
2531
- "message": "Rate limit exceeded",
2532
- },
2533
- "meta": {
2534
- "command": "explode-clac",
2535
- "duration": "<stripped>",
2560
+ {
2561
+ "body": {
2562
+ "error": {
2563
+ "code": "QUOTA_EXCEEDED",
2564
+ "message": "Rate limit exceeded",
2565
+ "retryable": true,
2566
+ },
2567
+ "meta": {
2568
+ "command": "explode-clac",
2569
+ "duration": "<stripped>",
2570
+ },
2571
+ "ok": false,
2536
2572
  },
2537
- "ok": false,
2538
- },
2539
- "status": 500,
2540
- }
2541
- `)
2573
+ "status": 500,
2574
+ }
2575
+ `)
2542
2576
  })
2543
2577
 
2544
2578
  test('validation error → 400', async () => {
@@ -2832,6 +2866,47 @@ describe('fetch api', () => {
2832
2866
  `)
2833
2867
  })
2834
2868
 
2869
+ test('tools/call with no-args command', async () => {
2870
+ const cli = createApp()
2871
+ const sessionId = await initSession(cli)
2872
+ const res = await mcpRequest(
2873
+ cli,
2874
+ {
2875
+ jsonrpc: '2.0',
2876
+ id: 6,
2877
+ method: 'tools/call',
2878
+ params: { name: 'ping', arguments: {} },
2879
+ },
2880
+ sessionId,
2881
+ )
2882
+ expect(res.status).toBe(200)
2883
+ const body = await res.json()
2884
+ expect(JSON.parse(body.result.content[0].text)).toMatchInlineSnapshot(`
2885
+ {
2886
+ "pong": true,
2887
+ }
2888
+ `)
2889
+ })
2890
+
2891
+ test('tools/call with streaming command', async () => {
2892
+ const cli = createApp()
2893
+ const sessionId = await initSession(cli)
2894
+ const res = await mcpRequest(
2895
+ cli,
2896
+ {
2897
+ jsonrpc: '2.0',
2898
+ id: 7,
2899
+ method: 'tools/call',
2900
+ params: { name: 'stream', arguments: {} },
2901
+ },
2902
+ sessionId,
2903
+ )
2904
+ expect(res.status).toBe(200)
2905
+ const body = await res.json()
2906
+ const chunks = JSON.parse(body.result.content[0].text)
2907
+ expect(chunks).toEqual([{ content: 'hello' }, { content: 'world' }])
2908
+ })
2909
+
2835
2910
  test('non-/mcp paths still work alongside MCP', async () => {
2836
2911
  const cli = createApp()
2837
2912
  // Initialize MCP first