specli 0.0.5 → 0.0.8

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 (209) hide show
  1. package/bin/specli.js +19 -0
  2. package/cli.ts +13 -4
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +2331 -0
  6. package/dist/cli.js.map +53 -0
  7. package/dist/index.d.ts +2 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +2032 -0
  10. package/dist/index.js.map +48 -0
  11. package/dist/src/ai/tools.d.ts +139 -0
  12. package/dist/src/ai/tools.d.ts.map +1 -0
  13. package/dist/src/ai/tools.js +1656 -0
  14. package/dist/src/ai/tools.js.map +45 -0
  15. package/dist/src/cli/auth-requirements.d.ts +10 -0
  16. package/dist/src/cli/auth-requirements.d.ts.map +1 -0
  17. package/dist/src/cli/auth-requirements.js +66 -0
  18. package/dist/src/cli/auth-requirements.js.map +10 -0
  19. package/dist/src/cli/auth-schemes.d.ts +22 -0
  20. package/dist/src/cli/auth-schemes.d.ts.map +1 -0
  21. package/dist/src/cli/auth-schemes.js +116 -0
  22. package/dist/src/cli/auth-schemes.js.map +11 -0
  23. package/dist/src/cli/capabilities.d.ts +32 -0
  24. package/dist/src/cli/capabilities.d.ts.map +1 -0
  25. package/dist/src/cli/capabilities.js +45 -0
  26. package/dist/src/cli/capabilities.js.map +10 -0
  27. package/dist/src/cli/command-id.d.ts +8 -0
  28. package/dist/src/cli/command-id.d.ts.map +1 -0
  29. package/dist/src/cli/command-id.js +18 -0
  30. package/dist/src/cli/command-id.js.map +11 -0
  31. package/dist/src/cli/command-index.d.ts +6 -0
  32. package/dist/src/cli/command-index.d.ts.map +1 -0
  33. package/dist/src/cli/command-index.js +15 -0
  34. package/dist/src/cli/command-index.js.map +10 -0
  35. package/dist/src/cli/command-model.d.ts +40 -0
  36. package/dist/src/cli/command-model.d.ts.map +1 -0
  37. package/dist/src/cli/command-model.js +274 -0
  38. package/dist/src/cli/command-model.js.map +18 -0
  39. package/dist/src/cli/compile.d.ts +15 -0
  40. package/dist/src/cli/compile.d.ts.map +1 -0
  41. package/dist/src/cli/compile.js +146 -0
  42. package/dist/src/cli/compile.js.map +11 -0
  43. package/dist/src/cli/crypto.d.ts +2 -0
  44. package/dist/src/cli/crypto.d.ts.map +1 -0
  45. package/dist/src/cli/crypto.js +15 -0
  46. package/dist/src/cli/crypto.js.map +10 -0
  47. package/dist/src/cli/derive-name.d.ts +9 -0
  48. package/dist/src/cli/derive-name.d.ts.map +1 -0
  49. package/dist/src/cli/derive-name.js +70 -0
  50. package/dist/src/cli/derive-name.js.map +10 -0
  51. package/dist/src/cli/exec.d.ts +14 -0
  52. package/dist/src/cli/exec.d.ts.map +1 -0
  53. package/dist/src/cli/exec.js +2077 -0
  54. package/dist/src/cli/exec.js.map +49 -0
  55. package/dist/src/cli/main.d.ts +10 -0
  56. package/dist/src/cli/main.d.ts.map +1 -0
  57. package/dist/src/cli/main.js +2032 -0
  58. package/dist/src/cli/main.js.map +48 -0
  59. package/dist/src/cli/naming.d.ts +12 -0
  60. package/dist/src/cli/naming.d.ts.map +1 -0
  61. package/dist/src/cli/naming.js +216 -0
  62. package/dist/src/cli/naming.js.map +12 -0
  63. package/dist/src/cli/operations.d.ts +3 -0
  64. package/dist/src/cli/operations.d.ts.map +1 -0
  65. package/dist/src/cli/operations.js +103 -0
  66. package/dist/src/cli/operations.js.map +10 -0
  67. package/dist/src/cli/params.d.ts +19 -0
  68. package/dist/src/cli/params.d.ts.map +1 -0
  69. package/dist/src/cli/params.js +79 -0
  70. package/dist/src/cli/params.js.map +12 -0
  71. package/dist/src/cli/pluralize.d.ts +2 -0
  72. package/dist/src/cli/pluralize.d.ts.map +1 -0
  73. package/dist/src/cli/pluralize.js +43 -0
  74. package/dist/src/cli/pluralize.js.map +10 -0
  75. package/dist/src/cli/positional.d.ts +19 -0
  76. package/dist/src/cli/positional.d.ts.map +1 -0
  77. package/dist/src/cli/positional.js +39 -0
  78. package/dist/src/cli/positional.js.map +10 -0
  79. package/dist/src/cli/request-body.d.ts +20 -0
  80. package/dist/src/cli/request-body.d.ts.map +1 -0
  81. package/dist/src/cli/request-body.js +82 -0
  82. package/dist/src/cli/request-body.js.map +12 -0
  83. package/dist/src/cli/runtime/argv.d.ts +3 -0
  84. package/dist/src/cli/runtime/argv.d.ts.map +1 -0
  85. package/dist/src/cli/runtime/argv.js +22 -0
  86. package/dist/src/cli/runtime/argv.js.map +10 -0
  87. package/dist/src/cli/runtime/auth/resolve.d.ts +9 -0
  88. package/dist/src/cli/runtime/auth/resolve.d.ts.map +1 -0
  89. package/dist/src/cli/runtime/auth/resolve.js +38 -0
  90. package/dist/src/cli/runtime/auth/resolve.js.map +10 -0
  91. package/dist/src/cli/runtime/body-flags.d.ts +41 -0
  92. package/dist/src/cli/runtime/body-flags.d.ts.map +1 -0
  93. package/dist/src/cli/runtime/body-flags.js +86 -0
  94. package/dist/src/cli/runtime/body-flags.js.map +10 -0
  95. package/dist/src/cli/runtime/body.d.ts +15 -0
  96. package/dist/src/cli/runtime/body.d.ts.map +1 -0
  97. package/dist/src/cli/runtime/body.js +40 -0
  98. package/dist/src/cli/runtime/body.js.map +11 -0
  99. package/dist/src/cli/runtime/collect.d.ts +2 -0
  100. package/dist/src/cli/runtime/collect.d.ts.map +1 -0
  101. package/dist/src/cli/runtime/collect.js +9 -0
  102. package/dist/src/cli/runtime/collect.js.map +10 -0
  103. package/dist/src/cli/runtime/compat.d.ts +35 -0
  104. package/dist/src/cli/runtime/compat.d.ts.map +1 -0
  105. package/dist/src/cli/runtime/compat.js +62 -0
  106. package/dist/src/cli/runtime/compat.js.map +10 -0
  107. package/dist/src/cli/runtime/context.d.ts +16 -0
  108. package/dist/src/cli/runtime/context.d.ts.map +1 -0
  109. package/dist/src/cli/runtime/context.js +936 -0
  110. package/dist/src/cli/runtime/context.js.map +32 -0
  111. package/dist/src/cli/runtime/execute.d.ts +33 -0
  112. package/dist/src/cli/runtime/execute.d.ts.map +1 -0
  113. package/dist/src/cli/runtime/execute.js +670 -0
  114. package/dist/src/cli/runtime/execute.js.map +22 -0
  115. package/dist/src/cli/runtime/generated.d.ts +14 -0
  116. package/dist/src/cli/runtime/generated.d.ts.map +1 -0
  117. package/dist/src/cli/runtime/generated.js +869 -0
  118. package/dist/src/cli/runtime/generated.js.map +23 -0
  119. package/dist/src/cli/runtime/headers.d.ts +9 -0
  120. package/dist/src/cli/runtime/headers.d.ts.map +1 -0
  121. package/dist/src/cli/runtime/headers.js +36 -0
  122. package/dist/src/cli/runtime/headers.js.map +10 -0
  123. package/dist/src/cli/runtime/index.d.ts +4 -0
  124. package/dist/src/cli/runtime/index.d.ts.map +1 -0
  125. package/dist/src/cli/runtime/index.js +1808 -0
  126. package/dist/src/cli/runtime/index.js.map +46 -0
  127. package/dist/src/cli/runtime/profile/secrets.d.ts +25 -0
  128. package/dist/src/cli/runtime/profile/secrets.d.ts.map +1 -0
  129. package/dist/src/cli/runtime/profile/secrets.js +51 -0
  130. package/dist/src/cli/runtime/profile/secrets.js.map +11 -0
  131. package/dist/src/cli/runtime/profile/store.d.ts +15 -0
  132. package/dist/src/cli/runtime/profile/store.d.ts.map +1 -0
  133. package/dist/src/cli/runtime/profile/store.js +102 -0
  134. package/dist/src/cli/runtime/profile/store.js.map +11 -0
  135. package/dist/src/cli/runtime/request.d.ts +36 -0
  136. package/dist/src/cli/runtime/request.d.ts.map +1 -0
  137. package/dist/src/cli/runtime/request.js +571 -0
  138. package/dist/src/cli/runtime/request.js.map +21 -0
  139. package/dist/src/cli/runtime/server-url.d.ts +8 -0
  140. package/dist/src/cli/runtime/server-url.d.ts.map +1 -0
  141. package/dist/src/cli/runtime/server-url.js +55 -0
  142. package/dist/src/cli/runtime/server-url.js.map +11 -0
  143. package/dist/src/cli/runtime/template.d.ts +5 -0
  144. package/dist/src/cli/runtime/template.d.ts.map +1 -0
  145. package/dist/src/cli/runtime/template.js +29 -0
  146. package/dist/src/cli/runtime/template.js.map +10 -0
  147. package/dist/src/cli/runtime/validate/ajv.d.ts +3 -0
  148. package/dist/src/cli/runtime/validate/ajv.d.ts.map +1 -0
  149. package/dist/src/cli/runtime/validate/ajv.js +17 -0
  150. package/dist/src/cli/runtime/validate/ajv.js.map +10 -0
  151. package/dist/src/cli/runtime/validate/coerce.d.ts +4 -0
  152. package/dist/src/cli/runtime/validate/coerce.d.ts.map +1 -0
  153. package/dist/src/cli/runtime/validate/coerce.js +60 -0
  154. package/dist/src/cli/runtime/validate/coerce.js.map +10 -0
  155. package/dist/src/cli/runtime/validate/error.d.ts +3 -0
  156. package/dist/src/cli/runtime/validate/error.d.ts.map +1 -0
  157. package/dist/src/cli/runtime/validate/error.js +21 -0
  158. package/dist/src/cli/runtime/validate/error.js.map +10 -0
  159. package/dist/src/cli/runtime/validate/index.d.ts +5 -0
  160. package/dist/src/cli/runtime/validate/index.d.ts.map +1 -0
  161. package/dist/src/cli/runtime/validate/index.js +122 -0
  162. package/dist/src/cli/runtime/validate/index.js.map +13 -0
  163. package/dist/src/cli/runtime/validate/schema.d.ts +9 -0
  164. package/dist/src/cli/runtime/validate/schema.d.ts.map +1 -0
  165. package/dist/src/cli/runtime/validate/schema.js +36 -0
  166. package/dist/src/cli/runtime/validate/schema.js.map +10 -0
  167. package/dist/src/cli/schema-shape.d.ts +5 -0
  168. package/dist/src/cli/schema-shape.d.ts.map +1 -0
  169. package/dist/src/cli/schema-shape.js +41 -0
  170. package/dist/src/cli/schema-shape.js.map +10 -0
  171. package/dist/src/cli/schema.d.ts +30 -0
  172. package/dist/src/cli/schema.d.ts.map +1 -0
  173. package/dist/src/cli/schema.js +38 -0
  174. package/dist/src/cli/schema.js.map +10 -0
  175. package/dist/src/cli/server.d.ts +16 -0
  176. package/dist/src/cli/server.d.ts.map +1 -0
  177. package/dist/src/cli/server.js +64 -0
  178. package/dist/src/cli/server.js.map +11 -0
  179. package/dist/src/cli/spec-id.d.ts +3 -0
  180. package/dist/src/cli/spec-id.d.ts.map +1 -0
  181. package/dist/src/cli/spec-id.js +21 -0
  182. package/dist/src/cli/spec-id.js.map +11 -0
  183. package/dist/src/cli/spec-loader.d.ts +7 -0
  184. package/dist/src/cli/spec-loader.d.ts.map +1 -0
  185. package/dist/src/cli/spec-loader.js +110 -0
  186. package/dist/src/cli/spec-loader.js.map +15 -0
  187. package/dist/src/cli/stable-json.d.ts +4 -0
  188. package/dist/src/cli/stable-json.d.ts.map +1 -0
  189. package/dist/src/cli/stable-json.js +35 -0
  190. package/dist/src/cli/stable-json.js.map +10 -0
  191. package/dist/src/cli/strings.d.ts +3 -0
  192. package/dist/src/cli/strings.d.ts.map +1 -0
  193. package/dist/src/cli/strings.js +16 -0
  194. package/dist/src/cli/strings.js.map +10 -0
  195. package/dist/src/cli/types.d.ts +53 -0
  196. package/dist/src/cli/types.d.ts.map +1 -0
  197. package/dist/src/cli/types.js +9 -0
  198. package/dist/src/cli/types.js.map +10 -0
  199. package/package.json +32 -4
  200. package/src/ai/tools.ts +211 -0
  201. package/src/cli/main.ts +3 -2
  202. package/src/cli/runtime/body.ts +3 -3
  203. package/src/cli/runtime/compat.ts +89 -0
  204. package/src/cli/runtime/execute.ts +98 -39
  205. package/src/cli/runtime/generated.ts +111 -4
  206. package/src/cli/runtime/profile/secrets.ts +42 -1
  207. package/src/cli/runtime/profile/store.ts +15 -13
  208. package/src/cli/runtime/request.ts +12 -4
  209. package/src/cli/spec-loader.ts +2 -2
@@ -0,0 +1,53 @@
1
+ export type SpecSource = "embedded" | "file" | "url";
2
+ export type SecurityRequirement = Record<string, string[]>;
3
+ export type OpenApiDoc = {
4
+ openapi: string;
5
+ info?: {
6
+ title?: string;
7
+ version?: string;
8
+ };
9
+ servers?: Array<{
10
+ url: string;
11
+ description?: string;
12
+ variables?: unknown;
13
+ }>;
14
+ security?: SecurityRequirement[];
15
+ components?: {
16
+ securitySchemes?: Record<string, unknown>;
17
+ };
18
+ paths?: Record<string, unknown>;
19
+ };
20
+ export type NormalizedParameter = {
21
+ in: "path" | "query" | "header" | "cookie";
22
+ name: string;
23
+ required: boolean;
24
+ description?: string;
25
+ schema?: unknown;
26
+ };
27
+ export type JsonSchema = Record<string, unknown>;
28
+ export declare function isJsonSchema(value: unknown): value is JsonSchema;
29
+ export type NormalizedRequestBody = {
30
+ required: boolean;
31
+ contentTypes: string[];
32
+ schemasByContentType: Record<string, unknown | undefined>;
33
+ };
34
+ export type NormalizedOperation = {
35
+ key: string;
36
+ method: string;
37
+ path: string;
38
+ operationId?: string;
39
+ tags: string[];
40
+ summary?: string;
41
+ description?: string;
42
+ deprecated?: boolean;
43
+ security?: SecurityRequirement[];
44
+ parameters: NormalizedParameter[];
45
+ requestBody?: NormalizedRequestBody;
46
+ };
47
+ export type LoadedSpec = {
48
+ source: SpecSource;
49
+ id: string;
50
+ fingerprint: string;
51
+ doc: OpenApiDoc;
52
+ };
53
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cli/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,CAAC;AAErD,MAAM,MAAM,mBAAmB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;AAE3D,MAAM,MAAM,UAAU,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,OAAO,CAAC,EAAE,MAAM,CAAC;KACjB,CAAC;IACF,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5E,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,UAAU,CAAC,EAAE;QACZ,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC1C,CAAC;IACF,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAGF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAEhE;AAED,MAAM,MAAM,qBAAqB,GAAG;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,SAAS,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,UAAU,EAAE,mBAAmB,EAAE,CAAC;IAClC,WAAW,CAAC,EAAE,qBAAqB,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACxB,MAAM,EAAE,UAAU,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,UAAU,CAAC;CAChB,CAAC"}
@@ -0,0 +1,9 @@
1
+ // src/cli/types.ts
2
+ function isJsonSchema(value) {
3
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
+ }
5
+ export {
6
+ isJsonSchema
7
+ };
8
+
9
+ //# debugId=D591F75F6084972664756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/cli/types.ts"],
4
+ "sourcesContent": [
5
+ "export type SpecSource = \"embedded\" | \"file\" | \"url\";\n\nexport type SecurityRequirement = Record<string, string[]>;\n\nexport type OpenApiDoc = {\n\topenapi: string;\n\tinfo?: {\n\t\ttitle?: string;\n\t\tversion?: string;\n\t};\n\tservers?: Array<{ url: string; description?: string; variables?: unknown }>;\n\tsecurity?: SecurityRequirement[];\n\tcomponents?: {\n\t\tsecuritySchemes?: Record<string, unknown>;\n\t};\n\tpaths?: Record<string, unknown>;\n};\n\nexport type NormalizedParameter = {\n\tin: \"path\" | \"query\" | \"header\" | \"cookie\";\n\tname: string;\n\trequired: boolean;\n\tdescription?: string;\n\tschema?: unknown;\n};\n\n// Minimal JSON Schema-like shape for validation and flag expansion.\nexport type JsonSchema = Record<string, unknown>;\n\nexport function isJsonSchema(value: unknown): value is JsonSchema {\n\treturn Boolean(value) && typeof value === \"object\" && !Array.isArray(value);\n}\n\nexport type NormalizedRequestBody = {\n\trequired: boolean;\n\tcontentTypes: string[];\n\tschemasByContentType: Record<string, unknown | undefined>;\n};\n\nexport type NormalizedOperation = {\n\tkey: string;\n\tmethod: string;\n\tpath: string;\n\toperationId?: string;\n\ttags: string[];\n\tsummary?: string;\n\tdescription?: string;\n\tdeprecated?: boolean;\n\tsecurity?: SecurityRequirement[];\n\tparameters: NormalizedParameter[];\n\trequestBody?: NormalizedRequestBody;\n};\n\nexport type LoadedSpec = {\n\tsource: SpecSource;\n\tid: string;\n\tfingerprint: string;\n\tdoc: OpenApiDoc;\n};\n"
6
+ ],
7
+ "mappings": ";AA6BO,SAAS,YAAY,CAAC,OAAqC;AAAA,EACjE,OAAO,QAAQ,KAAK,KAAK,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAAA;",
8
+ "debugId": "D591F75F6084972664756E2164756E21",
9
+ "names": []
10
+ }
package/package.json CHANGED
@@ -1,18 +1,40 @@
1
1
  {
2
2
  "name": "specli",
3
- "module": "index.ts",
3
+ "version": "0.0.8",
4
4
  "type": "module",
5
- "version": "0.0.5",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
6
8
  "bin": {
7
- "specli": "./cli.ts"
9
+ "specli": "./bin/specli.js"
10
+ },
11
+ "exports": {
12
+ ".": {
13
+ "bun": "./index.ts",
14
+ "import": {
15
+ "types": "./dist/index.d.ts",
16
+ "default": "./dist/index.js"
17
+ }
18
+ },
19
+ "./ai/tools": {
20
+ "bun": "./src/ai/tools.ts",
21
+ "import": {
22
+ "types": "./dist/src/ai/tools.d.ts",
23
+ "default": "./dist/src/ai/tools.js"
24
+ }
25
+ }
8
26
  },
9
27
  "files": [
28
+ "dist",
29
+ "bin",
10
30
  "cli.ts",
11
31
  "index.ts",
12
32
  "src",
13
33
  "!**/*.test.ts"
14
34
  ],
15
35
  "scripts": {
36
+ "build": "bun run scripts/build.ts",
37
+ "prepublishOnly": "bun run build",
16
38
  "lint": "biome ci",
17
39
  "lint:check": "biome check --write --unsafe",
18
40
  "lint:format": "biome format --write",
@@ -21,6 +43,7 @@
21
43
  "devDependencies": {
22
44
  "@biomejs/biome": "^2.3.11",
23
45
  "@types/bun": "^1.3.6",
46
+ "@types/node": "^22.19.7",
24
47
  "@typescript/native-preview": "^7.0.0-dev.20260120.1"
25
48
  },
26
49
  "dependencies": {
@@ -28,6 +51,11 @@
28
51
  "ajv": "^8.17.1",
29
52
  "ajv-formats": "^3.0.1",
30
53
  "commander": "^14.0.2",
31
- "openapi-types": "^12.1.3"
54
+ "openapi-types": "^12.1.3",
55
+ "yaml": "^2.8.2"
56
+ },
57
+ "peerDependencies": {
58
+ "ai": "^6.0.42",
59
+ "zod": "^4.3.5"
32
60
  }
33
61
  }
@@ -0,0 +1,211 @@
1
+ /**
2
+ * AI SDK tools for specli
3
+ *
4
+ * Provides tools for AI agents to explore and execute OpenAPI specs.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * import { specli } from "specli/ai";
9
+ * import { generateText } from "ai";
10
+ *
11
+ * const result = await generateText({
12
+ * model: yourModel,
13
+ * tools: {
14
+ * api: specli({ spec: "https://api.example.com/openapi.json" }),
15
+ * },
16
+ * prompt: "List all users",
17
+ * });
18
+ * ```
19
+ */
20
+
21
+ import { tool } from "ai";
22
+ import { z } from "zod";
23
+
24
+ import type { CommandAction } from "../cli/command-model.ts";
25
+ import { buildRuntimeContext } from "../cli/runtime/context.ts";
26
+ import { execute } from "../cli/runtime/execute.ts";
27
+ import type { RuntimeGlobals } from "../cli/runtime/request.ts";
28
+
29
+ export type SpecliToolOptions = {
30
+ /** The OpenAPI spec URL or file path */
31
+ spec: string;
32
+ /** Override the server/base URL */
33
+ server?: string;
34
+ /** Server URL template variables */
35
+ serverVars?: Record<string, string>;
36
+ /** Bearer token for authentication */
37
+ bearerToken?: string;
38
+ /** API key for authentication */
39
+ apiKey?: string;
40
+ /** Basic auth credentials */
41
+ basicAuth?: { username: string; password: string };
42
+ /** Auth scheme to use (if multiple are available) */
43
+ authScheme?: string;
44
+ };
45
+
46
+ // Cache contexts to avoid reloading spec on every call
47
+ const contextCache = new Map<
48
+ string,
49
+ Awaited<ReturnType<typeof buildRuntimeContext>>
50
+ >();
51
+
52
+ async function getContext(spec: string) {
53
+ let ctx = contextCache.get(spec);
54
+ if (!ctx) {
55
+ ctx = await buildRuntimeContext({ spec });
56
+ contextCache.set(spec, ctx);
57
+ }
58
+ return ctx;
59
+ }
60
+
61
+ function findAction(
62
+ ctx: Awaited<ReturnType<typeof buildRuntimeContext>>,
63
+ resource: string,
64
+ action: string,
65
+ ): CommandAction | undefined {
66
+ const r = ctx.commands.resources.find(
67
+ (r) => r.resource.toLowerCase() === resource.toLowerCase(),
68
+ );
69
+ return r?.actions.find(
70
+ (a) => a.action.toLowerCase() === action.toLowerCase(),
71
+ );
72
+ }
73
+
74
+ /**
75
+ * Create an AI SDK tool for interacting with an OpenAPI spec.
76
+ */
77
+ export function specli(options: SpecliToolOptions) {
78
+ const {
79
+ spec,
80
+ server,
81
+ serverVars,
82
+ bearerToken,
83
+ apiKey,
84
+ basicAuth,
85
+ authScheme,
86
+ } = options;
87
+
88
+ return tool({
89
+ description: `Execute API operations. Commands: "list" (show resources/actions), "help" (action details), "exec" (call API).`,
90
+ inputSchema: z.object({
91
+ command: z.enum(["list", "help", "exec"]).describe("Command to run"),
92
+ resource: z.string().optional().describe("Resource name (e.g. users)"),
93
+ action: z
94
+ .string()
95
+ .optional()
96
+ .describe("Action name (e.g. list, get, create)"),
97
+ args: z.array(z.string()).optional().describe("Positional arguments"),
98
+ flags: z
99
+ .record(z.string(), z.unknown())
100
+ .optional()
101
+ .describe("Named flags"),
102
+ }),
103
+ execute: async ({ command, resource, action, args, flags }) => {
104
+ const ctx = await getContext(spec);
105
+
106
+ if (command === "list") {
107
+ return {
108
+ resources: ctx.commands.resources.map((r) => ({
109
+ name: r.resource,
110
+ actions: r.actions.map((a) => ({
111
+ name: a.action,
112
+ summary: a.summary,
113
+ method: a.method,
114
+ path: a.path,
115
+ args: a.positionals.map((p) => p.name),
116
+ requiredFlags: a.flags
117
+ .filter((f) => f.required)
118
+ .map((f) => f.flag),
119
+ })),
120
+ })),
121
+ };
122
+ }
123
+
124
+ if (command === "help") {
125
+ if (!resource) return { error: "Missing resource" };
126
+ const r = ctx.commands.resources.find(
127
+ (r) => r.resource.toLowerCase() === resource.toLowerCase(),
128
+ );
129
+ if (!r) return { error: `Unknown resource: ${resource}` };
130
+ if (!action) {
131
+ return {
132
+ resource: r.resource,
133
+ actions: r.actions.map((a) => a.action),
134
+ };
135
+ }
136
+ const a = r.actions.find(
137
+ (a) => a.action.toLowerCase() === action.toLowerCase(),
138
+ );
139
+ if (!a) return { error: `Unknown action: ${action}` };
140
+ return {
141
+ action: a.action,
142
+ method: a.method,
143
+ path: a.path,
144
+ summary: a.summary,
145
+ args: a.positionals.map((p) => ({
146
+ name: p.name,
147
+ description: p.description,
148
+ })),
149
+ flags: a.flags.map((f) => ({
150
+ name: f.flag,
151
+ type: f.type,
152
+ required: f.required,
153
+ description: f.description,
154
+ })),
155
+ };
156
+ }
157
+
158
+ if (command === "exec") {
159
+ if (!resource || !action)
160
+ return { error: "Missing resource or action" };
161
+ const actionDef = findAction(ctx, resource, action);
162
+ if (!actionDef) return { error: `Unknown: ${resource} ${action}` };
163
+
164
+ const positionalValues = args ?? [];
165
+ if (positionalValues.length < actionDef.positionals.length) {
166
+ return {
167
+ error: `Missing args: ${actionDef.positionals
168
+ .slice(positionalValues.length)
169
+ .map((p) => p.name)
170
+ .join(", ")}`,
171
+ };
172
+ }
173
+
174
+ const globals: RuntimeGlobals = {
175
+ server,
176
+ serverVar: serverVars
177
+ ? Object.entries(serverVars).map(([k, v]) => `${k}=${v}`)
178
+ : undefined,
179
+ auth: authScheme,
180
+ bearerToken,
181
+ apiKey,
182
+ username: basicAuth?.username,
183
+ password: basicAuth?.password,
184
+ };
185
+
186
+ try {
187
+ const result = await execute({
188
+ specId: ctx.loaded.id,
189
+ action: actionDef,
190
+ positionalValues,
191
+ flagValues: flags ?? {},
192
+ globals,
193
+ servers: ctx.servers,
194
+ authSchemes: ctx.authSchemes,
195
+ });
196
+ return { status: result.status, ok: result.ok, body: result.body };
197
+ } catch (err) {
198
+ return { error: err instanceof Error ? err.message : String(err) };
199
+ }
200
+ }
201
+
202
+ return { error: `Unknown command: ${command}` };
203
+ },
204
+ });
205
+ }
206
+
207
+ /** Clear cached spec context */
208
+ export function clearSpecliCache(spec?: string): void {
209
+ if (spec) contextCache.delete(spec);
210
+ else contextCache.clear();
211
+ }
package/src/cli/main.ts CHANGED
@@ -2,6 +2,7 @@ import { Command } from "commander";
2
2
 
3
3
  import { getArgValue, hasAnyArg } from "./runtime/argv.ts";
4
4
  import { collectRepeatable } from "./runtime/collect.ts";
5
+ import { readStdinText } from "./runtime/compat.ts";
5
6
  import { buildRuntimeContext } from "./runtime/context.ts";
6
7
  import { addGeneratedCommands } from "./runtime/generated.ts";
7
8
  import { deleteToken, getToken, setToken } from "./runtime/profile/secrets.ts";
@@ -86,8 +87,8 @@ export async function main(argv: string[], options: MainOptions = {}) {
86
87
  }
87
88
  token = Buffer.concat(chunks).toString().trim();
88
89
  } else {
89
- // Piped input - use Bun's stdin stream
90
- const text = await Bun.stdin.text();
90
+ // Piped input - use cross-runtime stdin reading
91
+ const text = await readStdinText();
91
92
  token = text.trim();
92
93
  }
93
94
  }
@@ -1,4 +1,4 @@
1
- import { YAML } from "bun";
1
+ import { parseYamlContent, readFileText } from "./compat.ts";
2
2
 
3
3
  export type BodyInput =
4
4
  | { kind: "none" }
@@ -11,7 +11,7 @@ export async function loadBody(
11
11
  if (input.kind === "none") return undefined;
12
12
  if (input.kind === "data") return { raw: input.data };
13
13
 
14
- const text = await Bun.file(input.path).text();
14
+ const text = await readFileText(input.path);
15
15
  return { raw: text };
16
16
  }
17
17
 
@@ -20,5 +20,5 @@ export function parseBodyAsJsonOrYaml(text: string): unknown {
20
20
  if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
21
21
  return JSON.parse(text);
22
22
  }
23
- return YAML.parse(text);
23
+ return parseYamlContent(text);
24
24
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Cross-runtime compatibility utilities for Bun and Node.js
3
+ *
4
+ * This module provides abstractions over Bun-specific APIs to allow
5
+ * the exec command to run in Node.js while compile remains Bun-only.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
9
+ import { parse as parseYaml } from "yaml";
10
+
11
+ /**
12
+ * Detect if we're running in Bun
13
+ */
14
+ export const isBun = typeof globalThis.Bun !== "undefined";
15
+
16
+ /**
17
+ * Read a file's text content - works in both Bun and Node.js
18
+ */
19
+ export async function readFileText(path: string): Promise<string> {
20
+ if (isBun) {
21
+ return Bun.file(path).text();
22
+ }
23
+ return readFileSync(path, "utf-8");
24
+ }
25
+
26
+ /**
27
+ * Check if a file exists - works in both Bun and Node.js
28
+ */
29
+ export async function fileExists(path: string): Promise<boolean> {
30
+ if (isBun) {
31
+ return Bun.file(path).exists();
32
+ }
33
+ return existsSync(path);
34
+ }
35
+
36
+ /**
37
+ * Write text to a file - works in both Bun and Node.js
38
+ */
39
+ export async function writeFileText(
40
+ path: string,
41
+ content: string,
42
+ ): Promise<void> {
43
+ if (isBun) {
44
+ await Bun.write(path, content);
45
+ return;
46
+ }
47
+ writeFileSync(path, content, "utf-8");
48
+ }
49
+
50
+ /**
51
+ * Create directory recursively - works in both Bun and Node.js
52
+ */
53
+ export async function mkdirp(path: string): Promise<void> {
54
+ if (isBun) {
55
+ await Bun.$`mkdir -p ${path}`;
56
+ return;
57
+ }
58
+ mkdirSync(path, { recursive: true });
59
+ }
60
+
61
+ /**
62
+ * Parse YAML content - works in both Bun and Node.js
63
+ */
64
+ export function parseYamlContent(text: string): unknown {
65
+ if (isBun) {
66
+ const { YAML } = globalThis.Bun;
67
+ return YAML.parse(text);
68
+ }
69
+ return parseYaml(text);
70
+ }
71
+
72
+ /**
73
+ * Read from stdin - works in both Bun and Node.js
74
+ */
75
+ export async function readStdinText(): Promise<string> {
76
+ if (isBun) {
77
+ return Bun.stdin.text();
78
+ }
79
+ // Node.js stdin reading
80
+ return new Promise((resolve, reject) => {
81
+ let data = "";
82
+ process.stdin.setEncoding("utf8");
83
+ process.stdin.on("data", (chunk) => {
84
+ data += chunk;
85
+ });
86
+ process.stdin.on("end", () => resolve(data));
87
+ process.stdin.on("error", reject);
88
+ });
89
+ }
@@ -13,51 +13,108 @@ export type ExecuteInput = {
13
13
  specId: string;
14
14
  embeddedDefaults?: EmbeddedDefaults;
15
15
  bodyFlagDefs?: BodyFlagDef[];
16
+ /** Resource name for error messages (e.g. "plans") */
17
+ resourceName?: string;
16
18
  };
17
19
 
20
+ export type ExecuteResult = {
21
+ ok: boolean;
22
+ status: number;
23
+ body: unknown;
24
+ curl: string;
25
+ };
26
+
27
+ /**
28
+ * Format an error message with a help hint.
29
+ */
30
+ function formatError(
31
+ message: string,
32
+ resourceName: string | undefined,
33
+ actionName: string,
34
+ ): string {
35
+ const helpCmd = resourceName
36
+ ? `${resourceName} ${actionName} --help`
37
+ : `${actionName} --help`;
38
+ return `${message}\n\nRun '${helpCmd}' to see available options.`;
39
+ }
40
+
41
+ /**
42
+ * Execute an action and return the result as data.
43
+ * This is the core execution function used by both CLI and programmatic API.
44
+ */
45
+ export async function execute(
46
+ input: Omit<ExecuteInput, "resourceName">,
47
+ ): Promise<ExecuteResult> {
48
+ const { request, curl } = await buildRequest({
49
+ specId: input.specId,
50
+ action: input.action,
51
+ positionalValues: input.positionalValues,
52
+ flagValues: input.flagValues,
53
+ globals: input.globals,
54
+ servers: input.servers,
55
+ authSchemes: input.authSchemes,
56
+ embeddedDefaults: input.embeddedDefaults,
57
+ bodyFlagDefs: input.bodyFlagDefs,
58
+ });
59
+
60
+ const res = await fetch(request);
61
+ const contentType = res.headers.get("content-type") ?? "";
62
+
63
+ const text = await res.text();
64
+ let body: unknown = text;
65
+
66
+ if (contentType.includes("json") && text) {
67
+ try {
68
+ body = JSON.parse(text);
69
+ } catch {
70
+ // keep as text
71
+ }
72
+ }
73
+
74
+ return {
75
+ ok: res.ok,
76
+ status: res.status,
77
+ body,
78
+ curl,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Execute an action and write output to stdout/stderr.
84
+ * This is the CLI-facing wrapper around execute().
85
+ */
18
86
  export async function executeAction(input: ExecuteInput): Promise<void> {
19
- try {
20
- const { request, curl } = await buildRequest({
21
- specId: input.specId,
22
- action: input.action,
23
- positionalValues: input.positionalValues,
24
- flagValues: input.flagValues,
25
- globals: input.globals,
26
- servers: input.servers,
27
- authSchemes: input.authSchemes,
28
- embeddedDefaults: input.embeddedDefaults,
29
- bodyFlagDefs: input.bodyFlagDefs,
30
- });
87
+ const actionName = input.action.action;
88
+ const resourceName = input.resourceName;
31
89
 
90
+ try {
32
91
  if (input.globals.curl) {
92
+ const { curl } = await buildRequest({
93
+ specId: input.specId,
94
+ action: input.action,
95
+ positionalValues: input.positionalValues,
96
+ flagValues: input.flagValues,
97
+ globals: input.globals,
98
+ servers: input.servers,
99
+ authSchemes: input.authSchemes,
100
+ embeddedDefaults: input.embeddedDefaults,
101
+ bodyFlagDefs: input.bodyFlagDefs,
102
+ });
33
103
  process.stdout.write(`${curl}\n`);
34
104
  return;
35
105
  }
36
106
 
37
- const res = await fetch(request);
38
- const contentType = res.headers.get("content-type") ?? "";
39
- const status = res.status;
40
-
41
- const text = await res.text();
42
- let body: unknown = text;
43
- let parsedJson: unknown | undefined;
107
+ const result = await execute(input);
44
108
 
45
- if (contentType.includes("json")) {
46
- try {
47
- parsedJson = text ? JSON.parse(text) : null;
48
- body = parsedJson;
49
- } catch {
50
- // keep as text
51
- }
52
- }
53
-
54
- if (!res.ok) {
109
+ if (!result.ok) {
55
110
  if (input.globals.json) {
56
- process.stdout.write(`${JSON.stringify({ status, body })}\n`);
111
+ process.stdout.write(
112
+ `${JSON.stringify({ status: result.status, body: result.body })}\n`,
113
+ );
57
114
  } else {
58
- process.stderr.write(`HTTP ${status}\n`);
115
+ process.stderr.write(`HTTP ${result.status}\n`);
59
116
  process.stderr.write(
60
- `${typeof body === "string" ? body : JSON.stringify(body, null, 2)}\n`,
117
+ `${typeof result.body === "string" ? result.body : JSON.stringify(result.body, null, 2)}\n`,
61
118
  );
62
119
  }
63
120
  process.exitCode = 1;
@@ -65,21 +122,23 @@ export async function executeAction(input: ExecuteInput): Promise<void> {
65
122
  }
66
123
 
67
124
  if (input.globals.json) {
68
- process.stdout.write(`${JSON.stringify(body)}\n`);
125
+ process.stdout.write(`${JSON.stringify(result.body)}\n`);
69
126
  return;
70
127
  }
71
128
 
72
129
  // default (human + agent readable)
73
- if (typeof parsedJson !== "undefined") {
74
- process.stdout.write(`${JSON.stringify(parsedJson, null, 2)}\n`);
130
+ if (typeof result.body === "string") {
131
+ process.stdout.write(result.body);
132
+ if (!result.body.endsWith("\n")) process.stdout.write("\n");
75
133
  } else {
76
- process.stdout.write(text);
77
- if (!text.endsWith("\n")) process.stdout.write("\n");
134
+ process.stdout.write(`${JSON.stringify(result.body, null, 2)}\n`);
78
135
  }
79
136
  } catch (err) {
80
- const message = err instanceof Error ? err.message : String(err);
137
+ const rawMessage = err instanceof Error ? err.message : String(err);
138
+ const message = formatError(rawMessage, resourceName, actionName);
139
+
81
140
  if (input.globals.json) {
82
- process.stdout.write(`${JSON.stringify({ error: message })}\n`);
141
+ process.stdout.write(`${JSON.stringify({ error: rawMessage })}\n`);
83
142
  } else {
84
143
  process.stderr.write(`error: ${message}\n`);
85
144
  }