specli 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CLAUDE.md +111 -0
  2. package/PLAN.md +274 -0
  3. package/README.md +474 -0
  4. package/biome.jsonc +1 -0
  5. package/bun.lock +98 -0
  6. package/cli.ts +74 -0
  7. package/fixtures/openapi-array-items.json +22 -0
  8. package/fixtures/openapi-auth.json +34 -0
  9. package/fixtures/openapi-body.json +41 -0
  10. package/fixtures/openapi-collision.json +21 -0
  11. package/fixtures/openapi-oauth.json +54 -0
  12. package/fixtures/openapi-servers.json +35 -0
  13. package/fixtures/openapi.json +87 -0
  14. package/index.ts +1 -0
  15. package/package.json +27 -0
  16. package/scripts/smoke-specs.ts +64 -0
  17. package/src/cli/auth-requirements.test.ts +27 -0
  18. package/src/cli/auth-requirements.ts +91 -0
  19. package/src/cli/auth-schemes.test.ts +66 -0
  20. package/src/cli/auth-schemes.ts +187 -0
  21. package/src/cli/capabilities.test.ts +94 -0
  22. package/src/cli/capabilities.ts +88 -0
  23. package/src/cli/command-id.test.ts +32 -0
  24. package/src/cli/command-id.ts +16 -0
  25. package/src/cli/command-index.ts +19 -0
  26. package/src/cli/command-model.test.ts +44 -0
  27. package/src/cli/command-model.ts +128 -0
  28. package/src/cli/compile.ts +119 -0
  29. package/src/cli/crypto.ts +9 -0
  30. package/src/cli/derive-name.ts +101 -0
  31. package/src/cli/exec.ts +72 -0
  32. package/src/cli/main.ts +336 -0
  33. package/src/cli/naming.test.ts +86 -0
  34. package/src/cli/naming.ts +224 -0
  35. package/src/cli/operations.test.ts +57 -0
  36. package/src/cli/operations.ts +152 -0
  37. package/src/cli/params.test.ts +70 -0
  38. package/src/cli/params.ts +71 -0
  39. package/src/cli/pluralize.ts +41 -0
  40. package/src/cli/positional.test.ts +65 -0
  41. package/src/cli/positional.ts +75 -0
  42. package/src/cli/request-body.test.ts +35 -0
  43. package/src/cli/request-body.ts +94 -0
  44. package/src/cli/runtime/argv.ts +14 -0
  45. package/src/cli/runtime/auth/resolve.ts +31 -0
  46. package/src/cli/runtime/body.ts +24 -0
  47. package/src/cli/runtime/collect.ts +6 -0
  48. package/src/cli/runtime/context.ts +62 -0
  49. package/src/cli/runtime/execute.ts +138 -0
  50. package/src/cli/runtime/generated.ts +200 -0
  51. package/src/cli/runtime/headers.ts +37 -0
  52. package/src/cli/runtime/index.ts +3 -0
  53. package/src/cli/runtime/profile/secrets.ts +42 -0
  54. package/src/cli/runtime/profile/store.ts +98 -0
  55. package/src/cli/runtime/request.test.ts +153 -0
  56. package/src/cli/runtime/request.ts +487 -0
  57. package/src/cli/runtime/server-url.ts +44 -0
  58. package/src/cli/runtime/template.ts +26 -0
  59. package/src/cli/runtime/validate/ajv.ts +13 -0
  60. package/src/cli/runtime/validate/coerce.ts +71 -0
  61. package/src/cli/runtime/validate/error.ts +29 -0
  62. package/src/cli/runtime/validate/index.ts +4 -0
  63. package/src/cli/runtime/validate/schema.ts +54 -0
  64. package/src/cli/schema-shape.ts +36 -0
  65. package/src/cli/schema.ts +76 -0
  66. package/src/cli/server.test.ts +35 -0
  67. package/src/cli/server.ts +88 -0
  68. package/src/cli/spec-id.ts +12 -0
  69. package/src/cli/spec-loader.ts +58 -0
  70. package/src/cli/stable-json.ts +35 -0
  71. package/src/cli/strings.ts +21 -0
  72. package/src/cli/types.ts +59 -0
  73. package/src/compiled.ts +23 -0
  74. package/src/macros/env.ts +25 -0
  75. package/src/macros/spec.ts +17 -0
  76. package/tsconfig.json +29 -0
package/README.md ADDED
@@ -0,0 +1,474 @@
1
+ # opencli
2
+
3
+ OpenCLI turns an OpenAPI spec into a non-interactive, “curl replacement” CLI.
4
+
5
+ It has two modes:
6
+
7
+ - **exec**: run commands dynamically from a spec URL/path.
8
+ - **compile**: bundle the spec into a standalone executable.
9
+
10
+ The guiding constraints:
11
+
12
+ - Bun-first (no Node runtime needed).
13
+ - Suitable for automation/agents (stable commands, machine output via `--json`).
14
+ - Best-effort validation of inputs against the OpenAPI schema.
15
+
16
+ ## Status / What Works Today
17
+
18
+ It works well for a large chunk of “typical” OpenAPI 3.x REST specs:
19
+
20
+ - Multiple servers + server variables.
21
+ - Path/query/header/cookie parameters.
22
+ - Request bodies via `--data` / `--file`.
23
+ - JSON request body parsing + schema validation.
24
+ - Expanded JSON body flags for simple object bodies (`--body-*`).
25
+ - Auth injection for common schemes (bearer/basic/apiKey).
26
+ - A deterministic `__schema` output for introspection.
27
+
28
+ It is not “universal OpenAPI support” yet. See “Limitations” for important gaps.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ bun install
34
+ ```
35
+
36
+ ## Quickstart
37
+
38
+ Inspect what commands will be generated:
39
+
40
+ ```bash
41
+ bunx opencli exec ./fixtures/openapi.json __schema
42
+ ```
43
+
44
+ Machine-readable schema output:
45
+
46
+ ```bash
47
+ bunx opencli exec ./fixtures/openapi.json __schema --json
48
+ ```
49
+
50
+ Minimal schema output (best for large specs):
51
+
52
+ ```bash
53
+ bunx opencli exec ./fixtures/openapi.json __schema --json --min
54
+ ```
55
+
56
+ Run a generated operation:
57
+
58
+ ```bash
59
+ bunx opencli exec ./fixtures/openapi.json contacts list --oc-curl
60
+ ```
61
+
62
+ ## Build a Standalone Executable
63
+
64
+ Use the `compile` command to create a standalone binary with the spec embedded:
65
+
66
+ ```bash
67
+ # compile with auto-derived name (from spec title)
68
+ bunx opencli compile ./path/to/openapi.yaml
69
+ # → ./dist/my-api (derived from info.title)
70
+
71
+ # compile with explicit name
72
+ bunx opencli compile ./path/to/openapi.yaml --name myapi
73
+ # → ./dist/myapi
74
+
75
+ # cross-compile (example: linux x64)
76
+ bunx opencli compile https://api.vercel.com/copper/_openapi.json --target bun-linux-x64 --outfile ./dist/copper-linux
77
+
78
+ # disable runtime config loading for deterministic behavior
79
+ bunx opencli compile ./path/to/openapi.yaml --no-dotenv --no-bunfig
80
+
81
+ # bake in defaults (these become default flags; runtime flags override)
82
+ bunx opencli compile https://api.vercel.com/copper/_openapi.json \
83
+ --name copper \
84
+ --server https://api.vercel.com \
85
+ --auth VercelOidc
86
+ ```
87
+
88
+ The compiled binary is a root CLI - no `opencli` prefix needed:
89
+
90
+ ```bash
91
+ ./dist/copper contacts list
92
+ ./dist/copper users get abc123 --json
93
+ ```
94
+
95
+ Notes:
96
+
97
+ - The spec is embedded at compile-time using a Bun macro.
98
+ - If `--name` is not provided, it is derived from the OpenAPI `info.title` or URL hostname.
99
+ - Runtime flags (e.g., `--server`) override baked-in defaults.
100
+
101
+ ## CLI Shape
102
+
103
+ OpenCLI generates commands of the form:
104
+
105
+ ```
106
+ opencli <resource> <action> [...positionals] [options]
107
+ ```
108
+
109
+ - `resource` comes from `tags[0]`, `operationId` prefix, or the first path segment (heuristics).
110
+ - `action` is inferred from HTTP method + “has id in path”, or from `operationId` suffix.
111
+ - Name collisions are disambiguated deterministically by suffixing the action name.
112
+
113
+ Use `__schema` to see the planned mapping for your spec.
114
+
115
+ ## Global Options
116
+
117
+ Available on the root command:
118
+
119
+ - `--spec <urlOrPath>`: OpenAPI URL or file path (only needed for compiled binaries to override embedded spec)
120
+ - `--server <url>`: override server/base URL
121
+ - `--server-var <name=value>`: server URL template variable (repeatable)
122
+ - `--profile <name>`: profile name (config under `~/.config/opencli`)
123
+
124
+ Auth selection + credentials:
125
+
126
+ - `--auth <scheme>`: pick an auth scheme by key
127
+ - `--bearer-token <token>`: set `Authorization: Bearer <token>`
128
+ - `--oauth-token <token>`: alias of `--bearer-token`
129
+ - `--username <username>` / `--password <password>`: basic auth
130
+ - `--api-key <key>`: value for apiKey auth
131
+
132
+ Output mode:
133
+
134
+ - `--json`: machine-readable output (never prints stack traces)
135
+
136
+ ## Per-Operation Common Options
137
+
138
+ Every generated operation command includes:
139
+
140
+ - `--header <header>` (repeatable): extra headers; accepts `Name: Value` or `Name=Value`
141
+ - `--accept <type>`: override `Accept` header
142
+ - `--timeout <ms>`: request timeout in milliseconds
143
+ - `--dry-run`: print the request that would be sent (no network call)
144
+ - `--curl`: print an equivalent `curl` command (no network call)
145
+
146
+ For operations with `requestBody`, it also includes:
147
+
148
+ - `--data <data>`: inline request body
149
+ - `--file <path>`: read request body from a file
150
+ - `--content-type <type>`: override `Content-Type` (defaults from OpenAPI)
151
+
152
+ ## Parameter Mapping
153
+
154
+ ### Path Parameters
155
+
156
+ OpenAPI parameters where `in: path` become positional arguments.
157
+
158
+ - Order is derived from the path template: `/users/{id}/keys/{key_id}` becomes `<id> <key-id>`.
159
+ - Values are URL-encoded when applied to the path.
160
+
161
+ ### Query / Header / Cookie Parameters
162
+
163
+ OpenAPI parameters where `in: query|header|cookie` become flags.
164
+
165
+ Flag name rules:
166
+
167
+ - `--${kebabCase(parameter.name)}`
168
+ - Examples:
169
+ - `limit` -> `--limit`
170
+ - `X-Request-Id` -> `--x-request-id`
171
+
172
+ Required flags:
173
+
174
+ - If the spec says `required: true`, the CLI flag is marked required (Commander enforces this).
175
+
176
+ Type coercion:
177
+
178
+ - `string` -> string
179
+ - `integer` -> `parseInt`
180
+ - `number` -> `Number(...)`
181
+ - `boolean` -> flag presence (no value)
182
+ - `object` -> JSON object literal string, parsed via `JSON.parse`
183
+ - `array` -> see below
184
+
185
+ ### Arrays (Improved UX)
186
+
187
+ Array parameters are treated as repeatable flags and appended to the query string.
188
+
189
+ All of these become `?tag=a&tag=b`:
190
+
191
+ ```bash
192
+ opencli ... --tag a --tag b
193
+ opencli ... --tag a,b
194
+ opencli ... --tag '["a","b"]'
195
+ ```
196
+
197
+ Implementation notes:
198
+
199
+ - The query string is built with repeated keys (`URLSearchParams.append`).
200
+ - Array item types are derived from `schema.items.type` when present (e.g. integer arrays validate and coerce correctly).
201
+
202
+ ## Request Bodies
203
+
204
+ ### Selecting the Body Input
205
+
206
+ If an operation has a `requestBody`, you may provide a body via:
207
+
208
+ - `--data <string>`
209
+ - `--file <path>`
210
+ - Expanded `--body-*` flags (JSON-only; see below)
211
+
212
+ Rules:
213
+
214
+ - `--data` and `--file` are mutually exclusive.
215
+ - Expanded `--body-*` flags cannot be used with `--data` or `--file`.
216
+ - If `requestBody.required` is true and you provide none of the above, the command fails with:
217
+ - `Missing request body. Provide --data, --file, or --body-* flags.`
218
+
219
+ ### Content-Type
220
+
221
+ `Content-Type` is chosen as:
222
+
223
+ 1. `--content-type` (explicit override)
224
+ 2. The preferred content type derived from the OpenAPI requestBody (prefers `application/json` when present)
225
+
226
+ ### JSON Parsing + Normalization
227
+
228
+ If the selected `Content-Type` includes `json`:
229
+
230
+ - `--data`/`--file` content is parsed as either JSON or YAML
231
+ - the request is sent as normalized JSON (`JSON.stringify(parsed)`)
232
+
233
+ If `Content-Type` does not include `json`:
234
+
235
+ - the body is treated as a raw string
236
+
237
+ ### Schema Validation (Ajv)
238
+
239
+ OpenCLI uses Ajv (best-effort, `strict: false`) to validate:
240
+
241
+ - query/header/cookie params
242
+ - JSON request bodies when a requestBody schema is available
243
+
244
+ Validation errors are formatted into a readable multiline message. For `required` errors, the message is normalized to:
245
+
246
+ - `/<path> missing required property '<name>'`
247
+
248
+ ### Expanded JSON Body Flags (`--body-*`)
249
+
250
+ When an operation has a `requestBody` and the preferred schema is a JSON object with scalar properties, OpenCLI generates convenience flags:
251
+
252
+ - For `string|number|integer`: `--body-<prop> <value>`
253
+ - For `boolean`: `--body-<prop>` (presence sets it to `true`)
254
+
255
+ Example (from `fixtures/openapi-body.json`):
256
+
257
+ ```bash
258
+ bunx opencli exec ./fixtures/openapi-body.json contacts create --body-name "Ada" --oc-curl
259
+ ```
260
+
261
+ Produces a JSON body:
262
+
263
+ ```json
264
+ {"name":"Ada"}
265
+ ```
266
+
267
+ Notes / edge cases:
268
+
269
+ - Expanded flags are only supported for JSON bodies. If you try to use them without a JSON content type, OpenCLI errors.
270
+ - Required fields in the schema are checked in a “friendly” way for expanded flags:
271
+ - `Missing required body field 'name'. Provide --body-name or use --data/--file.`
272
+ - Numeric coercion uses `Number(...)` / `parseInt(...)`. Today it does not explicitly reject `NaN` (this is an area to harden).
273
+
274
+ ## Servers
275
+
276
+ OpenCLI resolves the request base URL in this order:
277
+
278
+ 1. `--server <url>`
279
+ 2. profile `server` (if `--profile` is set and the profile has a server)
280
+ 3. the first `servers[0].url` in the OpenAPI spec
281
+
282
+ If the chosen server URL has template variables (e.g. `https://{region}.api.example.com`):
283
+
284
+ - Provide `--server-var region=us-east-1` (repeatable)
285
+ - If the spec defines a default for that variable, it is used automatically
286
+
287
+ ## Authentication
288
+
289
+ ### Supported Scheme Kinds
290
+
291
+ From `components.securitySchemes`, OpenCLI recognizes:
292
+
293
+ - HTTP bearer (`type: http`, `scheme: bearer`)
294
+ - HTTP basic (`type: http`, `scheme: basic`)
295
+ - API key (`type: apiKey`, `in: header|query|cookie`)
296
+ - OAuth2 (`type: oauth2`) (treated as bearer token injection)
297
+ - OpenID Connect (`type: openIdConnect`) (treated as bearer token injection)
298
+
299
+ ### Selecting an Auth Scheme
300
+
301
+ Scheme selection happens in this order:
302
+
303
+ 1. `--auth <scheme>` (explicit)
304
+ 2. profile `authScheme` (only if that key exists in the current spec)
305
+ 3. if the operation requires exactly one scheme, it is chosen
306
+ 4. if the spec defines exactly one scheme total, it is chosen
307
+
308
+ This “only if present in current spec” behavior prevents accidental auth leakage between different specs.
309
+
310
+ ### Providing Credentials
311
+
312
+ Bearer-like schemes (`http-bearer`, `oauth2`, `openIdConnect`):
313
+
314
+ - `--bearer-token <token>` or `--oauth-token <token>`
315
+ - or a profile token stored via `opencli auth token ...`
316
+
317
+ Basic auth:
318
+
319
+ - `--username <username>` and `--password <password>`
320
+
321
+ API key:
322
+
323
+ - `--api-key <key>` (injected into the header/query/cookie location declared by the scheme)
324
+
325
+ ## Profiles (Non-Interactive)
326
+
327
+ Profiles are for automation.
328
+
329
+ Config file:
330
+
331
+ - Read preference: `~/.config/opencli/profiles.json`, else `~/.config/opencli/profiles.yaml` if present
332
+ - Writes always go to: `~/.config/opencli/profiles.json`
333
+
334
+ Secrets:
335
+
336
+ - Tokens are stored in Bun’s secrets store (`bun.secrets`) under a spec-scoped service name.
337
+
338
+ Commands:
339
+
340
+ ```bash
341
+ opencli profile list
342
+ opencli profile set --name dev --server https://api.example.com --auth bearerAuth --default
343
+ opencli profile use --name dev
344
+ opencli profile rm --name dev
345
+
346
+ opencli auth token --name dev --set "..."
347
+ opencli auth token --name dev --get
348
+ opencli auth token --name dev --delete
349
+ ```
350
+
351
+ ## Output Behavior
352
+
353
+ ### Default (Human + Agent Readable)
354
+
355
+ - On success:
356
+ - if response `content-type` includes `json`, prints pretty JSON
357
+ - otherwise prints raw text
358
+ - On non-2xx HTTP:
359
+ - prints `HTTP <status>` and response body
360
+ - exits with code 1
361
+ - On CLI/validation errors:
362
+ - prints `error: <message>`
363
+ - exits with code 1
364
+
365
+ ### `--json` (Machine Readable)
366
+
367
+ - On success:
368
+ - if response is JSON, prints the parsed JSON
369
+ - otherwise prints the raw string
370
+ - With `--status` and/or `--headers`, wraps output:
371
+
372
+ ```json
373
+ { "status": 200, "headers": { "content-type": "..." }, "body": ... }
374
+ ```
375
+
376
+ - On non-2xx HTTP:
377
+
378
+ ```json
379
+ { "status": 404, "body": ..., "headers": { ... } }
380
+ ```
381
+
382
+ - On CLI/validation errors:
383
+
384
+ ```json
385
+ { "error": "..." }
386
+ ```
387
+
388
+ ### `--curl`
389
+
390
+ Prints an equivalent curl invocation without sending the request.
391
+
392
+ ### `--dry-run`
393
+
394
+ Prints the method, URL, headers, and body that would be sent without sending the request.
395
+
396
+ ## `__schema`
397
+
398
+ `opencli __schema` reports:
399
+
400
+ - OpenAPI title/version
401
+ - spec source + computed spec id + fingerprint
402
+ - servers/auth schemes counts
403
+ - list of normalized operations
404
+ - planned command mapping
405
+
406
+ Flags:
407
+
408
+ - `--json`: JSON output
409
+ - `--pretty`: pretty JSON
410
+ - `--min`: minimal schema payload (commands + metadata only)
411
+
412
+ ## Recommended Public Specs to Test
413
+
414
+ A good “real world” smoke test matrix includes:
415
+
416
+ 1. Vercel API (OpenAPI 3.x)
417
+ - URL: `https://api.vercel.com/copper/_openapi.json`
418
+ - Focus: real-world parameter naming collisions (e.g. `accept`), large-ish spec
419
+
420
+ 2. GitHub REST API (OpenAPI 3.1, very large)
421
+ - URL (huge): `https://raw.githubusercontent.com/github/rest-api-description/main/descriptions-next/api.github.com/api.github.com.json`
422
+ - Focus: size, OAS 3.1, many endpoints, varied parameter shapes
423
+
424
+ 3. DigitalOcean API (OpenAPI 3.0)
425
+ - URL: `https://raw.githubusercontent.com/digitalocean/openapi/main/specification/DigitalOcean-public.v2.yaml`
426
+ - Focus: big spec + deref cycles, request bodies, auth schemes
427
+
428
+ 4. Stripe API (OpenAPI 3.x, very complex schemas)
429
+ - URL: `https://raw.githubusercontent.com/stripe/openapi/master/openapi/spec3.yaml`
430
+ - Focus: very heavy schema usage (`anyOf`, large components)
431
+
432
+ Smoke harness:
433
+
434
+ - Run the built-in smoke script:
435
+
436
+ ```bash
437
+ bun run smoke:specs
438
+ ```
439
+
440
+ Or run ad-hoc smoke tests:
441
+
442
+ ```bash
443
+ bunx opencli exec <URL> __schema --json --min > /dev/null
444
+ bunx opencli exec <URL> <some-resource> <some-action> --oc-curl
445
+ ```
446
+
447
+ Note: Kubernetes publishes a Swagger 2.0 document (`swagger.json`) which is not OpenAPI 3.x. OpenCLI currently expects `openapi: "3.x"` and will reject Swagger 2.0 specs.
448
+
449
+ ## Limitations (Important)
450
+
451
+ OpenCLI is intentionally v1-simple; common gaps for real-world specs:
452
+
453
+ - OpenAPI 3.x only (Swagger 2.0 not supported).
454
+ - Parameter serialization is simplified:
455
+ - arrays are always encoded as repeated keys (`?tag=a&tag=b`)
456
+ - does not implement OpenAPI `style` / `explode` / deepObject / etc.
457
+ - Array item types are not tracked yet (arrays treated as string arrays for coercion).
458
+ - Request body convenience flags only support “simple object with scalar properties”.
459
+ - Multipart, binary uploads, and file/form modeling are not implemented.
460
+ - `Content-Type`/`Accept` negotiation is basic (string includes checks for `json`).
461
+ - OAuth2 flows are not implemented (token acquisition is out of scope); oauth2 is treated as bearer token injection.
462
+
463
+ ## Development
464
+
465
+ Scripts:
466
+
467
+ - `bun run lint` (Biome CI)
468
+ - `bun run typecheck` (tsgo)
469
+ - `bun test`
470
+
471
+ Repo entry points:
472
+
473
+ - `cli.ts`: main CLI entry with `exec` and `compile` subcommands
474
+ - `src/compiled.ts`: entry point for compiled binaries (embedded spec via Bun macro)
package/biome.jsonc ADDED
@@ -0,0 +1 @@
1
+ {}
package/bun.lock ADDED
@@ -0,0 +1,98 @@
1
+ {
2
+ "lockfileVersion": 1,
3
+ "configVersion": 1,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "opencli",
7
+ "dependencies": {
8
+ "@apidevtools/swagger-parser": "^12.1.0",
9
+ "ajv": "^8.17.1",
10
+ "ajv-formats": "^3.0.1",
11
+ "commander": "^14.0.2",
12
+ "openapi-types": "^12.1.3",
13
+ },
14
+ "devDependencies": {
15
+ "@biomejs/biome": "^2.3.11",
16
+ "@types/bun": "^1.3.6",
17
+ "@typescript/native-preview": "^7.0.0-dev.20260120.1",
18
+ },
19
+ },
20
+ },
21
+ "packages": {
22
+ "@apidevtools/json-schema-ref-parser": ["@apidevtools/json-schema-ref-parser@14.0.1", "", { "dependencies": { "@types/json-schema": "^7.0.15", "js-yaml": "^4.1.0" } }, "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw=="],
23
+
24
+ "@apidevtools/openapi-schemas": ["@apidevtools/openapi-schemas@2.1.0", "", {}, "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ=="],
25
+
26
+ "@apidevtools/swagger-methods": ["@apidevtools/swagger-methods@3.0.2", "", {}, "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg=="],
27
+
28
+ "@apidevtools/swagger-parser": ["@apidevtools/swagger-parser@12.1.0", "", { "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", "@apidevtools/openapi-schemas": "^2.1.0", "@apidevtools/swagger-methods": "^3.0.2", "ajv": "^8.17.1", "ajv-draft-04": "^1.0.0", "call-me-maybe": "^1.0.2" }, "peerDependencies": { "openapi-types": ">=7" } }, "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng=="],
29
+
30
+ "@biomejs/biome": ["@biomejs/biome@2.3.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.11", "@biomejs/cli-darwin-x64": "2.3.11", "@biomejs/cli-linux-arm64": "2.3.11", "@biomejs/cli-linux-arm64-musl": "2.3.11", "@biomejs/cli-linux-x64": "2.3.11", "@biomejs/cli-linux-x64-musl": "2.3.11", "@biomejs/cli-win32-arm64": "2.3.11", "@biomejs/cli-win32-x64": "2.3.11" }, "bin": { "biome": "bin/biome" } }, "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ=="],
31
+
32
+ "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="],
33
+
34
+ "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg=="],
35
+
36
+ "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g=="],
37
+
38
+ "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg=="],
39
+
40
+ "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg=="],
41
+
42
+ "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw=="],
43
+
44
+ "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw=="],
45
+
46
+ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.11", "", { "os": "win32", "cpu": "x64" }, "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg=="],
47
+
48
+ "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="],
49
+
50
+ "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
51
+
52
+ "@types/node": ["@types/node@25.0.9", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw=="],
53
+
54
+ "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260120.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260120.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-nnEf37C9ue7OBRnF2zmV/OCBmV5Y7T/K4mCHa+nxgiXcF/1w8sA0cgdFl+gHQ0mysqUJ+Bu5btAMeWgpLyjrgg=="],
55
+
56
+ "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-r3pWFuR2H7mn6ScwpH5jJljKQqKto0npVuJSk6pRwFwexpTyxOGmJTZJ1V0AWiisaNxU2+CNAqWFJSJYIE/QTg=="],
57
+
58
+ "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cuC1+wLbUP+Ip2UT94G134fqRdp5w3b3dhcCO6/FQ4yXxvRNyv/WK+upHBUFDaeSOeHgDTyO9/QFYUWwC4If1A=="],
59
+
60
+ "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm" }, "sha512-vN6OYVySol/kQZjJGmAzd6L30SyVlCgmCXS8WjUYtE5clN0YrzQHop16RK29fYZHMxpkOniVBtRPxUYQANZBlQ=="],
61
+
62
+ "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-zZGvEGY7wcHYefMZ87KNmvjN3NLIhsCMHEpHZiGCS3khKf+8z6ZsanrzCjOTodvL01VPyBzHxV1EtkSxAcLiQg=="],
63
+
64
+ "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "x64" }, "sha512-JBfNhWd/asd5MDeS3VgRvE24pGKBkmvLub6tsux6ypr+Yhy+o0WaAEzVpmlRYZUqss2ai5tvOu4dzPBXzZAtFw=="],
65
+
66
+ "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-tTndRtYCq2xwgE0VkTi9ACNiJaV43+PqvBqCxk8ceYi3X36Ve+CCnwlZfZJ4k9NxZthtrAwF/kUmpC9iIYbq1w=="],
67
+
68
+ "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "x64" }, "sha512-oZia7hFL6k9pVepfonuPI86Jmyz6WlJKR57tWCDwRNmpA7odxuTq1PbvcYgy1z4+wHF1nnKKJY0PMAiq6ac18w=="],
69
+
70
+ "ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="],
71
+
72
+ "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
73
+
74
+ "ajv-formats": ["ajv-formats@3.0.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ=="],
75
+
76
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
77
+
78
+ "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="],
79
+
80
+ "call-me-maybe": ["call-me-maybe@1.0.2", "", {}, "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ=="],
81
+
82
+ "commander": ["commander@14.0.2", "", {}, "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ=="],
83
+
84
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
85
+
86
+ "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
87
+
88
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
89
+
90
+ "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
91
+
92
+ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="],
93
+
94
+ "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
95
+
96
+ "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
97
+ }
98
+ }
package/cli.ts ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+
5
+ function collect(value: string, previous: string[] = []): string[] {
6
+ return previous.concat([value]);
7
+ }
8
+
9
+ const program = new Command();
10
+
11
+ program.name("opencli").description("Generate CLIs from OpenAPI specs");
12
+
13
+ // ─────────────────────────────────────────────────────────────
14
+ // exec command - runs spec dynamically
15
+ // ─────────────────────────────────────────────────────────────
16
+ program
17
+ .command("exec <spec>")
18
+ .description("Execute commands from an OpenAPI spec")
19
+ .option("--server <url>", "Override server/base URL")
20
+ .option(
21
+ "--server-var <name=value>",
22
+ "Server variable (repeatable)",
23
+ collect,
24
+ [],
25
+ )
26
+ .option("--auth <scheme>", "Select auth scheme")
27
+ .option("--bearer-token <token>", "Bearer token")
28
+ .option("--oauth-token <token>", "OAuth token")
29
+ .option("--username <username>", "Basic auth username")
30
+ .option("--password <password>", "Basic auth password")
31
+ .option("--api-key <key>", "API key value")
32
+ .option("--profile <name>", "Profile name")
33
+ .option("--json", "Machine-readable output")
34
+ .allowUnknownOption()
35
+ .allowExcessArguments()
36
+ .action(async (spec, options, command) => {
37
+ const { execCommand } = await import("./src/cli/exec.ts");
38
+ await execCommand(spec, options, command.args);
39
+ });
40
+
41
+ // ─────────────────────────────────────────────────────────────
42
+ // compile command - creates standalone binary
43
+ // ─────────────────────────────────────────────────────────────
44
+ program
45
+ .command("compile <spec>")
46
+ .description("Compile an OpenAPI spec into a standalone CLI binary")
47
+ .option("--name <name>", "Binary name (default: derived from spec)")
48
+ .option("--outfile <path>", "Output path (default: ./dist/<name>)")
49
+ .option("--target <target>", "Bun compile target (e.g. bun-linux-x64)")
50
+ .option("--minify", "Enable minification")
51
+ .option("--bytecode", "Enable bytecode compilation")
52
+ .option("--no-dotenv", "Disable .env autoload")
53
+ .option("--no-bunfig", "Disable bunfig.toml autoload")
54
+ .option(
55
+ "--exec-argv <arg>",
56
+ "Embedded process.execArgv (repeatable)",
57
+ collect,
58
+ [],
59
+ )
60
+ .option("--define <k=v>", "Build-time constant (repeatable)", collect, [])
61
+ .option("--server <url>", "Default server URL (baked in)")
62
+ .option(
63
+ "--server-var <k=v>",
64
+ "Default server variable (repeatable)",
65
+ collect,
66
+ [],
67
+ )
68
+ .option("--auth <scheme>", "Default auth scheme")
69
+ .action(async (spec, options) => {
70
+ const { compileCommand } = await import("./src/cli/compile.ts");
71
+ await compileCommand(spec, options);
72
+ });
73
+
74
+ await program.parseAsync(process.argv);
@@ -0,0 +1,22 @@
1
+ {
2
+ "openapi": "3.0.3",
3
+ "info": { "title": "Array Items API", "version": "1.0.0" },
4
+ "servers": [{ "url": "https://api.example.com" }],
5
+ "paths": {
6
+ "/things": {
7
+ "get": {
8
+ "operationId": "Things.List",
9
+ "tags": ["Things"],
10
+ "parameters": [
11
+ {
12
+ "in": "query",
13
+ "name": "ids",
14
+ "required": false,
15
+ "schema": { "type": "array", "items": { "type": "integer" } }
16
+ }
17
+ ],
18
+ "responses": { "200": { "description": "OK" } }
19
+ }
20
+ }
21
+ }
22
+ }