api-spec-cli 0.2.3 → 0.2.5

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.
package/package.json CHANGED
@@ -1,41 +1,55 @@
1
- {
2
- "name": "api-spec-cli",
3
- "version": "0.2.3",
4
- "description": "Agent-friendly CLI for exploring and calling OpenAPI and GraphQL APIs",
5
- "type": "module",
6
- "bin": {
7
- "spec": "bin/spec.js"
8
- },
9
- "scripts": {
10
- "test": "bun test"
11
- },
12
- "files": [
13
- "bin/",
14
- "src/",
15
- "README.md"
16
- ],
17
- "keywords": [
18
- "openapi",
19
- "graphql",
20
- "cli",
21
- "agent",
22
- "api",
23
- "swagger",
24
- "ai-agent",
25
- "mcp",
26
- "introspection"
27
- ],
28
- "author": "niradler55",
29
- "license": "MIT",
30
- "repository": {
31
- "type": "git",
32
- "url": "git+https://github.com/niradler/api-spec-cli.git"
33
- },
34
- "engines": {
35
- "node": ">=18.0.0"
36
- },
37
- "dependencies": {
38
- "@modelcontextprotocol/sdk": "^1.28.0",
39
- "yaml": "^2.7.0"
40
- }
41
- }
1
+ {
2
+ "name": "api-spec-cli",
3
+ "version": "0.2.5",
4
+ "description": "Agent-friendly CLI for exploring and calling OpenAPI and GraphQL APIs",
5
+ "type": "module",
6
+ "bin": {
7
+ "spec": "bin/spec.js"
8
+ },
9
+ "scripts": {
10
+ "test": "bun test",
11
+ "format": "prettier --write \"src/**/*.js\" \"test/**/*.js\"",
12
+ "format:check": "prettier --check \"src/**/*.js\" \"test/**/*.js\"",
13
+ "release": "bash release.sh"
14
+ },
15
+ "prettier": {
16
+ "semi": true,
17
+ "singleQuote": false,
18
+ "printWidth": 100,
19
+ "trailingComma": "es5"
20
+ },
21
+ "files": [
22
+ "bin/",
23
+ "src/",
24
+ "README.md"
25
+ ],
26
+ "keywords": [
27
+ "openapi",
28
+ "graphql",
29
+ "cli",
30
+ "agent",
31
+ "api",
32
+ "swagger",
33
+ "ai-agent",
34
+ "mcp",
35
+ "introspection"
36
+ ],
37
+ "author": "niradler55",
38
+ "license": "MIT",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/niradler/api-spec-cli.git"
42
+ },
43
+ "engines": {
44
+ "node": ">=18.0.0"
45
+ },
46
+ "dependencies": {
47
+ "@modelcontextprotocol/sdk": "^1.29.0",
48
+ "@toon-format/toon": "^2.3.0",
49
+ "yaml": "^2.9.0",
50
+ "yargs": "^18.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "prettier": "^3.8.1"
54
+ }
55
+ }
package/src/cli.js CHANGED
@@ -1,172 +1,338 @@
1
- import { listOperations } from "./commands/list.js";
2
- import { showOperation } from "./commands/show.js";
3
- import { callOperation } from "./commands/call.js";
4
- import { configCmd } from "./commands/config.js";
5
- import { validateSpec } from "./commands/validate.js";
6
- import { typesCmd } from "./commands/types.js";
7
- import { addCmd } from "./commands/add.js";
8
- import { specsCmd, registryMutate } from "./commands/specs.js";
9
- import { grepCmd } from "./commands/grep.js";
10
- import { out, err, setFormat } from "./output.js";
11
-
12
- const HELP = `spec-cli — Explore and call APIs from the command line.
13
- All output is JSON. Designed for AI agents but works for humans too.
14
-
15
- Every command is stateless specify the spec source on each call.
16
-
17
- SPEC SOURCE (required on every list/show/call):
18
- --spec <name> Use a registered spec (auto-fetches + caches)
19
- --openapi <url-or-file> OpenAPI inline (no registration needed)
20
- --graphql <url> GraphQL inline
21
- --mcp-http <url> MCP streamable-HTTP inline
22
- --mcp-sse <url> MCP SSE inline
23
- --mcp-stdio "<cmd args>" MCP stdio inline
24
-
25
- REGISTRY (register once, use anywhere):
26
- spec add <name> --openapi <url> Register an OpenAPI spec
27
- spec add <name> --graphql <url> Register a GraphQL endpoint
28
- spec add <name> --mcp-http <url> Register an MCP server (streamable-HTTP)
29
- spec add <name> --mcp-sse <url> Register an MCP server (SSE)
30
- spec add <name> --mcp-stdio "<cmd>" Register an MCP server (stdio)
31
- Options: --description <text> --base-url <url> --auth <token>
32
- --header k=v (repeatable) --env KEY=VAL (repeatable, stdio only)
33
- --cwd <path> (stdio only)
34
- --allow-tool <glob> (repeatable)
35
- --disable-tool <glob> (repeatable)
36
-
37
- spec specs List all registered specs
38
- spec specs --compact false Show full entry config
39
- spec remove <name> Delete from registry
40
- spec enable <name> Enable a disabled spec
41
- spec disable <name> Disable without removing
42
- spec refresh <name> Force re-fetch and update cache
43
-
44
- DISCOVER:
45
- spec list --spec <name> All operations/tools (compact IDs)
46
- spec list --spec <name> --filter user Search by keyword
47
- spec list --spec <name> --tag pets OpenAPI tag or GraphQL kind
48
- spec list --spec <name> --limit 10 Paginate
49
- spec list --mcp-http <url> Inline: no registration needed
50
- spec grep <pattern> Search across all registered specs
51
- spec grep <pattern> --spec <name> Search within one spec
52
-
53
- INSPECT:
54
- spec show --spec <name> <op> Operation details (params, body, responses)
55
- spec show --spec <name> <tool> MCP tool input schema
56
- spec types --spec <name> List all schema/type names (OpenAPI/GraphQL)
57
- spec types --spec <name> <TypeName> Inspect one type
58
-
59
- CALL:
60
- spec call --spec <name> <op> --var petId=1 Path/GraphQL vars
61
- spec call --spec <name> <op> --query status=available Query params
62
- spec call --spec <name> <op> --data '{"name":"Rex"}' JSON body / MCP args
63
- spec call --spec <name> <op> --data-file args.json Body from file
64
- spec call --spec <name> <op> --data - Read JSON body from stdin (pipe)
65
- spec call --spec <name> <op> --header X-Custom=val Extra headers
66
- spec call --spec <name> <op> --method PUT Override HTTP method
67
-
68
- PER-CALL OVERRIDES (win over registry entry config):
69
- --auth <token> Override auth for this call
70
- --base-url <url> Override base URL for this call
71
- --header k=v Merge/override headers for this call
72
-
73
- CONFIG (persisted in .spec-cli/config.json lowest priority):
74
- spec config set baseUrl https://api.example.com
75
- spec config set auth <token>
76
- spec config set headers.X-API-Key <key>
77
- spec config get
78
- spec config unset auth
79
-
80
- OTHER:
81
- spec validate <file-or-url> Check OpenAPI spec for errors
82
- --format json|text|yaml Output format (default: json)
83
-
84
- ENV VARS (MCP):
85
- MCP_MAX_RETRIES=3 Retry attempts on connection failure (default: 3)
86
- MCP_RETRY_DELAY=1000 Base retry delay in ms, doubles each attempt (default: 1000)
87
-
88
- EXAMPLES:
89
- spec add agno --mcp-http https://docs.agno.com/mcp --description "Agno docs"
90
- spec add petstore --openapi https://petstore3.swagger.io/api/v3/openapi.json \\
91
- --base-url https://petstore3.swagger.io/api/v3
92
- spec specs
93
- spec list --spec agno
94
- spec show --spec agno search_agno
95
- spec call --spec agno search_agno --var query="agents"
96
- spec call --spec agno search_agno --var query="foo" --header X-Tenant=acme
97
- spec list --mcp-http https://docs.agno.com/mcp (inline, no registration)`;
98
-
99
- export async function run(args) {
100
- // Extract --format before routing (supports both --format json and --format=json)
101
- const newArgs = [];
102
- for (let i = 0; i < args.length; i++) {
103
- if (args[i] === "--format" && i + 1 < args.length) {
104
- setFormat(args[++i]);
105
- } else if (args[i].startsWith("--format=")) {
106
- setFormat(args[i].slice(9));
107
- } else {
108
- newArgs.push(args[i]);
109
- }
110
- }
111
- args = newArgs;
112
-
113
- const cmd = args[0];
114
-
115
- if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
116
- out({ help: HELP });
117
- return;
118
- }
119
-
120
- try {
121
- switch (cmd) {
122
- case "list":
123
- case "ls":
124
- await listOperations(args.slice(1));
125
- break;
126
- case "show":
127
- await showOperation(args.slice(1));
128
- break;
129
- case "call":
130
- await callOperation(args.slice(1));
131
- break;
132
- case "validate":
133
- await validateSpec(args.slice(1));
134
- break;
135
- case "types":
136
- case "type":
137
- await typesCmd(args.slice(1));
138
- break;
139
- case "config":
140
- case "cfg":
141
- await configCmd(args.slice(1));
142
- break;
143
- case "add":
144
- await addCmd(args.slice(1));
145
- break;
146
- case "specs":
147
- case "registry":
148
- await specsCmd(args.slice(1));
149
- break;
150
- case "remove":
151
- await registryMutate("remove", args.slice(1));
152
- break;
153
- case "enable":
154
- await registryMutate("enable", args.slice(1));
155
- break;
156
- case "disable":
157
- await registryMutate("disable", args.slice(1));
158
- break;
159
- case "refresh":
160
- await registryMutate("refresh", args.slice(1));
161
- break;
162
- case "grep":
163
- await grepCmd(args.slice(1));
164
- break;
165
- default:
166
- err(`Unknown command: ${cmd}. Run 'spec help' for usage.`);
167
- }
168
- } catch (e) {
169
- err(e.message);
170
- process.exit(1);
171
- }
172
- }
1
+ import yargs from "yargs";
2
+ import { listOperations } from "./commands/list.js";
3
+ import { showOperation } from "./commands/show.js";
4
+ import { callOperation } from "./commands/call.js";
5
+ import { configCmd } from "./commands/config.js";
6
+ import { validateSpec } from "./commands/validate.js";
7
+ import { typesCmd } from "./commands/types.js";
8
+ import { addCmd } from "./commands/add.js";
9
+ import { specsCmd, registryMutate } from "./commands/specs.js";
10
+ import { grepCmd } from "./commands/grep.js";
11
+ import { authCmd } from "./commands/auth.js";
12
+ import { usageCmd } from "./commands/usage.js";
13
+ import { skillCmd } from "./commands/skill.js";
14
+ import { loadDotenv } from "./dotenv.js";
15
+ import { out, err, setFormat } from "./output.js";
16
+
17
+ const HELP = `spec-cli Explore and call APIs from the command line.
18
+ All output is JSON. Designed for AI agents but works for humans too.
19
+
20
+ Every command is stateless — specify the spec source on each call.
21
+
22
+ SPEC SOURCE (required on every list/show/call):
23
+ --spec <name> Use a registered spec (auto-fetches + caches)
24
+ --openapi <url-or-file> OpenAPI inline (no registration needed)
25
+ --graphql <url> GraphQL inline
26
+ --mcp-http <url> MCP streamable-HTTP inline
27
+ --mcp-sse <url> MCP SSE inline
28
+ --mcp-stdio "<cmd args>" MCP stdio inline
29
+
30
+ REGISTRY (register once, use anywhere):
31
+ spec add <name> --openapi <url> Register an OpenAPI spec
32
+ spec add <name> --graphql <url> Register a GraphQL endpoint
33
+ spec add <name> --mcp-http <url> Register an MCP server (streamable-HTTP)
34
+ spec add <name> --mcp-sse <url> Register an MCP server (SSE)
35
+ spec add <name> --mcp-stdio "<cmd>" Register an MCP server (stdio)
36
+ Options: --description <text> --base-url <url> --auth <token>
37
+ --header k=v (repeatable) --env KEY=VAL (repeatable, stdio only)
38
+ --cwd <path> (stdio only)
39
+ --allow-tool <glob> (repeatable)
40
+ --disable-tool <glob> (repeatable)
41
+ --oauth-flow browser|device OAuth flow (http/sse only, default: browser)
42
+ --oauth-client-id <id> Pre-registered OAuth client ID
43
+ --oauth-client-secret <secret> Client secret (stored securely, not in registry)
44
+ --oauth-callback-port <1-65535> Fixed local port for browser callback
45
+
46
+ spec specs List all registered specs
47
+ spec specs --compact false Show full entry config
48
+ spec remove <name> Delete from registry
49
+ spec enable <name> Enable a disabled spec
50
+ spec disable <name> Disable without removing
51
+ spec refresh <name> Force re-fetch and update cache
52
+
53
+ DISCOVER:
54
+ spec list --spec <name> All operations/tools (compact IDs)
55
+ spec list --spec <name> --filter user Search by keyword
56
+ spec list --spec <name> --tag pets OpenAPI tag or GraphQL kind
57
+ spec list --spec <name> --limit 10 Paginate
58
+ spec list --spec <name> --top 10 Rank by call count (most-used first)
59
+ spec list --mcp-http <url> Inline: no registration needed
60
+ spec grep <pattern> Search across all registered specs
61
+ spec grep <pattern> --spec <name> Search within one spec
62
+ spec usage Show recorded usage for all specs
63
+ spec usage <name> Ranked operations for one spec
64
+
65
+ INSPECT:
66
+ spec show --spec <name> <op> Operation details (params, body, responses)
67
+ spec show --spec <name> <tool> MCP tool input schema
68
+ spec types --spec <name> List all schema/type names (OpenAPI/GraphQL)
69
+ spec types --spec <name> <TypeName> Inspect one type
70
+
71
+ CALL:
72
+ spec call --spec <name> <op> --var petId=1 Path/GraphQL vars
73
+ spec call --spec <name> <op> --query status=available Query params
74
+ spec call --spec <name> <op> --data '{"name":"Rex"}' JSON body / MCP args
75
+ spec call --spec <name> <op> --data-file args.json Body from file
76
+ spec call --spec <name> <op> --data - Read JSON body from stdin (pipe)
77
+ spec call --spec <name> <op> --header X-Custom=val Extra headers
78
+ spec call --spec <name> <op> --method PUT Override HTTP method
79
+
80
+ PER-CALL OVERRIDES (win over registry entry config):
81
+ --auth <token> Override auth for this call
82
+ --base-url <url> Override base URL for this call
83
+ --header k=v Merge/override headers for this call
84
+
85
+ CONFIG (persisted in .spec-cli/config.json lowest priority):
86
+ spec config set baseUrl https://api.example.com
87
+ spec config set auth <token>
88
+ spec config set headers.X-API-Key <key>
89
+ spec config get
90
+ spec config unset auth
91
+
92
+ OTHER:
93
+ spec auth <name> Re-authenticate an OAuth-protected MCP spec
94
+ spec auth <name> --revoke Clear stored OAuth token
95
+ spec validate <file-or-url> Check OpenAPI spec for errors
96
+ spec skill install Install the agent skill into ~/.claude/skills/
97
+ spec skill path Print the bundled SKILL.md location
98
+ --format json|text|yaml|toon Output format (default: json; toon is densest)
99
+
100
+ SECRETS & OVERRIDES:
101
+ Stored values (auth, headers) may use \${VAR} — expanded from the environment at call time.
102
+ A .env file in the working directory is auto-loaded (real env vars take precedence).
103
+ SPEC_URL=<url> Override a registered MCP/GraphQL spec's endpoint for this call
104
+ SPEC_HEADER_<NAME>=<value> Add/override a header (SPEC_HEADER_X_TENANT -> X-Tenant)
105
+
106
+ ENV VARS:
107
+ MCP_MAX_RETRIES=3 Retry attempts on connection failure (default: 3)
108
+ MCP_RETRY_DELAY=1000 Base retry delay in ms, doubles each attempt (default: 1000)
109
+ SPEC_OAUTH_CALLBACK_PORT=3141 Default fixed port for browser OAuth callback
110
+ SPEC_NO_USAGE=1 Disable usage tracking
111
+ SPEC_NO_DOTENV=1 Disable .env auto-loading
112
+
113
+ EXAMPLES:
114
+ spec add agno --mcp-http https://docs.agno.com/mcp --description "Agno docs"
115
+ spec add petstore --openapi https://petstore3.swagger.io/api/v3/openapi.json \\
116
+ --base-url https://petstore3.swagger.io/api/v3
117
+ spec specs
118
+ spec list --spec agno
119
+ spec show --spec agno search_agno
120
+ spec call --spec agno search_agno --var query="agents"
121
+ spec call --spec agno search_agno --var query="foo" --header X-Tenant=acme
122
+ spec list --mcp-http https://docs.agno.com/mcp (inline, no registration)`;
123
+
124
+ const specSourceOptions = {
125
+ spec: { type: "string", describe: "Use a registered spec" },
126
+ openapi: { type: "string", describe: "Inline OpenAPI URL or file" },
127
+ graphql: { type: "string", describe: "Inline GraphQL URL" },
128
+ "mcp-http": { type: "string", describe: "Inline MCP streamable-HTTP URL" },
129
+ "mcp-sse": { type: "string", describe: "Inline MCP SSE URL" },
130
+ "mcp-stdio": { type: "string", describe: "Inline MCP stdio command" },
131
+ };
132
+
133
+ const overrideOptions = {
134
+ auth: { type: "string", describe: "Override auth token" },
135
+ "base-url": { type: "string", describe: "Override base URL" },
136
+ header: { type: "string", array: true, describe: "Header k=v (repeatable)" },
137
+ "allow-tool": { type: "string", array: true, describe: "Allow tool glob (repeatable)" },
138
+ "disable-tool": { type: "string", array: true, describe: "Disable tool glob (repeatable)" },
139
+ env: { type: "string", array: true, describe: "Env KEY=VAL (repeatable, stdio only)" },
140
+ cwd: { type: "string", describe: "Working directory (stdio only)" },
141
+ };
142
+
143
+ const sourceOptions = { ...specSourceOptions, ...overrideOptions };
144
+
145
+ const commands = (rest) => [
146
+ {
147
+ command: ["list", "ls"],
148
+ describe: "List operations or tools for a spec",
149
+ builder: (y) =>
150
+ y.options({
151
+ ...sourceOptions,
152
+ filter: { type: "string", describe: "Substring search across fields" },
153
+ compact: { type: "string", describe: "Set false to show full details" },
154
+ limit: { type: "string", describe: "Max results" },
155
+ offset: { type: "string", describe: "Skip the first N results" },
156
+ tag: { type: "string", describe: "OpenAPI tag or GraphQL kind" },
157
+ top: { type: "string", describe: "Rank by call count (most-used first)" },
158
+ }),
159
+ handler: () => listOperations(rest),
160
+ },
161
+ {
162
+ command: "show <operation>",
163
+ describe: "Show operation or tool details",
164
+ builder: (y) =>
165
+ y
166
+ .positional("operation", { type: "string", describe: "Operation id, path, or tool name" })
167
+ .options(sourceOptions),
168
+ handler: () => showOperation(rest),
169
+ },
170
+ {
171
+ command: "call <operation>",
172
+ describe: "Call an operation or MCP tool",
173
+ builder: (y) =>
174
+ y
175
+ .positional("operation", { type: "string", describe: "Operation id, path, or tool name" })
176
+ .options({
177
+ ...sourceOptions,
178
+ data: { type: "string", describe: "JSON body / MCP args, or - for stdin" },
179
+ "data-file": { type: "string", describe: "Read JSON body from a file" },
180
+ var: { type: "string", array: true, describe: "Path/GraphQL var k=v (repeatable)" },
181
+ query: { type: "string", array: true, describe: "Query param k=v (repeatable)" },
182
+ method: { type: "string", describe: "Override HTTP method" },
183
+ }),
184
+ handler: () => callOperation(rest),
185
+ },
186
+ {
187
+ command: ["types [type]", "type [type]"],
188
+ describe: "List schema/type names or inspect one type",
189
+ builder: (y) =>
190
+ y
191
+ .positional("type", { type: "string", describe: "Type or schema name to inspect" })
192
+ .options(sourceOptions),
193
+ handler: () => typesCmd(rest),
194
+ },
195
+ {
196
+ command: "grep <pattern>",
197
+ describe: "Search operations across registered specs",
198
+ builder: (y) =>
199
+ y
200
+ .positional("pattern", { type: "string", describe: "Glob or substring pattern" })
201
+ .option("spec", specSourceOptions.spec),
202
+ handler: () => grepCmd(rest),
203
+ },
204
+ {
205
+ command: "usage [name]",
206
+ describe: "Show recorded usage",
207
+ builder: (y) =>
208
+ y.positional("name", { type: "string", describe: "Spec name for ranked operations" }),
209
+ handler: () => usageCmd(rest),
210
+ },
211
+ {
212
+ command: "add <name>",
213
+ describe: "Register a spec in the registry",
214
+ builder: (y) =>
215
+ y.positional("name", { type: "string", describe: "Registry name" }).options({
216
+ ...specSourceOptions,
217
+ ...overrideOptions,
218
+ description: { type: "string", describe: "Human-readable description" },
219
+ "oauth-flow": { type: "string", choices: ["browser", "device"], describe: "OAuth flow" },
220
+ "oauth-client-id": { type: "string", describe: "Pre-registered OAuth client ID" },
221
+ "oauth-client-secret": {
222
+ type: "string",
223
+ describe: "OAuth client secret (stored securely)",
224
+ },
225
+ "oauth-callback-port": { type: "number", describe: "Fixed local OAuth callback port" },
226
+ }),
227
+ handler: () => addCmd(rest),
228
+ },
229
+ {
230
+ command: ["specs", "registry"],
231
+ describe: "List all registered specs",
232
+ builder: (y) =>
233
+ y.option("compact", { type: "string", describe: "Set false to show full entry config" }),
234
+ handler: () => specsCmd(rest),
235
+ },
236
+ {
237
+ command: "remove <name>",
238
+ describe: "Delete a spec from the registry",
239
+ builder: (y) => y.positional("name", { type: "string", describe: "Registry name" }),
240
+ handler: () => registryMutate("remove", rest),
241
+ },
242
+ {
243
+ command: "enable <name>",
244
+ describe: "Enable a disabled spec",
245
+ builder: (y) => y.positional("name", { type: "string", describe: "Registry name" }),
246
+ handler: () => registryMutate("enable", rest),
247
+ },
248
+ {
249
+ command: "disable <name>",
250
+ describe: "Disable a spec without removing it",
251
+ builder: (y) => y.positional("name", { type: "string", describe: "Registry name" }),
252
+ handler: () => registryMutate("disable", rest),
253
+ },
254
+ {
255
+ command: "refresh <name>",
256
+ describe: "Force re-fetch and update the cache",
257
+ builder: (y) => y.positional("name", { type: "string", describe: "Registry name" }),
258
+ handler: () => registryMutate("refresh", rest),
259
+ },
260
+ {
261
+ command: "auth <name>",
262
+ describe: "Re-authenticate or revoke an OAuth MCP spec",
263
+ builder: (y) =>
264
+ y
265
+ .positional("name", { type: "string", describe: "Registry name" })
266
+ .option("revoke", { type: "boolean", describe: "Clear the stored OAuth token" }),
267
+ handler: () => authCmd(rest),
268
+ },
269
+ {
270
+ command: ["config [action] [key] [value]", "cfg [action] [key] [value]"],
271
+ describe: "Get, set, or unset persisted config",
272
+ builder: (y) =>
273
+ y
274
+ .positional("action", { type: "string", choices: ["get", "show", "set", "unset"] })
275
+ .positional("key", { type: "string" })
276
+ .positional("value", { type: "string" }),
277
+ handler: () => configCmd(rest),
278
+ },
279
+ {
280
+ command: "validate <source>",
281
+ describe: "Check an OpenAPI spec for errors",
282
+ builder: (y) => y.positional("source", { type: "string", describe: "OpenAPI file or URL" }),
283
+ handler: () => validateSpec(rest),
284
+ },
285
+ {
286
+ command: "skill [sub]",
287
+ describe: "Manage the bundled agent skill",
288
+ builder: (y) =>
289
+ y
290
+ .positional("sub", { type: "string", choices: ["install", "path"] })
291
+ .option("install", { type: "boolean" })
292
+ .option("path", { type: "boolean" }),
293
+ handler: () => skillCmd(rest),
294
+ },
295
+ ];
296
+
297
+ function isHelpRequest(args) {
298
+ return !args[0] || args[0] === "help" || args.includes("--help") || args.includes("-h");
299
+ }
300
+
301
+ export async function run(argv) {
302
+ loadDotenv();
303
+
304
+ const args = [];
305
+ for (let i = 0; i < argv.length; i++) {
306
+ if (argv[i] === "--format" && i + 1 < argv.length) setFormat(argv[++i]);
307
+ else if (argv[i].startsWith("--format=")) setFormat(argv[i].slice(9));
308
+ else args.push(argv[i]);
309
+ }
310
+
311
+ if (isHelpRequest(args)) {
312
+ out({ help: HELP });
313
+ return;
314
+ }
315
+
316
+ const rest = args.slice(1);
317
+ const validationArgs = args.filter((arg) => arg !== "-");
318
+ const cli = yargs(validationArgs)
319
+ .scriptName("spec")
320
+ .help(false)
321
+ .version(false)
322
+ .strict()
323
+ .demandCommand(1, "No command given. Run 'spec help' for usage.")
324
+ .fail((msg, error) => {
325
+ err(error?.message || msg);
326
+ process.exit(1);
327
+ })
328
+ .exitProcess(false);
329
+
330
+ for (const command of commands(rest)) cli.command(command);
331
+
332
+ try {
333
+ await cli.parseAsync();
334
+ } catch (e) {
335
+ err(e.message);
336
+ process.exit(1);
337
+ }
338
+ }