apigrip 0.4.2 → 0.5.2
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 +21 -1
- package/cli/commands/show.js +148 -0
- package/cli/index.js +13 -0
- package/completions/_apigrip +144 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -67,6 +67,14 @@ apigrip curl GET /users -e Production
|
|
|
67
67
|
apigrip list # grouped by tag
|
|
68
68
|
apigrip list --search "auth" --json # fuzzy search, JSON output
|
|
69
69
|
|
|
70
|
+
# Show endpoint details
|
|
71
|
+
apigrip show GET /users # parameters, schemas, responses
|
|
72
|
+
apigrip show POST /users --json # full JSON output
|
|
73
|
+
|
|
74
|
+
# Print parsed spec
|
|
75
|
+
apigrip spec # dereferenced JSON
|
|
76
|
+
apigrip spec --raw # original file as-is
|
|
77
|
+
|
|
70
78
|
# Manage projects
|
|
71
79
|
apigrip projects # list bookmarks
|
|
72
80
|
apigrip projects add ./my-api # bookmark a directory
|
|
@@ -89,6 +97,18 @@ apigrip last GET /users --json # full JSON output
|
|
|
89
97
|
apigrip mcp --project /path/to/api
|
|
90
98
|
```
|
|
91
99
|
|
|
100
|
+
### Shell completion
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
# Zsh — copy to your fpath
|
|
104
|
+
cp "$(npm root -g)/apigrip/completions/_apigrip" /usr/local/share/zsh/site-functions/
|
|
105
|
+
# or symlink into any directory in your $fpath, then:
|
|
106
|
+
autoload -Uz compinit && compinit
|
|
107
|
+
|
|
108
|
+
# Bash
|
|
109
|
+
apigrip completion >> ~/.bashrc
|
|
110
|
+
```
|
|
111
|
+
|
|
92
112
|
### Exit codes
|
|
93
113
|
|
|
94
114
|
| Code | Meaning |
|
|
@@ -207,7 +227,7 @@ apigrip/
|
|
|
207
227
|
│ ├── index.js Yargs command registration
|
|
208
228
|
│ ├── output.js CLI formatting (table, JSON, color)
|
|
209
229
|
│ ├── resolve-project.js Shared project context resolution
|
|
210
|
-
│ └── commands/ serve, send, curl, list, projects, env, last, mcp
|
|
230
|
+
│ └── commands/ serve, send, curl, list, show, spec, projects, env, last, mcp
|
|
211
231
|
├── core/ Shared logic (spec parsing, curl, environments, persistence)
|
|
212
232
|
│ ├── spec-discovery.js Find OpenAPI spec in a directory
|
|
213
233
|
│ ├── spec-parser.js Parse/dereference specs (2.0, 3.0, 3.1)
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// cli/commands/show.js — Show details for a specific endpoint
|
|
2
|
+
|
|
3
|
+
import { parseSpec, getEndpointDetails } from '../../core/spec-parser.js';
|
|
4
|
+
import { colorMethod } from '../output.js';
|
|
5
|
+
import { resolveProjectContext } from '../resolve-project.js';
|
|
6
|
+
|
|
7
|
+
const DIM = '\x1b[2m';
|
|
8
|
+
const BOLD = '\x1b[1m';
|
|
9
|
+
const RESET = '\x1b[0m';
|
|
10
|
+
const CYAN = '\x1b[36m';
|
|
11
|
+
const YELLOW = '\x1b[33m';
|
|
12
|
+
|
|
13
|
+
function indent(text, level = 2) {
|
|
14
|
+
const pad = ' '.repeat(level);
|
|
15
|
+
return text.split('\n').map(l => pad + l).join('\n');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function formatSchema(schema, depth = 0) {
|
|
19
|
+
if (!schema) return DIM + 'any' + RESET;
|
|
20
|
+
const pad = ' '.repeat(depth);
|
|
21
|
+
|
|
22
|
+
if (schema.type === 'object' && schema.properties) {
|
|
23
|
+
const required = new Set(schema.required || []);
|
|
24
|
+
const lines = ['{'];
|
|
25
|
+
for (const [key, prop] of Object.entries(schema.properties)) {
|
|
26
|
+
const req = required.has(key) ? ` ${YELLOW}*required${RESET}` : '';
|
|
27
|
+
const type = prop.type || 'any';
|
|
28
|
+
const desc = prop.description ? ` ${DIM}${prop.description}${RESET}` : '';
|
|
29
|
+
if (prop.type === 'object' && prop.properties) {
|
|
30
|
+
lines.push(`${pad} ${key}: ${formatSchema(prop, depth + 1)}${req}${desc}`);
|
|
31
|
+
} else if (prop.type === 'array' && prop.items) {
|
|
32
|
+
const itemType = prop.items.type || 'any';
|
|
33
|
+
lines.push(`${pad} ${key}: ${type}<${itemType}>${req}${desc}`);
|
|
34
|
+
} else {
|
|
35
|
+
lines.push(`${pad} ${key}: ${type}${req}${desc}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
lines.push(`${pad}}`);
|
|
39
|
+
return lines.join('\n');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (schema.type === 'array' && schema.items) {
|
|
43
|
+
return `array<${schema.items.type || 'any'}>`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return schema.type || 'any';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function showCommand(argv) {
|
|
50
|
+
const { specPath } = await resolveProjectContext(argv);
|
|
51
|
+
if (!specPath) {
|
|
52
|
+
console.error('No spec file found');
|
|
53
|
+
process.exit(2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let spec;
|
|
57
|
+
try {
|
|
58
|
+
spec = await parseSpec(specPath);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error(`Spec parse error: ${err.message}`);
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const method = argv.method.toUpperCase();
|
|
65
|
+
const pathArg = argv.path;
|
|
66
|
+
const apiPath = typeof pathArg === 'string' ? (pathArg.startsWith('/') ? pathArg : '/' + pathArg) : '/' + String(pathArg);
|
|
67
|
+
|
|
68
|
+
const details = getEndpointDetails(spec, method, apiPath);
|
|
69
|
+
if (!details) {
|
|
70
|
+
console.error(`Endpoint not found: ${method} ${apiPath}`);
|
|
71
|
+
process.exit(3);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (argv.json) {
|
|
75
|
+
console.log(JSON.stringify(details, null, 2));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const lines = [];
|
|
80
|
+
|
|
81
|
+
// Header
|
|
82
|
+
lines.push(`${colorMethod(method)} ${BOLD}${apiPath}${RESET}`);
|
|
83
|
+
if (details.summary) lines.push(details.summary);
|
|
84
|
+
if (details.description) lines.push(`${DIM}${details.description}${RESET}`);
|
|
85
|
+
if (details.tags && details.tags.length) lines.push(`${DIM}Tags: ${details.tags.join(', ')}${RESET}`);
|
|
86
|
+
if (details.deprecated) lines.push(`${YELLOW}DEPRECATED${RESET}`);
|
|
87
|
+
|
|
88
|
+
// Parameters
|
|
89
|
+
const params = details.parameters || [];
|
|
90
|
+
if (params.length > 0) {
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push(`${BOLD}PARAMETERS${RESET}`);
|
|
93
|
+
for (const p of params) {
|
|
94
|
+
const req = p.required ? ` ${YELLOW}*required${RESET}` : '';
|
|
95
|
+
const type = p.schema?.type || 'string';
|
|
96
|
+
const def = p.schema?.default != null ? ` ${DIM}(default: ${p.schema.default})${RESET}` : '';
|
|
97
|
+
lines.push(` ${CYAN}${p.in}${RESET} ${p.name}: ${type}${req}${def}`);
|
|
98
|
+
if (p.description) lines.push(` ${DIM}${p.description}${RESET}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Request body
|
|
103
|
+
const reqBody = details.request_body;
|
|
104
|
+
if (reqBody) {
|
|
105
|
+
lines.push('');
|
|
106
|
+
lines.push(`${BOLD}REQUEST BODY${RESET}${reqBody.required ? ` ${YELLOW}*required${RESET}` : ''}`);
|
|
107
|
+
if (reqBody.content) {
|
|
108
|
+
for (const [ct, media] of Object.entries(reqBody.content)) {
|
|
109
|
+
lines.push(` ${DIM}${ct}${RESET}`);
|
|
110
|
+
if (media.schema) {
|
|
111
|
+
lines.push(indent(formatSchema(media.schema), 4));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Responses
|
|
118
|
+
const responses = details.responses || {};
|
|
119
|
+
if (Object.keys(responses).length > 0) {
|
|
120
|
+
lines.push('');
|
|
121
|
+
lines.push(`${BOLD}RESPONSES${RESET}`);
|
|
122
|
+
for (const [code, resp] of Object.entries(responses)) {
|
|
123
|
+
const desc = resp.description || '';
|
|
124
|
+
lines.push(` ${CYAN}${code}${RESET} ${desc}`);
|
|
125
|
+
if (resp.content) {
|
|
126
|
+
for (const [ct, media] of Object.entries(resp.content)) {
|
|
127
|
+
lines.push(` ${DIM}${ct}${RESET}`);
|
|
128
|
+
if (media.schema) {
|
|
129
|
+
lines.push(indent(formatSchema(media.schema), 6));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Servers
|
|
137
|
+
const servers = details.servers || [];
|
|
138
|
+
if (servers.length > 0) {
|
|
139
|
+
lines.push('');
|
|
140
|
+
lines.push(`${BOLD}SERVERS${RESET}`);
|
|
141
|
+
for (const s of servers) {
|
|
142
|
+
const desc = s.description ? ` ${DIM}(${s.description})${RESET}` : '';
|
|
143
|
+
lines.push(` ${s.url}${desc}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log(lines.join('\n'));
|
|
148
|
+
}
|
package/cli/index.js
CHANGED
|
@@ -62,6 +62,17 @@ const cli = yargs(hideBin(process.argv))
|
|
|
62
62
|
const { listCommand } = await import('./commands/list.js');
|
|
63
63
|
await listCommand(argv);
|
|
64
64
|
})
|
|
65
|
+
.command('show <method> <path>', 'Show endpoint details', (yargs) => {
|
|
66
|
+
return yargs
|
|
67
|
+
.positional('method', { type: 'string', describe: 'HTTP method' })
|
|
68
|
+
.positional('path', { type: 'string', describe: 'API path' })
|
|
69
|
+
.option('json', { type: 'boolean', describe: 'Full JSON output' })
|
|
70
|
+
.option('spec', { type: 'string', describe: 'Path to spec file' })
|
|
71
|
+
.option('project', { type: 'string', describe: 'Project directory' });
|
|
72
|
+
}, async (argv) => {
|
|
73
|
+
const { showCommand } = await import('./commands/show.js');
|
|
74
|
+
await showCommand(argv);
|
|
75
|
+
})
|
|
65
76
|
.command('projects [action] [dir]', 'Manage project bookmarks', (yargs) => {
|
|
66
77
|
return yargs
|
|
67
78
|
.positional('action', { choices: ['add', 'remove'], describe: 'Action' })
|
|
@@ -114,5 +125,7 @@ const cli = yargs(hideBin(process.argv))
|
|
|
114
125
|
await mcpCommand(argv);
|
|
115
126
|
})
|
|
116
127
|
.demandCommand(1, 'You need at least one command')
|
|
128
|
+
.strictCommands()
|
|
129
|
+
.completion('completion', 'Generate shell completion script')
|
|
117
130
|
.help()
|
|
118
131
|
.argv;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#compdef apigrip
|
|
2
|
+
|
|
3
|
+
_apigrip() {
|
|
4
|
+
local -a commands
|
|
5
|
+
commands=(
|
|
6
|
+
'serve:Start the web UI server'
|
|
7
|
+
'send:Send an API request'
|
|
8
|
+
'curl:Generate curl command'
|
|
9
|
+
'list:List endpoints from spec'
|
|
10
|
+
'show:Show endpoint details'
|
|
11
|
+
'projects:Manage project bookmarks'
|
|
12
|
+
'last:Show last cached response'
|
|
13
|
+
'env:Manage environments'
|
|
14
|
+
'spec:Print the parsed OpenAPI spec'
|
|
15
|
+
'mcp:Start MCP server'
|
|
16
|
+
'completion:Generate shell completion script'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
local -a global_opts
|
|
20
|
+
global_opts=(
|
|
21
|
+
'--help[Show help]'
|
|
22
|
+
'--version[Show version number]'
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
local -a project_opts
|
|
26
|
+
project_opts=(
|
|
27
|
+
'--project[Project directory]:directory:_directories'
|
|
28
|
+
'--spec[Path to spec file]:file:_files'
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_arguments -C \
|
|
32
|
+
'1:command:->command' \
|
|
33
|
+
'*::arg:->args'
|
|
34
|
+
|
|
35
|
+
case $state in
|
|
36
|
+
command)
|
|
37
|
+
_describe -t commands 'apigrip command' commands
|
|
38
|
+
;;
|
|
39
|
+
args)
|
|
40
|
+
case $words[1] in
|
|
41
|
+
serve)
|
|
42
|
+
_arguments \
|
|
43
|
+
'--port[Port to listen on]:port' \
|
|
44
|
+
'--host[Host to bind to]:host' \
|
|
45
|
+
'--open[Open browser on start]' \
|
|
46
|
+
'--no-browse[Disable directory browsing]' \
|
|
47
|
+
$project_opts \
|
|
48
|
+
$global_opts
|
|
49
|
+
;;
|
|
50
|
+
send)
|
|
51
|
+
_arguments \
|
|
52
|
+
'1:method:(GET POST PUT PATCH DELETE HEAD OPTIONS)' \
|
|
53
|
+
'2:path' \
|
|
54
|
+
'-d[Request body]:body' \
|
|
55
|
+
'--data[Request body]:body' \
|
|
56
|
+
'-e[Environment name]:env' \
|
|
57
|
+
'--env[Environment name]:env' \
|
|
58
|
+
'*--query[Query params (key=value)]:param' \
|
|
59
|
+
'*--header[Headers (Name: value)]:header' \
|
|
60
|
+
'*--pathParam[Path params (key=value)]:param' \
|
|
61
|
+
'-v[Verbose output]' \
|
|
62
|
+
'--verbose[Verbose output]' \
|
|
63
|
+
'-i[Include headers]' \
|
|
64
|
+
'--headers[Include headers]' \
|
|
65
|
+
'--dry-run[Print without sending]' \
|
|
66
|
+
'--curl[Print curl command only]' \
|
|
67
|
+
'--filter[jq filter for response body]:filter' \
|
|
68
|
+
$project_opts \
|
|
69
|
+
$global_opts
|
|
70
|
+
;;
|
|
71
|
+
curl)
|
|
72
|
+
_arguments \
|
|
73
|
+
'1:method:(GET POST PUT PATCH DELETE HEAD OPTIONS)' \
|
|
74
|
+
'2:path' \
|
|
75
|
+
'-d[Request body]:body' \
|
|
76
|
+
'--data[Request body]:body' \
|
|
77
|
+
'-e[Environment name]:env' \
|
|
78
|
+
'--env[Environment name]:env' \
|
|
79
|
+
'*--query[Query params (key=value)]:param' \
|
|
80
|
+
'*--header[Headers (Name: value)]:header' \
|
|
81
|
+
'*--pathParam[Path params (key=value)]:param' \
|
|
82
|
+
$project_opts \
|
|
83
|
+
$global_opts
|
|
84
|
+
;;
|
|
85
|
+
list)
|
|
86
|
+
_arguments \
|
|
87
|
+
'--tag[Filter by tag]:tag' \
|
|
88
|
+
'--search[Search endpoints]:search' \
|
|
89
|
+
'--json[JSON output]' \
|
|
90
|
+
$project_opts \
|
|
91
|
+
$global_opts
|
|
92
|
+
;;
|
|
93
|
+
show)
|
|
94
|
+
_arguments \
|
|
95
|
+
'1:method:(GET POST PUT PATCH DELETE HEAD OPTIONS)' \
|
|
96
|
+
'2:path' \
|
|
97
|
+
'--json[Full JSON output]' \
|
|
98
|
+
$project_opts \
|
|
99
|
+
$global_opts
|
|
100
|
+
;;
|
|
101
|
+
projects)
|
|
102
|
+
_arguments \
|
|
103
|
+
'1:action:(add remove)' \
|
|
104
|
+
'2:directory:_directories'
|
|
105
|
+
;;
|
|
106
|
+
last)
|
|
107
|
+
_arguments \
|
|
108
|
+
'1:method:(GET POST PUT PATCH DELETE HEAD OPTIONS)' \
|
|
109
|
+
'2:path' \
|
|
110
|
+
'--json[Full JSON output]' \
|
|
111
|
+
'-v[Verbose output]' \
|
|
112
|
+
'--verbose[Verbose output]' \
|
|
113
|
+
'-i[Include headers]' \
|
|
114
|
+
'--headers[Include headers]' \
|
|
115
|
+
$project_opts \
|
|
116
|
+
$global_opts
|
|
117
|
+
;;
|
|
118
|
+
env)
|
|
119
|
+
_arguments \
|
|
120
|
+
'1:action:(show set delete import)' \
|
|
121
|
+
'2:name' \
|
|
122
|
+
'3:key' \
|
|
123
|
+
'4:value' \
|
|
124
|
+
'--import-name[Target environment name]:name' \
|
|
125
|
+
'--merge[Merge with existing values]' \
|
|
126
|
+
'--project[Project directory]:directory:_directories'
|
|
127
|
+
;;
|
|
128
|
+
spec)
|
|
129
|
+
_arguments \
|
|
130
|
+
'--raw[Print raw spec file]' \
|
|
131
|
+
$project_opts \
|
|
132
|
+
$global_opts
|
|
133
|
+
;;
|
|
134
|
+
mcp)
|
|
135
|
+
_arguments \
|
|
136
|
+
$project_opts \
|
|
137
|
+
$global_opts
|
|
138
|
+
;;
|
|
139
|
+
esac
|
|
140
|
+
;;
|
|
141
|
+
esac
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
_apigrip
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apigrip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "A spec-first, read-only OpenAPI client for developers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.cjs",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"lib/",
|
|
21
21
|
"mcp/",
|
|
22
22
|
"server/",
|
|
23
|
-
"client/dist/"
|
|
23
|
+
"client/dist/",
|
|
24
|
+
"completions/"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"start": "node cli/index.js serve",
|