apigrip 0.6.4 → 0.6.7
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 +35 -2
- package/cli/commands/last.js +32 -0
- package/cli/commands/serve.js +13 -0
- package/cli/index.js +2 -0
- package/client/dist/assets/index-BNWZuYOS.css +1 -0
- package/client/dist/assets/index-gg39Ts4w.js +75 -0
- package/client/dist/index.html +2 -2
- package/core/history-store.js +138 -0
- package/core/project-config.js +108 -0
- package/core/spec-discovery.js +17 -1
- package/lib/index.js +27 -0
- package/mcp/server.js +21 -0
- package/package.json +35 -2
- package/server/routes/project.js +4 -0
- package/server/routes/requests.js +50 -0
- package/server/spec-watcher.js +34 -3
- package/client/dist/assets/index-CRa1JtiS.css +0 -1
- package/client/dist/assets/index-Iwhkggco.js +0 -75
package/README.md
CHANGED
|
@@ -38,11 +38,13 @@ Open `http://127.0.0.1:3000` in your browser.
|
|
|
38
38
|
- Live reload on spec changes, git branch switches, and `$ref` dependency updates (web UI and MCP)
|
|
39
39
|
- Parameter persistence across restarts (auto-saved per project, per endpoint)
|
|
40
40
|
- Named environments with `{{ variable }}` syntax resolved at send time
|
|
41
|
+
- `.apigrip.json` project config for team-shared environment seeding and spec path override
|
|
41
42
|
- Git status bar showing branch, commit, and dirty/clean state
|
|
42
43
|
- `Ctrl+P` command palette with fuzzy search across projects and endpoints
|
|
43
44
|
- Response schema validation against the spec
|
|
44
45
|
- Dracula dark theme by default, light mode available
|
|
45
46
|
- MCP server for AI coding assistants
|
|
47
|
+
- Electron desktop app (standalone window, no browser needed)
|
|
46
48
|
|
|
47
49
|
## CLI
|
|
48
50
|
|
|
@@ -140,6 +142,33 @@ Two-panel layout: request on the left, response on the right.
|
|
|
140
142
|
|
|
141
143
|
**Keyboard shortcuts**: `Ctrl+P` (command palette), `Ctrl+O` (open project), `Ctrl+E` (environment editor), `Ctrl+Enter` (send request), `Ctrl+Shift+L` (toggle theme).
|
|
142
144
|
|
|
145
|
+
## Desktop app (Electron)
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npm run electron:dev # launch in development
|
|
149
|
+
npm run electron:build # package distributable
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The Electron wrapper runs the Express server in-process and opens the UI in a native window. Same features as the web UI, no browser required.
|
|
153
|
+
|
|
154
|
+
## Project config (`.apigrip.json`)
|
|
155
|
+
|
|
156
|
+
Optional checked-in file at your project root that seeds environments for the team:
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"spec": "docs/openapi.yaml",
|
|
161
|
+
"active_environment": "staging",
|
|
162
|
+
"environments": {
|
|
163
|
+
"base": { "base_url": "https://api.example.com" },
|
|
164
|
+
"staging": { "base_url": "https://staging.example.com" },
|
|
165
|
+
"production": { "base_url": "https://prod.example.com" }
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
All fields are optional. The seed algorithm is non-destructive: it only adds environments that don't already exist in the user's config and never overwrites existing values. The `spec` field overrides automatic spec discovery.
|
|
171
|
+
|
|
143
172
|
## Environments
|
|
144
173
|
|
|
145
174
|
Environments are named sets of key-value pairs stored per project in the config directory (never in your project).
|
|
@@ -195,6 +224,7 @@ const cached = loadLastResponse('./my-api/', 'GET', '/users');
|
|
|
195
224
|
| **Environment** | `loadEnvironments`, `saveEnvironments`, `resolveEnvironment`, `resolveVariables`, `resolveAllParams`, `getConfigDir`, `getProjectHash` |
|
|
196
225
|
| **Validation** | `validateBody`, `validateResponse` |
|
|
197
226
|
| **Storage** | `loadParams`, `saveParams`, `saveLastResponse`, `loadLastResponse`, `loadProjects`, `addProject`, `removeProject`, `loadPreferences`, `savePreferences`, `getGitInfo` |
|
|
227
|
+
| **Project config** | `loadProjectConfig`, `applyProjectConfig`, `getSpecOverride` |
|
|
198
228
|
| **Convenience** | `loadSpec`, `send` |
|
|
199
229
|
|
|
200
230
|
Direct core module access is also available: `import { parseSpec } from 'apigrip/core/spec-parser.js'`
|
|
@@ -240,7 +270,10 @@ apigrip/
|
|
|
240
270
|
│ ├── response-store.js Per-endpoint response caching
|
|
241
271
|
│ ├── projects-store.js Project bookmark CRUD
|
|
242
272
|
│ ├── preferences-store.js Global + per-project preferences
|
|
273
|
+
│ ├── project-config.js .apigrip.json loader and environment seeder
|
|
243
274
|
│ └── schema-validator.js JSON Schema validation (ajv)
|
|
275
|
+
├── electron/ Electron desktop wrapper
|
|
276
|
+
│ └── main.js Main process (Express + BrowserWindow)
|
|
244
277
|
├── lib/ Programmatic library API
|
|
245
278
|
│ ├── index.js ESM entrypoint (re-exports + loadSpec, send)
|
|
246
279
|
│ └── index.cjs CJS compatibility wrapper
|
|
@@ -248,7 +281,7 @@ apigrip/
|
|
|
248
281
|
│ └── server.js Tools + resources -> core modules
|
|
249
282
|
├── server/ Express server, routes, SSE, file watcher
|
|
250
283
|
│ ├── index.js Server factory, route mounting
|
|
251
|
-
│ ├── spec-watcher.js Chokidar watchers (spec, $ref deps, .git
|
|
284
|
+
│ ├── spec-watcher.js Chokidar watchers (spec, $ref deps, .git/, .apigrip.json)
|
|
252
285
|
│ └── routes/ REST endpoint handlers
|
|
253
286
|
├── client/ React frontend (Vite build)
|
|
254
287
|
│ ├── components/ TopBar, EndpointList, RequestPanel, ResponsePanel, ...
|
|
@@ -270,7 +303,7 @@ Config is stored at `$XDG_CONFIG_HOME/apigrip/` (or `~/.config/apigrip/`). Proje
|
|
|
270
303
|
npm test
|
|
271
304
|
```
|
|
272
305
|
|
|
273
|
-
Runs
|
|
306
|
+
Runs 814 tests using Node's built-in test runner (`node --test`). The test suite uses the tool's own OpenAPI spec as a fixture (self-referential testing).
|
|
274
307
|
|
|
275
308
|
## License
|
|
276
309
|
|
package/cli/commands/last.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// cli/commands/last.js — Show the last cached response for an endpoint
|
|
2
2
|
|
|
3
3
|
import { loadLastResponse, loadAllResponses } from '../../core/response-store.js';
|
|
4
|
+
import { loadHistory, loadHistoryEntry } from '../../core/history-store.js';
|
|
4
5
|
import { formatResponse } from '../output.js';
|
|
5
6
|
import { resolveProjectContext } from '../resolve-project.js';
|
|
6
7
|
|
|
@@ -35,6 +36,37 @@ export async function lastCommand(argv) {
|
|
|
35
36
|
}
|
|
36
37
|
const apiPath = pathArg.startsWith('/') ? pathArg : '/' + pathArg;
|
|
37
38
|
|
|
39
|
+
// --history flag: show history list
|
|
40
|
+
if (argv.history) {
|
|
41
|
+
const entries = loadHistory(projectDir, method, apiPath);
|
|
42
|
+
if (entries.length === 0) {
|
|
43
|
+
console.log(`No history for ${method} ${apiPath}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
for (const entry of entries) {
|
|
47
|
+
const age = entry.timestamp ? timeSince(entry.timestamp) : 'unknown';
|
|
48
|
+
const time = entry.timing?.total_ms != null ? `${entry.timing.total_ms}ms` : '';
|
|
49
|
+
console.log(`#${entry.index} ${entry.status} ${time} ${age}`);
|
|
50
|
+
}
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --index N flag: show specific history entry
|
|
55
|
+
if (argv.index != null) {
|
|
56
|
+
const entry = loadHistoryEntry(projectDir, method, apiPath, Number(argv.index));
|
|
57
|
+
if (!entry) {
|
|
58
|
+
console.error(`No history entry #${argv.index} for ${method} ${apiPath}`);
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (argv.json) {
|
|
63
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
console.log(formatResponse(entry, { verbose: argv.verbose, headers: argv.headers }));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
38
70
|
const cached = loadLastResponse(projectDir, method, apiPath);
|
|
39
71
|
if (!cached) {
|
|
40
72
|
console.error(`No cached response for ${method} ${apiPath}`);
|
package/cli/commands/serve.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createSpecWatcher } from '../../server/spec-watcher.js';
|
|
|
6
6
|
import { broadcast } from '../../server/routes/events.js';
|
|
7
7
|
import { findProjectForDir, loadProjects } from '../../core/projects-store.js';
|
|
8
8
|
import { discoverSpec } from '../../core/spec-discovery.js';
|
|
9
|
+
import { applyProjectConfig } from '../../core/project-config.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Quick project resolution for the serve command.
|
|
@@ -72,6 +73,18 @@ export async function serveCommand(argv) {
|
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
// Apply .apigrip.json project config (seeds environments)
|
|
77
|
+
if (projectDir) {
|
|
78
|
+
try {
|
|
79
|
+
const configResult = applyProjectConfig(projectDir);
|
|
80
|
+
if (configResult.seeded.length > 0) {
|
|
81
|
+
console.log(`Seeded environments from .apigrip.json: ${configResult.seeded.join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Non-critical — continue without project config
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
const noBrowse = argv.noBrowse || argv['no-browse'] || false;
|
|
76
89
|
const port = argv.port || 3000;
|
|
77
90
|
const host = argv.host || '127.0.0.1';
|
package/cli/index.js
CHANGED
|
@@ -194,6 +194,8 @@ const cli = yargs(hideBin(process.argv))
|
|
|
194
194
|
.option('json', { type: 'boolean', describe: 'Full JSON output' })
|
|
195
195
|
.option('v', { alias: 'verbose', type: 'boolean', describe: 'Verbose output' })
|
|
196
196
|
.option('i', { alias: 'headers', type: 'boolean', describe: 'Include headers' })
|
|
197
|
+
.option('history', { type: 'boolean', describe: 'Show request history' })
|
|
198
|
+
.option('index', { type: 'number', describe: 'Show specific history entry by index' })
|
|
197
199
|
.option('project', { type: 'string', describe: 'Project directory' })
|
|
198
200
|
.option('spec', { type: 'string', describe: 'Path to spec file' });
|
|
199
201
|
}, async (argv) => {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--bg: #282a36;--bg-secondary: #44475a;--bg-input: #1e1f29;--fg: #f8f8f2;--fg-muted: #6272a4;--fg-faint: #44475a;--border: #44475a;--accent: #bd93f9;--accent-hover: #caa8ff;--method-get: #50fa7b;--method-post: #bd93f9;--method-put: #ffb86c;--method-delete: #ff5555;--method-patch: #f1fa8c;--status-2xx: #50fa7b;--status-3xx: #f1fa8c;--status-4xx: #ffb86c;--status-5xx: #ff5555;--font-mono: "SF Mono", "Fira Code", "Cascadia Code", monospace;--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--radius: 6px;--transition: .15s ease}[data-theme=light]{--bg: #f8f8f2;--bg-secondary: #e8e8e2;--bg-input: #ffffff;--fg: #282a36;--fg-muted: #6272a4;--fg-faint: #c0c0c0;--border: #d0d0d0;--accent: #7c5cbf;--accent-hover: #6a4daa}*,*:before,*:after{box-sizing:border-box;margin:0;padding:0}body{background:var(--bg);color:var(--fg);font-family:var(--font-sans);font-size:14px;line-height:1.5;height:100vh;overflow:hidden}#root{display:flex;flex-direction:column;height:100%}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:var(--bg)}::-webkit-scrollbar-thumb{background:var(--bg-secondary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--fg-muted)}*{scrollbar-width:thin;scrollbar-color:var(--bg-secondary) var(--bg)}::selection{background:var(--accent);color:var(--bg)}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}h1{font-size:1.5rem;font-weight:700;line-height:1.3}h2{font-size:1.25rem;font-weight:600;line-height:1.3}h3{font-size:1rem;font-weight:600;line-height:1.4}h4{font-size:.875rem;font-weight:600;line-height:1.4}p{margin-bottom:.5em}code{font-family:var(--font-mono);font-size:.9em;background:var(--bg-secondary);padding:.15em .35em;border-radius:3px}pre{font-family:var(--font-mono);font-size:.85rem;line-height:1.5;white-space:pre-wrap;word-wrap:break-word}.app{display:flex;flex-direction:column;height:100%}.app__body{display:flex;flex:1;min-height:0;overflow:hidden}.app__sidebar{width:280px;min-width:200px;border-right:1px solid var(--border);display:flex;flex-direction:column;overflow:hidden}.app__main{flex:1;display:flex;min-width:0;overflow:hidden}.app__request-panel{flex:1;min-width:0;overflow-y:auto;border-right:1px solid var(--border);padding:16px}.app__response-panel{flex:1;min-width:0;overflow-y:auto;padding:16px}.topbar{display:flex;align-items:center;gap:12px;padding:8px 16px;background:var(--bg-secondary);border-bottom:1px solid var(--border);min-height:44px;flex-shrink:0}.topbar__section{display:flex;align-items:center;gap:8px}.topbar__section--right{margin-left:auto}.topbar__label{font-size:12px;color:var(--fg-muted);text-transform:uppercase;letter-spacing:.05em}.topbar__select{background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:4px 8px;font-size:13px;font-family:var(--font-sans);cursor:pointer}.topbar__select:hover{border-color:var(--accent)}.topbar__btn{background:none;border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);padding:4px 10px;font-size:12px;cursor:pointer;transition:background var(--transition)}.topbar__btn:hover{background:var(--bg)}.topbar__git{display:flex;align-items:center;gap:6px;font-size:12px;color:var(--fg-muted);font-family:var(--font-mono)}.topbar__git-branch{color:var(--accent)}.topbar__git-commit{color:var(--fg-muted)}.topbar__git-dirty{color:var(--method-put)}.topbar__toast{font-size:12px;color:var(--method-get);animation:topbar-toast-fade 3s ease forwards}@keyframes topbar-toast-fade{0%{opacity:1}70%{opacity:1}to{opacity:0}}.endpoint-list{display:flex;flex-direction:column;height:100%}.endpoint-list__search{padding:8px;border-bottom:1px solid var(--border)}.endpoint-list__search-input{width:100%;background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:6px 10px;font-size:13px;font-family:var(--font-sans)}.endpoint-list__search-input::placeholder{color:var(--fg-muted)}.endpoint-list__toolbar{display:flex;align-items:center;justify-content:flex-end;padding:4px 8px;border-bottom:1px solid var(--border)}.endpoint-list__view-btn{background:none;border:none;color:var(--fg-muted);cursor:pointer;padding:2px 6px;font-size:12px}.endpoint-list__view-btn--active{color:var(--accent)}.endpoint-list__items{flex:1;overflow-y:auto;padding:4px 0}.endpoint-list__tag-header{padding:8px 12px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--fg-muted)}.endpoint-list__tree-folder{padding:4px 12px;font-size:12px;color:var(--fg-muted);cursor:pointer;display:flex;align-items:center;gap:4px;-webkit-user-select:none;user-select:none}.endpoint-list__tree-folder:hover{color:var(--fg)}.endpoint-list__tree-children{padding-left:16px}.endpoint-item{display:flex;align-items:center;gap:8px;padding:5px 12px;cursor:pointer;transition:background var(--transition);border-left:3px solid transparent}.endpoint-item:hover{background:var(--bg-secondary)}.endpoint-item--selected{background:var(--bg-secondary);border-left-color:var(--accent)}.endpoint-item__method{font-size:10px;font-weight:700;font-family:var(--font-mono);text-transform:uppercase;min-width:52px;text-align:center;padding:2px 6px;border-radius:3px;letter-spacing:.02em}.endpoint-item__method--get{color:var(--method-get);background:#50fa7b1a}.endpoint-item__method--post{color:var(--method-post);background:#bd93f91a}.endpoint-item__method--put{color:var(--method-put);background:#ffb86c1a}.endpoint-item__method--delete{color:var(--method-delete);background:#ff55551a}.endpoint-item__method--patch{color:var(--method-patch);background:#f1fa8c1a}.endpoint-item__path{font-family:var(--font-mono);font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.endpoint-item__summary{font-size:11px;color:var(--fg-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-left:auto}.request-panel__section{margin-bottom:16px}.request-panel__section-title{font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--fg-muted);margin-bottom:8px}.request-panel__server-select{width:100%;background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:6px 10px;font-family:var(--font-mono);font-size:13px}.request-panel__server-vars{display:flex;flex-direction:column;gap:6px;margin-top:8px}.request-panel__send-bar{display:flex;align-items:center;gap:8px;margin-top:16px;margin-bottom:24px}.request-panel__send-btn{background:var(--accent);color:var(--bg);border:none;border-radius:var(--radius);padding:8px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:background var(--transition)}.request-panel__send-btn:hover{background:var(--accent-hover)}.request-panel__send-btn:disabled{opacity:.5;cursor:not-allowed}.request-panel__cancel-btn{background:none;border:1px solid var(--border);border-radius:var(--radius);color:var(--fg-muted);padding:6px 14px;font-size:13px;cursor:pointer;transition:color var(--transition),border-color var(--transition)}.request-panel__cancel-btn:hover{color:var(--method-delete);border-color:var(--method-delete)}.request-panel__spinner{display:inline-block;width:16px;height:16px;border:2px solid var(--fg-muted);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.param-form{display:flex;flex-direction:column;gap:10px}.param-form__field{display:flex;flex-direction:column;gap:3px}.param-form__label{display:flex;align-items:center;gap:4px;font-size:12px;font-weight:600}.param-form__required{color:var(--method-delete)}.param-form__desc{font-size:11px;color:var(--fg-muted)}.param-form__input{background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:6px 10px;font-family:var(--font-mono);font-size:13px}.param-form__input:focus{border-color:var(--accent)}.param-form__resolved{font-size:11px;color:var(--fg-muted);font-family:var(--font-mono)}.hl-wrap{position:relative}.hl-backdrop{position:absolute;inset:0;padding:6px 10px;font-family:var(--font-mono);font-size:13px;line-height:normal;color:transparent;white-space:pre;overflow:hidden;pointer-events:none;border:1px solid transparent;border-radius:var(--radius)}.hl-input{background:transparent!important;position:relative;caret-color:var(--fg)}.hl-wrap .param-form__input{background:transparent}.hl-wrap:before{content:"";position:absolute;inset:0;background:var(--bg-input);border-radius:var(--radius);z-index:-1}.hl-var{background:color-mix(in srgb,var(--method-get) 20%,transparent);color:var(--method-get);border-radius:3px;padding:0 1px}.body-editor__toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px}.body-editor__content-type{background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:4px 8px;font-size:13px}.body-editor__sample-btn{background:none;border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);padding:4px 10px;font-size:12px;cursor:pointer}.body-editor__sample-btn:hover{border-color:var(--accent);color:var(--accent)}.body-editor__textarea{width:100%;min-height:200px;background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:10px;font-family:var(--font-mono);font-size:13px;line-height:1.5;resize:vertical}.body-editor__kv-table{width:100%;border-collapse:collapse}.body-editor__kv-table th{text-align:left;font-size:11px;font-weight:600;color:var(--fg-muted);padding:4px 8px;border-bottom:1px solid var(--border)}.body-editor__kv-table td{padding:4px 8px}.body-editor__kv-input{width:100%;background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:3px;padding:4px 8px;font-family:var(--font-mono);font-size:13px}.body-editor__kv-remove{background:none;border:none;color:var(--method-delete);cursor:pointer;font-size:16px;padding:2px 6px}.body-editor__kv-add{background:none;border:1px dashed var(--border);border-radius:var(--radius);color:var(--fg-muted);padding:4px 12px;font-size:12px;cursor:pointer;margin-top:6px}.body-editor__kv-add:hover{border-color:var(--accent);color:var(--accent)}.body-editor__validation{margin-top:6px;font-size:12px;display:flex;align-items:center;gap:4px}.body-editor__validation--valid{color:var(--method-get)}.body-editor__validation--invalid{color:var(--method-delete)}.body-editor__validation--none{color:var(--fg-muted)}.response-panel__empty{display:flex;align-items:center;justify-content:center;height:100%;color:var(--fg-muted);font-size:14px}.response-panel__loading{display:flex;align-items:center;justify-content:center;height:100%;gap:10px;color:var(--fg-muted)}.response-panel__header{display:flex;align-items:center;gap:12px;margin-bottom:12px;flex-wrap:wrap}.response-panel__status{font-weight:700;font-family:var(--font-mono);font-size:14px;padding:3px 10px;border-radius:var(--radius)}.response-panel__status--2xx{color:var(--status-2xx);background:#50fa7b1a}.response-panel__status--3xx{color:var(--status-3xx);background:#f1fa8c1a}.response-panel__status--4xx{color:var(--status-4xx);background:#ffb86c1a}.response-panel__status--5xx{color:var(--status-5xx);background:#ff55551a}.response-panel__meta{font-size:12px;color:var(--fg-muted)}.response-panel__tabs{display:flex;border-bottom:1px solid var(--border);margin-bottom:12px}.response-panel__tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--fg-muted);padding:6px 16px;font-size:13px;cursor:pointer;transition:color var(--transition),border-color var(--transition)}.response-panel__tab:hover{color:var(--fg)}.response-panel__tab--active{color:var(--accent);border-bottom-color:var(--accent)}.response-panel__history{position:relative;margin-left:auto}.response-panel__history-btn{background:var(--bg-input);border:1px solid var(--border);color:var(--fg-muted);padding:3px 10px;border-radius:var(--radius);font-size:12px;cursor:pointer;transition:color var(--transition),border-color var(--transition)}.response-panel__history-btn:hover{color:var(--fg);border-color:var(--accent)}.response-panel__history-dropdown{position:absolute;top:100%;right:0;z-index:50;margin-top:4px;min-width:240px;max-height:320px;overflow-y:auto;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);box-shadow:0 4px 12px #0000004d}.response-panel__history-item{display:flex;align-items:center;gap:8px;width:100%;padding:6px 10px;border:none;background:none;color:var(--fg);font-size:12px;cursor:pointer;text-align:left}.response-panel__history-item:hover{background:var(--bg-secondary)}.response-panel__history-item .response-panel__status{font-size:11px;padding:1px 6px}.response-panel__history-time{color:var(--fg-muted);font-family:var(--font-mono);font-size:11px}.response-panel__history-ago{color:var(--fg-muted);font-size:11px;margin-left:auto}.response-panel__history-clear{display:block;width:100%;padding:6px 10px;border:none;border-top:1px solid var(--border);background:none;color:var(--method-delete);font-size:12px;cursor:pointer;text-align:center}.response-panel__history-clear:hover{background:var(--bg-secondary)}.body-view__toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px}.body-view__btn{background:none;border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);padding:3px 10px;font-size:12px;cursor:pointer}.body-view__btn:hover,.body-view__btn--active{border-color:var(--accent);color:var(--accent)}.body-view__pre{background:var(--bg-input);border-radius:var(--radius);padding:12px;overflow:auto;max-height:calc(100vh - 260px);font-size:13px;line-height:1.5}.body-view__pre code{background:none;padding:0;border-radius:0}.body-view__pre .hljs-attr{color:#50fa7b}.body-view__pre .hljs-string{color:#f1fa8c}.body-view__pre .hljs-number{color:#bd93f9}.body-view__pre .hljs-literal,.body-view__pre .hljs-keyword,.body-view__pre .hljs-tag{color:#ff79c6}.body-view__pre .hljs-name{color:#50fa7b}.body-view__pre .hljs-selector-tag{color:#ff79c6}.body-view__pre .hljs-punctuation{color:var(--fg)}.body-view__pre--wrap{white-space:pre-wrap;word-wrap:break-word}.body-view__pre--nowrap{white-space:pre}.body-view__filter-group{display:flex;align-items:center;flex:1;position:relative}.body-view__filter{width:100%;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);padding:3px 26px 3px 10px;font-size:12px;font-family:inherit}.body-view__filter::placeholder{color:var(--fg-muted)}.body-view__filter:focus{outline:none;border-color:var(--accent)}.body-view__filter--active{border-color:var(--method-get)}.body-view__filter--error{border-color:var(--method-delete)}.body-view__filter-clear{position:absolute;right:4px;background:none;border:none;color:var(--fg-muted);cursor:pointer;font-size:12px;padding:2px 6px;line-height:1}.body-view__filter-clear:hover{color:var(--fg)}.body-view__filter-error{color:var(--method-delete);font-size:11px;margin-bottom:6px;padding:2px 0}.headers-view__table{width:100%;border-collapse:collapse}.headers-view__table th{text-align:left;font-size:11px;font-weight:600;color:var(--fg-muted);padding:6px 10px;border-bottom:1px solid var(--border)}.headers-view__table td{padding:5px 10px;font-family:var(--font-mono);font-size:13px;border-bottom:1px solid var(--fg-faint)}.headers-view__table td:first-child{color:var(--accent);white-space:nowrap}.headers-view__section-title{font-size:12px;font-weight:600;color:var(--fg-muted);margin:12px 0 6px;cursor:pointer;-webkit-user-select:none;user-select:none}.verbose-view{background:var(--bg-input);border-radius:var(--radius);padding:12px;overflow:auto;max-height:calc(100vh - 220px);font-family:var(--font-mono);font-size:13px;line-height:1.6;white-space:pre-wrap;word-wrap:break-word}.verbose-view__line--comment{color:#6272a4}.verbose-view__line--request{color:#8be9fd}.verbose-view__line--response{color:#50fa7b}.verbose-view__line--other{color:#f8f8f2}.curl-view{position:relative}.curl-view__pre{background:var(--bg-input);border-radius:var(--radius);padding:12px;font-family:var(--font-mono);font-size:13px;line-height:1.5;white-space:pre-wrap;word-wrap:break-word;overflow:auto;max-height:calc(100vh - 220px)}.curl-view__copy-btn{position:absolute;top:8px;right:8px}.env-editor{position:fixed;inset:0;z-index:1000;display:flex;align-items:center;justify-content:center}.env-editor__backdrop{position:absolute;inset:0;background:#0009}.env-editor__dialog{position:relative;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);width:640px;max-width:90vw;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 8px 32px #0006}.env-editor__header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--border)}.env-editor__title{font-size:16px;font-weight:600}.env-editor__close{background:none;border:none;color:var(--fg-muted);font-size:20px;cursor:pointer;padding:2px 6px}.env-editor__close:hover{color:var(--fg)}.env-editor__tabs{display:flex;border-bottom:1px solid var(--border);padding:0 16px;overflow-x:auto}.env-editor__tab{background:none;border:none;border-bottom:2px solid transparent;color:var(--fg-muted);padding:8px 14px;font-size:13px;cursor:pointer;white-space:nowrap}.env-editor__tab:hover{color:var(--fg)}.env-editor__tab--active{color:var(--accent);border-bottom-color:var(--accent)}.env-editor__body{flex:1;overflow-y:auto;padding:16px}.env-editor__new-env{display:flex;align-items:center;gap:8px;margin-bottom:12px}.env-editor__new-env-input{flex:1;background:var(--bg-input);color:var(--fg);border:1px solid var(--border);border-radius:var(--radius);padding:6px 10px;font-size:13px}.env-editor__footer{display:flex;align-items:center;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--border)}.env-editor__save-btn{background:var(--accent);color:var(--bg);border:none;border-radius:var(--radius);padding:6px 20px;font-size:13px;font-weight:600;cursor:pointer}.env-editor__save-btn:hover{background:var(--accent-hover)}.env-editor__delete-btn{background:none;border:1px solid var(--method-delete);border-radius:var(--radius);color:var(--method-delete);padding:6px 14px;font-size:12px;cursor:pointer}.env-editor__delete-btn:hover{background:#ff55551a}.command-palette{position:fixed;inset:0;z-index:2000;display:flex;justify-content:center;padding-top:15vh}.command-palette__backdrop{position:absolute;inset:0;background:#00000080}.command-palette__dialog{position:relative;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);width:520px;max-width:90vw;max-height:60vh;display:flex;flex-direction:column;box-shadow:0 8px 32px #0006}.command-palette__input{background:var(--bg-input);color:var(--fg);border:none;border-bottom:1px solid var(--border);padding:14px 16px;font-size:15px;font-family:var(--font-sans);border-radius:var(--radius) var(--radius) 0 0}.command-palette__input::placeholder{color:var(--fg-muted)}.command-palette__results{flex:1;overflow-y:auto;padding:4px 0}.command-palette__section-title{padding:8px 16px 4px;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--fg-muted)}.command-palette__item{display:flex;align-items:center;gap:10px;padding:8px 16px;cursor:pointer;transition:background var(--transition)}.command-palette__item:hover,.command-palette__item--active{background:var(--bg-secondary)}.command-palette__item-label{font-size:14px}.command-palette__item-detail{font-size:12px;color:var(--fg-muted);margin-left:auto}.command-palette__item-shortcut{margin-left:auto;font-family:var(--font-mono);font-size:11px;color:var(--fg-muted);background:var(--bg-input);border:1px solid var(--border);border-radius:3px;padding:1px 6px;white-space:nowrap}.endpoint-doc{border:1px solid var(--border);border-radius:var(--radius);margin-bottom:16px}.endpoint-doc__toggle{display:flex;align-items:center;justify-content:space-between;width:100%;background:none;border:none;color:var(--fg);padding:10px 24px;font-size:14px;font-weight:600;cursor:pointer;text-align:left}.endpoint-doc__toggle:hover{background:var(--bg-secondary)}.endpoint-doc__toggle-icon{color:var(--fg-muted);font-size:12px;transition:transform var(--transition)}.endpoint-doc__toggle-icon--expanded{transform:rotate(90deg)}.endpoint-doc__body{padding:0 24px 14px;font-size:13px;line-height:1.6}.endpoint-doc__deprecated{background:#ff55551a;color:var(--method-delete);padding:6px 10px;border-radius:var(--radius);font-size:12px;font-weight:600;margin-bottom:8px}.endpoint-doc__body h1,.endpoint-doc__body h2,.endpoint-doc__body h3{margin-top:.8em;margin-bottom:.4em}.endpoint-doc__body p{margin-bottom:.5em}.endpoint-doc__body code{font-size:.9em}.body-editor__schema{margin-top:8px}.body-editor__schema-toggle{display:flex;align-items:center;justify-content:space-between;width:100%;background:var(--bg-elevated);border:1px solid var(--border);border-radius:var(--radius);padding:6px 12px;color:var(--fg-muted);font-size:12px;cursor:pointer}.body-editor__schema-toggle:hover{color:var(--fg)}.body-editor__schema-body{padding:10px 12px;border:1px solid var(--border);border-top:none;border-radius:0 0 var(--radius) var(--radius);font-size:12px;font-family:var(--font-mono);max-height:300px;overflow-y:auto}.schema-view__prop{padding:3px 0;display:flex;align-items:baseline;flex-wrap:wrap;gap:6px}.schema-view__name{color:var(--accent);font-weight:600}.schema-view__type{color:var(--fg-muted);font-size:11px}.schema-view__required{color:var(--method-delete);font-size:10px;font-weight:600}.schema-view__desc{color:var(--fg-muted);font-style:italic;font-family:var(--font-sans);font-size:11px}.schema-view__enum{color:var(--fg-muted);font-size:10px}.validation-details{display:inline-flex;align-items:center;gap:6px;cursor:pointer;font-size:13px}.validation-details__indicator--valid{color:var(--method-get)}.validation-details__indicator--invalid{color:var(--method-delete)}.validation-details__indicator--none{color:var(--fg-muted)}.validation-details__errors{margin-top:8px;font-size:12px}.validation-details__error{padding:6px 10px;border-left:3px solid var(--method-delete);background:#ff55550d;margin-bottom:4px;border-radius:0 var(--radius) var(--radius) 0}.validation-details__error-path{font-family:var(--font-mono);color:var(--accent);font-size:11px}.validation-details__error-msg{color:var(--fg)}.validation-details__error-detail{color:var(--fg-muted);font-size:11px}.copy-btn{background:none;border:1px solid var(--border);border-radius:var(--radius);color:var(--fg);padding:3px 10px;font-size:12px;cursor:pointer}.copy-btn:hover{border-color:var(--accent);color:var(--accent)}[data-theme=light] .body-view__pre .hljs-attr{color:#1a8a3f}[data-theme=light] .body-view__pre .hljs-string{color:#b35c00}[data-theme=light] .body-view__pre .hljs-number{color:#7c3aed}[data-theme=light] .body-view__pre .hljs-literal,[data-theme=light] .body-view__pre .hljs-keyword,[data-theme=light] .body-view__pre .hljs-tag{color:#d6336c}[data-theme=light] .body-view__pre .hljs-name{color:#1a8a3f}[data-theme=light] .body-view__pre .hljs-selector-tag{color:#d6336c}[data-theme=light] .body-view__pre .hljs-punctuation{color:var(--fg)}.resize-handle{flex-shrink:0;background:var(--border);transition:background .15s}.resize-handle--horizontal{width:4px;cursor:col-resize}.resize-handle--vertical{height:4px;cursor:row-resize}.resize-handle:hover,.resize-handle:active{background:var(--accent)}
|