specli 0.0.11 → 0.0.13

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 (335) hide show
  1. package/README.md +216 -325
  2. package/bin/cli.sh +27 -0
  3. package/dist/{src/ai → ai}/tools.d.ts +0 -1
  4. package/dist/ai/tools.js +161 -0
  5. package/dist/ai/tools.test.d.ts +1 -0
  6. package/dist/ai/tools.test.js +56 -0
  7. package/dist/{src/cli → cli}/auth-requirements.d.ts +0 -1
  8. package/dist/cli/auth-requirements.js +65 -0
  9. package/dist/cli/auth-requirements.test.d.ts +1 -0
  10. package/dist/cli/auth-requirements.test.js +16 -0
  11. package/dist/{src/cli → cli}/auth-schemes.d.ts +0 -1
  12. package/dist/cli/auth-schemes.js +112 -0
  13. package/dist/cli/auth-schemes.test.d.ts +1 -0
  14. package/dist/cli/auth-schemes.test.js +56 -0
  15. package/dist/{src/cli → cli}/capabilities.d.ts +0 -1
  16. package/dist/cli/capabilities.js +41 -0
  17. package/dist/cli/capabilities.test.d.ts +1 -0
  18. package/dist/cli/capabilities.test.js +84 -0
  19. package/dist/{src/cli → cli}/command-id.d.ts +0 -1
  20. package/dist/cli/command-id.js +8 -0
  21. package/dist/cli/command-id.test.d.ts +1 -0
  22. package/dist/cli/command-id.test.js +27 -0
  23. package/dist/{src/cli → cli}/command-index.d.ts +0 -1
  24. package/dist/cli/command-index.js +9 -0
  25. package/dist/{src/cli → cli}/command-model.d.ts +0 -1
  26. package/dist/cli/command-model.js +53 -0
  27. package/dist/cli/command-model.test.d.ts +1 -0
  28. package/dist/cli/command-model.test.js +40 -0
  29. package/dist/{src/cli → cli}/compile.d.ts +0 -1
  30. package/dist/cli/compile.js +79 -0
  31. package/dist/{src/cli → cli}/crypto.d.ts +0 -1
  32. package/dist/cli/crypto.js +9 -0
  33. package/dist/{src/cli → cli}/derive-name.d.ts +0 -1
  34. package/dist/cli/derive-name.js +96 -0
  35. package/dist/{src/cli → cli}/exec.d.ts +0 -1
  36. package/dist/cli/exec.js +50 -0
  37. package/dist/{src/cli → cli}/main.d.ts +0 -1
  38. package/dist/cli/main.js +177 -0
  39. package/dist/{src/cli → cli}/naming.d.ts +0 -1
  40. package/dist/cli/naming.js +191 -0
  41. package/dist/cli/naming.test.d.ts +1 -0
  42. package/dist/cli/naming.test.js +75 -0
  43. package/dist/{src/cli → cli}/operations.d.ts +0 -1
  44. package/dist/cli/operations.js +100 -0
  45. package/dist/cli/operations.test.d.ts +1 -0
  46. package/dist/cli/operations.test.js +51 -0
  47. package/dist/{src/cli → cli}/params.d.ts +0 -1
  48. package/dist/cli/params.js +36 -0
  49. package/dist/cli/params.test.d.ts +1 -0
  50. package/dist/cli/params.test.js +62 -0
  51. package/dist/{src/cli → cli}/pluralize.d.ts +0 -1
  52. package/dist/cli/pluralize.js +38 -0
  53. package/dist/{src/cli → cli}/positional.d.ts +0 -1
  54. package/dist/cli/positional.js +35 -0
  55. package/dist/cli/positional.test.d.ts +1 -0
  56. package/dist/cli/positional.test.js +60 -0
  57. package/dist/{src/cli → cli}/request-body.d.ts +0 -1
  58. package/dist/cli/request-body.js +44 -0
  59. package/dist/cli/request-body.test.d.ts +1 -0
  60. package/dist/cli/request-body.test.js +31 -0
  61. package/dist/{src/cli → cli}/runtime/argv.d.ts +0 -1
  62. package/dist/cli/runtime/argv.js +15 -0
  63. package/dist/{src/cli → cli}/runtime/auth/resolve.d.ts +0 -1
  64. package/dist/cli/runtime/auth/resolve.js +39 -0
  65. package/dist/{src/cli → cli}/runtime/body-flags.d.ts +0 -1
  66. package/dist/cli/runtime/body-flags.js +117 -0
  67. package/dist/cli/runtime/body-flags.test.d.ts +1 -0
  68. package/dist/cli/runtime/body-flags.test.js +192 -0
  69. package/dist/{src/cli → cli}/runtime/body.d.ts +0 -1
  70. package/dist/cli/runtime/body.js +16 -0
  71. package/dist/{src/cli → cli}/runtime/collect.d.ts +0 -1
  72. package/dist/cli/runtime/collect.js +3 -0
  73. package/dist/{src/cli → cli}/runtime/compat.d.ts +0 -1
  74. package/dist/cli/runtime/compat.js +78 -0
  75. package/dist/{src/cli → cli}/runtime/context.d.ts +0 -1
  76. package/dist/cli/runtime/context.js +44 -0
  77. package/dist/{src/cli → cli}/runtime/execute.d.ts +0 -1
  78. package/dist/cli/runtime/execute.js +106 -0
  79. package/dist/{src/cli → cli}/runtime/generated.d.ts +0 -1
  80. package/dist/cli/runtime/generated.js +168 -0
  81. package/dist/{src/cli → cli}/runtime/headers.d.ts +0 -1
  82. package/dist/cli/runtime/headers.js +30 -0
  83. package/dist/{src/cli → cli}/runtime/index.d.ts +0 -1
  84. package/dist/cli/runtime/index.js +3 -0
  85. package/dist/{src/cli → cli}/runtime/profile/secrets.d.ts +0 -1
  86. package/dist/cli/runtime/profile/secrets.js +53 -0
  87. package/dist/{src/cli → cli}/runtime/profile/store.d.ts +0 -1
  88. package/dist/cli/runtime/profile/store.js +63 -0
  89. package/dist/{src/cli → cli}/runtime/request.d.ts +0 -1
  90. package/dist/cli/runtime/request.js +283 -0
  91. package/dist/cli/runtime/request.test.d.ts +1 -0
  92. package/dist/cli/runtime/request.test.js +332 -0
  93. package/dist/{src/cli → cli}/runtime/server-url.d.ts +0 -1
  94. package/dist/cli/runtime/server-url.js +28 -0
  95. package/dist/{src/cli → cli}/runtime/template.d.ts +0 -1
  96. package/dist/cli/runtime/template.js +22 -0
  97. package/dist/cli/runtime/validate/ajv.d.ts +2 -0
  98. package/dist/cli/runtime/validate/ajv.js +11 -0
  99. package/dist/{src/cli → cli}/runtime/validate/coerce.d.ts +0 -1
  100. package/dist/cli/runtime/validate/coerce.js +63 -0
  101. package/dist/cli/runtime/validate/coerce.test.d.ts +1 -0
  102. package/dist/cli/runtime/validate/coerce.test.js +75 -0
  103. package/dist/{src/cli → cli}/runtime/validate/error.d.ts +0 -1
  104. package/dist/cli/runtime/validate/error.js +19 -0
  105. package/dist/{src/cli → cli}/runtime/validate/index.d.ts +0 -1
  106. package/dist/cli/runtime/validate/index.js +4 -0
  107. package/dist/{src/cli → cli}/runtime/validate/schema.d.ts +0 -1
  108. package/dist/cli/runtime/validate/schema.js +38 -0
  109. package/dist/{src/cli → cli}/schema-shape.d.ts +0 -1
  110. package/dist/cli/schema-shape.js +34 -0
  111. package/dist/{src/cli → cli}/schema.d.ts +0 -1
  112. package/dist/cli/schema.js +31 -0
  113. package/dist/{src/cli → cli}/server.d.ts +0 -1
  114. package/dist/cli/server.js +130 -0
  115. package/dist/cli/server.test.d.ts +1 -0
  116. package/dist/cli/server.test.js +49 -0
  117. package/dist/{src/cli → cli}/spec-id.d.ts +0 -1
  118. package/dist/cli/spec-id.js +8 -0
  119. package/dist/{src/cli → cli}/spec-loader.d.ts +0 -1
  120. package/dist/cli/spec-loader.js +40 -0
  121. package/dist/{src/cli → cli}/stable-json.d.ts +0 -1
  122. package/dist/cli/stable-json.js +29 -0
  123. package/dist/{src/cli → cli}/strings.d.ts +0 -1
  124. package/dist/cli/strings.js +20 -0
  125. package/dist/{src/cli → cli}/types.d.ts +0 -1
  126. package/dist/cli/types.js +3 -0
  127. package/dist/cli.d.ts +0 -1
  128. package/dist/cli.js +51 -2324
  129. package/dist/compiled.d.ts +2 -0
  130. package/{src/compiled.ts → dist/compiled.js} +8 -11
  131. package/dist/macros/env.d.ts +10 -0
  132. package/dist/macros/env.js +22 -0
  133. package/dist/macros/spec.d.ts +5 -0
  134. package/dist/macros/spec.js +16 -0
  135. package/package.json +17 -25
  136. package/bin/specli.js +0 -26
  137. package/cli.ts +0 -77
  138. package/dist/cli.d.ts.map +0 -1
  139. package/dist/cli.js.map +0 -53
  140. package/dist/index.d.ts +0 -2
  141. package/dist/index.d.ts.map +0 -1
  142. package/dist/index.js +0 -2032
  143. package/dist/index.js.map +0 -48
  144. package/dist/src/ai/tools.d.ts.map +0 -1
  145. package/dist/src/ai/tools.js +0 -1656
  146. package/dist/src/ai/tools.js.map +0 -45
  147. package/dist/src/cli/auth-requirements.d.ts.map +0 -1
  148. package/dist/src/cli/auth-requirements.js +0 -66
  149. package/dist/src/cli/auth-requirements.js.map +0 -10
  150. package/dist/src/cli/auth-schemes.d.ts.map +0 -1
  151. package/dist/src/cli/auth-schemes.js +0 -116
  152. package/dist/src/cli/auth-schemes.js.map +0 -11
  153. package/dist/src/cli/capabilities.d.ts.map +0 -1
  154. package/dist/src/cli/capabilities.js +0 -45
  155. package/dist/src/cli/capabilities.js.map +0 -10
  156. package/dist/src/cli/command-id.d.ts.map +0 -1
  157. package/dist/src/cli/command-id.js +0 -18
  158. package/dist/src/cli/command-id.js.map +0 -11
  159. package/dist/src/cli/command-index.d.ts.map +0 -1
  160. package/dist/src/cli/command-index.js +0 -15
  161. package/dist/src/cli/command-index.js.map +0 -10
  162. package/dist/src/cli/command-model.d.ts.map +0 -1
  163. package/dist/src/cli/command-model.js +0 -274
  164. package/dist/src/cli/command-model.js.map +0 -18
  165. package/dist/src/cli/compile.d.ts.map +0 -1
  166. package/dist/src/cli/compile.js +0 -146
  167. package/dist/src/cli/compile.js.map +0 -11
  168. package/dist/src/cli/crypto.d.ts.map +0 -1
  169. package/dist/src/cli/crypto.js +0 -15
  170. package/dist/src/cli/crypto.js.map +0 -10
  171. package/dist/src/cli/derive-name.d.ts.map +0 -1
  172. package/dist/src/cli/derive-name.js +0 -70
  173. package/dist/src/cli/derive-name.js.map +0 -10
  174. package/dist/src/cli/exec.d.ts.map +0 -1
  175. package/dist/src/cli/exec.js +0 -2077
  176. package/dist/src/cli/exec.js.map +0 -49
  177. package/dist/src/cli/main.d.ts.map +0 -1
  178. package/dist/src/cli/main.js +0 -2032
  179. package/dist/src/cli/main.js.map +0 -48
  180. package/dist/src/cli/naming.d.ts.map +0 -1
  181. package/dist/src/cli/naming.js +0 -216
  182. package/dist/src/cli/naming.js.map +0 -12
  183. package/dist/src/cli/operations.d.ts.map +0 -1
  184. package/dist/src/cli/operations.js +0 -103
  185. package/dist/src/cli/operations.js.map +0 -10
  186. package/dist/src/cli/params.d.ts.map +0 -1
  187. package/dist/src/cli/params.js +0 -79
  188. package/dist/src/cli/params.js.map +0 -12
  189. package/dist/src/cli/pluralize.d.ts.map +0 -1
  190. package/dist/src/cli/pluralize.js +0 -43
  191. package/dist/src/cli/pluralize.js.map +0 -10
  192. package/dist/src/cli/positional.d.ts.map +0 -1
  193. package/dist/src/cli/positional.js +0 -39
  194. package/dist/src/cli/positional.js.map +0 -10
  195. package/dist/src/cli/request-body.d.ts.map +0 -1
  196. package/dist/src/cli/request-body.js +0 -82
  197. package/dist/src/cli/request-body.js.map +0 -12
  198. package/dist/src/cli/runtime/argv.d.ts.map +0 -1
  199. package/dist/src/cli/runtime/argv.js +0 -22
  200. package/dist/src/cli/runtime/argv.js.map +0 -10
  201. package/dist/src/cli/runtime/auth/resolve.d.ts.map +0 -1
  202. package/dist/src/cli/runtime/auth/resolve.js +0 -38
  203. package/dist/src/cli/runtime/auth/resolve.js.map +0 -10
  204. package/dist/src/cli/runtime/body-flags.d.ts.map +0 -1
  205. package/dist/src/cli/runtime/body-flags.js +0 -86
  206. package/dist/src/cli/runtime/body-flags.js.map +0 -10
  207. package/dist/src/cli/runtime/body.d.ts.map +0 -1
  208. package/dist/src/cli/runtime/body.js +0 -40
  209. package/dist/src/cli/runtime/body.js.map +0 -11
  210. package/dist/src/cli/runtime/collect.d.ts.map +0 -1
  211. package/dist/src/cli/runtime/collect.js +0 -9
  212. package/dist/src/cli/runtime/collect.js.map +0 -10
  213. package/dist/src/cli/runtime/compat.d.ts.map +0 -1
  214. package/dist/src/cli/runtime/compat.js +0 -62
  215. package/dist/src/cli/runtime/compat.js.map +0 -10
  216. package/dist/src/cli/runtime/context.d.ts.map +0 -1
  217. package/dist/src/cli/runtime/context.js +0 -936
  218. package/dist/src/cli/runtime/context.js.map +0 -32
  219. package/dist/src/cli/runtime/execute.d.ts.map +0 -1
  220. package/dist/src/cli/runtime/execute.js +0 -670
  221. package/dist/src/cli/runtime/execute.js.map +0 -22
  222. package/dist/src/cli/runtime/generated.d.ts.map +0 -1
  223. package/dist/src/cli/runtime/generated.js +0 -869
  224. package/dist/src/cli/runtime/generated.js.map +0 -23
  225. package/dist/src/cli/runtime/headers.d.ts.map +0 -1
  226. package/dist/src/cli/runtime/headers.js +0 -36
  227. package/dist/src/cli/runtime/headers.js.map +0 -10
  228. package/dist/src/cli/runtime/index.d.ts.map +0 -1
  229. package/dist/src/cli/runtime/index.js +0 -1808
  230. package/dist/src/cli/runtime/index.js.map +0 -46
  231. package/dist/src/cli/runtime/profile/secrets.d.ts.map +0 -1
  232. package/dist/src/cli/runtime/profile/secrets.js +0 -51
  233. package/dist/src/cli/runtime/profile/secrets.js.map +0 -11
  234. package/dist/src/cli/runtime/profile/store.d.ts.map +0 -1
  235. package/dist/src/cli/runtime/profile/store.js +0 -102
  236. package/dist/src/cli/runtime/profile/store.js.map +0 -11
  237. package/dist/src/cli/runtime/request.d.ts.map +0 -1
  238. package/dist/src/cli/runtime/request.js +0 -571
  239. package/dist/src/cli/runtime/request.js.map +0 -21
  240. package/dist/src/cli/runtime/server-url.d.ts.map +0 -1
  241. package/dist/src/cli/runtime/server-url.js +0 -55
  242. package/dist/src/cli/runtime/server-url.js.map +0 -11
  243. package/dist/src/cli/runtime/template.d.ts.map +0 -1
  244. package/dist/src/cli/runtime/template.js +0 -29
  245. package/dist/src/cli/runtime/template.js.map +0 -10
  246. package/dist/src/cli/runtime/validate/ajv.d.ts +0 -3
  247. package/dist/src/cli/runtime/validate/ajv.d.ts.map +0 -1
  248. package/dist/src/cli/runtime/validate/ajv.js +0 -17
  249. package/dist/src/cli/runtime/validate/ajv.js.map +0 -10
  250. package/dist/src/cli/runtime/validate/coerce.d.ts.map +0 -1
  251. package/dist/src/cli/runtime/validate/coerce.js +0 -60
  252. package/dist/src/cli/runtime/validate/coerce.js.map +0 -10
  253. package/dist/src/cli/runtime/validate/error.d.ts.map +0 -1
  254. package/dist/src/cli/runtime/validate/error.js +0 -21
  255. package/dist/src/cli/runtime/validate/error.js.map +0 -10
  256. package/dist/src/cli/runtime/validate/index.d.ts.map +0 -1
  257. package/dist/src/cli/runtime/validate/index.js +0 -122
  258. package/dist/src/cli/runtime/validate/index.js.map +0 -13
  259. package/dist/src/cli/runtime/validate/schema.d.ts.map +0 -1
  260. package/dist/src/cli/runtime/validate/schema.js +0 -36
  261. package/dist/src/cli/runtime/validate/schema.js.map +0 -10
  262. package/dist/src/cli/schema-shape.d.ts.map +0 -1
  263. package/dist/src/cli/schema-shape.js +0 -41
  264. package/dist/src/cli/schema-shape.js.map +0 -10
  265. package/dist/src/cli/schema.d.ts.map +0 -1
  266. package/dist/src/cli/schema.js +0 -38
  267. package/dist/src/cli/schema.js.map +0 -10
  268. package/dist/src/cli/server.d.ts.map +0 -1
  269. package/dist/src/cli/server.js +0 -64
  270. package/dist/src/cli/server.js.map +0 -11
  271. package/dist/src/cli/spec-id.d.ts.map +0 -1
  272. package/dist/src/cli/spec-id.js +0 -21
  273. package/dist/src/cli/spec-id.js.map +0 -11
  274. package/dist/src/cli/spec-loader.d.ts.map +0 -1
  275. package/dist/src/cli/spec-loader.js +0 -110
  276. package/dist/src/cli/spec-loader.js.map +0 -15
  277. package/dist/src/cli/stable-json.d.ts.map +0 -1
  278. package/dist/src/cli/stable-json.js +0 -35
  279. package/dist/src/cli/stable-json.js.map +0 -10
  280. package/dist/src/cli/strings.d.ts.map +0 -1
  281. package/dist/src/cli/strings.js +0 -16
  282. package/dist/src/cli/strings.js.map +0 -10
  283. package/dist/src/cli/types.d.ts.map +0 -1
  284. package/dist/src/cli/types.js +0 -9
  285. package/dist/src/cli/types.js.map +0 -10
  286. package/index.ts +0 -1
  287. package/src/ai/tools.ts +0 -211
  288. package/src/cli/auth-requirements.ts +0 -91
  289. package/src/cli/auth-schemes.ts +0 -187
  290. package/src/cli/capabilities.ts +0 -88
  291. package/src/cli/command-id.ts +0 -16
  292. package/src/cli/command-index.ts +0 -19
  293. package/src/cli/command-model.ts +0 -128
  294. package/src/cli/compile.ts +0 -101
  295. package/src/cli/crypto.ts +0 -9
  296. package/src/cli/derive-name.ts +0 -101
  297. package/src/cli/exec.ts +0 -72
  298. package/src/cli/main.ts +0 -231
  299. package/src/cli/naming.ts +0 -224
  300. package/src/cli/operations.ts +0 -152
  301. package/src/cli/params.ts +0 -71
  302. package/src/cli/pluralize.ts +0 -41
  303. package/src/cli/positional.ts +0 -75
  304. package/src/cli/request-body.ts +0 -94
  305. package/src/cli/runtime/argv.ts +0 -14
  306. package/src/cli/runtime/auth/resolve.ts +0 -59
  307. package/src/cli/runtime/body-flags.ts +0 -176
  308. package/src/cli/runtime/body.ts +0 -24
  309. package/src/cli/runtime/collect.ts +0 -6
  310. package/src/cli/runtime/compat.ts +0 -89
  311. package/src/cli/runtime/context.ts +0 -62
  312. package/src/cli/runtime/execute.ts +0 -147
  313. package/src/cli/runtime/generated.ts +0 -242
  314. package/src/cli/runtime/headers.ts +0 -37
  315. package/src/cli/runtime/index.ts +0 -3
  316. package/src/cli/runtime/profile/secrets.ts +0 -83
  317. package/src/cli/runtime/profile/store.ts +0 -100
  318. package/src/cli/runtime/request.ts +0 -390
  319. package/src/cli/runtime/server-url.ts +0 -45
  320. package/src/cli/runtime/template.ts +0 -26
  321. package/src/cli/runtime/validate/ajv.ts +0 -13
  322. package/src/cli/runtime/validate/coerce.ts +0 -71
  323. package/src/cli/runtime/validate/error.ts +0 -29
  324. package/src/cli/runtime/validate/index.ts +0 -4
  325. package/src/cli/runtime/validate/schema.ts +0 -54
  326. package/src/cli/schema-shape.ts +0 -36
  327. package/src/cli/schema.ts +0 -76
  328. package/src/cli/server.ts +0 -88
  329. package/src/cli/spec-id.ts +0 -12
  330. package/src/cli/spec-loader.ts +0 -58
  331. package/src/cli/stable-json.ts +0 -35
  332. package/src/cli/strings.ts +0 -21
  333. package/src/cli/types.ts +0 -59
  334. package/src/macros/env.ts +0 -21
  335. package/src/macros/spec.ts +0 -17
@@ -0,0 +1,283 @@
1
+ import { resolveAuthScheme } from "./auth/resolve.js";
2
+ import { getToken } from "./profile/secrets.js";
3
+ import { getProfile, readProfiles } from "./profile/store.js";
4
+ import { resolveServerUrl } from "./server-url.js";
5
+ import { applyTemplate } from "./template.js";
6
+ import { createAjv, deriveValidationSchemas, formatAjvErrors, } from "./validate/index.js";
7
+ function parseKeyValuePairs(pairs) {
8
+ const out = {};
9
+ for (const pair of pairs ?? []) {
10
+ const idx = pair.indexOf("=");
11
+ if (idx === -1)
12
+ throw new Error(`Invalid pair '${pair}', expected name=value`);
13
+ const name = pair.slice(0, idx).trim();
14
+ const value = pair.slice(idx + 1).trim();
15
+ if (!name)
16
+ throw new Error(`Invalid pair '${pair}', missing name`);
17
+ out[name] = value;
18
+ }
19
+ return out;
20
+ }
21
+ function _parseTimeoutMs(value) {
22
+ if (!value)
23
+ return undefined;
24
+ const n = Number(value);
25
+ if (!Number.isFinite(n) || n <= 0)
26
+ throw new Error("--timeout must be a positive number");
27
+ return n;
28
+ }
29
+ function pickAuthSchemeKey(action, globals) {
30
+ if (globals.auth)
31
+ return globals.auth;
32
+ // If operation declares a single requirement set with a single scheme, default to it.
33
+ const req = action.auth.alternatives;
34
+ if (req.length === 1 && req[0]?.length === 1) {
35
+ return req[0][0]?.key;
36
+ }
37
+ return undefined;
38
+ }
39
+ function applyAuth(headers, url, action, globals, authSchemes) {
40
+ const schemeKey = pickAuthSchemeKey(action, globals);
41
+ if (!schemeKey)
42
+ return { headers, url };
43
+ const scheme = authSchemes.find((s) => s.key === schemeKey);
44
+ if (!scheme) {
45
+ throw new Error(`Unknown auth scheme '${schemeKey}'. Available: ${authSchemes
46
+ .map((s) => s.key)
47
+ .join(", ")}`);
48
+ }
49
+ if (scheme.kind === "http-bearer" ||
50
+ scheme.kind === "oauth2" ||
51
+ scheme.kind === "openIdConnect") {
52
+ const token = globals.bearerToken ?? globals.oauthToken;
53
+ if (!token)
54
+ throw new Error("Missing token. Provide --bearer-token <token>.");
55
+ headers.set("Authorization", `Bearer ${token}`);
56
+ return { headers, url };
57
+ }
58
+ if (scheme.kind === "http-basic") {
59
+ if (!globals.username)
60
+ throw new Error("Missing --username for basic auth");
61
+ if (!globals.password)
62
+ throw new Error("Missing --password for basic auth");
63
+ const raw = `${globals.username}:${globals.password}`;
64
+ const encoded = Buffer.from(raw, "utf8").toString("base64");
65
+ headers.set("Authorization", `Basic ${encoded}`);
66
+ return { headers, url };
67
+ }
68
+ if (scheme.kind === "api-key") {
69
+ if (!scheme.name)
70
+ throw new Error(`apiKey scheme '${scheme.key}' missing name`);
71
+ if (!scheme.in)
72
+ throw new Error(`apiKey scheme '${scheme.key}' missing location`);
73
+ if (!globals.apiKey)
74
+ throw new Error("Missing --api-key for apiKey auth");
75
+ if (scheme.in === "header") {
76
+ headers.set(scheme.name, globals.apiKey);
77
+ }
78
+ if (scheme.in === "query") {
79
+ url.searchParams.set(scheme.name, globals.apiKey);
80
+ }
81
+ if (scheme.in === "cookie") {
82
+ const existing = headers.get("Cookie");
83
+ const part = `${scheme.name}=${globals.apiKey}`;
84
+ headers.set("Cookie", existing ? `${existing}; ${part}` : part);
85
+ }
86
+ return { headers, url };
87
+ }
88
+ return { headers, url };
89
+ }
90
+ export async function buildRequest(input) {
91
+ // Always use the "default" profile for simplicity
92
+ const defaultProfileName = "default";
93
+ const profilesFile = await readProfiles();
94
+ const profile = getProfile(profilesFile, defaultProfileName);
95
+ const embedded = input.embeddedDefaults;
96
+ // Merge server vars: CLI flags override embedded defaults
97
+ const embeddedServerVars = parseKeyValuePairs(embedded?.serverVars);
98
+ const cliServerVars = parseKeyValuePairs(input.globals.serverVar);
99
+ const serverVars = { ...embeddedServerVars, ...cliServerVars };
100
+ // Priority: CLI flag > profile > embedded default
101
+ const serverUrl = resolveServerUrl({
102
+ serverOverride: input.globals.server ?? profile?.server ?? embedded?.server,
103
+ servers: input.servers,
104
+ serverVars,
105
+ });
106
+ // Path params: action.positionals order matches templated params order.
107
+ const pathVars = {};
108
+ for (let i = 0; i < input.action.positionals.length; i++) {
109
+ const pos = input.action.positionals[i];
110
+ const raw = input.action.pathArgs[i];
111
+ const value = input.positionalValues[i];
112
+ if (typeof raw === "string" && typeof value === "string") {
113
+ pathVars[raw] = value;
114
+ }
115
+ // Use cli name too as fallback
116
+ if (pos?.name && typeof value === "string") {
117
+ pathVars[pos.name] = value;
118
+ }
119
+ }
120
+ const path = applyTemplate(input.action.path, pathVars, { encode: true });
121
+ // Build the full URL by combining server URL and path.
122
+ // We need to handle the case where path starts with "/" carefully:
123
+ // URL constructor treats absolute paths as relative to origin, not base path.
124
+ const baseUrl = serverUrl.endsWith("/") ? serverUrl : `${serverUrl}/`;
125
+ const relativePath = path.startsWith("/") ? path.slice(1) : path;
126
+ const url = new URL(relativePath, baseUrl);
127
+ const headers = new Headers();
128
+ // Collect declared params for validation.
129
+ const queryValues = {};
130
+ const headerValues = {};
131
+ const cookieValues = {};
132
+ for (const p of input.action.params) {
133
+ if (p.kind !== "flag")
134
+ continue;
135
+ const optValue = input.flagValues[optionKeyFromFlag(p.flag)];
136
+ if (typeof optValue === "undefined")
137
+ continue;
138
+ if (p.in === "query") {
139
+ queryValues[p.name] = optValue;
140
+ }
141
+ if (p.in === "header") {
142
+ headerValues[p.name] = optValue;
143
+ }
144
+ if (p.in === "cookie") {
145
+ cookieValues[p.name] = optValue;
146
+ }
147
+ }
148
+ // Validate params (query/header/cookie) using Ajv.
149
+ const schemas = deriveValidationSchemas(input.action);
150
+ const ajv = createAjv();
151
+ if (schemas.querySchema) {
152
+ const validate = ajv.compile(schemas.querySchema);
153
+ if (!validate(queryValues)) {
154
+ throw new Error(formatAjvErrors(validate.errors));
155
+ }
156
+ }
157
+ if (schemas.headerSchema) {
158
+ const validate = ajv.compile(schemas.headerSchema);
159
+ if (!validate(headerValues)) {
160
+ throw new Error(formatAjvErrors(validate.errors));
161
+ }
162
+ }
163
+ if (schemas.cookieSchema) {
164
+ const validate = ajv.compile(schemas.cookieSchema);
165
+ if (!validate(cookieValues)) {
166
+ throw new Error(formatAjvErrors(validate.errors));
167
+ }
168
+ }
169
+ // Apply params -> query/header/cookie
170
+ for (const [name, value] of Object.entries(queryValues)) {
171
+ if (Array.isArray(value)) {
172
+ for (const item of value) {
173
+ url.searchParams.append(name, String(item));
174
+ }
175
+ continue;
176
+ }
177
+ url.searchParams.set(name, String(value));
178
+ }
179
+ for (const [name, value] of Object.entries(headerValues)) {
180
+ headers.set(name, String(value));
181
+ }
182
+ for (const [name, value] of Object.entries(cookieValues)) {
183
+ const existing = headers.get("Cookie");
184
+ const part = `${name}=${String(value)}`;
185
+ headers.set("Cookie", existing ? `${existing}; ${part}` : part);
186
+ }
187
+ let body;
188
+ if (input.action.requestBody) {
189
+ // Check if any body flags were provided using the flag definitions
190
+ const bodyFlagDefs = input.bodyFlagDefs ?? [];
191
+ const hasBodyFlags = bodyFlagDefs.some((def) => {
192
+ // Commander keeps dots in option names: --address.street -> "address.street"
193
+ const dotKey = def.path.join(".");
194
+ return input.flagValues[dotKey] !== undefined;
195
+ });
196
+ const contentType = input.action.requestBody.preferredContentType;
197
+ if (contentType)
198
+ headers.set("Content-Type", contentType);
199
+ const schema = input.action.requestBodySchema;
200
+ // Check if there are any required fields in the body
201
+ const requiredFields = bodyFlagDefs.filter((d) => d.required);
202
+ if (!hasBodyFlags) {
203
+ if (requiredFields.length > 0) {
204
+ // Error: user must provide required fields
205
+ const flagList = requiredFields.map((d) => `--${d.path.join(".")}`);
206
+ throw new Error(`Required: ${flagList.join(", ")}`);
207
+ }
208
+ // No required fields - send empty body if body is required, otherwise skip
209
+ if (input.action.requestBody.required) {
210
+ body = "{}";
211
+ }
212
+ }
213
+ else {
214
+ if (!contentType?.includes("json")) {
215
+ throw new Error("Body field flags are only supported for JSON request bodies.");
216
+ }
217
+ // Check for missing required fields
218
+ const { findMissingRequired, parseDotNotationFlags } = await import("./body-flags.js");
219
+ const missing = findMissingRequired(input.flagValues, bodyFlagDefs);
220
+ if (missing.length > 0) {
221
+ const missingFlags = missing.map((m) => `--${m}`).join(", ");
222
+ throw new Error(`Missing required fields: ${missingFlags}`);
223
+ }
224
+ // Build nested object from dot-notation flags
225
+ const built = parseDotNotationFlags(input.flagValues, bodyFlagDefs);
226
+ if (schema) {
227
+ const validate = ajv.compile(schema);
228
+ if (!validate(built)) {
229
+ throw new Error(formatAjvErrors(validate.errors));
230
+ }
231
+ }
232
+ body = JSON.stringify(built);
233
+ }
234
+ }
235
+ // Check if user has a stored token (needed for auth scheme auto-selection)
236
+ const storedToken = profile?.name
237
+ ? await getToken(input.specId, profile.name)
238
+ : null;
239
+ // Auth resolution priority: CLI flag > profile > embedded default
240
+ const resolvedAuthScheme = resolveAuthScheme(input.authSchemes, input.action.auth, {
241
+ flagAuthScheme: input.globals.auth,
242
+ profileAuthScheme: profile?.authScheme,
243
+ embeddedAuthScheme: embedded?.auth,
244
+ hasStoredToken: Boolean(storedToken),
245
+ });
246
+ const tokenFromProfile = resolvedAuthScheme ? storedToken : null;
247
+ const globalsWithProfileAuth = {
248
+ ...input.globals,
249
+ auth: resolvedAuthScheme,
250
+ bearerToken: input.globals.bearerToken ??
251
+ input.globals.oauthToken ??
252
+ tokenFromProfile ??
253
+ undefined,
254
+ };
255
+ const final = applyAuth(headers, url, input.action, globalsWithProfileAuth, input.authSchemes);
256
+ const req = new Request(final.url.toString(), {
257
+ method: input.action.method,
258
+ headers: final.headers,
259
+ body,
260
+ });
261
+ const curl = buildCurl(req, body);
262
+ return { request: req, curl };
263
+ }
264
+ function buildCurl(req, body) {
265
+ const parts = ["curl", "-sS", "-X", req.method];
266
+ for (const [k, v] of req.headers.entries()) {
267
+ parts.push("-H", shellQuote(`${k}: ${v}`));
268
+ }
269
+ if (typeof body === "string") {
270
+ parts.push("--data", shellQuote(body));
271
+ }
272
+ parts.push(shellQuote(req.url));
273
+ return parts.join(" ");
274
+ }
275
+ function shellQuote(value) {
276
+ return `'${value.replace(/'/g, `'\\''`)}'`;
277
+ }
278
+ function optionKeyFromFlag(flag) {
279
+ // Commander uses camelCase property names derived from long flag.
280
+ // Example: --x-request-id -> xRequestId
281
+ const name = flag.replace(/^--/, "");
282
+ return name.replace(/-([a-z])/g, (_, c) => String(c).toUpperCase());
283
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,332 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { tmpdir } from "node:os";
3
+ import { generateBodyFlags } from "./body-flags.js";
4
+ import { buildRequest } from "./request.js";
5
+ import { createAjv, formatAjvErrors } from "./validate/index.js";
6
+ function makeAction(partial) {
7
+ return {
8
+ id: "test",
9
+ key: "POST /contacts",
10
+ action: "create",
11
+ pathArgs: [],
12
+ method: "POST",
13
+ path: "/contacts",
14
+ tags: [],
15
+ style: "rest",
16
+ positionals: [],
17
+ flags: [],
18
+ params: [],
19
+ auth: { alternatives: [] },
20
+ requestBody: {
21
+ required: true,
22
+ content: [
23
+ {
24
+ contentType: "application/json",
25
+ required: true,
26
+ schemaType: "object",
27
+ },
28
+ ],
29
+ hasJson: true,
30
+ hasFormUrlEncoded: false,
31
+ hasMultipart: false,
32
+ bodyFlags: ["--data", "--file"],
33
+ preferredContentType: "application/json",
34
+ preferredSchema: undefined,
35
+ },
36
+ requestBodySchema: {
37
+ type: "object",
38
+ properties: {
39
+ name: { type: "string" },
40
+ },
41
+ required: ["name"],
42
+ },
43
+ ...partial,
44
+ };
45
+ }
46
+ describe("buildRequest (requestBody)", () => {
47
+ test("builds body from expanded body flags", async () => {
48
+ const prevHome = process.env.HOME;
49
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
50
+ process.env.HOME = home;
51
+ try {
52
+ const action = makeAction();
53
+ const bodyFlagDefs = generateBodyFlags(action.requestBodySchema, new Set());
54
+ const { request, curl } = await buildRequest({
55
+ specId: "spec",
56
+ action,
57
+ positionalValues: [],
58
+ flagValues: { name: "A" }, // --name A
59
+ globals: {},
60
+ servers: [
61
+ { url: "https://api.example.com", variables: [], variableNames: [] },
62
+ ],
63
+ authSchemes: [],
64
+ bodyFlagDefs,
65
+ });
66
+ expect(request.headers.get("Content-Type")).toBe("application/json");
67
+ expect(await request.clone().text()).toBe('{"name":"A"}');
68
+ expect(curl).toContain("--data");
69
+ expect(curl).toContain('{"name":"A"}');
70
+ }
71
+ finally {
72
+ process.env.HOME = prevHome;
73
+ }
74
+ });
75
+ test("throws when requestBody is required but missing", async () => {
76
+ const prevHome = process.env.HOME;
77
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
78
+ process.env.HOME = home;
79
+ try {
80
+ const action = makeAction();
81
+ const bodyFlagDefs = generateBodyFlags(action.requestBodySchema, new Set());
82
+ await expect(() => buildRequest({
83
+ specId: "spec",
84
+ action,
85
+ positionalValues: [],
86
+ flagValues: {},
87
+ globals: {},
88
+ servers: [
89
+ {
90
+ url: "https://api.example.com",
91
+ variables: [],
92
+ variableNames: [],
93
+ },
94
+ ],
95
+ authSchemes: [],
96
+ bodyFlagDefs,
97
+ })).toThrow("Required: --name");
98
+ }
99
+ finally {
100
+ process.env.HOME = prevHome;
101
+ }
102
+ });
103
+ test("throws friendly error for missing required expanded field", async () => {
104
+ const prevHome = process.env.HOME;
105
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
106
+ process.env.HOME = home;
107
+ try {
108
+ // Schema with two fields, one required
109
+ const action = makeAction({
110
+ requestBodySchema: {
111
+ type: "object",
112
+ properties: {
113
+ name: { type: "string" },
114
+ email: { type: "string" },
115
+ },
116
+ required: ["name"],
117
+ },
118
+ });
119
+ const bodyFlagDefs = generateBodyFlags(action.requestBodySchema, new Set());
120
+ // Provide email but not name (the required one)
121
+ await expect(() => buildRequest({
122
+ specId: "spec",
123
+ action,
124
+ positionalValues: [],
125
+ flagValues: { email: "test@example.com" }, // --email (but missing --name)
126
+ globals: {},
127
+ servers: [
128
+ {
129
+ url: "https://api.example.com",
130
+ variables: [],
131
+ variableNames: [],
132
+ },
133
+ ],
134
+ authSchemes: [],
135
+ bodyFlagDefs,
136
+ })).toThrow("Missing required fields: --name");
137
+ }
138
+ finally {
139
+ process.env.HOME = prevHome;
140
+ }
141
+ });
142
+ test("builds nested object from dot notation flags", async () => {
143
+ const prevHome = process.env.HOME;
144
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
145
+ process.env.HOME = home;
146
+ try {
147
+ const action = makeAction({
148
+ requestBodySchema: {
149
+ type: "object",
150
+ properties: {
151
+ name: { type: "string" },
152
+ address: {
153
+ type: "object",
154
+ properties: {
155
+ street: { type: "string" },
156
+ city: { type: "string" },
157
+ },
158
+ },
159
+ },
160
+ required: ["name"],
161
+ },
162
+ });
163
+ const bodyFlagDefs = generateBodyFlags(action.requestBodySchema, new Set());
164
+ // Dot notation: --address.street and --address.city should create nested object
165
+ const { request } = await buildRequest({
166
+ specId: "spec",
167
+ action,
168
+ positionalValues: [],
169
+ flagValues: {
170
+ name: "Ada",
171
+ "address.street": "123 Main St", // Commander keeps dots in keys
172
+ "address.city": "NYC",
173
+ },
174
+ globals: {},
175
+ servers: [
176
+ { url: "https://api.example.com", variables: [], variableNames: [] },
177
+ ],
178
+ authSchemes: [],
179
+ bodyFlagDefs,
180
+ });
181
+ const body = JSON.parse(await request.clone().text());
182
+ expect(body).toEqual({
183
+ name: "Ada",
184
+ address: {
185
+ street: "123 Main St",
186
+ city: "NYC",
187
+ },
188
+ });
189
+ }
190
+ finally {
191
+ process.env.HOME = prevHome;
192
+ }
193
+ });
194
+ });
195
+ describe("buildRequest (query parameters)", () => {
196
+ test("builds query string from flag values", async () => {
197
+ const prevHome = process.env.HOME;
198
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
199
+ process.env.HOME = home;
200
+ try {
201
+ const action = {
202
+ id: "test",
203
+ key: "GET /contacts",
204
+ action: "list",
205
+ pathArgs: [],
206
+ method: "GET",
207
+ path: "/contacts",
208
+ tags: [],
209
+ style: "rest",
210
+ positionals: [],
211
+ flags: [
212
+ {
213
+ flag: "--limit",
214
+ name: "limit",
215
+ in: "query",
216
+ type: "integer",
217
+ required: false,
218
+ },
219
+ {
220
+ flag: "--name",
221
+ name: "name",
222
+ in: "query",
223
+ type: "string",
224
+ required: false,
225
+ },
226
+ ],
227
+ params: [
228
+ {
229
+ kind: "flag",
230
+ flag: "--limit",
231
+ name: "limit",
232
+ in: "query",
233
+ required: false,
234
+ type: "integer",
235
+ },
236
+ {
237
+ kind: "flag",
238
+ flag: "--name",
239
+ name: "name",
240
+ in: "query",
241
+ required: false,
242
+ type: "string",
243
+ },
244
+ ],
245
+ auth: { alternatives: [] },
246
+ };
247
+ const { request } = await buildRequest({
248
+ specId: "spec",
249
+ action,
250
+ positionalValues: [],
251
+ flagValues: { limit: 10, name: "andrew" },
252
+ globals: {},
253
+ servers: [
254
+ { url: "https://api.example.com", variables: [], variableNames: [] },
255
+ ],
256
+ authSchemes: [],
257
+ });
258
+ expect(request.method).toBe("GET");
259
+ expect(request.url).toBe("https://api.example.com/contacts?limit=10&name=andrew");
260
+ }
261
+ finally {
262
+ process.env.HOME = prevHome;
263
+ }
264
+ });
265
+ test("handles array query parameters", async () => {
266
+ const prevHome = process.env.HOME;
267
+ const home = `${tmpdir()}/specli-test-${crypto.randomUUID()}`;
268
+ process.env.HOME = home;
269
+ try {
270
+ const action = {
271
+ id: "test",
272
+ key: "GET /contacts",
273
+ action: "list",
274
+ pathArgs: [],
275
+ method: "GET",
276
+ path: "/contacts",
277
+ tags: [],
278
+ style: "rest",
279
+ positionals: [],
280
+ flags: [
281
+ {
282
+ flag: "--tag",
283
+ name: "tag",
284
+ in: "query",
285
+ type: "array",
286
+ itemType: "string",
287
+ required: false,
288
+ },
289
+ ],
290
+ params: [
291
+ {
292
+ kind: "flag",
293
+ flag: "--tag",
294
+ name: "tag",
295
+ in: "query",
296
+ required: false,
297
+ type: "array",
298
+ },
299
+ ],
300
+ auth: { alternatives: [] },
301
+ };
302
+ const { request } = await buildRequest({
303
+ specId: "spec",
304
+ action,
305
+ positionalValues: [],
306
+ flagValues: { tag: ["vip", "active"] },
307
+ globals: {},
308
+ servers: [
309
+ { url: "https://api.example.com", variables: [], variableNames: [] },
310
+ ],
311
+ authSchemes: [],
312
+ });
313
+ expect(request.url).toBe("https://api.example.com/contacts?tag=vip&tag=active");
314
+ }
315
+ finally {
316
+ process.env.HOME = prevHome;
317
+ }
318
+ });
319
+ });
320
+ describe("formatAjvErrors", () => {
321
+ test("pretty prints required errors", () => {
322
+ const ajv = createAjv();
323
+ const validate = ajv.compile({
324
+ type: "object",
325
+ properties: { name: { type: "string" } },
326
+ required: ["name"],
327
+ });
328
+ validate({});
329
+ const msg = formatAjvErrors(validate.errors);
330
+ expect(msg).toBe("/ missing required property 'name'");
331
+ });
332
+ });
@@ -5,4 +5,3 @@ export type ResolveServerInput = {
5
5
  serverVars: Record<string, string>;
6
6
  };
7
7
  export declare function resolveServerUrl(input: ResolveServerInput): string;
8
- //# sourceMappingURL=server-url.d.ts.map
@@ -0,0 +1,28 @@
1
+ import { applyTemplate, extractTemplateVars } from "./template.js";
2
+ export function resolveServerUrl(input) {
3
+ // Treat empty string as undefined (serverOverride can come from env vars or profiles)
4
+ const base = input.serverOverride || input.servers[0]?.url;
5
+ if (!base) {
6
+ throw new Error("No server URL found. Provide --server <url> or define servers in the OpenAPI spec.");
7
+ }
8
+ const names = extractTemplateVars(base);
9
+ if (!names.length)
10
+ return base;
11
+ const vars = {};
12
+ for (const name of names) {
13
+ const provided = input.serverVars[name];
14
+ if (typeof provided === "string") {
15
+ vars[name] = provided;
16
+ continue;
17
+ }
18
+ // If spec has default for this var, use it.
19
+ const match = input.servers.find((s) => s.url === base);
20
+ const v = match?.variables.find((x) => x.name === name);
21
+ if (typeof v?.default === "string") {
22
+ vars[name] = v.default;
23
+ continue;
24
+ }
25
+ throw new Error(`Missing server variable '${name}'. Provide --server-var ${name}=...`);
26
+ }
27
+ return applyTemplate(base, vars);
28
+ }
@@ -2,4 +2,3 @@ export declare function extractTemplateVars(template: string): string[];
2
2
  export declare function applyTemplate(template: string, vars: Record<string, string>, options?: {
3
3
  encode?: boolean;
4
4
  }): string;
5
- //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1,22 @@
1
+ export function extractTemplateVars(template) {
2
+ const out = [];
3
+ const re = /\{([^}]+)\}/g;
4
+ while (true) {
5
+ const match = re.exec(template);
6
+ if (!match)
7
+ break;
8
+ out.push((match[1] ?? "").trim());
9
+ }
10
+ return out.filter(Boolean);
11
+ }
12
+ export function applyTemplate(template, vars, options) {
13
+ const encode = options?.encode ?? false;
14
+ return template.replace(/\{([^}]+)\}/g, (_, rawName) => {
15
+ const name = String(rawName).trim();
16
+ const value = vars[name];
17
+ if (typeof value !== "string") {
18
+ throw new Error(`Missing template variable: ${name}`);
19
+ }
20
+ return encode ? encodeURIComponent(value) : value;
21
+ });
22
+ }
@@ -0,0 +1,2 @@
1
+ import { Ajv } from "ajv";
2
+ export declare function createAjv(): Ajv;