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,191 @@
1
+ import { pluralize } from "./pluralize.js";
2
+ import { kebabCase } from "./strings.js";
3
+ const GENERIC_TAGS = new Set(["default", "defaults", "api"]);
4
+ function getPathSegments(path) {
5
+ return path
6
+ .split("/")
7
+ .map((s) => s.trim())
8
+ .filter(Boolean);
9
+ }
10
+ function getPathArgs(path) {
11
+ const args = [];
12
+ const re = /\{([^}]+)\}/g;
13
+ while (true) {
14
+ const match = re.exec(path);
15
+ if (!match)
16
+ break;
17
+ // biome-ignore lint/style/noNonNullAssertion: unknown
18
+ args.push(match[1]);
19
+ }
20
+ return args;
21
+ }
22
+ function pickResourceFromTags(tags) {
23
+ if (!tags.length)
24
+ return undefined;
25
+ const first = tags[0]?.trim();
26
+ if (!first)
27
+ return undefined;
28
+ if (GENERIC_TAGS.has(first.toLowerCase()))
29
+ return undefined;
30
+ return first;
31
+ }
32
+ function splitOperationId(operationId) {
33
+ const trimmed = operationId.trim();
34
+ if (!trimmed)
35
+ return {};
36
+ // Prefer dot-notation when present: Contacts.List
37
+ if (trimmed.includes(".")) {
38
+ const [prefix, ...rest] = trimmed.split(".");
39
+ return { prefix, suffix: rest.join(".") };
40
+ }
41
+ // Try separators: Contacts_List, Contacts__List
42
+ if (trimmed.includes("__")) {
43
+ const [prefix, ...rest] = trimmed.split("__");
44
+ return { prefix, suffix: rest.join("__") };
45
+ }
46
+ if (trimmed.includes("_")) {
47
+ const [prefix, ...rest] = trimmed.split("_");
48
+ return { prefix, suffix: rest.join("_") };
49
+ }
50
+ return { suffix: trimmed };
51
+ }
52
+ function inferStyle(op) {
53
+ // Path-based RPC convention (common in gRPC-ish HTTP gateways)
54
+ // - POST /Contacts.List
55
+ // - POST /Contacts/Service.List
56
+ if (op.path.includes("."))
57
+ return "rpc";
58
+ // operationId dot-notation alone is not enough to call it RPC; many REST APIs
59
+ // have dotted ids. We treat dotted operationId as a weak signal.
60
+ if (op.operationId?.includes(".") && op.method === "POST")
61
+ return "rpc";
62
+ return "rest";
63
+ }
64
+ function inferResource(op) {
65
+ const tag = pickResourceFromTags(op.tags);
66
+ if (tag)
67
+ return pluralize(kebabCase(tag));
68
+ if (op.operationId) {
69
+ const { prefix } = splitOperationId(op.operationId);
70
+ if (prefix) {
71
+ const fromId = kebabCase(prefix);
72
+ if (fromId === "ping")
73
+ return "ping";
74
+ return pluralize(fromId);
75
+ }
76
+ }
77
+ const segments = getPathSegments(op.path);
78
+ let first = segments[0] ?? "api";
79
+ // If first segment is rpc-ish, like Contacts.List, split it.
80
+ // biome-ignore lint/style/noNonNullAssertion: split always returns at least one element
81
+ first = first.includes(".") ? first.split(".")[0] : first;
82
+ // Singletons like /ping generally shouldn't become `pings`.
83
+ if (first.toLowerCase() === "ping")
84
+ return "ping";
85
+ // Strip path params if they appear in first segment (rare)
86
+ const cleaned = first.replace(/^\{.+\}$/, "");
87
+ return pluralize(kebabCase(cleaned || "api"));
88
+ }
89
+ function canonicalizeAction(action) {
90
+ const a = kebabCase(action);
91
+ // Common RPC verbs -> REST canonical verbs
92
+ if (a === "retrieve" || a === "read")
93
+ return "get";
94
+ if (a === "list" || a === "search")
95
+ return "list";
96
+ if (a === "create")
97
+ return "create";
98
+ if (a === "update" || a === "patch")
99
+ return "update";
100
+ if (a === "delete" || a === "remove")
101
+ return "delete";
102
+ return a;
103
+ }
104
+ function inferRestAction(op) {
105
+ // If operationId is present and looks intentional, prefer it.
106
+ // This helps with singleton endpoints like GET /ping (Ping.Get) vs collections.
107
+ if (op.operationId) {
108
+ const { suffix } = splitOperationId(op.operationId);
109
+ if (suffix) {
110
+ const fromId = canonicalizeAction(suffix);
111
+ if (fromId === "get" ||
112
+ fromId === "list" ||
113
+ fromId === "create" ||
114
+ fromId === "update" ||
115
+ fromId === "delete") {
116
+ return fromId;
117
+ }
118
+ }
119
+ }
120
+ const method = op.method.toUpperCase();
121
+ const args = getPathArgs(op.path);
122
+ const hasId = args.length > 0;
123
+ if (method === "GET" && !hasId)
124
+ return "list";
125
+ if (method === "POST" && !hasId)
126
+ return "create";
127
+ if (method === "GET" && hasId)
128
+ return "get";
129
+ if ((method === "PUT" || method === "PATCH") && hasId)
130
+ return "update";
131
+ if (method === "DELETE" && hasId)
132
+ return "delete";
133
+ return kebabCase(method);
134
+ }
135
+ function inferRpcAction(op) {
136
+ // Prefer operationId suffix: Contacts.List -> list
137
+ if (op.operationId) {
138
+ const { suffix } = splitOperationId(op.operationId);
139
+ if (suffix)
140
+ return canonicalizeAction(suffix);
141
+ }
142
+ // Else take last segment and split by '.'
143
+ const segments = getPathSegments(op.path);
144
+ const last = segments[segments.length - 1] ?? "";
145
+ if (last.includes(".")) {
146
+ const part = last.split(".").pop() ?? last;
147
+ return canonicalizeAction(part);
148
+ }
149
+ return kebabCase(op.method);
150
+ }
151
+ export function planOperation(op) {
152
+ const style = inferStyle(op);
153
+ const resource = inferResource(op);
154
+ const action = style === "rpc" ? inferRpcAction(op) : inferRestAction(op);
155
+ return {
156
+ ...op,
157
+ key: op.key,
158
+ style,
159
+ resource,
160
+ action,
161
+ canonicalAction: action,
162
+ pathArgs: getPathArgs(op.path).map((a) => kebabCase(a)),
163
+ };
164
+ }
165
+ export function planOperations(ops) {
166
+ const planned = ops.map(planOperation);
167
+ // Stable collision handling: if resource+action repeats, add a suffix.
168
+ const counts = new Map();
169
+ for (const op of planned) {
170
+ const key = `${op.resource}:${op.action}`;
171
+ counts.set(key, (counts.get(key) ?? 0) + 1);
172
+ }
173
+ const seen = new Map();
174
+ return planned.map((op) => {
175
+ const key = `${op.resource}:${op.action}`;
176
+ const total = counts.get(key) ?? 0;
177
+ if (total <= 1)
178
+ return op;
179
+ const idx = (seen.get(key) ?? 0) + 1;
180
+ seen.set(key, idx);
181
+ const suffix = op.operationId
182
+ ? kebabCase(op.operationId)
183
+ : kebabCase(`${op.method}-${op.path}`);
184
+ const disambiguatedAction = `${op.action}-${suffix}-${idx}`;
185
+ return {
186
+ ...op,
187
+ action: disambiguatedAction,
188
+ aliasOf: `${op.resource} ${op.canonicalAction}`,
189
+ };
190
+ });
191
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,75 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { planOperation } from "./naming.js";
3
+ describe("planOperation", () => {
4
+ test("REST: GET /contacts -> contacts list", () => {
5
+ const op = {
6
+ key: "GET /contacts",
7
+ method: "GET",
8
+ path: "/contacts",
9
+ operationId: "Contacts.List",
10
+ tags: ["Contacts"],
11
+ parameters: [],
12
+ };
13
+ const planned = planOperation(op);
14
+ expect(planned.style).toBe("rest");
15
+ expect(planned.resource).toBe("contacts");
16
+ expect(planned.action).toBe("list");
17
+ expect(planned.pathArgs).toEqual([]);
18
+ });
19
+ test("REST: singleton /ping stays ping and prefers operationId action", () => {
20
+ const op = {
21
+ key: "GET /ping",
22
+ method: "GET",
23
+ path: "/ping",
24
+ operationId: "Ping.Get",
25
+ tags: [],
26
+ parameters: [],
27
+ };
28
+ const planned = planOperation(op);
29
+ expect(planned.style).toBe("rest");
30
+ expect(planned.resource).toBe("ping");
31
+ expect(planned.action).toBe("get");
32
+ });
33
+ test("REST: singular path pluralizes to contacts", () => {
34
+ const op = {
35
+ key: "GET /contact/{id}",
36
+ method: "GET",
37
+ path: "/contact/{id}",
38
+ tags: [],
39
+ parameters: [],
40
+ };
41
+ const planned = planOperation(op);
42
+ expect(planned.style).toBe("rest");
43
+ expect(planned.resource).toBe("contacts");
44
+ expect(planned.action).toBe("get");
45
+ expect(planned.pathArgs).toEqual(["id"]);
46
+ });
47
+ test("RPC: POST /Contacts.List -> contacts list", () => {
48
+ const op = {
49
+ key: "POST /Contacts.List",
50
+ method: "POST",
51
+ path: "/Contacts.List",
52
+ operationId: "Contacts.List",
53
+ tags: [],
54
+ parameters: [],
55
+ };
56
+ const planned = planOperation(op);
57
+ expect(planned.style).toBe("rpc");
58
+ expect(planned.resource).toBe("contacts");
59
+ expect(planned.action).toBe("list");
60
+ });
61
+ test("RPC: Retrieve canonicalizes to get", () => {
62
+ const op = {
63
+ key: "POST /Contacts.Retrieve",
64
+ method: "POST",
65
+ path: "/Contacts.Retrieve",
66
+ operationId: "Contacts.Retrieve",
67
+ tags: [],
68
+ parameters: [],
69
+ };
70
+ const planned = planOperation(op);
71
+ expect(planned.style).toBe("rpc");
72
+ expect(planned.resource).toBe("contacts");
73
+ expect(planned.action).toBe("get");
74
+ });
75
+ });
@@ -1,3 +1,2 @@
1
1
  import type { NormalizedOperation, OpenApiDoc } from "./types.js";
2
2
  export declare function indexOperations(doc: OpenApiDoc): NormalizedOperation[];
3
- //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1,100 @@
1
+ function operationKey(method, path) {
2
+ return `${method.toUpperCase()} ${path}`;
3
+ }
4
+ const HTTP_METHODS = [
5
+ "get",
6
+ "post",
7
+ "put",
8
+ "patch",
9
+ "delete",
10
+ "options",
11
+ "head",
12
+ "trace",
13
+ ];
14
+ function normalizeParam(p) {
15
+ if (!p || typeof p !== "object")
16
+ return undefined;
17
+ const loc = p.in;
18
+ const name = p.name;
19
+ if (loc !== "path" &&
20
+ loc !== "query" &&
21
+ loc !== "header" &&
22
+ loc !== "cookie") {
23
+ return undefined;
24
+ }
25
+ if (!name)
26
+ return undefined;
27
+ return {
28
+ in: loc,
29
+ name,
30
+ required: Boolean(p.required || loc === "path"),
31
+ description: p.description,
32
+ schema: p.schema,
33
+ };
34
+ }
35
+ function mergeParameters(pathParams, opParams) {
36
+ const merged = new Map();
37
+ for (const p of pathParams ?? []) {
38
+ const normalized = normalizeParam(p);
39
+ if (!normalized)
40
+ continue;
41
+ merged.set(`${normalized.in}:${normalized.name}`, normalized);
42
+ }
43
+ for (const p of opParams ?? []) {
44
+ const normalized = normalizeParam(p);
45
+ if (!normalized)
46
+ continue;
47
+ merged.set(`${normalized.in}:${normalized.name}`, normalized);
48
+ }
49
+ return [...merged.values()];
50
+ }
51
+ function normalizeRequestBody(rb) {
52
+ if (!rb)
53
+ return undefined;
54
+ const content = rb.content ?? {};
55
+ const contentTypes = Object.keys(content);
56
+ const schemasByContentType = {};
57
+ for (const contentType of contentTypes) {
58
+ schemasByContentType[contentType] = content[contentType]?.schema;
59
+ }
60
+ return {
61
+ required: Boolean(rb.required),
62
+ contentTypes,
63
+ schemasByContentType,
64
+ };
65
+ }
66
+ export function indexOperations(doc) {
67
+ const out = [];
68
+ const paths = doc.paths ?? {};
69
+ for (const [path, rawPathItem] of Object.entries(paths)) {
70
+ if (!rawPathItem || typeof rawPathItem !== "object")
71
+ continue;
72
+ const pathItem = rawPathItem;
73
+ for (const method of HTTP_METHODS) {
74
+ const op = pathItem[method];
75
+ if (!op)
76
+ continue;
77
+ const parameters = mergeParameters(pathItem.parameters, op.parameters);
78
+ const normalizedMethod = method.toUpperCase();
79
+ out.push({
80
+ key: operationKey(normalizedMethod, path),
81
+ method: normalizedMethod,
82
+ path,
83
+ operationId: op.operationId,
84
+ tags: op.tags ?? [],
85
+ summary: op.summary,
86
+ description: op.description,
87
+ deprecated: op.deprecated,
88
+ security: (op.security ?? doc.security),
89
+ parameters,
90
+ requestBody: normalizeRequestBody(op.requestBody),
91
+ });
92
+ }
93
+ }
94
+ out.sort((a, b) => {
95
+ if (a.path !== b.path)
96
+ return a.path.localeCompare(b.path);
97
+ return a.method.localeCompare(b.method);
98
+ });
99
+ return out;
100
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { indexOperations } from "./operations.js";
3
+ describe("indexOperations", () => {
4
+ test("indexes basic operations", () => {
5
+ const doc = {
6
+ openapi: "3.0.3",
7
+ paths: {
8
+ "/contacts": {
9
+ get: {
10
+ operationId: "Contacts.List",
11
+ tags: ["Contacts"],
12
+ parameters: [
13
+ {
14
+ in: "query",
15
+ name: "limit",
16
+ schema: { type: "integer" },
17
+ },
18
+ ],
19
+ },
20
+ },
21
+ "/contacts/{id}": {
22
+ get: {
23
+ operationId: "Contacts.Get",
24
+ tags: ["Contacts"],
25
+ parameters: [
26
+ {
27
+ in: "path",
28
+ name: "id",
29
+ required: true,
30
+ schema: { type: "string" },
31
+ },
32
+ ],
33
+ },
34
+ },
35
+ },
36
+ };
37
+ const ops = indexOperations(doc);
38
+ expect(ops).toHaveLength(2);
39
+ expect(ops[0]?.key).toBe("GET /contacts");
40
+ expect(ops[0]?.path).toBe("/contacts");
41
+ expect(ops[0]?.method).toBe("GET");
42
+ expect(ops[0]?.parameters).toHaveLength(1);
43
+ expect(ops[0]?.parameters[0]?.in).toBe("query");
44
+ expect(ops[1]?.key).toBe("GET /contacts/{id}");
45
+ expect(ops[1]?.path).toBe("/contacts/{id}");
46
+ expect(ops[1]?.method).toBe("GET");
47
+ expect(ops[1]?.parameters).toHaveLength(1);
48
+ expect(ops[1]?.parameters[0]?.in).toBe("path");
49
+ expect(ops[1]?.parameters[0]?.required).toBe(true);
50
+ });
51
+ });
@@ -16,4 +16,3 @@ export type ParamSpec = {
16
16
  schema?: import("./types.js").JsonSchema;
17
17
  };
18
18
  export declare function deriveParamSpecs(op: NormalizedOperation): ParamSpec[];
19
- //# sourceMappingURL=params.d.ts.map
@@ -0,0 +1,36 @@
1
+ import { getSchemaEnumStrings, getSchemaFormat, getSchemaType, } from "./schema-shape.js";
2
+ import { kebabCase } from "./strings.js";
3
+ export function deriveParamSpecs(op) {
4
+ const out = [];
5
+ for (const p of op.parameters) {
6
+ const flag = `--${kebabCase(p.name)}`;
7
+ const type = getSchemaType(p.schema);
8
+ const schemaObj = p.schema && typeof p.schema === "object"
9
+ ? p.schema
10
+ : undefined;
11
+ const itemsSchema = schemaObj && type === "array" && typeof schemaObj.items === "object"
12
+ ? schemaObj.items
13
+ : undefined;
14
+ out.push({
15
+ kind: p.in === "path" ? "positional" : "flag",
16
+ in: p.in,
17
+ name: p.name,
18
+ flag,
19
+ required: p.required,
20
+ description: p.description,
21
+ type,
22
+ format: getSchemaFormat(p.schema),
23
+ enum: getSchemaEnumStrings(p.schema),
24
+ itemType: type === "array" ? getSchemaType(itemsSchema) : undefined,
25
+ itemFormat: type === "array" ? getSchemaFormat(itemsSchema) : undefined,
26
+ itemEnum: type === "array" ? getSchemaEnumStrings(itemsSchema) : undefined,
27
+ schema: schemaObj,
28
+ });
29
+ }
30
+ out.sort((a, b) => {
31
+ if (a.in !== b.in)
32
+ return a.in.localeCompare(b.in);
33
+ return a.name.localeCompare(b.name);
34
+ });
35
+ return out;
36
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { deriveParamSpecs } from "./params.js";
3
+ describe("deriveParamSpecs", () => {
4
+ test("derives basic types + flags", () => {
5
+ const op = {
6
+ key: "GET /contacts",
7
+ method: "GET",
8
+ path: "/contacts",
9
+ tags: [],
10
+ parameters: [
11
+ {
12
+ in: "query",
13
+ name: "limit",
14
+ required: false,
15
+ schema: {
16
+ type: "integer",
17
+ format: "int32",
18
+ enum: ["1", "2"],
19
+ },
20
+ },
21
+ {
22
+ in: "header",
23
+ name: "X-Request-Id",
24
+ required: false,
25
+ schema: { type: "string" },
26
+ },
27
+ ],
28
+ };
29
+ const specs = deriveParamSpecs(op);
30
+ expect(specs).toHaveLength(2);
31
+ const limit = specs.find((p) => p.name === "limit");
32
+ expect(limit?.kind).toBe("flag");
33
+ expect(limit?.flag).toBe("--limit");
34
+ expect(limit?.type).toBe("integer");
35
+ expect(limit?.format).toBe("int32");
36
+ expect(limit?.enum).toEqual(["1", "2"]);
37
+ const reqId = specs.find((p) => p.name === "X-Request-Id");
38
+ expect(reqId?.kind).toBe("flag");
39
+ expect(reqId?.flag).toBe("--x-request-id");
40
+ expect(reqId?.type).toBe("string");
41
+ });
42
+ test("derives array item types", () => {
43
+ const op = {
44
+ key: "GET /things",
45
+ method: "GET",
46
+ path: "/things",
47
+ tags: [],
48
+ parameters: [
49
+ {
50
+ in: "query",
51
+ name: "ids",
52
+ required: false,
53
+ schema: { type: "array", items: { type: "integer" } },
54
+ },
55
+ ],
56
+ };
57
+ const specs = deriveParamSpecs(op);
58
+ expect(specs).toHaveLength(1);
59
+ expect(specs[0]?.type).toBe("array");
60
+ expect(specs[0]?.itemType).toBe("integer");
61
+ });
62
+ });
@@ -1,2 +1 @@
1
1
  export declare function pluralize(word: string): string;
2
- //# sourceMappingURL=pluralize.d.ts.map
@@ -0,0 +1,38 @@
1
+ const IRREGULAR = {
2
+ person: "people",
3
+ man: "men",
4
+ woman: "women",
5
+ child: "children",
6
+ tooth: "teeth",
7
+ foot: "feet",
8
+ mouse: "mice",
9
+ goose: "geese",
10
+ };
11
+ const UNCOUNTABLE = new Set([
12
+ "metadata",
13
+ "information",
14
+ "equipment",
15
+ "money",
16
+ "series",
17
+ "species",
18
+ ]);
19
+ export function pluralize(word) {
20
+ const w = word.trim();
21
+ if (!w)
22
+ return w;
23
+ const lower = w.toLowerCase();
24
+ if (UNCOUNTABLE.has(lower))
25
+ return lower;
26
+ if (IRREGULAR[lower])
27
+ return IRREGULAR[lower];
28
+ // already plural-ish
29
+ if (lower.endsWith("s"))
30
+ return lower;
31
+ if (/[bcdfghjklmnpqrstvwxyz]y$/.test(lower)) {
32
+ return lower.replace(/y$/, "ies");
33
+ }
34
+ if (/(ch|sh|x|z)$/.test(lower)) {
35
+ return `${lower}es`;
36
+ }
37
+ return `${lower}s`;
38
+ }
@@ -16,4 +16,3 @@ export type FlagsIndex = {
16
16
  };
17
17
  export declare function derivePositionals(action: ActionShapeForCli): PositionalArg[];
18
18
  export declare function deriveFlags(action: ActionShapeForCli): FlagsIndex;
19
- //# sourceMappingURL=positional.d.ts.map
@@ -0,0 +1,35 @@
1
+ export function derivePositionals(action) {
2
+ const byName = new Map();
3
+ // Use pathArgs order; match metadata from params when available.
4
+ for (const name of action.pathArgs) {
5
+ const p = action.params.find((x) => x.in === "path" && x.name === name);
6
+ byName.set(name, {
7
+ name,
8
+ required: true,
9
+ description: p?.description,
10
+ type: p?.type ?? "unknown",
11
+ format: p?.format,
12
+ enum: p?.enum,
13
+ });
14
+ }
15
+ return [...byName.values()];
16
+ }
17
+ export function deriveFlags(action) {
18
+ return {
19
+ flags: action.params
20
+ .filter((p) => p.kind === "flag")
21
+ .map((p) => ({
22
+ in: p.in,
23
+ name: p.name,
24
+ flag: p.flag,
25
+ required: p.required,
26
+ description: p.description,
27
+ type: p.type,
28
+ format: p.format,
29
+ enum: p.enum,
30
+ itemType: p.itemType,
31
+ itemFormat: p.itemFormat,
32
+ itemEnum: p.itemEnum,
33
+ })),
34
+ };
35
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,60 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { deriveFlags, derivePositionals } from "./positional.js";
3
+ describe("derivePositionals", () => {
4
+ test("returns ordered positionals from pathArgs", () => {
5
+ const action = {
6
+ pathArgs: ["id"],
7
+ params: [
8
+ {
9
+ kind: "positional",
10
+ in: "path",
11
+ name: "id",
12
+ flag: "--id",
13
+ required: true,
14
+ type: "string",
15
+ },
16
+ ],
17
+ };
18
+ const pos = derivePositionals(action);
19
+ expect(pos).toEqual([
20
+ {
21
+ name: "id",
22
+ required: true,
23
+ type: "string",
24
+ format: undefined,
25
+ enum: undefined,
26
+ description: undefined,
27
+ },
28
+ ]);
29
+ });
30
+ });
31
+ describe("deriveFlags", () => {
32
+ test("returns only flag params", () => {
33
+ const action = {
34
+ pathArgs: [],
35
+ params: [
36
+ {
37
+ kind: "flag",
38
+ in: "query",
39
+ name: "limit",
40
+ flag: "--limit",
41
+ required: false,
42
+ type: "integer",
43
+ },
44
+ ],
45
+ };
46
+ const flags = deriveFlags(action);
47
+ expect(flags.flags).toEqual([
48
+ {
49
+ in: "query",
50
+ name: "limit",
51
+ flag: "--limit",
52
+ required: false,
53
+ description: undefined,
54
+ type: "integer",
55
+ format: undefined,
56
+ enum: undefined,
57
+ },
58
+ ]);
59
+ });
60
+ });