toolcraft-openapi 0.0.26 → 0.0.33

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 (218) hide show
  1. package/dist/auth/bearer-token-auth.js +1 -1
  2. package/dist/bin/generate.d.ts +9 -1
  3. package/dist/bin/generate.js +65 -9
  4. package/dist/error-codes.d.ts +1 -0
  5. package/dist/error-codes.js +6 -0
  6. package/dist/generate.d.ts +20 -3
  7. package/dist/generate.js +604 -144
  8. package/dist/http.d.ts +17 -1
  9. package/dist/http.js +113 -7
  10. package/dist/index.d.ts +6 -1
  11. package/dist/index.js +3 -0
  12. package/dist/inspect-source.d.ts +10 -0
  13. package/dist/inspect-source.js +14 -0
  14. package/dist/inspect.d.ts +22 -0
  15. package/dist/inspect.js +171 -0
  16. package/dist/interpreter.js +6 -2
  17. package/dist/mock/fetch.js +28 -12
  18. package/dist/mock/forgeyard.d.ts +4 -0
  19. package/dist/mock/forgeyard.js +452 -0
  20. package/dist/mock.d.ts +1 -0
  21. package/dist/mock.js +1 -0
  22. package/dist/naming.d.ts +4 -1
  23. package/dist/naming.js +93 -7
  24. package/dist/network-error.js +29 -12
  25. package/dist/normalize-swagger.d.ts +2 -0
  26. package/dist/normalize-swagger.js +201 -0
  27. package/dist/render-inspection.d.ts +2 -0
  28. package/dist/render-inspection.js +130 -0
  29. package/dist/request-shape.d.ts +4 -0
  30. package/dist/request-shape.js +1 -0
  31. package/dist/runtime.js +7 -1
  32. package/dist/spec-source.js +7 -4
  33. package/node_modules/auth-store/dist/create-secret-store.js +6 -1
  34. package/node_modules/auth-store/dist/encrypted-file-store.d.ts +1 -1
  35. package/node_modules/auth-store/dist/encrypted-file-store.js +32 -28
  36. package/node_modules/auth-store/dist/error-codes.d.ts +1 -0
  37. package/node_modules/auth-store/dist/error-codes.js +5 -0
  38. package/node_modules/auth-store/dist/keychain-store.js +23 -8
  39. package/node_modules/toolcraft-design/README.md +148 -0
  40. package/node_modules/toolcraft-design/dist/components/catalog.d.ts +26 -0
  41. package/node_modules/toolcraft-design/dist/components/catalog.js +94 -0
  42. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/index.d.ts +2 -0
  43. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/index.js +1 -0
  44. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/footer.js +1 -2
  45. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/output-pane.js +16 -19
  46. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/stats-pane.js +1 -15
  47. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/demo.js +2 -2
  48. package/node_modules/toolcraft-design/dist/explorer/theme.js +29 -0
  49. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/index.d.ts +7 -1
  50. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/index.js +4 -0
  51. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/theme-detect.d.ts +1 -0
  52. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/theme-detect.js +28 -6
  53. package/node_modules/toolcraft-design/dist/internal/theme-state.d.ts +12 -0
  54. package/node_modules/toolcraft-design/dist/internal/theme-state.js +35 -0
  55. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/theme.d.ts +1 -1
  56. package/node_modules/toolcraft-design/dist/prompts/theme.js +15 -0
  57. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/frontmatter.js +50 -0
  58. package/node_modules/toolcraft-design/dist/tokens/brand.d.ts +5 -0
  59. package/node_modules/toolcraft-design/dist/tokens/brand.js +5 -0
  60. package/node_modules/toolcraft-design/dist/tokens/colors.d.ts +37 -0
  61. package/node_modules/toolcraft-design/dist/tokens/colors.js +85 -0
  62. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/index.d.ts +2 -0
  63. package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/index.js +1 -0
  64. package/node_modules/toolcraft-design/package.json +42 -0
  65. package/package.json +8 -6
  66. package/node_modules/@poe-code/design-system/dist/explorer/theme.js +0 -97
  67. package/node_modules/@poe-code/design-system/dist/prompts/theme.js +0 -12
  68. package/node_modules/@poe-code/design-system/dist/tokens/colors.d.ts +0 -35
  69. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +0 -34
  70. package/node_modules/@poe-code/design-system/package.json +0 -29
  71. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/components.d.ts +0 -0
  72. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/components.js +0 -0
  73. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/index.d.ts +0 -0
  74. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/index.js +0 -0
  75. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/writer.d.ts +0 -0
  76. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/acp/writer.js +0 -0
  77. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/browser.d.ts +0 -0
  78. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/browser.js +0 -0
  79. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/color.d.ts +0 -0
  80. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/color.js +0 -0
  81. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/command-errors.d.ts +0 -0
  82. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/command-errors.js +0 -0
  83. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/detail-card.d.ts +0 -0
  84. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/detail-card.js +0 -0
  85. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/help-formatter-plain.d.ts +0 -0
  86. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/help-formatter-plain.js +0 -0
  87. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/help-formatter.d.ts +0 -0
  88. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/help-formatter.js +0 -0
  89. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/logger.d.ts +0 -0
  90. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/logger.js +0 -0
  91. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/symbols.d.ts +0 -0
  92. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/symbols.js +0 -0
  93. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/table.d.ts +0 -0
  94. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/table.js +0 -0
  95. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/template.d.ts +0 -0
  96. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/template.js +0 -0
  97. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/text.d.ts +0 -0
  98. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/components/text.js +0 -0
  99. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/ansi.d.ts +0 -0
  100. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/ansi.js +0 -0
  101. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/buffer.d.ts +0 -0
  102. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/buffer.js +0 -0
  103. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/border.d.ts +0 -0
  104. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/border.js +0 -0
  105. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/footer.d.ts +0 -0
  106. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/output-pane.d.ts +0 -0
  107. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/components/stats-pane.d.ts +0 -0
  108. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/dashboard.d.ts +0 -0
  109. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/dashboard.js +0 -0
  110. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/demo.d.ts +0 -0
  111. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/demo.js +0 -0
  112. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/index.d.ts +0 -0
  113. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/index.js +0 -0
  114. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/keymap.d.ts +0 -0
  115. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/keymap.js +0 -0
  116. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/layout.d.ts +0 -0
  117. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/layout.js +0 -0
  118. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/should-use-dashboard.d.ts +0 -0
  119. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/should-use-dashboard.js +0 -0
  120. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/snapshot.d.ts +0 -0
  121. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/snapshot.js +0 -0
  122. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/store.d.ts +0 -0
  123. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/store.js +0 -0
  124. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/terminal-width.d.ts +0 -0
  125. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/terminal-width.js +0 -0
  126. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/terminal.d.ts +0 -0
  127. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/terminal.js +0 -0
  128. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/types.d.ts +0 -0
  129. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/dashboard/types.js +0 -0
  130. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/actions.d.ts +0 -0
  131. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/actions.js +0 -0
  132. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/demo.d.ts +0 -0
  133. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/events.d.ts +0 -0
  134. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/events.js +0 -0
  135. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/filter.d.ts +0 -0
  136. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/filter.js +0 -0
  137. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/index.d.ts +0 -0
  138. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/index.js +0 -0
  139. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/jobs.d.ts +0 -0
  140. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/jobs.js +0 -0
  141. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/keymap.d.ts +0 -0
  142. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/keymap.js +0 -0
  143. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/layout.d.ts +0 -0
  144. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/layout.js +0 -0
  145. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/reducer.d.ts +0 -0
  146. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/reducer.js +0 -0
  147. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/detail.d.ts +0 -0
  148. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/detail.js +0 -0
  149. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/footer.d.ts +0 -0
  150. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/footer.js +0 -0
  151. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/header.d.ts +0 -0
  152. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/header.js +0 -0
  153. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/index.d.ts +0 -0
  154. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/index.js +0 -0
  155. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/list.d.ts +0 -0
  156. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/list.js +0 -0
  157. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/modal.d.ts +0 -0
  158. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/modal.js +0 -0
  159. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/test-fixtures.d.ts +0 -0
  160. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/test-fixtures.js +0 -0
  161. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/text.d.ts +0 -0
  162. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/render/text.js +0 -0
  163. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/runtime.d.ts +0 -0
  164. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/runtime.js +0 -0
  165. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/runtime.test-helpers.d.ts +0 -0
  166. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/runtime.test-helpers.js +0 -0
  167. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/state.d.ts +0 -0
  168. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/state.js +0 -0
  169. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/explorer/theme.d.ts +0 -0
  170. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/color-support.d.ts +0 -0
  171. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/color-support.js +0 -0
  172. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/output-format.d.ts +0 -0
  173. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/output-format.js +0 -0
  174. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/strip-ansi.d.ts +0 -0
  175. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/internal/strip-ansi.js +0 -0
  176. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/index.d.ts +0 -0
  177. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/index.js +0 -0
  178. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/cancel.d.ts +0 -0
  179. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/cancel.js +0 -0
  180. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/intro.d.ts +0 -0
  181. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/intro.js +0 -0
  182. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/log.d.ts +0 -0
  183. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/log.js +0 -0
  184. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/note.d.ts +0 -0
  185. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/note.js +0 -0
  186. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/outro.d.ts +0 -0
  187. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/outro.js +0 -0
  188. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/spinner.d.ts +0 -0
  189. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/prompts/primitives/spinner.js +0 -0
  190. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/index.d.ts +0 -0
  191. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/index.js +0 -0
  192. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/menu.d.ts +0 -0
  193. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/menu.js +0 -0
  194. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/spinner.d.ts +0 -0
  195. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/static/spinner.js +0 -0
  196. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/ast.d.ts +0 -0
  197. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/ast.js +0 -0
  198. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/demo-content.d.ts +0 -0
  199. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/demo-content.js +0 -0
  200. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/index.d.ts +0 -0
  201. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/index.js +0 -0
  202. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/block.d.ts +0 -0
  203. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/block.js +0 -0
  204. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/frontmatter.d.ts +0 -0
  205. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/inline.d.ts +0 -0
  206. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser/inline.js +0 -0
  207. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser.d.ts +0 -0
  208. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/parser.js +0 -0
  209. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/renderer.d.ts +0 -0
  210. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/renderer.js +0 -0
  211. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/testing/theme-render-fixture.d.ts +0 -0
  212. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/terminal-markdown/testing/theme-render-fixture.js +0 -0
  213. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/spacing.d.ts +0 -0
  214. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/spacing.js +0 -0
  215. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/typography.d.ts +0 -0
  216. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/typography.js +0 -0
  217. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/widths.d.ts +0 -0
  218. /package/node_modules/{@poe-code/design-system → toolcraft-design}/dist/tokens/widths.js +0 -0
package/dist/generate.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { ToolcraftBugError, UserError } from "toolcraft";
2
- import { METHOD_DEFAULTS, deriveNoun, deriveVerb, isIdentifierName, normalizeParamName, toCamelCase, toPascalCase } from "./naming.js";
2
+ import { METHOD_DEFAULTS, deriveDisambiguatedVerb, deriveNoun, derivePathDisambiguatedVerb, deriveVerb, isIdentifierName, normalizeNoun, normalizeParamName, toCamelCase, toPascalCase } from "./naming.js";
3
3
  import { groupByNoun } from "./group-by-noun.js";
4
4
  import { renderPreflightBlock, renderRequestShape } from "./interpreter.js";
5
- const HTTP_METHOD_ORDER = ["get", "post", "put", "patch", "delete"];
6
- const UNSUPPORTED_HTTP_METHODS = ["head", "options", "trace"];
7
- const METHODS_WITHOUT_REQUEST_BODY = new Set(["get"]);
5
+ import { normalizeOpenApiDocument } from "./normalize-swagger.js";
6
+ const HTTP_METHOD_ORDER = ["get", "post", "put", "patch", "delete", "head", "options"];
7
+ const UNSUPPORTED_HTTP_METHODS = ["trace"];
8
+ const METHODS_WITHOUT_REQUEST_BODY = new Set(["head", "options"]);
8
9
  const SCHEMA_TYPE_TO_KIND = {
9
10
  boolean: { kind: "boolean" },
10
11
  integer: { kind: "number", jsonType: "integer" },
@@ -13,6 +14,7 @@ const SCHEMA_TYPE_TO_KIND = {
13
14
  };
14
15
  const NULL_HELPER_SUPPORT = {
15
16
  body: { array: true, scalar: true },
17
+ header: { array: false, scalar: false },
16
18
  path: { array: false, scalar: false },
17
19
  // Query null already serializes as the existing empty-string wire encoding, so v1
18
20
  // keeps null-helper flags body-only until a real query-null convention lands.
@@ -108,7 +110,10 @@ const SCHEMA_OPTION_SOURCES = [
108
110
  }
109
111
  ];
110
112
  export function generate(document, options) {
111
- const commands = collectGeneratedCommands(document);
113
+ const normalizedDocument = normalizeOpenApiDocument(document);
114
+ const commands = collectGeneratedCommands(normalizedDocument);
115
+ const brand = options.brand ?? "blue";
116
+ const label = normalizedDocument.info?.title ?? "Toolcraft";
112
117
  return [
113
118
  ...commands.map((command) => ({
114
119
  path: command.filePath,
@@ -117,18 +122,30 @@ export function generate(document, options) {
117
122
  ...command
118
123
  })
119
124
  })),
120
- createIndexFile(commands)
125
+ createIndexFile(commands),
126
+ createCliFile({ brand, label })
121
127
  ];
122
128
  }
123
129
  export function collectGeneratedCommands(document) {
124
- const paths = document.paths;
130
+ const normalizedDocument = normalizeOpenApiDocument(document);
131
+ const paths = normalizedDocument.paths;
125
132
  if (paths === undefined) {
126
133
  throw new UserError('OpenAPI document must define a top-level "paths" object.');
127
134
  }
128
- const commands = collectOperations(paths).map((entry) => createGeneratedCommand(document, entry));
135
+ const commands = collectOperations(paths).map((entry) => createGeneratedCommand(normalizedDocument, entry));
136
+ disambiguateCommandPaths(commands);
129
137
  assertUniqueCommandPaths(commands);
130
138
  return commands.slice().sort((left, right) => compareGeneratedCommandPaths(left, right));
131
139
  }
140
+ export function collectGeneratedCommand(document, path, method) {
141
+ const normalizedDocument = normalizeOpenApiDocument(document);
142
+ const pathItem = normalizedDocument.paths?.[path];
143
+ const operation = pathItem?.[method];
144
+ if (pathItem === undefined || operation === undefined) {
145
+ throw new ToolcraftBugError(`Cannot generate missing OpenAPI operation ${method.toUpperCase()} ${path}.`);
146
+ }
147
+ return createGeneratedCommand(normalizedDocument, { method, path, operation, pathItem });
148
+ }
132
149
  function collectOperations(paths) {
133
150
  return Object.entries(paths)
134
151
  .sort(([left], [right]) => left.localeCompare(right))
@@ -138,7 +155,7 @@ function collectOperations(paths) {
138
155
  }
139
156
  assertSupportedHttpMethods(path, pathItem);
140
157
  return HTTP_METHOD_ORDER.flatMap((method) => {
141
- const operation = pathItem[method];
158
+ const operation = getOwnPathItemValue(pathItem, method);
142
159
  if (operation === undefined) {
143
160
  return [];
144
161
  }
@@ -146,15 +163,20 @@ function collectOperations(paths) {
146
163
  });
147
164
  });
148
165
  }
166
+ function getOwnPathItemValue(pathItem, key) {
167
+ return Object.prototype.hasOwnProperty.call(pathItem, key)
168
+ ? pathItem[key]
169
+ : undefined;
170
+ }
149
171
  function createGeneratedCommand(document, entry) {
150
172
  const operation = expectOperation(document, entry.operation, entry.method, entry.path);
151
173
  const operationId = operation.operationId ?? `${entry.method.toUpperCase()} ${entry.path}`;
152
- assertSupportedOperationMetadata(operation, operationId);
153
- assertSupportedSuccessResponses(document, operation, operationId);
154
- const noun = deriveNoun(operation, entry.path, operationId);
155
- assertValidGeneratedNoun(operationId, noun);
174
+ const operationBaseUrl = resolveOperationBaseUrl(operation, operationId);
175
+ const auth = getOperationAuthMode(document, operation, operationId);
176
+ const response = resolveSuccessResponse(document, operation, operationId);
177
+ const noun = createSafeGeneratedNoun(deriveNoun(operation, entry.path, operationId));
156
178
  const verb = deriveVerb(entry.method, entry.path, operation, operationId, noun);
157
- const collected = collectParams(document, entry, operation, operationId);
179
+ const collected = collectParams(document, entry, operation, operationId, auth);
158
180
  const methodDefaults = METHOD_DEFAULTS[entry.method];
159
181
  const exportName = `${toCamelCase(noun)}${toPascalCase(verb)}Command`;
160
182
  const filePath = `${noun}/${verb}.ts`;
@@ -167,7 +189,13 @@ function createGeneratedCommand(document, entry) {
167
189
  description: mergeCommandDescriptions(operation.description ?? operation.summary, collected.requestBodyDescription),
168
190
  method: entry.method.toUpperCase(),
169
191
  path: entry.path,
170
- auth: getOperationAuthMode(document, operation, operationId),
192
+ auth,
193
+ responseMode: response.mode,
194
+ accept: response.accept,
195
+ baseUrl: operationBaseUrl,
196
+ bodyMode: collected.bodyMode,
197
+ contentType: collected.contentType,
198
+ multipartBinaryFields: collected.multipartBinaryFields,
171
199
  confirm: methodDefaults?.confirm === true,
172
200
  params: collected.params,
173
201
  paramsSchemaOptions: collected.paramsSchemaOptions,
@@ -177,15 +205,26 @@ function createGeneratedCommand(document, entry) {
177
205
  optionalSections: collected.optionalSections
178
206
  };
179
207
  }
180
- function assertSupportedOperationMetadata(operation, operationId) {
181
- if (operation.servers !== undefined) {
182
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported per-operation servers. Configure the client baseUrl instead.`);
208
+ function resolveOperationBaseUrl(operation, operationId) {
209
+ if (operation.servers === undefined) {
210
+ return undefined;
211
+ }
212
+ const [server] = operation.servers;
213
+ if (operation.servers.length !== 1 || server === undefined || server.url.includes("{")) {
214
+ throw new UserError(`Operation ${JSON.stringify(operationId)} must define exactly one fixed per-operation server URL in v1.`);
215
+ }
216
+ try {
217
+ return new URL(server.url).toString().replace(/\/$/, "");
218
+ }
219
+ catch {
220
+ throw new UserError(`Operation ${JSON.stringify(operationId)} defines invalid per-operation server URL ${JSON.stringify(server.url)}.`);
183
221
  }
184
222
  }
185
- function collectParams(document, entry, operation, operationId) {
186
- const operationParams = collectOperationParameters(document, entry.path, entry.pathItem.parameters ?? [], operation.parameters ?? [], operationId);
223
+ function collectParams(document, entry, operation, operationId, auth) {
224
+ const operationParams = collectOperationParameters(document, entry.path, entry.pathItem.parameters ?? [], operation.parameters ?? [], operationId, auth);
187
225
  const requestBodyParams = collectRequestBodyParams(document, operation, operationId, entry.method);
188
- const params = [...operationParams.params, ...requestBodyParams.params, ...TRANSPORT_PARAMS];
226
+ const qualifiedRequestBodyParams = qualifyBodyParamCollisions(requestBodyParams, new Set([...operationParams.params, ...TRANSPORT_PARAMS].map((param) => param.paramName)));
227
+ const params = [...operationParams.params, ...qualifiedRequestBodyParams.params, ...TRANSPORT_PARAMS];
189
228
  const deduped = new Map();
190
229
  for (const param of params) {
191
230
  const existing = deduped.get(param.paramName);
@@ -196,18 +235,75 @@ function collectParams(document, entry, operation, operationId) {
196
235
  }
197
236
  return {
198
237
  params: [...deduped.values()],
199
- paramsSchemaOptions: requestBodyParams.paramsSchemaOptions,
200
- preflightBlocks: [...operationParams.preflightBlocks, ...requestBodyParams.preflightBlocks],
201
- requestFields: [...operationParams.requestFields, ...requestBodyParams.requestFields],
202
- sectionRenders: { ...operationParams.sectionRenders, ...requestBodyParams.sectionRenders },
238
+ paramsSchemaOptions: qualifiedRequestBodyParams.paramsSchemaOptions,
239
+ preflightBlocks: [...operationParams.preflightBlocks, ...qualifiedRequestBodyParams.preflightBlocks],
240
+ requestFields: [...operationParams.requestFields, ...qualifiedRequestBodyParams.requestFields],
241
+ sectionRenders: { ...operationParams.sectionRenders, ...qualifiedRequestBodyParams.sectionRenders },
203
242
  optionalSections: new Set([
204
243
  ...operationParams.optionalSections,
205
- ...requestBodyParams.optionalSections
244
+ ...qualifiedRequestBodyParams.optionalSections
206
245
  ]),
207
- requestBodyDescription: requestBodyParams.requestBodyDescription
246
+ requestBodyDescription: qualifiedRequestBodyParams.requestBodyDescription,
247
+ bodyMode: qualifiedRequestBodyParams.bodyMode,
248
+ contentType: qualifiedRequestBodyParams.contentType,
249
+ multipartBinaryFields: qualifiedRequestBodyParams.multipartBinaryFields
208
250
  };
209
251
  }
210
- function collectOperationParameters(document, path, pathItemParameters, operationParameters, operationId) {
252
+ function qualifyBodyParamCollisions(collected, reservedNames) {
253
+ const renames = new Map();
254
+ const usedNames = new Set(reservedNames);
255
+ for (const param of collected.params) {
256
+ let paramName = param.paramName;
257
+ if (usedNames.has(paramName)) {
258
+ const baseName = `body${toPascalCase(paramName)}`;
259
+ paramName = baseName;
260
+ let suffix = 2;
261
+ while (usedNames.has(paramName)) {
262
+ paramName = `${baseName}${suffix}`;
263
+ suffix += 1;
264
+ }
265
+ renames.set(param.paramName, paramName);
266
+ }
267
+ usedNames.add(paramName);
268
+ }
269
+ if (renames.size === 0) {
270
+ return collected;
271
+ }
272
+ const renameReference = (reference) => reference.kind === "param" && renames.has(reference.paramName)
273
+ ? { ...reference, paramName: renames.get(reference.paramName) }
274
+ : reference;
275
+ return {
276
+ ...collected,
277
+ params: collected.params.map((param) => ({
278
+ ...param,
279
+ paramName: renames.get(param.paramName) ?? param.paramName
280
+ })),
281
+ preflightBlocks: collected.preflightBlocks.map((block) => block.kind === "scalar-null"
282
+ ? {
283
+ ...block,
284
+ paramName: renames.get(block.paramName) ?? block.paramName,
285
+ nullParamName: renames.get(block.nullParamName) ?? block.nullParamName
286
+ }
287
+ : {
288
+ ...block,
289
+ paramName: renames.get(block.paramName) ?? block.paramName,
290
+ jsonParamName: renames.get(block.jsonParamName) ?? block.jsonParamName,
291
+ ...(block.nullParamName === undefined
292
+ ? {}
293
+ : { nullParamName: renames.get(block.nullParamName) ?? block.nullParamName })
294
+ }),
295
+ requestFields: collected.requestFields.map((field) => ({
296
+ ...field,
297
+ omitWhenUndefinedReference: renameReference(field.omitWhenUndefinedReference),
298
+ value: field.value.kind === "reference"
299
+ ? { ...field.value, reference: renameReference(field.value.reference) }
300
+ : field.value.kind === "queryArray"
301
+ ? { ...field.value, reference: renameReference(field.value.reference) }
302
+ : field.value
303
+ }))
304
+ };
305
+ }
306
+ function collectOperationParameters(document, path, pathItemParameters, operationParameters, operationId, auth) {
211
307
  const merged = new Map();
212
308
  for (const parameter of pathItemParameters) {
213
309
  const resolved = expectParameter(document, parameter, operationId);
@@ -217,12 +313,28 @@ function collectOperationParameters(document, path, pathItemParameters, operatio
217
313
  const resolved = expectParameter(document, parameter, operationId);
218
314
  merged.set(`${resolved.in}:${resolved.name}`, resolved);
219
315
  }
316
+ promoteQueryDeclaredPathParameters(path, merged);
220
317
  assertPathTemplateParameters(path, merged, operationId);
221
318
  const params = [];
222
319
  const preflightBlocks = [];
223
320
  const requestFields = [];
321
+ const usedParamNames = new Set();
224
322
  for (const parameter of merged.values()) {
225
- const generated = createGeneratedParameter(document, parameter, operationId);
323
+ if (parameter.in === "header" &&
324
+ ["accept", "content-type"].includes(parameter.name.toLowerCase())) {
325
+ continue;
326
+ }
327
+ const paramName = usedParamNames.has(parameter.name)
328
+ ? `${parameter.in}${toPascalCase(parameter.name)}`
329
+ : parameter.name;
330
+ const generated = createGeneratedParameter(document, paramName === parameter.name ? parameter : { ...parameter, name: paramName }, operationId, auth);
331
+ generated.requestField.wireName = generated.requestField.wireName.endsWith("[]")
332
+ ? `${parameter.name}[]`
333
+ : parameter.name;
334
+ for (const param of generated.params) {
335
+ param.sourceName = parameter.name;
336
+ usedParamNames.add(param.paramName);
337
+ }
226
338
  params.push(...generated.params);
227
339
  preflightBlocks.push(...generated.preflightBlocks);
228
340
  requestFields.push(generated.requestField);
@@ -232,11 +344,24 @@ function collectOperationParameters(document, path, pathItemParameters, operatio
232
344
  paramsSchemaOptions: undefined,
233
345
  preflightBlocks,
234
346
  requestFields,
235
- sectionRenders: { path: "wrapped", query: "wrapped" },
347
+ sectionRenders: { path: "wrapped", query: "wrapped", header: "wrapped" },
236
348
  optionalSections: new Set(),
237
349
  requestBodyDescription: undefined
238
350
  };
239
351
  }
352
+ function promoteQueryDeclaredPathParameters(path, parameters) {
353
+ for (const placeholder of collectPathPlaceholders(path)) {
354
+ if (parameters.has(`path:${placeholder}`)) {
355
+ continue;
356
+ }
357
+ const queryParameter = parameters.get(`query:${placeholder}`);
358
+ if (queryParameter === undefined) {
359
+ continue;
360
+ }
361
+ parameters.delete(`query:${placeholder}`);
362
+ parameters.set(`path:${placeholder}`, { ...queryParameter, in: "path", required: true });
363
+ }
364
+ }
240
365
  function collectRequestBodyParams(document, operation, operationId, method) {
241
366
  if (operation.requestBody === undefined) {
242
367
  return {
@@ -253,29 +378,65 @@ function collectRequestBodyParams(document, operation, operationId, method) {
253
378
  throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported requestBody on ${method.toUpperCase()}. Request bodies are not supported on GET in v1.`);
254
379
  }
255
380
  const requestBody = expectRequestBody(document, operation.requestBody, operationId, "requestBody");
256
- const content = Object.entries(requestBody.content ?? {}).find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isJsonMediaType(mediaType))?.[1];
381
+ const contentEntries = Object.entries(requestBody.content ?? {});
382
+ const contentEntry = contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isJsonMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && mediaType.toLowerCase() === "application/x-www-form-urlencoded") ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isTextMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && isBinaryMediaType(mediaType)) ?? contentEntries.find(([mediaType, mediaTypeObject]) => mediaTypeObject !== undefined && mediaType.toLowerCase() === "multipart/form-data");
383
+ const content = contentEntry?.[1];
384
+ const requestMediaType = contentEntry?.[0];
385
+ const bodyMode = requestMediaType?.toLowerCase() === "application/x-www-form-urlencoded"
386
+ ? "form"
387
+ : requestMediaType?.toLowerCase() === "multipart/form-data"
388
+ ? "multipart"
389
+ : requestMediaType !== undefined && !isJsonMediaType(requestMediaType)
390
+ ? isTextMediaType(requestMediaType)
391
+ ? "raw"
392
+ : "base64"
393
+ : "json";
257
394
  if (content === undefined) {
258
395
  throw new UserError(`Operation ${JSON.stringify(operationId)} must define a JSON request body media type in v1.`);
259
396
  }
260
- const schema = expectSchema(document, content.schema, operationId, "requestBody");
397
+ if (bodyMode === "raw" || bodyMode === "base64") {
398
+ const description = requestBody.description;
399
+ return {
400
+ ...createCollectedRequestBodyParams([
401
+ createBodyField(document, "body", { type: "string", ...(description === undefined ? {} : { description }) }, requestBody.required !== true, operationId)
402
+ ], requestBody.required !== true, description, "inline", undefined, bodyMode),
403
+ contentType: requestMediaType
404
+ };
405
+ }
406
+ const schema = resolveBodySchema(document, content.schema, operationId, "requestBody");
261
407
  const bodyOptional = requestBody.required !== true;
262
- if (schema.type !== "object") {
408
+ const multipartBinaryFields = bodyMode === "multipart" && schema.properties !== undefined
409
+ ? Object.entries(schema.properties)
410
+ .filter(([, property]) => {
411
+ const resolved = resolveBodySchema(document, property, operationId, "requestBody multipart field");
412
+ return resolved.type === "string" && resolved.format === "binary";
413
+ })
414
+ .map(([name]) => name)
415
+ : undefined;
416
+ if (schema.type !== "object" || getCompositionKeyword(schema) !== undefined) {
263
417
  const bodySchema = schema.description === undefined && requestBody.description !== undefined
264
418
  ? { ...schema, description: requestBody.description }
265
419
  : schema;
266
- return createCollectedRequestBodyParams([createBodyField(document, "body", bodySchema, bodyOptional, operationId)], bodyOptional, schema.description === undefined ? undefined : requestBody.description, "inline", undefined);
267
- }
268
- if (schema.additionalProperties !== undefined && schema.additionalProperties !== false) {
269
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported requestBody. Object request bodies with additionalProperties are not supported in v1.`);
270
- }
271
- if (schema.properties === undefined) {
272
- throw new UserError(`Operation ${JSON.stringify(operationId)} must define an object-shaped JSON request body.`);
420
+ return createCollectedRequestBodyParams([createBodyField(document, "body", bodySchema, bodyOptional, operationId)], bodyOptional, schema.description === undefined ? undefined : requestBody.description, "inline", undefined, bodyMode, multipartBinaryFields);
421
+ }
422
+ if ((schema.additionalProperties !== undefined && schema.additionalProperties !== false) ||
423
+ schema.properties === undefined) {
424
+ return createCollectedRequestBodyParams([createJsonBodyField({
425
+ document,
426
+ name: "body",
427
+ description: schema.description ?? requestBody.description,
428
+ schema,
429
+ optional: bodyOptional,
430
+ operationId,
431
+ context: "requestBody",
432
+ location: "body"
433
+ })], bodyOptional, requestBody.description, "inline", undefined, bodyMode, multipartBinaryFields);
273
434
  }
274
435
  const required = new Set(schema.required ?? []);
275
436
  const assemblies = [];
276
437
  const declaredPropertyCount = Object.keys(schema.properties).length;
277
438
  for (const [name, property] of Object.entries(schema.properties)) {
278
- const propertySchema = expectSchema(document, property, operationId, `requestBody.properties.${name}`);
439
+ const propertySchema = resolveBodySchema(document, property, operationId, `requestBody.properties.${name}`);
279
440
  if (propertySchema.readOnly === true) {
280
441
  continue;
281
442
  }
@@ -283,14 +444,31 @@ function collectRequestBodyParams(document, operation, operationId, method) {
283
444
  assemblies.push(generated);
284
445
  }
285
446
  if (!bodyOptional && assemblies.length === 0) {
286
- const reason = declaredPropertyCount === 0
287
- ? "does not define any writable fields"
288
- : "all declared fields are readOnly";
289
- throw new UserError(`Operation ${JSON.stringify(operationId)} requestBody is required but ${reason}.`);
447
+ if (declaredPropertyCount === 0) {
448
+ return {
449
+ params: [],
450
+ paramsSchemaOptions: schema.additionalProperties === false ? { additionalProperties: false } : undefined,
451
+ preflightBlocks: [],
452
+ requestFields: [{
453
+ location: "body",
454
+ wireName: "body",
455
+ value: { kind: "emptyObject" },
456
+ omitWhenUndefinedReference: { kind: "resolved", resolvedName: "emptyBody" }
457
+ }],
458
+ sectionRenders: { body: "inline" },
459
+ optionalSections: new Set(),
460
+ requestBodyDescription: requestBody.description,
461
+ bodyMode,
462
+ ...(multipartBinaryFields === undefined ? {} : { multipartBinaryFields })
463
+ };
464
+ }
465
+ throw new UserError(`Operation ${JSON.stringify(operationId)} requestBody is required but all declared fields are readOnly.`);
290
466
  }
291
- return createCollectedRequestBodyParams(assemblies, bodyOptional, requestBody.description, "wrapped", schema.additionalProperties === false ? { additionalProperties: false } : undefined);
467
+ return createCollectedRequestBodyParams(assemblies, bodyOptional, requestBody.description, "wrapped", schema.additionalProperties === false ? { additionalProperties: false } : undefined, bodyMode, multipartBinaryFields);
292
468
  }
293
- function assertSupportedSuccessResponses(document, operation, operationId) {
469
+ function resolveSuccessResponse(document, operation, operationId) {
470
+ let textualMediaType;
471
+ let binaryMediaType;
294
472
  for (const [statusCode, response] of Object.entries(operation.responses ?? {})) {
295
473
  if (!isSuccessStatusCode(statusCode)) {
296
474
  continue;
@@ -300,45 +478,33 @@ function assertSupportedSuccessResponses(document, operation, operationId) {
300
478
  .filter(([, mediaType]) => mediaType !== undefined)
301
479
  .map(([mediaType]) => mediaType);
302
480
  if (declaredMediaTypes.length === 0 ||
303
- declaredMediaTypes.every((mediaType) => isJsonMediaType(mediaType))) {
304
- for (const [mediaType, mediaTypeObject] of Object.entries(resolvedResponse.content ?? {})) {
305
- if (mediaTypeObject === undefined ||
306
- mediaTypeObject.schema === undefined ||
307
- !isJsonMediaType(mediaType)) {
308
- continue;
309
- }
310
- assertSupportedSuccessResponseSchema(document, mediaTypeObject.schema, operationId, `success response schema for status ${JSON.stringify(statusCode)}`);
311
- }
481
+ declaredMediaTypes.some((mediaType) => isJsonMediaType(mediaType))) {
312
482
  continue;
313
483
  }
314
- throw new UserError(`Operation ${JSON.stringify(operationId)} declares unsupported success response content type(s) for status ${JSON.stringify(statusCode)}: ${declaredMediaTypes
315
- .map((mediaType) => JSON.stringify(mediaType))
316
- .join(", ")}. Only application/json responses (or empty success responses) are supported in v1.`);
317
- }
318
- }
319
- function assertSupportedSuccessResponseSchema(document, schema, operationId, context) {
320
- const resolvedSchema = expectSupportedSuccessResponseSchema(document, schema, operationId, context);
321
- if (resolvedSchema.additionalProperties !== undefined &&
322
- resolvedSchema.additionalProperties !== false) {
323
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Object response schemas with additionalProperties are not supported in v1.`);
324
- }
325
- for (const [propertyName, propertySchema] of Object.entries(resolvedSchema.properties ?? {})) {
326
- assertSupportedSuccessResponseSchema(document, propertySchema, operationId, `${context} property ${JSON.stringify(propertyName)}`);
484
+ const textual = declaredMediaTypes.find(isTextMediaType);
485
+ if (textual !== undefined && declaredMediaTypes.every(isTextMediaType)) {
486
+ textualMediaType ??= textual;
487
+ continue;
488
+ }
489
+ binaryMediaType ??= declaredMediaTypes[0];
327
490
  }
328
- if (resolvedSchema.items !== undefined) {
329
- assertSupportedSuccessResponseSchema(document, resolvedSchema.items, operationId, `${context} items`);
491
+ if (binaryMediaType !== undefined) {
492
+ return { mode: "binary", accept: binaryMediaType };
330
493
  }
494
+ return textualMediaType === undefined
495
+ ? { mode: "json", accept: "application/json" }
496
+ : { mode: "text", accept: textualMediaType };
331
497
  }
332
- function expectSupportedSuccessResponseSchema(document, schema, operationId, context) {
333
- const resolvedSchema = normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context), operationId, context);
334
- const compositionKeyword = getCompositionKeyword(resolvedSchema);
335
- if (compositionKeyword === undefined) {
336
- return resolvedSchema;
498
+ function createGeneratedParameter(document, parameter, operationId, auth) {
499
+ if (parameter.in === "header" &&
500
+ parameter.name.toLowerCase() === "authorization" &&
501
+ auth === "required") {
502
+ throw new UserError(`Operation ${JSON.stringify(operationId)} header parameter ${JSON.stringify(parameter.name)} is managed by the HTTP transport and cannot be generated as a command parameter.`);
337
503
  }
338
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. JSON Schema composition keyword ${JSON.stringify(compositionKeyword)} is not supported in v1.`);
339
- }
340
- function createGeneratedParameter(document, parameter, operationId) {
341
- const schema = expectSchema(document, parameter.schema, operationId, `parameter ${JSON.stringify(parameter.name)}`);
504
+ const context = `parameter ${JSON.stringify(parameter.name)}`;
505
+ const schema = parameter.in === "query" && parameter.style === "deepObject"
506
+ ? resolveBodySchema(document, parameter.schema, operationId, context)
507
+ : expectSchema(document, parameter.schema, operationId, context);
342
508
  if (parameter.in === "path") {
343
509
  if (parameter.required !== true) {
344
510
  throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} must set required: true.`);
@@ -348,19 +514,43 @@ function createGeneratedParameter(document, parameter, operationId) {
348
514
  }
349
515
  assertSupportedPathParameterSerialization(parameter, operationId);
350
516
  }
351
- return createField({
517
+ if (parameter.in === "query" &&
518
+ parameter.style === "deepObject" &&
519
+ (getCompositionKeyword(schema) !== undefined ||
520
+ (schema.type === "array" && schema.items !== undefined && isComplexJsonBodySchema(document, schema.items, operationId, `${context} items`)))) {
521
+ return createJsonQueryField({
522
+ document,
523
+ name: parameter.name,
524
+ description: parameter.description ?? schema.description,
525
+ schema,
526
+ optional: parameter.required !== true,
527
+ operationId,
528
+ context,
529
+ location: "query",
530
+ queryObjectSerialization: resolveQueryObjectSerialization(parameter, operationId)
531
+ });
532
+ }
533
+ const querySerialization = parameter.in === "query" && schema.type === "array"
534
+ ? resolveQueryArraySerialization(parameter, operationId)
535
+ : undefined;
536
+ const generated = createField({
352
537
  document,
353
538
  name: parameter.name,
354
539
  description: parameter.description ?? schema.description,
355
540
  schema,
356
541
  optional: parameter.required !== true,
357
542
  operationId,
358
- context: `parameter ${JSON.stringify(parameter.name)}`,
543
+ context,
359
544
  location: parameter.in,
360
- querySerialization: parameter.in === "query" && schema.type === "array"
361
- ? resolveQueryArraySerialization(parameter, operationId)
545
+ querySerialization,
546
+ queryObjectSerialization: parameter.in === "query" && schema.type === "object"
547
+ ? resolveQueryObjectSerialization(parameter, operationId)
362
548
  : undefined
363
549
  });
550
+ if (querySerialization === "brackets") {
551
+ generated.requestField.wireName = `${parameter.name}[]`;
552
+ }
553
+ return generated;
364
554
  }
365
555
  function assertSupportedPathParameterSerialization(parameter, operationId) {
366
556
  const style = parameter.style ?? "simple";
@@ -371,6 +561,18 @@ function assertSupportedPathParameterSerialization(parameter, operationId) {
371
561
  throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(parameter.name)} uses unsupported serialization. Path parameters must use style "simple" with explode false in v1.`);
372
562
  }
373
563
  function createBodyField(document, name, schema, optional, operationId) {
564
+ if (isComplexJsonBodySchema(document, schema, operationId, `request body field ${JSON.stringify(name)}`)) {
565
+ return createJsonBodyField({
566
+ document,
567
+ name,
568
+ description: schema.description,
569
+ schema,
570
+ optional,
571
+ operationId,
572
+ context: `request body field ${JSON.stringify(name)}`,
573
+ location: "body"
574
+ });
575
+ }
374
576
  return createField({
375
577
  document,
376
578
  name,
@@ -382,6 +584,32 @@ function createBodyField(document, name, schema, optional, operationId) {
382
584
  location: "body"
383
585
  });
384
586
  }
587
+ function isComplexJsonBodySchema(document, schema, operationId, context) {
588
+ if (isUnconstrainedJsonSchema(schema)) {
589
+ return true;
590
+ }
591
+ if (schema.type === "object" ||
592
+ schema.properties !== undefined ||
593
+ schema.additionalProperties !== undefined ||
594
+ getCompositionKeyword(schema) !== undefined ||
595
+ Array.isArray(schema.type)) {
596
+ return true;
597
+ }
598
+ if (schema.type !== "array" || schema.items === undefined) {
599
+ return false;
600
+ }
601
+ const itemSchema = resolveBodySchema(document, schema.items, operationId, `${context} items`);
602
+ return isComplexJsonBodySchema(document, itemSchema, operationId, `${context} items`);
603
+ }
604
+ function isUnconstrainedJsonSchema(schema) {
605
+ return (schema.type === undefined &&
606
+ schema.enum === undefined &&
607
+ schema.properties === undefined &&
608
+ schema.additionalProperties === undefined &&
609
+ schema.allOf === undefined &&
610
+ schema.anyOf === undefined &&
611
+ schema.oneOf === undefined);
612
+ }
385
613
  function createField(options) {
386
614
  return FIELD_ASSEMBLERS[options.location][getFieldSchemaKind(options.schema)](options);
387
615
  }
@@ -442,11 +670,53 @@ function createScalarParam(options) {
442
670
  function getFieldSchemaKind(schema) {
443
671
  return schema.type === "array" ? "array" : schema.type === "object" ? "object" : "scalar";
444
672
  }
445
- function createPathScalarOnlyError(name, operationId) {
446
- throw new UserError(`Operation ${JSON.stringify(operationId)} path parameter ${JSON.stringify(name)} must use a scalar schema (string, number, integer, or boolean).`);
673
+ function createScalarOnlyParameterError(name, operationId, location) {
674
+ throw new UserError(`Operation ${JSON.stringify(operationId)} ${location} parameter ${JSON.stringify(name)} must use a scalar schema (string, number, integer, or boolean).`);
447
675
  }
448
- function createUnsupportedNestedBodyFieldError(name, operationId) {
449
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported request body field ${JSON.stringify(name)}. Nested object body fields are not supported in v1.`);
676
+ function createJsonBodyField(options) {
677
+ return {
678
+ params: [
679
+ {
680
+ paramName: options.name,
681
+ sourceName: options.name,
682
+ location: "body",
683
+ description: options.description,
684
+ optional: options.optional,
685
+ definition: { kind: "json" }
686
+ }
687
+ ],
688
+ preflightBlocks: [],
689
+ requestField: {
690
+ location: "body",
691
+ wireName: options.name,
692
+ value: { kind: "reference", reference: { kind: "param", paramName: options.name } },
693
+ omitWhenUndefinedReference: { kind: "param", paramName: options.name }
694
+ }
695
+ };
696
+ }
697
+ function createJsonQueryField(options) {
698
+ if (options.queryObjectSerialization !== "deepObject") {
699
+ throw new ToolcraftBugError("Missing deep-object serialization for generated JSON query field.");
700
+ }
701
+ return {
702
+ params: [
703
+ {
704
+ paramName: options.name,
705
+ sourceName: options.name,
706
+ location: "query",
707
+ description: options.description,
708
+ optional: options.optional,
709
+ definition: { kind: "json" }
710
+ }
711
+ ],
712
+ preflightBlocks: [],
713
+ requestField: {
714
+ location: "query",
715
+ wireName: options.name,
716
+ value: { kind: "reference", reference: { kind: "param", paramName: options.name } },
717
+ omitWhenUndefinedReference: { kind: "param", paramName: options.name }
718
+ }
719
+ };
450
720
  }
451
721
  function createArrayParam(options) {
452
722
  const { document, name, description, schema, optional, operationId, context, location } = options;
@@ -526,15 +796,23 @@ const FIELD_ASSEMBLERS = {
526
796
  ...options,
527
797
  location: "body"
528
798
  }),
529
- object: (options) => createUnsupportedNestedBodyFieldError(options.name, options.operationId),
799
+ object: (options) => createJsonBodyField(options),
530
800
  scalar: (options) => createScalarParam({
531
801
  ...options,
532
802
  location: "body"
533
803
  })
534
804
  },
805
+ header: {
806
+ array: (options) => createScalarOnlyParameterError(options.name, options.operationId, "header"),
807
+ object: (options) => createScalarOnlyParameterError(options.name, options.operationId, "header"),
808
+ scalar: (options) => createScalarParam({
809
+ ...options,
810
+ location: "header"
811
+ })
812
+ },
535
813
  path: {
536
- array: (options) => createPathScalarOnlyError(options.name, options.operationId),
537
- object: (options) => createPathScalarOnlyError(options.name, options.operationId),
814
+ array: (options) => createPathArrayParam(options),
815
+ object: (options) => createScalarOnlyParameterError(options.name, options.operationId, "path"),
538
816
  scalar: (options) => createScalarParam({
539
817
  ...options,
540
818
  location: "path"
@@ -545,16 +823,34 @@ const FIELD_ASSEMBLERS = {
545
823
  ...options,
546
824
  location: "query"
547
825
  }),
548
- object: (options) => createScalarParam({
549
- ...options,
550
- location: "query"
551
- }),
826
+ object: (options) => createJsonQueryField(options),
552
827
  scalar: (options) => createScalarParam({
553
828
  ...options,
554
829
  location: "query"
555
830
  })
556
831
  }
557
832
  };
833
+ function createPathArrayParam(options) {
834
+ const definition = createParamDefinition(options.document, options.schema, options.operationId, options.context);
835
+ const paramName = options.name;
836
+ return {
837
+ params: [{
838
+ paramName,
839
+ sourceName: options.name,
840
+ location: "path",
841
+ description: options.description,
842
+ optional: false,
843
+ definition
844
+ }],
845
+ preflightBlocks: [],
846
+ requestField: {
847
+ location: "path",
848
+ wireName: options.name,
849
+ value: { kind: "queryArray", reference: { kind: "param", paramName }, serialization: "comma" },
850
+ omitWhenUndefinedReference: { kind: "param", paramName }
851
+ }
852
+ };
853
+ }
558
854
  function expectQueryArraySerialization(querySerialization) {
559
855
  if (querySerialization === undefined) {
560
856
  throw new ToolcraftBugError("Missing query array serialization for generated query array field.");
@@ -564,7 +860,7 @@ function expectQueryArraySerialization(querySerialization) {
564
860
  function supportsNullHelper(location, shape, nullable) {
565
861
  return nullable === true && NULL_HELPER_SUPPORT[location][shape];
566
862
  }
567
- function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyDescription, bodyRender, paramsSchemaOptions) {
863
+ function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyDescription, bodyRender, paramsSchemaOptions, bodyMode, multipartBinaryFields) {
568
864
  return {
569
865
  params: assemblies.flatMap((assembly) => assembly.params),
570
866
  paramsSchemaOptions,
@@ -574,7 +870,9 @@ function createCollectedRequestBodyParams(assemblies, bodyOptional, requestBodyD
574
870
  optionalSections: bodyOptional
575
871
  ? new Set(["body"])
576
872
  : new Set(),
577
- requestBodyDescription
873
+ requestBodyDescription,
874
+ bodyMode,
875
+ ...(multipartBinaryFields === undefined ? {} : { multipartBinaryFields })
578
876
  };
579
877
  }
580
878
  function createParamDefinition(document, schema, operationId, context) {
@@ -595,7 +893,7 @@ function createParamDefinition(document, schema, operationId, context) {
595
893
  const scalarDefinition = isOpenApiScalarType(schema.type)
596
894
  ? SCHEMA_TYPE_TO_KIND[schema.type]
597
895
  : undefined;
598
- const enumValues = normalizeEnumValues(schema.enum, operationId, context, schema.nullable === true, schema.type);
896
+ const enumValues = normalizeEnumValues(schema.enum, operationId, context);
599
897
  if (enumValues !== undefined) {
600
898
  return {
601
899
  kind: "enum",
@@ -670,44 +968,32 @@ function isAsciiDigit(character) {
670
968
  }
671
969
  function isJsonMediaType(mediaType) {
672
970
  const normalized = mediaType.toLowerCase();
673
- return normalized.includes("application/json") || normalized.includes("+json");
971
+ return normalized === "*/*" || normalized === "text/json" || normalized.includes("application/json") || normalized.includes("+json");
674
972
  }
675
- function normalizeEnumValues(enumValues, operationId, context, nullable, schemaType) {
973
+ function isTextMediaType(mediaType) {
974
+ const normalized = mediaType.toLowerCase();
975
+ return normalized.startsWith("text/") || normalized === "plain/text" || normalized === "application/jwt";
976
+ }
977
+ function isBinaryMediaType(mediaType) {
978
+ const normalized = mediaType.toLowerCase();
979
+ return (normalized === "application/octet-stream" ||
980
+ normalized === "application/zip" ||
981
+ normalized === "application/gzip" ||
982
+ normalized === "application/pdf");
983
+ }
984
+ function normalizeEnumValues(enumValues, operationId, context) {
676
985
  if (enumValues === undefined) {
677
986
  return undefined;
678
987
  }
679
- const filteredValues = enumValues.filter((value) => value !== null);
680
- if (enumValues.includes(null) && nullable !== true) {
681
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enums may include null only when the schema is marked nullable.`);
682
- }
988
+ const filteredValues = enumValues.filter(isEnumPrimitiveValue);
683
989
  if (filteredValues.length === 0) {
684
990
  throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enum values cannot be empty.`);
685
991
  }
686
- if (filteredValues.some((value) => !isEnumPrimitiveValue(value))) {
687
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. OpenAPI enums must contain only string, number, boolean, or null values.`);
688
- }
689
992
  const primitiveTypes = new Set(filteredValues.map((value) => typeof value));
690
993
  if (primitiveTypes.size > 1) {
691
994
  throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enums must not mix primitive types.`);
692
995
  }
693
- if (schemaType !== undefined) {
694
- const matchesSchemaType = filteredValues.every((value) => {
695
- if (schemaType === "integer") {
696
- return typeof value === "number" && Number.isInteger(value);
697
- }
698
- if (schemaType === "number") {
699
- return typeof value === "number";
700
- }
701
- if (schemaType === "string" || schemaType === "boolean") {
702
- return typeof value === schemaType;
703
- }
704
- return true;
705
- });
706
- if (!matchesSchemaType) {
707
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported ${context}. Enum values must match declared schema.type ${JSON.stringify(schemaType)}.`);
708
- }
709
- }
710
- return filteredValues.filter(isEnumPrimitiveValue);
996
+ return filteredValues.filter((value) => value !== null);
711
997
  }
712
998
  function resolveLocalReference(document, ref, operationId, context) {
713
999
  if (!ref.startsWith("#/")) {
@@ -715,7 +1001,9 @@ function resolveLocalReference(document, ref, operationId, context) {
715
1001
  }
716
1002
  let current = document;
717
1003
  for (const segment of ref.slice(2).split("/").map(unescapeJsonPointerSegment)) {
718
- if (typeof current !== "object" || current === null || !(segment in current)) {
1004
+ if (typeof current !== "object" ||
1005
+ current === null ||
1006
+ !Object.prototype.hasOwnProperty.call(current, segment)) {
719
1007
  throw new UserError(`Operation ${JSON.stringify(operationId)} references missing $ref ${JSON.stringify(ref)} in ${context}.`);
720
1008
  }
721
1009
  current = current[segment];
@@ -723,7 +1011,13 @@ function resolveLocalReference(document, ref, operationId, context) {
723
1011
  return current;
724
1012
  }
725
1013
  function unescapeJsonPointerSegment(segment) {
726
- return segment.replaceAll("~1", "/").replaceAll("~0", "~");
1014
+ const unescaped = segment.replaceAll("~1", "/").replaceAll("~0", "~");
1015
+ try {
1016
+ return decodeURIComponent(unescaped);
1017
+ }
1018
+ catch {
1019
+ return unescaped;
1020
+ }
727
1021
  }
728
1022
  function expectParameter(document, parameter, operationId, refChain = []) {
729
1023
  if (isReferenceObject(parameter)) {
@@ -733,8 +1027,8 @@ function expectParameter(document, parameter, operationId, refChain = []) {
733
1027
  if (parameter.content !== undefined) {
734
1028
  throw new UserError(`Operation ${JSON.stringify(operationId)} parameter ${JSON.stringify(parameter.name)} uses unsupported parameter.content. Define path/query parameters with parameter.schema in v1.`);
735
1029
  }
736
- if (parameter.in !== "path" && parameter.in !== "query") {
737
- throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported parameter location ${JSON.stringify(parameter.in)}. Only path and query parameters are supported in v1; use auth or handwritten commands for headers/cookies.`);
1030
+ if (parameter.in !== "path" && parameter.in !== "query" && parameter.in !== "header") {
1031
+ throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported parameter location ${JSON.stringify(parameter.in)}. Only path, query, and header parameters are supported in v1; use auth or handwritten commands for cookies.`);
738
1032
  }
739
1033
  return parameter;
740
1034
  }
@@ -751,7 +1045,7 @@ function isEnumPrimitiveValue(value) {
751
1045
  return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
752
1046
  }
753
1047
  function isOpenApiScalarType(type) {
754
- return typeof type === "string" && Object.prototype.hasOwnProperty.call(SCHEMA_TYPE_TO_KIND, type);
1048
+ return (typeof type === "string" && Object.prototype.hasOwnProperty.call(SCHEMA_TYPE_TO_KIND, type));
755
1049
  }
756
1050
  function expectRequestBody(document, requestBody, operationId, context, refChain = []) {
757
1051
  if (!isReferenceObject(requestBody)) {
@@ -776,24 +1070,86 @@ function expectSchema(document, schema, operationId, context, refChain = []) {
776
1070
  }
777
1071
  return resolvedSchema;
778
1072
  }
1073
+ function resolveBodySchema(document, schema, operationId, context) {
1074
+ return normalizeNullableSchema(document, resolveSchema(document, schema, operationId, context), operationId, context);
1075
+ }
779
1076
  function resolveSchema(document, schema, operationId, context, refChain = []) {
780
1077
  if (schema === undefined) {
781
1078
  throw new UserError(`Operation ${JSON.stringify(operationId)} is missing a schema for ${context}.`);
782
1079
  }
783
1080
  if (isReferenceObject(schema)) {
784
1081
  assertAcyclicRef(schema.$ref, refChain, operationId, context);
785
- return resolveSchema(document, resolveLocalReference(document, schema.$ref, operationId, context), operationId, context, [...refChain, schema.$ref]);
1082
+ const resolved = resolveLocalReference(document, schema.$ref, operationId, context);
1083
+ const nestedSchema = isSchemaContainer(resolved) ? resolved.schema : resolved;
1084
+ return resolveSchema(document, nestedSchema, operationId, context, [...refChain, schema.$ref]);
1085
+ }
1086
+ return cloneOwnSchemaObject(schema);
1087
+ }
1088
+ function cloneOwnSchemaObject(schema) {
1089
+ const clone = Object.create(null);
1090
+ for (const [key, value] of Object.entries(schema)) {
1091
+ Object.defineProperty(clone, key, {
1092
+ value,
1093
+ enumerable: true,
1094
+ configurable: true,
1095
+ writable: true
1096
+ });
786
1097
  }
787
- return schema;
1098
+ return clone;
1099
+ }
1100
+ function isSchemaContainer(value) {
1101
+ return !isReferenceObject(value) && "schema" in value && value.schema !== undefined;
788
1102
  }
789
1103
  function normalizeNullableSchema(document, schema, operationId, context) {
790
1104
  const typeNormalizedSchema = normalizeNullableTypeArray(schema);
1105
+ const equivalentComposition = resolveEquivalentCompositionSchema(document, typeNormalizedSchema, operationId, context);
1106
+ if (equivalentComposition !== undefined) {
1107
+ return equivalentComposition;
1108
+ }
791
1109
  if (getCompositionKeyword(typeNormalizedSchema) !== "anyOf") {
792
1110
  return typeNormalizedSchema;
793
1111
  }
794
1112
  return (resolveNullableAnyOfSchema(document, typeNormalizedSchema, operationId, context) ??
795
1113
  typeNormalizedSchema);
796
1114
  }
1115
+ function resolveEquivalentCompositionSchema(document, schema, operationId, context) {
1116
+ const keyword = getCompositionKeyword(schema);
1117
+ if (keyword !== "anyOf" && keyword !== "oneOf") {
1118
+ return undefined;
1119
+ }
1120
+ const variants = schema[keyword];
1121
+ if (variants === undefined || variants.length === 0) {
1122
+ return undefined;
1123
+ }
1124
+ const resolved = variants.map((variant, index) => normalizeNullableSchema(document, resolveSchema(document, variant, operationId, `${context} ${keyword} variant ${index}`), operationId, `${context} ${keyword} variant ${index}`));
1125
+ const shapes = resolved.map(getEquivalentSchemaShape);
1126
+ const [firstShape] = shapes;
1127
+ if (firstShape === undefined || shapes.some((shape) => shape !== firstShape)) {
1128
+ return undefined;
1129
+ }
1130
+ const { anyOf: _anyOf, oneOf: _oneOf, ...wrapper } = schema;
1131
+ void _anyOf;
1132
+ void _oneOf;
1133
+ return { ...wrapper, ...createSchemaFromEquivalentShape(firstShape) };
1134
+ }
1135
+ function getEquivalentSchemaShape(schema) {
1136
+ if (getCompositionKeyword(schema) !== undefined || Array.isArray(schema.type)) {
1137
+ return undefined;
1138
+ }
1139
+ if (schema.type === "array") {
1140
+ if (schema.items === undefined || isReferenceObject(schema.items))
1141
+ return undefined;
1142
+ const itemShape = getEquivalentSchemaShape(schema.items);
1143
+ return itemShape === undefined ? undefined : `array:${itemShape}`;
1144
+ }
1145
+ return isOpenApiScalarType(schema.type) ? schema.type : undefined;
1146
+ }
1147
+ function createSchemaFromEquivalentShape(shape) {
1148
+ if (!shape.startsWith("array:")) {
1149
+ return { type: shape };
1150
+ }
1151
+ return { type: "array", items: createSchemaFromEquivalentShape(shape.slice("array:".length)) };
1152
+ }
797
1153
  function normalizeNullableTypeArray(schema) {
798
1154
  if (!Array.isArray(schema.type)) {
799
1155
  return schema;
@@ -810,7 +1166,8 @@ function normalizeNullableTypeArray(schema) {
810
1166
  }
811
1167
  function getCompositionKeyword(schema) {
812
1168
  for (const keyword of ["allOf", "anyOf", "oneOf"]) {
813
- if (schema[keyword] !== undefined) {
1169
+ if (Object.prototype.hasOwnProperty.call(schema, keyword) &&
1170
+ schema[keyword] !== undefined) {
814
1171
  return keyword;
815
1172
  }
816
1173
  }
@@ -872,6 +1229,59 @@ function assertUniqueCommandPaths(commands) {
872
1229
  seen.set(key, command);
873
1230
  }
874
1231
  }
1232
+ function disambiguateCommandPaths(commands) {
1233
+ const byCommandPath = new Map();
1234
+ for (const command of commands) {
1235
+ const key = `${command.noun} ${command.verb}`;
1236
+ byCommandPath.set(key, [...(byCommandPath.get(key) ?? []), command]);
1237
+ }
1238
+ for (const collisions of byCommandPath.values()) {
1239
+ if (collisions.length < 2) {
1240
+ continue;
1241
+ }
1242
+ const existingPaths = new Set(commands
1243
+ .filter((command) => !collisions.includes(command))
1244
+ .map((command) => `${command.noun} ${command.verb}`));
1245
+ const operationIdCandidates = collisions.map((command) => ({
1246
+ command,
1247
+ verb: deriveDisambiguatedVerb(command.operationId, command.noun)
1248
+ }));
1249
+ if (applyDisambiguatedVerbs(operationIdCandidates, existingPaths)) {
1250
+ continue;
1251
+ }
1252
+ const pathCandidates = collisions.map((command) => ({
1253
+ command,
1254
+ verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb)
1255
+ }));
1256
+ if (applyDisambiguatedVerbs(pathCandidates, existingPaths)) {
1257
+ continue;
1258
+ }
1259
+ if (applyDisambiguatedVerbs(collisions.map((command) => ({
1260
+ command,
1261
+ verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb, true)
1262
+ })), existingPaths)) {
1263
+ continue;
1264
+ }
1265
+ applyDisambiguatedVerbs(collisions.map((command) => ({
1266
+ command,
1267
+ verb: derivePathDisambiguatedVerb(command.method.toLowerCase(), command.path, command.noun, command.verb, true, true)
1268
+ })), existingPaths);
1269
+ }
1270
+ }
1271
+ function applyDisambiguatedVerbs(candidates, existingPaths) {
1272
+ const candidatePaths = candidates.map(({ command, verb }) => `${command.noun} ${verb}`);
1273
+ if (candidates.some(({ verb }) => verb.length === 0) ||
1274
+ new Set(candidatePaths).size !== candidatePaths.length ||
1275
+ candidatePaths.some((path) => existingPaths.has(path))) {
1276
+ return false;
1277
+ }
1278
+ for (const { command, verb } of candidates) {
1279
+ command.verb = verb;
1280
+ command.exportName = `${toCamelCase(command.noun)}${toPascalCase(verb)}Command`;
1281
+ command.filePath = `${command.noun}/${verb}.ts`;
1282
+ }
1283
+ return true;
1284
+ }
875
1285
  function createCommandFile(options) {
876
1286
  const requiresUserError = options.preflightBlocks.length > 0;
877
1287
  const lines = createGeneratedTypeScriptFileLines([
@@ -896,10 +1306,36 @@ function createCommandFile(options) {
896
1306
  lines.push(" handler: async ({ params, baseUrl, tokenSource, fetch }) => {");
897
1307
  lines.push(...options.preflightBlocks.flatMap((block) => renderPreflightBlock(block)));
898
1308
  lines.push(" return requestJson({");
899
- lines.push(" baseUrl,");
1309
+ lines.push(options.baseUrl === undefined
1310
+ ? " baseUrl,"
1311
+ : ` baseUrl: ${JSON.stringify(options.baseUrl)},`);
900
1312
  lines.push(` path: ${JSON.stringify(options.path)},`);
901
1313
  lines.push(` method: ${JSON.stringify(options.method)},`);
902
1314
  lines.push(` auth: ${JSON.stringify(options.auth)},`);
1315
+ if (options.responseMode !== "json") {
1316
+ lines.push(` responseMode: ${JSON.stringify(options.responseMode)},`);
1317
+ }
1318
+ if (options.accept !== "application/json") {
1319
+ lines.push(` accept: ${JSON.stringify(options.accept)},`);
1320
+ }
1321
+ if (options.bodyMode === "form") {
1322
+ lines.push(' bodyMode: "form",');
1323
+ }
1324
+ else if (options.bodyMode === "raw") {
1325
+ lines.push(' bodyMode: "raw",');
1326
+ }
1327
+ else if (options.bodyMode === "base64") {
1328
+ lines.push(' bodyMode: "base64",');
1329
+ }
1330
+ else if (options.bodyMode === "multipart") {
1331
+ lines.push(' bodyMode: "multipart",');
1332
+ }
1333
+ if (options.multipartBinaryFields !== undefined) {
1334
+ lines.push(` multipartBinaryFields: ${JSON.stringify(options.multipartBinaryFields)},`);
1335
+ }
1336
+ if (options.contentType !== undefined) {
1337
+ lines.push(` contentType: ${JSON.stringify(options.contentType)},`);
1338
+ }
903
1339
  lines.push(" tokenSource,");
904
1340
  lines.push(" fetch,");
905
1341
  lines.push(" dryRun: params.dryRun,");
@@ -941,13 +1377,12 @@ function getOperationAuthMode(document, operation, operationId) {
941
1377
  return security === undefined || security.length === 0 ? "none" : "required";
942
1378
  }
943
1379
  function assertSupportedHttpMethods(path, pathItem) {
944
- const rawPathItem = pathItem;
945
1380
  for (const method of UNSUPPORTED_HTTP_METHODS) {
946
- const operation = rawPathItem[method];
1381
+ const operation = getOwnPathItemValue(pathItem, method);
947
1382
  if (operation === undefined) {
948
1383
  continue;
949
1384
  }
950
- throw new UserError(`Operation ${JSON.stringify(getOperationId(path, method, operation))} uses unsupported HTTP method ${JSON.stringify(method.toUpperCase())}. Supported in v1: GET, POST, PUT, PATCH, DELETE.`);
1385
+ throw new UserError(`Operation ${JSON.stringify(getOperationId(path, method, operation))} uses unsupported HTTP method ${JSON.stringify(method.toUpperCase())}. Supported in v1: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS.`);
951
1386
  }
952
1387
  }
953
1388
  function getOperationId(path, method, operation) {
@@ -988,6 +1423,7 @@ const DEFINITION_RENDERERS = {
988
1423
  array: (definition, options) => renderSchemaCall("S.Array", renderDefinition(definition.itemDefinition, undefined, undefined, undefined), options),
989
1424
  boolean: (_definition, options) => renderSchemaCall("S.Boolean", options),
990
1425
  enum: (definition, options) => renderSchemaCall("S.Enum", renderConstArray(definition.enumValues), options),
1426
+ json: (_definition) => renderSchemaCall("S.Json"),
991
1427
  number: (_definition, options) => renderSchemaCall("S.Number", options),
992
1428
  string: (_definition, options) => renderSchemaCall("S.String", options)
993
1429
  };
@@ -1010,12 +1446,9 @@ function renderObjectKey(name) {
1010
1446
  }
1011
1447
  return JSON.stringify(name);
1012
1448
  }
1013
- function assertValidGeneratedNoun(operationId, noun) {
1014
- const identifier = toCamelCase(noun);
1015
- if (isTypeScriptIdentifier(identifier)) {
1016
- return;
1017
- }
1018
- throw new UserError(`Operation ${JSON.stringify(operationId)} derives command noun ${JSON.stringify(noun)}, which maps to invalid TypeScript identifier ${JSON.stringify(identifier)}.`);
1449
+ function createSafeGeneratedNoun(noun) {
1450
+ const normalized = normalizeNoun(noun);
1451
+ return isTypeScriptIdentifier(toCamelCase(normalized)) ? normalized : `api-${normalized || "operation"}`;
1019
1452
  }
1020
1453
  export function collectSchemaOptionEntries(param) {
1021
1454
  return SCHEMA_OPTION_SOURCES.flatMap(({ key, get }) => {
@@ -1115,8 +1548,17 @@ function resolveQueryArraySerialization(parameter, operationId) {
1115
1548
  if (style === "pipeDelimited" && explode === false) {
1116
1549
  return "pipe";
1117
1550
  }
1551
+ if (style === "deepObject" && explode === true) {
1552
+ return "brackets";
1553
+ }
1118
1554
  throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-array serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: form (explode true/false) and pipeDelimited.`);
1119
1555
  }
1556
+ function resolveQueryObjectSerialization(parameter, operationId) {
1557
+ if (parameter.style === "deepObject" && parameter.explode !== false) {
1558
+ return "deepObject";
1559
+ }
1560
+ throw new UserError(`Operation ${JSON.stringify(operationId)} uses unsupported query-object serialization for parameter ${JSON.stringify(parameter.name)}. Supported in v1: deepObject with explode true.`);
1561
+ }
1120
1562
  function createIndexFile(commands) {
1121
1563
  const groups = groupByNoun(commands);
1122
1564
  if (groups.length === 0) {
@@ -1147,6 +1589,22 @@ function createIndexFile(commands) {
1147
1589
  contents: lines.join("\n")
1148
1590
  };
1149
1591
  }
1592
+ function createCliFile(theme) {
1593
+ return {
1594
+ path: "cli.ts",
1595
+ contents: [
1596
+ "#!/usr/bin/env node",
1597
+ ...createGeneratedTypeScriptFileLines(),
1598
+ 'import { configureTheme, runCLI } from "toolcraft/cli";',
1599
+ 'import * as groups from "./index.js";',
1600
+ "",
1601
+ `configureTheme({ brand: ${JSON.stringify(theme.brand)}, label: ${JSON.stringify(theme.label)} });`,
1602
+ "",
1603
+ "await runCLI(Object.values(groups));",
1604
+ ""
1605
+ ].join("\n")
1606
+ };
1607
+ }
1150
1608
  function createGeneratedTypeScriptFile(bodyLines, metadataLines = []) {
1151
1609
  return [...createGeneratedTypeScriptFileLines(metadataLines), ...bodyLines].join("\n");
1152
1610
  }
@@ -1166,5 +1624,7 @@ function compareGeneratedCommandPaths(left, right) {
1166
1624
  return left.verb.localeCompare(right.verb);
1167
1625
  }
1168
1626
  function isReferenceObject(value) {
1169
- return typeof value === "object" && value !== null && "$ref" in value;
1627
+ return (typeof value === "object" &&
1628
+ value !== null &&
1629
+ Object.prototype.hasOwnProperty.call(value, "$ref"));
1170
1630
  }