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/README.md +390 -282
- package/package.json +55 -41
- package/src/cli.js +338 -172
- package/src/commands/add.js +163 -78
- package/src/commands/auth.js +32 -0
- package/src/commands/call.js +224 -207
- package/src/commands/fetch.js +63 -27
- package/src/commands/grep.js +14 -11
- package/src/commands/list.js +89 -80
- package/src/commands/show.js +16 -7
- package/src/commands/skill.js +40 -0
- package/src/commands/specs.js +22 -9
- package/src/commands/types.js +6 -2
- package/src/commands/usage.js +15 -0
- package/src/commands/validate.js +33 -7
- package/src/dotenv.js +38 -0
- package/src/glob.js +6 -1
- package/src/mcp-client.js +91 -63
- package/src/oauth/auth-flow.js +59 -0
- package/src/oauth/provider.js +191 -0
- package/src/oauth/tokens.js +59 -0
- package/src/output.js +65 -61
- package/src/registry.js +36 -10
- package/src/resolve.js +66 -65
- package/src/secrets.js +46 -0
- package/src/skill/SKILL.md +112 -0
- package/src/usage.js +62 -0
package/package.json
CHANGED
|
@@ -1,41 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "api-spec-cli",
|
|
3
|
-
"version": "0.2.
|
|
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
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
"
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
--
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
spec
|
|
47
|
-
spec
|
|
48
|
-
spec
|
|
49
|
-
spec
|
|
50
|
-
spec
|
|
51
|
-
spec
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
spec
|
|
55
|
-
spec
|
|
56
|
-
spec
|
|
57
|
-
spec
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
spec
|
|
61
|
-
spec
|
|
62
|
-
spec
|
|
63
|
-
spec
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
spec
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
--
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
spec
|
|
75
|
-
spec
|
|
76
|
-
spec
|
|
77
|
-
spec
|
|
78
|
-
spec
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
--
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
spec
|
|
90
|
-
spec
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
spec
|
|
94
|
-
spec
|
|
95
|
-
spec
|
|
96
|
-
spec
|
|
97
|
-
spec
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
}
|