wasper-cli 0.3.1 → 0.3.3
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 +77 -83
- package/dist/cli.js +1845 -834
- package/dist/index.js +90 -31
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ var package_default;
|
|
|
21
21
|
var init_package = __esm(() => {
|
|
22
22
|
package_default = {
|
|
23
23
|
name: "wasper-cli",
|
|
24
|
-
version: "0.3.
|
|
24
|
+
version: "0.3.3",
|
|
25
25
|
description: "Host an MCP server + API proxy from any OpenAPI spec. Like Drizzle Studio, but for APIs.",
|
|
26
26
|
type: "module",
|
|
27
27
|
homepage: "https://wasper.site",
|
|
@@ -111,25 +111,69 @@ var init_version = __esm(() => {
|
|
|
111
111
|
// src/daemon.ts
|
|
112
112
|
import { join } from "path";
|
|
113
113
|
import { homedir } from "os";
|
|
114
|
-
import { mkdir, readFile, writeFile, unlink } from "fs/promises";
|
|
114
|
+
import { mkdir, readFile, readdir, writeFile, unlink } from "fs/promises";
|
|
115
115
|
async function ensureDir() {
|
|
116
|
-
await mkdir(
|
|
116
|
+
await mkdir(WASPER_DIR, { recursive: true });
|
|
117
|
+
}
|
|
118
|
+
function stateFile(port) {
|
|
119
|
+
return join(WASPER_DIR, `server-${port}.json`);
|
|
120
|
+
}
|
|
121
|
+
function logFile(port) {
|
|
122
|
+
return join(WASPER_DIR, `server-${port}.log`);
|
|
117
123
|
}
|
|
118
124
|
async function writeDaemonState(s) {
|
|
119
125
|
await ensureDir();
|
|
120
|
-
await writeFile(
|
|
126
|
+
await writeFile(stateFile(s.port), JSON.stringify(s, null, 2), "utf-8");
|
|
121
127
|
}
|
|
122
|
-
async function
|
|
128
|
+
async function readAllDaemonStates() {
|
|
123
129
|
try {
|
|
124
|
-
const
|
|
125
|
-
|
|
130
|
+
const files = await readdir(WASPER_DIR);
|
|
131
|
+
const states = [];
|
|
132
|
+
for (const f of files) {
|
|
133
|
+
if (!f.match(/^server(-\d+)?\.json$/))
|
|
134
|
+
continue;
|
|
135
|
+
const filePath = join(WASPER_DIR, f);
|
|
136
|
+
try {
|
|
137
|
+
const raw = await readFile(filePath, "utf-8");
|
|
138
|
+
const state = JSON.parse(raw);
|
|
139
|
+
if (isProcessAlive(state.pid)) {
|
|
140
|
+
states.push(state);
|
|
141
|
+
} else {
|
|
142
|
+
await unlink(filePath).catch(() => {});
|
|
143
|
+
}
|
|
144
|
+
} catch {}
|
|
145
|
+
}
|
|
146
|
+
return states.sort((a, b) => a.port - b.port);
|
|
126
147
|
} catch {
|
|
127
|
-
return
|
|
148
|
+
return [];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function readDaemonState(port) {
|
|
152
|
+
if (port !== undefined) {
|
|
153
|
+
try {
|
|
154
|
+
const raw = await readFile(stateFile(port), "utf-8");
|
|
155
|
+
const state = JSON.parse(raw);
|
|
156
|
+
return isProcessAlive(state.pid) ? state : null;
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
128
160
|
}
|
|
161
|
+
const all = await readAllDaemonStates();
|
|
162
|
+
if (all.length === 0)
|
|
163
|
+
return null;
|
|
164
|
+
if (all.length === 1)
|
|
165
|
+
return all[0];
|
|
166
|
+
return all.find((s) => s.port === DEFAULT_PORT) ?? all[0];
|
|
129
167
|
}
|
|
130
|
-
async function clearDaemonState() {
|
|
168
|
+
async function clearDaemonState(port) {
|
|
169
|
+
try {
|
|
170
|
+
await unlink(stateFile(port));
|
|
171
|
+
} catch {}
|
|
131
172
|
try {
|
|
132
|
-
await
|
|
173
|
+
const raw = await readFile(join(WASPER_DIR, "server.json"), "utf-8");
|
|
174
|
+
const state = JSON.parse(raw);
|
|
175
|
+
if (state.port === port)
|
|
176
|
+
await unlink(join(WASPER_DIR, "server.json")).catch(() => {});
|
|
133
177
|
} catch {}
|
|
134
178
|
}
|
|
135
179
|
function isProcessAlive(pid) {
|
|
@@ -163,353 +207,830 @@ async function spawnDaemon(specUrl, port, opts = {}) {
|
|
|
163
207
|
args.push("--readonly");
|
|
164
208
|
}
|
|
165
209
|
args.push("--_daemon");
|
|
166
|
-
const logDir = DIR;
|
|
167
210
|
await ensureDir();
|
|
168
|
-
const logPath = join(logDir, "server.log");
|
|
169
211
|
const child = Bun.spawn([process.execPath, Bun.main, ...args], {
|
|
170
212
|
detached: true,
|
|
171
213
|
cwd: process.cwd(),
|
|
172
214
|
env: { ...process.env },
|
|
173
|
-
stdio: ["ignore", Bun.file(
|
|
215
|
+
stdio: ["ignore", Bun.file(logFile(port)), Bun.file(logFile(port))]
|
|
174
216
|
});
|
|
175
217
|
child.unref();
|
|
176
218
|
return child.pid;
|
|
177
219
|
}
|
|
178
|
-
var
|
|
220
|
+
var WASPER_DIR, DEFAULT_PORT = 3388;
|
|
179
221
|
var init_daemon = __esm(() => {
|
|
180
|
-
|
|
181
|
-
STATE_FILE = join(DIR, "server.json");
|
|
222
|
+
WASPER_DIR = join(homedir(), ".wasper");
|
|
182
223
|
});
|
|
183
224
|
|
|
184
|
-
// src/
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
225
|
+
// src/db/schema.ts
|
|
226
|
+
var SCHEMA = `
|
|
227
|
+
CREATE TABLE IF NOT EXISTS auth_config (
|
|
228
|
+
id TEXT PRIMARY KEY DEFAULT 'default',
|
|
229
|
+
type TEXT NOT NULL DEFAULT 'none',
|
|
230
|
+
config TEXT NOT NULL DEFAULT '{}',
|
|
231
|
+
token_cache TEXT,
|
|
232
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
CREATE TABLE IF NOT EXISTS request_logs (
|
|
236
|
+
id TEXT PRIMARY KEY,
|
|
237
|
+
source TEXT NOT NULL DEFAULT 'mcp',
|
|
238
|
+
tool_name TEXT,
|
|
239
|
+
method TEXT NOT NULL,
|
|
240
|
+
url TEXT NOT NULL,
|
|
241
|
+
request_headers TEXT,
|
|
242
|
+
request_body TEXT,
|
|
243
|
+
status_code INTEGER,
|
|
244
|
+
response_headers TEXT,
|
|
245
|
+
response_body TEXT,
|
|
246
|
+
latency_ms INTEGER,
|
|
247
|
+
error TEXT,
|
|
248
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
CREATE INDEX IF NOT EXISTS idx_logs_created ON request_logs(created_at DESC);
|
|
252
|
+
|
|
253
|
+
CREATE TABLE IF NOT EXISTS settings (
|
|
254
|
+
key TEXT PRIMARY KEY,
|
|
255
|
+
value TEXT NOT NULL DEFAULT '{}'
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
CREATE TABLE IF NOT EXISTS intercept_rules (
|
|
259
|
+
id TEXT PRIMARY KEY,
|
|
260
|
+
enabled INTEGER NOT NULL DEFAULT 1,
|
|
261
|
+
name TEXT NOT NULL DEFAULT '',
|
|
262
|
+
sort_order INTEGER NOT NULL DEFAULT 0,
|
|
263
|
+
match_path TEXT NOT NULL DEFAULT '',
|
|
264
|
+
match_method TEXT NOT NULL DEFAULT '',
|
|
265
|
+
target_host TEXT NOT NULL DEFAULT '',
|
|
266
|
+
strip_prefix TEXT NOT NULL DEFAULT '',
|
|
267
|
+
add_prefix TEXT NOT NULL DEFAULT '',
|
|
268
|
+
add_headers TEXT NOT NULL DEFAULT '{}',
|
|
269
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
270
|
+
);
|
|
271
|
+
CREATE INDEX IF NOT EXISTS idx_rules_order ON intercept_rules(sort_order, created_at);
|
|
272
|
+
|
|
273
|
+
CREATE TABLE IF NOT EXISTS auth_profiles (
|
|
274
|
+
id TEXT PRIMARY KEY,
|
|
275
|
+
name TEXT NOT NULL,
|
|
276
|
+
description TEXT NOT NULL DEFAULT '',
|
|
277
|
+
type TEXT NOT NULL DEFAULT 'none',
|
|
278
|
+
config TEXT NOT NULL DEFAULT '{}',
|
|
279
|
+
token_cache TEXT,
|
|
280
|
+
is_active INTEGER NOT NULL DEFAULT 0,
|
|
281
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
282
|
+
);
|
|
283
|
+
|
|
284
|
+
CREATE TABLE IF NOT EXISTS saved_requests (
|
|
285
|
+
id TEXT PRIMARY KEY,
|
|
286
|
+
name TEXT NOT NULL DEFAULT 'Untitled',
|
|
287
|
+
folder TEXT NOT NULL DEFAULT '',
|
|
288
|
+
method TEXT NOT NULL DEFAULT 'GET',
|
|
289
|
+
url TEXT NOT NULL DEFAULT '',
|
|
290
|
+
headers TEXT NOT NULL DEFAULT '[]',
|
|
291
|
+
params TEXT NOT NULL DEFAULT '[]',
|
|
292
|
+
body TEXT NOT NULL DEFAULT '',
|
|
293
|
+
body_type TEXT NOT NULL DEFAULT 'none',
|
|
294
|
+
raw_type TEXT NOT NULL DEFAULT 'text/plain',
|
|
295
|
+
form_rows TEXT NOT NULL DEFAULT '[]',
|
|
296
|
+
auth TEXT NOT NULL DEFAULT '{}',
|
|
297
|
+
notes TEXT NOT NULL DEFAULT '',
|
|
298
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
299
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
300
|
+
);
|
|
301
|
+
CREATE INDEX IF NOT EXISTS idx_saved_folder ON saved_requests(folder, created_at DESC);
|
|
302
|
+
|
|
303
|
+
CREATE TABLE IF NOT EXISTS spec_history (
|
|
304
|
+
id TEXT PRIMARY KEY,
|
|
305
|
+
url TEXT NOT NULL UNIQUE,
|
|
306
|
+
title TEXT,
|
|
307
|
+
version TEXT,
|
|
308
|
+
endpoint_count INTEGER,
|
|
309
|
+
last_used INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
310
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
311
|
+
);
|
|
312
|
+
CREATE INDEX IF NOT EXISTS idx_spec_history_last_used ON spec_history(last_used DESC);
|
|
313
|
+
|
|
314
|
+
CREATE TABLE IF NOT EXISTS workflows (
|
|
315
|
+
id TEXT PRIMARY KEY,
|
|
316
|
+
name TEXT NOT NULL DEFAULT 'Untitled Workflow',
|
|
317
|
+
description TEXT NOT NULL DEFAULT '',
|
|
318
|
+
steps TEXT NOT NULL DEFAULT '[]',
|
|
319
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
320
|
+
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
321
|
+
);
|
|
322
|
+
CREATE INDEX IF NOT EXISTS idx_workflows_updated ON workflows(updated_at DESC);
|
|
323
|
+
|
|
324
|
+
CREATE TABLE IF NOT EXISTS capture_bins (
|
|
325
|
+
id TEXT PRIMARY KEY,
|
|
326
|
+
name TEXT NOT NULL DEFAULT '',
|
|
327
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
CREATE TABLE IF NOT EXISTS chat_memory (
|
|
331
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
332
|
+
role TEXT NOT NULL,
|
|
333
|
+
content TEXT NOT NULL,
|
|
334
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
335
|
+
);
|
|
336
|
+
CREATE INDEX IF NOT EXISTS idx_memory_created ON chat_memory(created_at DESC);
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
// src/db/index.ts
|
|
340
|
+
import { Database } from "bun:sqlite";
|
|
341
|
+
import { join as join2 } from "path";
|
|
342
|
+
import { mkdirSync, existsSync } from "fs";
|
|
343
|
+
import { homedir as homedir2 } from "os";
|
|
344
|
+
import { randomUUID } from "crypto";
|
|
345
|
+
function resolveDataDir() {
|
|
346
|
+
if (process.env.WASPER_DATA_DIR ?? process.env.OPENAPI_AGENT_DATA_DIR) {
|
|
347
|
+
return process.env.WASPER_DATA_DIR ?? process.env.OPENAPI_AGENT_DATA_DIR;
|
|
254
348
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return `${h}h ${m % 60}m`;
|
|
265
|
-
if (m > 0)
|
|
266
|
-
return `${m}m ${s % 60}s`;
|
|
267
|
-
return `${s}s`;
|
|
349
|
+
try {
|
|
350
|
+
const legacy = join2(import.meta.dir, "../../data");
|
|
351
|
+
if (!Bun.main.includes("$bunfs") && (existsSync(join2(legacy, "wasper.db")) || existsSync(join2(legacy, "openapi-agent.db"))))
|
|
352
|
+
return legacy;
|
|
353
|
+
} catch {}
|
|
354
|
+
const oldDir = join2(homedir2(), ".openapi-agent", "data");
|
|
355
|
+
if (existsSync(oldDir))
|
|
356
|
+
return oldDir;
|
|
357
|
+
return join2(homedir2(), ".wasper", "data");
|
|
268
358
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
359
|
+
var DATA_DIR, DB_PATH, db, hasOldSchema, dbQueries;
|
|
360
|
+
var init_db = __esm(() => {
|
|
361
|
+
DATA_DIR = resolveDataDir();
|
|
362
|
+
mkdirSync(DATA_DIR, { recursive: true });
|
|
363
|
+
DB_PATH = join2(DATA_DIR, existsSync(join2(DATA_DIR, "openapi-agent.db")) && !existsSync(join2(DATA_DIR, "wasper.db")) ? "openapi-agent.db" : "wasper.db");
|
|
364
|
+
db = new Database(DB_PATH, { create: true });
|
|
365
|
+
db.exec("PRAGMA journal_mode = WAL;");
|
|
366
|
+
db.exec("PRAGMA foreign_keys = OFF;");
|
|
367
|
+
hasOldSchema = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='specs'").get() !== null;
|
|
368
|
+
if (hasOldSchema) {
|
|
369
|
+
db.exec("DROP TABLE IF EXISTS tools; DROP TABLE IF EXISTS specs; DROP TABLE IF EXISTS auth_configs; DROP TABLE IF EXISTS request_logs;");
|
|
276
370
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
371
|
+
db.exec(SCHEMA);
|
|
372
|
+
dbQueries = {
|
|
373
|
+
getAuthConfig: () => db.query("SELECT * FROM auth_config WHERE id = 'default'").get(),
|
|
374
|
+
setAuthConfig: (type, config) => db.query(`INSERT INTO auth_config (id, type, config, updated_at)
|
|
375
|
+
VALUES ('default', ?, ?, unixepoch())
|
|
376
|
+
ON CONFLICT(id) DO UPDATE SET type = excluded.type, config = excluded.config, updated_at = unixepoch()`).run(type, JSON.stringify(config)),
|
|
377
|
+
updateTokenCache: (tokenCache) => db.query("UPDATE auth_config SET token_cache = ? WHERE id = 'default'").run(tokenCache ? JSON.stringify(tokenCache) : null),
|
|
378
|
+
getRecentLogs: (limit = 500) => db.query("SELECT * FROM request_logs ORDER BY created_at DESC LIMIT ?").all(limit),
|
|
379
|
+
insertLog: (data) => db.query(`INSERT INTO request_logs
|
|
380
|
+
(id, source, tool_name, method, url, request_headers, request_body,
|
|
381
|
+
status_code, response_headers, response_body, latency_ms, error)
|
|
382
|
+
VALUES ($id, $source, $tool_name, $method, $url, $request_headers, $request_body,
|
|
383
|
+
$status_code, $response_headers, $response_body, $latency_ms, $error)`).run({
|
|
384
|
+
$id: data.id,
|
|
385
|
+
$source: data.source,
|
|
386
|
+
$tool_name: data.tool_name,
|
|
387
|
+
$method: data.method,
|
|
388
|
+
$url: data.url,
|
|
389
|
+
$request_headers: data.request_headers,
|
|
390
|
+
$request_body: data.request_body,
|
|
391
|
+
$status_code: data.status_code,
|
|
392
|
+
$response_headers: data.response_headers,
|
|
393
|
+
$response_body: data.response_body,
|
|
394
|
+
$latency_ms: data.latency_ms,
|
|
395
|
+
$error: data.error
|
|
396
|
+
}),
|
|
397
|
+
clearLogs: () => db.query("DELETE FROM request_logs").run(),
|
|
398
|
+
getRules: () => db.query("SELECT * FROM intercept_rules ORDER BY sort_order, created_at").all(),
|
|
399
|
+
insertRule: (rule) => db.query(`INSERT INTO intercept_rules (id,enabled,name,sort_order,match_path,match_method,target_host,strip_prefix,add_prefix,add_headers)
|
|
400
|
+
VALUES ($id,$enabled,$name,$sort_order,$match_path,$match_method,$target_host,$strip_prefix,$add_prefix,$add_headers)`).run({
|
|
401
|
+
$id: rule.id,
|
|
402
|
+
$enabled: rule.enabled,
|
|
403
|
+
$name: rule.name,
|
|
404
|
+
$sort_order: rule.sort_order,
|
|
405
|
+
$match_path: rule.match_path,
|
|
406
|
+
$match_method: rule.match_method,
|
|
407
|
+
$target_host: rule.target_host,
|
|
408
|
+
$strip_prefix: rule.strip_prefix,
|
|
409
|
+
$add_prefix: rule.add_prefix,
|
|
410
|
+
$add_headers: rule.add_headers
|
|
411
|
+
}),
|
|
412
|
+
updateRule: (id, patch) => {
|
|
413
|
+
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
414
|
+
const params = { $id: id };
|
|
415
|
+
for (const [k, v] of Object.entries(patch))
|
|
416
|
+
params[`$${k}`] = v;
|
|
417
|
+
db.query(`UPDATE intercept_rules SET ${cols} WHERE id = $id`).run(params);
|
|
418
|
+
},
|
|
419
|
+
deleteRule: (id) => db.query("DELETE FROM intercept_rules WHERE id = ?").run(id),
|
|
420
|
+
getSettings: () => db.query("SELECT value FROM settings WHERE key='app' LIMIT 1").get() ?? null,
|
|
421
|
+
setSettings: (value) => {
|
|
422
|
+
db.run("INSERT INTO settings(key,value) VALUES('app',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", [JSON.stringify(value)]);
|
|
423
|
+
},
|
|
424
|
+
getProfiles: () => db.query("SELECT * FROM auth_profiles ORDER BY name COLLATE NOCASE").all(),
|
|
425
|
+
getActiveProfile: () => db.query("SELECT * FROM auth_profiles WHERE is_active = 1 LIMIT 1").get(),
|
|
426
|
+
insertProfile: (p) => db.query(`INSERT INTO auth_profiles (id,name,description,type,config,token_cache,is_active)
|
|
427
|
+
VALUES ($id,$name,$description,$type,$config,$token_cache,$is_active)`).run({
|
|
428
|
+
$id: p.id,
|
|
429
|
+
$name: p.name,
|
|
430
|
+
$description: p.description,
|
|
431
|
+
$type: p.type,
|
|
432
|
+
$config: p.config,
|
|
433
|
+
$token_cache: p.token_cache,
|
|
434
|
+
$is_active: p.is_active
|
|
435
|
+
}),
|
|
436
|
+
updateProfile: (id, patch) => {
|
|
437
|
+
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
438
|
+
const params = { $id: id };
|
|
439
|
+
for (const [k, v] of Object.entries(patch))
|
|
440
|
+
params[`$${k}`] = v;
|
|
441
|
+
db.query(`UPDATE auth_profiles SET ${cols} WHERE id = $id`).run(params);
|
|
442
|
+
},
|
|
443
|
+
deleteProfile: (id) => db.query("DELETE FROM auth_profiles WHERE id = ?").run(id),
|
|
444
|
+
activateProfile: (id) => {
|
|
445
|
+
const profile = db.query("SELECT * FROM auth_profiles WHERE id = ?").get(id);
|
|
446
|
+
if (!profile)
|
|
447
|
+
return;
|
|
448
|
+
db.query("UPDATE auth_profiles SET is_active = 0").run();
|
|
449
|
+
db.query("UPDATE auth_profiles SET is_active = 1 WHERE id = ?").run(id);
|
|
450
|
+
db.query(`INSERT INTO auth_config (id, type, config, updated_at) VALUES ('default', $type, $config, unixepoch())
|
|
451
|
+
ON CONFLICT(id) DO UPDATE SET type=excluded.type, config=excluded.config, updated_at=unixepoch()`).run({ $type: profile.type, $config: profile.config });
|
|
452
|
+
},
|
|
453
|
+
getSavedRequests: () => db.query("SELECT * FROM saved_requests ORDER BY folder, name COLLATE NOCASE").all(),
|
|
454
|
+
getSavedRequest: (id) => db.query("SELECT * FROM saved_requests WHERE id = ?").get(id),
|
|
455
|
+
insertSavedRequest: (r) => db.query(`INSERT INTO saved_requests
|
|
456
|
+
(id, name, folder, method, url, headers, params, body, body_type, raw_type, form_rows, auth, notes)
|
|
457
|
+
VALUES ($id,$name,$folder,$method,$url,$headers,$params,$body,$body_type,$raw_type,$form_rows,$auth,$notes)`).run({
|
|
458
|
+
$id: r.id,
|
|
459
|
+
$name: r.name,
|
|
460
|
+
$folder: r.folder,
|
|
461
|
+
$method: r.method,
|
|
462
|
+
$url: r.url,
|
|
463
|
+
$headers: r.headers,
|
|
464
|
+
$params: r.params,
|
|
465
|
+
$body: r.body,
|
|
466
|
+
$body_type: r.body_type,
|
|
467
|
+
$raw_type: r.raw_type,
|
|
468
|
+
$form_rows: r.form_rows,
|
|
469
|
+
$auth: r.auth,
|
|
470
|
+
$notes: r.notes
|
|
471
|
+
}),
|
|
472
|
+
updateSavedRequest: (id, patch) => {
|
|
473
|
+
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
474
|
+
const params = { $id: id };
|
|
475
|
+
for (const [k, v] of Object.entries(patch))
|
|
476
|
+
params[`$${k}`] = v;
|
|
477
|
+
db.query(`UPDATE saved_requests SET ${cols}, updated_at = unixepoch() WHERE id = $id`).run(params);
|
|
478
|
+
},
|
|
479
|
+
deleteSavedRequest: (id) => db.query("DELETE FROM saved_requests WHERE id = ?").run(id),
|
|
480
|
+
getSpecHistory: () => db.query("SELECT * FROM spec_history ORDER BY last_used DESC").all(),
|
|
481
|
+
getLastSpec: () => db.query("SELECT * FROM spec_history ORDER BY last_used DESC LIMIT 1").get(),
|
|
482
|
+
upsertSpec: (url, title, version, endpointCount) => {
|
|
483
|
+
const existing = db.query("SELECT id FROM spec_history WHERE url = ?").get(url);
|
|
484
|
+
if (existing) {
|
|
485
|
+
db.query("UPDATE spec_history SET title=?, version=?, endpoint_count=?, last_used=unixepoch() WHERE url=?").run(title, version, endpointCount, url);
|
|
486
|
+
} else {
|
|
487
|
+
db.query(`INSERT INTO spec_history (id, url, title, version, endpoint_count)
|
|
488
|
+
VALUES (?, ?, ?, ?, ?)`).run(randomUUID(), url, title, version, endpointCount);
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
deleteSpec: (id) => {
|
|
492
|
+
db.query("DELETE FROM spec_history WHERE id = ?").run(id);
|
|
493
|
+
},
|
|
494
|
+
getWorkflows: () => db.query("SELECT * FROM workflows ORDER BY updated_at DESC").all(),
|
|
495
|
+
getWorkflow: (id) => db.query("SELECT * FROM workflows WHERE id = ?").get(id),
|
|
496
|
+
insertWorkflow: (w) => db.query("INSERT INTO workflows (id, name, description, steps) VALUES ($id, $name, $description, $steps)").run({ $id: w.id, $name: w.name, $description: w.description, $steps: w.steps }),
|
|
497
|
+
updateWorkflow: (id, patch) => {
|
|
498
|
+
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
499
|
+
const params = { $id: id };
|
|
500
|
+
for (const [k, v] of Object.entries(patch))
|
|
501
|
+
params[`$${k}`] = v;
|
|
502
|
+
db.query(`UPDATE workflows SET ${cols}, updated_at = unixepoch() WHERE id = $id`).run(params);
|
|
503
|
+
},
|
|
504
|
+
deleteWorkflow: (id) => db.query("DELETE FROM workflows WHERE id = ?").run(id),
|
|
505
|
+
getCaptureBins: () => db.query("SELECT * FROM capture_bins ORDER BY created_at DESC").all(),
|
|
506
|
+
getCaptureBin: (id) => db.query("SELECT * FROM capture_bins WHERE id = ?").get(id),
|
|
507
|
+
insertCaptureBin: (id, name) => {
|
|
508
|
+
db.query("INSERT INTO capture_bins (id, name) VALUES (?, ?)").run(id, name);
|
|
509
|
+
},
|
|
510
|
+
deleteCaptureBin: (id) => {
|
|
511
|
+
db.query("DELETE FROM capture_bins WHERE id = ?").run(id);
|
|
512
|
+
},
|
|
513
|
+
getSetting: (key) => (db.query("SELECT value FROM settings WHERE key = ?").get(key) ?? null)?.value ?? null,
|
|
514
|
+
setSetting: (key, value) => {
|
|
515
|
+
db.run("INSERT INTO settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", [key, value]);
|
|
516
|
+
},
|
|
517
|
+
saveMemory: (role, content) => {
|
|
518
|
+
db.query("INSERT INTO chat_memory (role, content) VALUES (?, ?)").run(role, content);
|
|
519
|
+
},
|
|
520
|
+
getMemory: (limit = 20) => {
|
|
521
|
+
const rows = db.query("SELECT role, content FROM chat_memory ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
522
|
+
return rows.reverse();
|
|
523
|
+
},
|
|
524
|
+
clearMemory: () => {
|
|
525
|
+
db.query("DELETE FROM chat_memory").run();
|
|
526
|
+
},
|
|
527
|
+
trimMemory: (keepLast = 40) => {
|
|
528
|
+
db.query("DELETE FROM chat_memory WHERE id NOT IN (SELECT id FROM chat_memory ORDER BY created_at DESC LIMIT ?)").run(keepLast);
|
|
529
|
+
}
|
|
320
530
|
};
|
|
321
|
-
FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
322
531
|
});
|
|
323
532
|
|
|
324
|
-
// src/
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
533
|
+
// src/config.ts
|
|
534
|
+
function setServerConfig(c) {
|
|
535
|
+
config = c;
|
|
536
|
+
}
|
|
537
|
+
function getServerConfig() {
|
|
538
|
+
return config;
|
|
539
|
+
}
|
|
540
|
+
function updateServerConfig(patch) {
|
|
541
|
+
config = { ...config, ...patch };
|
|
542
|
+
}
|
|
543
|
+
function getFeatures() {
|
|
544
|
+
return features;
|
|
545
|
+
}
|
|
546
|
+
function setFeatures(patch) {
|
|
547
|
+
features = { ...features, ...patch };
|
|
548
|
+
}
|
|
549
|
+
function readonlyViolation(method) {
|
|
550
|
+
if (!features.readonly)
|
|
551
|
+
return null;
|
|
552
|
+
if (SAFE_METHODS.has(method.toUpperCase()))
|
|
553
|
+
return null;
|
|
554
|
+
return `Read-only mode is enabled \u2014 ${method.toUpperCase()} requests are blocked. Ask the operator to run /readonly off.`;
|
|
555
|
+
}
|
|
556
|
+
function isAuthorized(req) {
|
|
557
|
+
if (!config.token)
|
|
558
|
+
return true;
|
|
559
|
+
const auth = req.headers.get("authorization");
|
|
560
|
+
if (auth === `Bearer ${config.token}`)
|
|
561
|
+
return true;
|
|
344
562
|
try {
|
|
345
|
-
|
|
563
|
+
return new URL(req.url).searchParams.get("token") === config.token;
|
|
346
564
|
} catch {
|
|
347
|
-
|
|
348
|
-
${paint.red("\u2717")} Failed to stop process ${state.pid}
|
|
349
|
-
`);
|
|
350
|
-
process.exit(1);
|
|
351
|
-
}
|
|
352
|
-
for (let i = 0;i < 30; i++) {
|
|
353
|
-
await Bun.sleep(100);
|
|
354
|
-
if (!isProcessAlive(state.pid))
|
|
355
|
-
break;
|
|
565
|
+
return false;
|
|
356
566
|
}
|
|
357
|
-
await clearDaemonState();
|
|
358
|
-
console.log(`
|
|
359
|
-
${paint.green("\u2713")} ${paint.bold("Stopped OpenAPI Agent")} ${paint.dim(`(was PID ${state.pid})`)}
|
|
360
|
-
`);
|
|
361
567
|
}
|
|
362
|
-
var
|
|
363
|
-
|
|
364
|
-
|
|
568
|
+
var config, features, SAFE_METHODS;
|
|
569
|
+
var init_config = __esm(() => {
|
|
570
|
+
config = {
|
|
571
|
+
port: 3388,
|
|
572
|
+
host: "0.0.0.0",
|
|
573
|
+
origin: null,
|
|
574
|
+
token: null
|
|
575
|
+
};
|
|
576
|
+
features = { mcp: true, proxy: true, ai: true, readonly: false };
|
|
577
|
+
SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
365
578
|
});
|
|
366
579
|
|
|
367
|
-
// src/
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
import { chmod, rename, unlink as unlink2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
379
|
-
async function fetchLatestVersion() {
|
|
380
|
-
try {
|
|
381
|
-
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
382
|
-
signal: AbortSignal.timeout(5000),
|
|
383
|
-
headers: { Accept: "application/json" }
|
|
384
|
-
});
|
|
385
|
-
if (!res.ok)
|
|
386
|
-
return null;
|
|
387
|
-
const data = await res.json();
|
|
388
|
-
return data.version ?? null;
|
|
389
|
-
} catch {
|
|
390
|
-
return null;
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
async function checkForUpdate() {
|
|
394
|
-
if (process.env.WASPER_NO_UPDATE_CHECK)
|
|
395
|
-
return null;
|
|
396
|
-
let state = null;
|
|
397
|
-
try {
|
|
398
|
-
state = JSON.parse(await readFile2(CHECK_FILE, "utf-8"));
|
|
399
|
-
} catch {}
|
|
400
|
-
let latest = state?.latest ?? null;
|
|
401
|
-
if (!state || Date.now() - state.lastCheck > CHECK_INTERVAL) {
|
|
402
|
-
latest = await fetchLatestVersion();
|
|
403
|
-
if (latest) {
|
|
404
|
-
await mkdir2(join2(homedir2(), ".wasper"), { recursive: true }).catch(() => {});
|
|
405
|
-
await writeFile2(CHECK_FILE, JSON.stringify({ lastCheck: Date.now(), latest }), "utf-8").catch(() => {});
|
|
580
|
+
// src/ui.ts
|
|
581
|
+
class Spinner {
|
|
582
|
+
i = 0;
|
|
583
|
+
timer = null;
|
|
584
|
+
msg = "";
|
|
585
|
+
start(msg) {
|
|
586
|
+
this.msg = msg;
|
|
587
|
+
if (!isTTY) {
|
|
588
|
+
process.stdout.write(` ${msg}
|
|
589
|
+
`);
|
|
590
|
+
return;
|
|
406
591
|
}
|
|
592
|
+
this.i = 0;
|
|
593
|
+
this.timer = setInterval(() => {
|
|
594
|
+
const f = FRAMES[this.i++ % FRAMES.length];
|
|
595
|
+
process.stdout.write(`\r ${paint.cyan(f)} ${this.msg}\x1B[K`);
|
|
596
|
+
}, 80);
|
|
407
597
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
return null;
|
|
411
|
-
}
|
|
412
|
-
function printUpdateNotice(latest) {
|
|
413
|
-
console.log(` ${paint.yellow("\u25B2")} Update available ${paint.dim(VERSION)} \u2192 ${paint.green(latest)} ${paint.dim("\xB7")} run ${paint.bold("wasper update")}
|
|
414
|
-
`);
|
|
415
|
-
}
|
|
416
|
-
function binaryAssetName() {
|
|
417
|
-
const os = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux";
|
|
418
|
-
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
419
|
-
return `wasper-${os}-${arch}${os === "windows" ? ".exe" : ""}`;
|
|
420
|
-
}
|
|
421
|
-
async function updateCompiledBinary(latest) {
|
|
422
|
-
const exe = process.execPath;
|
|
423
|
-
const asset = binaryAssetName();
|
|
424
|
-
const url = `https://github.com/${REPO}/releases/download/v${latest}/${asset}`;
|
|
425
|
-
const res = await fetch(url, { redirect: "follow" });
|
|
426
|
-
if (!res.ok)
|
|
427
|
-
throw new Error(`Download failed (HTTP ${res.status}) \u2014 ${url}`);
|
|
428
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
429
|
-
if (bytes.byteLength < 1024 * 100)
|
|
430
|
-
throw new Error("Downloaded file is suspiciously small \u2014 aborting");
|
|
431
|
-
const tmp = `${exe}.update`;
|
|
432
|
-
const old = `${exe}.old`;
|
|
433
|
-
await Bun.write(tmp, bytes);
|
|
434
|
-
await chmod(tmp, 493);
|
|
435
|
-
await rename(exe, old);
|
|
436
|
-
try {
|
|
437
|
-
await rename(tmp, exe);
|
|
438
|
-
} catch (e) {
|
|
439
|
-
await rename(old, exe).catch(() => {});
|
|
440
|
-
throw e;
|
|
598
|
+
update(msg) {
|
|
599
|
+
this.msg = msg;
|
|
441
600
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
const p = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
448
|
-
const code = await p.exited;
|
|
449
|
-
return code === 0;
|
|
450
|
-
} catch {
|
|
451
|
-
return false;
|
|
601
|
+
stop(icon = "\u2713", msg = "", color = "green") {
|
|
602
|
+
if (this.timer) {
|
|
603
|
+
clearInterval(this.timer);
|
|
604
|
+
this.timer = null;
|
|
452
605
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
606
|
+
if (!isTTY) {
|
|
607
|
+
if (msg)
|
|
608
|
+
process.stdout.write(` ${icon} ${msg}
|
|
609
|
+
`);
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
process.stdout.write(msg ? `\r ${paint[color](icon)} ${msg}\x1B[K
|
|
613
|
+
` : `\r\x1B[K`);
|
|
614
|
+
}
|
|
459
615
|
}
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
616
|
+
function printBanner(opts) {
|
|
617
|
+
const { port, pid, specTitle, specVersion, endpointCount, origin, host, tokenSet } = opts;
|
|
618
|
+
const base = origin ?? `http://localhost:${port}`;
|
|
619
|
+
const arrow = paint.cyan("\u279C");
|
|
620
|
+
const dot = paint.dim("\xB7");
|
|
621
|
+
const hint = [
|
|
622
|
+
`${paint.bold("r")} reload`,
|
|
623
|
+
`${paint.bold("b")} background`,
|
|
624
|
+
`${paint.bold("/")} commands ${paint.dim("(Tab to complete)")}`,
|
|
625
|
+
`${paint.bold("q")} quit`,
|
|
626
|
+
`${paint.bold("?")} help`
|
|
627
|
+
].join(` ${dot} `);
|
|
628
|
+
const lines = [
|
|
629
|
+
"",
|
|
630
|
+
` ${paint.bold("wasper")} ${paint.dim("PID " + pid)}`,
|
|
631
|
+
"",
|
|
632
|
+
` ${arrow} ${paint.dim("Studio ")} ${paint.url(base + "/")}`,
|
|
633
|
+
` ${arrow} ${paint.dim("MCP ")} ${paint.url(base + "/mcp")}`,
|
|
634
|
+
` ${arrow} ${paint.dim("OpenAPI")} ${paint.url(base + "/openapi.json")}`,
|
|
635
|
+
""
|
|
636
|
+
];
|
|
637
|
+
if (origin) {
|
|
638
|
+
lines.push(` ${arrow} ${paint.dim("Local ")} ${paint.url(`http://localhost:${port}/`)}${host && host !== "0.0.0.0" ? ` ${dot} ${paint.dim("bound to " + host)}` : ""}`, "");
|
|
468
639
|
}
|
|
469
|
-
if (
|
|
470
|
-
|
|
471
|
-
|
|
640
|
+
if (specTitle) {
|
|
641
|
+
const ep = endpointCount != null ? ` ${dot} ${paint.green(endpointCount + " endpoints")}` : "";
|
|
642
|
+
lines.push(` ${paint.green("\u2713")} ${paint.bold(specTitle)} ${paint.dim("v" + (specVersion ?? ""))}${ep}`);
|
|
643
|
+
} else {
|
|
644
|
+
lines.push(` ${paint.yellow("\u25CB")} ${paint.dim("No spec \u2014 start with --url <url>")}`);
|
|
472
645
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
if (isCompiledBinary())
|
|
478
|
-
await updateCompiledBinary(latest);
|
|
479
|
-
else
|
|
480
|
-
await updatePackageInstall();
|
|
481
|
-
spinner.stop("\u2713", `Updated to ${paint.bold("v" + latest)} ${paint.dim("\u2014 restart any running servers to use it")}`, "green");
|
|
482
|
-
await writeFile2(CHECK_FILE, JSON.stringify({ lastCheck: Date.now(), latest }), "utf-8").catch(() => {});
|
|
483
|
-
return true;
|
|
484
|
-
} catch (e) {
|
|
485
|
-
spinner.stop("\u2717", `Update failed: ${e instanceof Error ? e.message : String(e)}`, "red");
|
|
486
|
-
return false;
|
|
646
|
+
if (tokenSet) {
|
|
647
|
+
lines.push(` ${paint.green("\u2713")} ${paint.dim("Access token required (Authorization: Bearer \u2026 or ?token=)")}`);
|
|
648
|
+
} else if (origin) {
|
|
649
|
+
lines.push(` ${paint.yellow("!")} ${paint.yellow("Publicly reachable without a token \u2014 consider --token <secret>")}`);
|
|
487
650
|
}
|
|
651
|
+
lines.push("", ` ${hint}`, "");
|
|
652
|
+
console.log(lines.join(`
|
|
653
|
+
`));
|
|
488
654
|
}
|
|
489
|
-
|
|
655
|
+
function fmtUptime(ms) {
|
|
656
|
+
const s = Math.floor(ms / 1000);
|
|
657
|
+
const m = Math.floor(s / 60);
|
|
658
|
+
const h = Math.floor(m / 60);
|
|
659
|
+
if (h > 0)
|
|
660
|
+
return `${h}h ${m % 60}m`;
|
|
661
|
+
if (m > 0)
|
|
662
|
+
return `${m}m ${s % 60}s`;
|
|
663
|
+
return `${s}s`;
|
|
664
|
+
}
|
|
665
|
+
function printStatus(opts) {
|
|
666
|
+
const { running, pid, port, uptime, specTitle, specVersion, endpointCount, origin } = opts;
|
|
667
|
+
if (!running) {
|
|
668
|
+
console.log(`
|
|
669
|
+
${paint.dim("\u25CB")} ${paint.bold("OpenAPI Agent")} ${paint.dim("\xB7")} ${paint.yellow("not running")}
|
|
670
|
+
`);
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const rows = [
|
|
674
|
+
["pid ", String(pid)],
|
|
675
|
+
["port ", String(port)],
|
|
676
|
+
["uptime ", uptime != null ? fmtUptime(uptime) : "\u2014"],
|
|
677
|
+
["spec ", specTitle ? `${specTitle} ${paint.dim("v" + (specVersion ?? ""))}` : "\u2014"],
|
|
678
|
+
["endpoints", String(endpointCount ?? "\u2014")]
|
|
679
|
+
];
|
|
680
|
+
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
681
|
+
console.log(`
|
|
682
|
+
${paint.green("\u25CF")} ${paint.bold("OpenAPI Agent")} ${paint.dim("\xB7")} ${paint.green("running")}`);
|
|
490
683
|
console.log();
|
|
491
|
-
|
|
684
|
+
for (const [k, v] of rows) {
|
|
685
|
+
console.log(` ${paint.dim(k.padEnd(maxKey))} ${v}`);
|
|
686
|
+
}
|
|
687
|
+
if (port) {
|
|
688
|
+
const base = origin ?? `http://localhost:${port}`;
|
|
689
|
+
console.log();
|
|
690
|
+
console.log(` ${paint.url(`${base}/`)} ${paint.dim("\xB7")} ${paint.url(`${base}/mcp`)}`);
|
|
691
|
+
}
|
|
492
692
|
console.log();
|
|
493
693
|
}
|
|
494
|
-
var
|
|
495
|
-
var
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
694
|
+
var isTTY, esc = (s) => isTTY ? s : "", clr, paint, FRAMES;
|
|
695
|
+
var init_ui = __esm(() => {
|
|
696
|
+
isTTY = process.stdout.isTTY ?? false;
|
|
697
|
+
clr = {
|
|
698
|
+
reset: esc("\x1B[0m"),
|
|
699
|
+
bold: esc("\x1B[1m"),
|
|
700
|
+
dim: esc("\x1B[2m"),
|
|
701
|
+
green: esc("\x1B[32m"),
|
|
702
|
+
cyan: esc("\x1B[36m"),
|
|
703
|
+
yellow: esc("\x1B[33m"),
|
|
704
|
+
red: esc("\x1B[31m"),
|
|
705
|
+
gray: esc("\x1B[90m")
|
|
706
|
+
};
|
|
707
|
+
paint = {
|
|
708
|
+
green: (s) => `${clr.green}${s}${clr.reset}`,
|
|
709
|
+
cyan: (s) => `${clr.cyan}${s}${clr.reset}`,
|
|
710
|
+
yellow: (s) => `${clr.yellow}${s}${clr.reset}`,
|
|
711
|
+
red: (s) => `${clr.red}${s}${clr.reset}`,
|
|
712
|
+
gray: (s) => `${clr.gray}${s}${clr.reset}`,
|
|
713
|
+
dim: (s) => `${clr.dim}${s}${clr.reset}`,
|
|
714
|
+
bold: (s) => `${clr.bold}${s}${clr.reset}`,
|
|
715
|
+
url: (s) => `${clr.cyan}${s}${clr.reset}`
|
|
716
|
+
};
|
|
717
|
+
FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
500
718
|
});
|
|
501
719
|
|
|
502
|
-
// src/commands/
|
|
503
|
-
var
|
|
504
|
-
__export(
|
|
505
|
-
run: () =>
|
|
720
|
+
// src/commands/up.ts
|
|
721
|
+
var exports_up = {};
|
|
722
|
+
__export(exports_up, {
|
|
723
|
+
run: () => run
|
|
506
724
|
});
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
725
|
+
import { parseArgs } from "util";
|
|
726
|
+
async function run() {
|
|
727
|
+
const { values } = parseArgs({
|
|
728
|
+
args: process.argv.slice(2).filter((a) => a !== "up"),
|
|
729
|
+
options: {
|
|
730
|
+
url: { type: "string" },
|
|
731
|
+
port: { type: "string", default: process.env.WASPER_PORT ?? "3388" },
|
|
732
|
+
host: { type: "string" },
|
|
733
|
+
origin: { type: "string" },
|
|
734
|
+
token: { type: "string" },
|
|
735
|
+
"no-mcp": { type: "boolean" },
|
|
736
|
+
"no-proxy": { type: "boolean" },
|
|
737
|
+
"no-ai": { type: "boolean" },
|
|
738
|
+
readonly: { type: "boolean" },
|
|
739
|
+
force: { type: "boolean", short: "f" },
|
|
740
|
+
help: { type: "boolean", short: "h" }
|
|
741
|
+
},
|
|
742
|
+
strict: false
|
|
743
|
+
});
|
|
744
|
+
if (values.help) {
|
|
745
|
+
printHelp();
|
|
746
|
+
process.exit(0);
|
|
747
|
+
}
|
|
748
|
+
let specUrl = values.url ? String(values.url) : null;
|
|
749
|
+
specUrl ??= process.env.WASPER_SPEC_URL ?? null;
|
|
750
|
+
const PORT = parseInt(String(values.port ?? "3388"), 10);
|
|
751
|
+
const HOST = (values.host ? String(values.host) : null) ?? process.env.WASPER_HOST ?? "0.0.0.0";
|
|
752
|
+
const ORIGIN = ((values.origin ? String(values.origin) : null) ?? process.env.WASPER_ORIGIN ?? null)?.replace(/\/$/, "") ?? null;
|
|
753
|
+
const TOKEN = (values.token ? String(values.token) : null) ?? process.env.WASPER_TOKEN ?? null;
|
|
754
|
+
if (!specUrl) {
|
|
755
|
+
const last = dbQueries.getLastSpec();
|
|
756
|
+
if (last)
|
|
757
|
+
specUrl = last.url;
|
|
758
|
+
}
|
|
759
|
+
const existing = await readDaemonState(PORT);
|
|
760
|
+
if (existing && isProcessAlive(existing.pid) && !values.force) {
|
|
761
|
+
const base2 = existing.origin ?? `http://localhost:${existing.port}`;
|
|
762
|
+
console.log(`
|
|
763
|
+
${paint.yellow("\u25CF")} Already running on :${PORT} ${paint.dim(`PID ${existing.pid}`)}`);
|
|
764
|
+
console.log(` ${paint.dim("\u279C")} ${paint.cyan(base2 + "/")}`);
|
|
765
|
+
console.log(`
|
|
766
|
+
${paint.dim("wasper ps \xB7 wasper down --port " + PORT + " \xB7 wasper up --port " + PORT + " --force")}
|
|
767
|
+
`);
|
|
768
|
+
process.exit(0);
|
|
769
|
+
}
|
|
770
|
+
setServerConfig({ port: PORT, host: HOST, origin: ORIGIN, token: TOKEN });
|
|
771
|
+
setFeatures({
|
|
772
|
+
...values["no-mcp"] ? { mcp: false } : {},
|
|
773
|
+
...values["no-proxy"] ? { proxy: false } : {},
|
|
774
|
+
...values["no-ai"] ? { ai: false } : {},
|
|
775
|
+
readonly: !!values.readonly
|
|
776
|
+
});
|
|
777
|
+
if (existing && isProcessAlive(existing.pid)) {
|
|
778
|
+
try {
|
|
779
|
+
process.kill(existing.pid, "SIGTERM");
|
|
780
|
+
} catch {}
|
|
781
|
+
await Bun.sleep(400);
|
|
782
|
+
}
|
|
783
|
+
const pid = await spawnDaemon(specUrl, PORT, {
|
|
784
|
+
host: HOST,
|
|
785
|
+
origin: ORIGIN,
|
|
786
|
+
token: TOKEN,
|
|
787
|
+
features: getFeatures()
|
|
788
|
+
});
|
|
789
|
+
await Bun.sleep(600);
|
|
790
|
+
await writeDaemonState({ pid, port: PORT, specUrl, startedAt: Date.now(), host: HOST, origin: ORIGIN, token: TOKEN });
|
|
791
|
+
const base = ORIGIN ?? `http://localhost:${PORT}`;
|
|
792
|
+
console.log(`
|
|
793
|
+
${paint.green("\u2713")} Started on :${PORT} ${paint.dim(`PID ${pid}`)}`);
|
|
794
|
+
if (specUrl)
|
|
795
|
+
console.log(` ${paint.dim("\u21A9")} ${specUrl}`);
|
|
796
|
+
console.log(` ${paint.dim("\u279C")} ${paint.cyan(base + "/")}`);
|
|
797
|
+
console.log(` MCP ${paint.dim(base + "/mcp")}`);
|
|
798
|
+
console.log(`
|
|
799
|
+
${paint.dim("wasper ps \xB7 wasper down \xB7 wasper logs -f" + (PORT !== 3388 ? " --port " + PORT : ""))}
|
|
800
|
+
`);
|
|
801
|
+
process.exit(0);
|
|
802
|
+
}
|
|
803
|
+
function printHelp() {
|
|
804
|
+
console.log(`
|
|
805
|
+
Usage: wasper up [options]
|
|
806
|
+
|
|
807
|
+
Start the wasper daemon in the background.
|
|
808
|
+
Run multiple instances with different --port values.
|
|
809
|
+
|
|
810
|
+
Options:
|
|
811
|
+
--url, -u OpenAPI spec URL or local path
|
|
812
|
+
--port Port (default: 3388, env WASPER_PORT)
|
|
813
|
+
--host Bind address (default: 0.0.0.0)
|
|
814
|
+
--origin Public URL (env WASPER_ORIGIN)
|
|
815
|
+
--token Access token (env WASPER_TOKEN)
|
|
816
|
+
--no-mcp Disable the MCP endpoint
|
|
817
|
+
--no-proxy Disable the HTTP proxy
|
|
818
|
+
--no-ai Disable the AI chat endpoint
|
|
819
|
+
--readonly Block non-GET upstream requests
|
|
820
|
+
--force, -f Restart if already running on that port
|
|
821
|
+
|
|
822
|
+
Examples:
|
|
823
|
+
wasper up --url https://api.example.com/openapi.json
|
|
824
|
+
wasper up --url https://api2.example.com/openapi.json --port 3389
|
|
825
|
+
wasper ps # list all running instances
|
|
826
|
+
`);
|
|
827
|
+
}
|
|
828
|
+
var init_up = __esm(() => {
|
|
829
|
+
init_daemon();
|
|
830
|
+
init_db();
|
|
831
|
+
init_config();
|
|
832
|
+
init_ui();
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// src/commands/ps.ts
|
|
836
|
+
var exports_ps = {};
|
|
837
|
+
__export(exports_ps, {
|
|
838
|
+
run: () => run2
|
|
839
|
+
});
|
|
840
|
+
function uptime(startedAt) {
|
|
841
|
+
const secs = Math.floor((Date.now() - startedAt) / 1000);
|
|
842
|
+
if (secs < 60)
|
|
843
|
+
return `${secs}s`;
|
|
844
|
+
if (secs < 3600)
|
|
845
|
+
return `${Math.floor(secs / 60)}m`;
|
|
846
|
+
if (secs < 86400)
|
|
847
|
+
return `${Math.floor(secs / 3600)}h`;
|
|
848
|
+
return `${Math.floor(secs / 86400)}d`;
|
|
849
|
+
}
|
|
850
|
+
async function run2() {
|
|
851
|
+
const all = await readAllDaemonStates();
|
|
852
|
+
if (all.length === 0) {
|
|
853
|
+
console.log(`
|
|
854
|
+
${paint.dim("\u25CB")} No running instances
|
|
855
|
+
${paint.dim("wasper up --url <spec> [--port <port>]")}
|
|
856
|
+
`);
|
|
857
|
+
process.exit(0);
|
|
858
|
+
}
|
|
859
|
+
console.log();
|
|
860
|
+
const header = ` ${"PORT".padEnd(7)} ${"PID".padEnd(8)} ${"UP".padEnd(6)} ${"SPEC / URL"}`;
|
|
861
|
+
console.log(paint.dim(header));
|
|
862
|
+
console.log(paint.dim(" " + "\u2500".repeat(header.length - 2)));
|
|
863
|
+
for (const s of all) {
|
|
864
|
+
const port = paint.green(String(s.port).padEnd(7));
|
|
865
|
+
const pid = paint.dim(String(s.pid).padEnd(8));
|
|
866
|
+
const up = paint.dim(uptime(s.startedAt).padEnd(6));
|
|
867
|
+
const spec = s.specUrl ? s.specUrl : paint.dim("no spec");
|
|
868
|
+
console.log(` ${port} ${pid} ${up} ${spec}`);
|
|
869
|
+
}
|
|
870
|
+
console.log();
|
|
871
|
+
console.log(paint.dim(` wasper status --port <port> \u2014 details`));
|
|
872
|
+
console.log(paint.dim(` wasper down --port <port> \u2014 stop one`));
|
|
873
|
+
console.log(paint.dim(` wasper down --all \u2014 stop all`));
|
|
874
|
+
console.log();
|
|
875
|
+
process.exit(0);
|
|
876
|
+
}
|
|
877
|
+
var init_ps = __esm(() => {
|
|
878
|
+
init_daemon();
|
|
879
|
+
init_ui();
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// src/commands/stop.ts
|
|
883
|
+
var exports_stop = {};
|
|
884
|
+
__export(exports_stop, {
|
|
885
|
+
run: () => run3
|
|
886
|
+
});
|
|
887
|
+
async function run3() {
|
|
888
|
+
const args = process.argv.slice(2).filter((a) => a !== "down" && a !== "stop");
|
|
889
|
+
const stopAll = args.includes("--all") || args.includes("-a");
|
|
890
|
+
const portIdx = args.findIndex((a) => a === "--port" || a === "-p");
|
|
891
|
+
const portArg = portIdx >= 0 ? parseInt(args[portIdx + 1] ?? "", 10) : undefined;
|
|
892
|
+
if (stopAll) {
|
|
893
|
+
const all = await readAllDaemonStates();
|
|
894
|
+
if (!all.length) {
|
|
895
|
+
console.log(`
|
|
896
|
+
${paint.dim("\u25CB")} No running instances
|
|
897
|
+
`);
|
|
898
|
+
process.exit(0);
|
|
899
|
+
}
|
|
900
|
+
for (const state2 of all) {
|
|
901
|
+
try {
|
|
902
|
+
process.kill(state2.pid, "SIGTERM");
|
|
903
|
+
} catch {}
|
|
904
|
+
await Bun.sleep(300);
|
|
905
|
+
await clearDaemonState(state2.port);
|
|
906
|
+
const label = state2.specUrl ? paint.dim(state2.specUrl) : paint.dim("no spec");
|
|
907
|
+
console.log(` ${paint.green("\u2713")} Stopped :${state2.port} ${label}`);
|
|
908
|
+
}
|
|
909
|
+
console.log();
|
|
910
|
+
process.exit(0);
|
|
911
|
+
}
|
|
912
|
+
const state = await readDaemonState(portArg);
|
|
913
|
+
if (!state) {
|
|
914
|
+
if (portArg) {
|
|
915
|
+
console.log(`
|
|
916
|
+
${paint.dim("\u25CB")} No instance running on :${portArg}
|
|
917
|
+
`);
|
|
918
|
+
} else {
|
|
919
|
+
const all = await readAllDaemonStates();
|
|
920
|
+
if (all.length > 1) {
|
|
921
|
+
console.log(`
|
|
922
|
+
${paint.yellow("\u25CB")} Multiple instances running \u2014 specify a port or use --all:
|
|
923
|
+
`);
|
|
924
|
+
for (const s of all) {
|
|
925
|
+
console.log(` :${s.port} ${paint.dim(s.specUrl ?? "no spec")}`);
|
|
926
|
+
}
|
|
927
|
+
console.log(`
|
|
928
|
+
${paint.dim("wasper down --port <port> \xB7 wasper down --all")}
|
|
929
|
+
`);
|
|
930
|
+
process.exit(1);
|
|
931
|
+
}
|
|
932
|
+
console.log(`
|
|
933
|
+
${paint.dim("\u25CB")} No running instance found
|
|
934
|
+
`);
|
|
935
|
+
}
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
if (!isProcessAlive(state.pid)) {
|
|
939
|
+
await clearDaemonState(state.port);
|
|
940
|
+
console.log(`
|
|
941
|
+
${paint.dim("\u25CB")} Process ${state.pid} already gone. Cleaned up state.
|
|
942
|
+
`);
|
|
943
|
+
process.exit(0);
|
|
944
|
+
}
|
|
945
|
+
try {
|
|
946
|
+
process.kill(state.pid, "SIGTERM");
|
|
947
|
+
} catch {
|
|
948
|
+
console.log(`
|
|
949
|
+
${paint.red("\u2717")} Failed to stop PID ${state.pid}
|
|
950
|
+
`);
|
|
951
|
+
process.exit(1);
|
|
952
|
+
}
|
|
953
|
+
for (let i = 0;i < 30; i++) {
|
|
954
|
+
await Bun.sleep(100);
|
|
955
|
+
if (!isProcessAlive(state.pid))
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
await clearDaemonState(state.port);
|
|
959
|
+
console.log(`
|
|
960
|
+
${paint.green("\u2713")} Stopped :${state.port} ${paint.dim(`PID ${state.pid}`)}
|
|
961
|
+
`);
|
|
962
|
+
process.exit(0);
|
|
963
|
+
}
|
|
964
|
+
var init_stop = __esm(() => {
|
|
965
|
+
init_daemon();
|
|
966
|
+
init_ui();
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
// src/commands/status.ts
|
|
970
|
+
var exports_status = {};
|
|
971
|
+
__export(exports_status, {
|
|
972
|
+
run: () => run4
|
|
973
|
+
});
|
|
974
|
+
function uptime2(startedAt) {
|
|
975
|
+
const secs = Math.floor((Date.now() - startedAt) / 1000);
|
|
976
|
+
if (secs < 60)
|
|
977
|
+
return `${secs}s`;
|
|
978
|
+
if (secs < 3600)
|
|
979
|
+
return `${Math.floor(secs / 60)}m`;
|
|
980
|
+
if (secs < 86400)
|
|
981
|
+
return `${Math.floor(secs / 3600)}h`;
|
|
982
|
+
return `${Math.floor(secs / 86400)}d`;
|
|
983
|
+
}
|
|
984
|
+
async function run4() {
|
|
985
|
+
const args = process.argv.slice(2).filter((a) => a !== "status");
|
|
986
|
+
const portIdx = args.findIndex((a) => a === "--port" || a === "-p");
|
|
987
|
+
const portArg = portIdx >= 0 ? parseInt(args[portIdx + 1] ?? "", 10) : undefined;
|
|
988
|
+
if (portArg) {
|
|
989
|
+
const state = await readDaemonState(portArg);
|
|
990
|
+
if (!state || !isProcessAlive(state.pid)) {
|
|
991
|
+
console.log(`
|
|
992
|
+
${paint.dim("\u25CB")} No instance on :${portArg}
|
|
993
|
+
`);
|
|
994
|
+
process.exit(1);
|
|
995
|
+
}
|
|
996
|
+
await printSingleStatus(state);
|
|
997
|
+
process.exit(0);
|
|
998
|
+
}
|
|
999
|
+
const all = await readAllDaemonStates();
|
|
1000
|
+
if (all.length === 0) {
|
|
510
1001
|
printStatus({ running: false });
|
|
511
|
-
process.exit(
|
|
1002
|
+
process.exit(0);
|
|
1003
|
+
}
|
|
1004
|
+
if (all.length === 1) {
|
|
1005
|
+
await printSingleStatus(all[0]);
|
|
1006
|
+
process.exit(0);
|
|
1007
|
+
}
|
|
1008
|
+
console.log(`
|
|
1009
|
+
${paint.green("\u25CF")} ${paint.bold("wasper")} ${all.length} instances running
|
|
1010
|
+
`);
|
|
1011
|
+
for (const s of all) {
|
|
1012
|
+
const base = s.origin ?? `http://localhost:${s.port}`;
|
|
1013
|
+
let specInfo = "";
|
|
1014
|
+
try {
|
|
1015
|
+
const res = await fetch(`http://localhost:${s.port}/api/server-info`, {
|
|
1016
|
+
headers: s.token ? { Authorization: `Bearer ${s.token}` } : undefined,
|
|
1017
|
+
signal: AbortSignal.timeout(1500)
|
|
1018
|
+
});
|
|
1019
|
+
if (res.ok) {
|
|
1020
|
+
const info = await res.json();
|
|
1021
|
+
if (info.spec)
|
|
1022
|
+
specInfo = ` ${info.spec.title} ${paint.dim(info.spec.endpointCount + " ep")}`;
|
|
1023
|
+
}
|
|
1024
|
+
} catch {}
|
|
1025
|
+
console.log(` ${paint.green("\u25CF")} :${String(s.port).padEnd(5)} ${paint.cyan(base + "/")}${specInfo}`);
|
|
1026
|
+
console.log(` ${paint.dim(`PID ${s.pid} up ${uptime2(s.startedAt)}`)}`);
|
|
512
1027
|
}
|
|
1028
|
+
console.log(`
|
|
1029
|
+
${paint.dim("wasper status --port <port> \u2014 details for one instance")}
|
|
1030
|
+
`);
|
|
1031
|
+
process.exit(0);
|
|
1032
|
+
}
|
|
1033
|
+
async function printSingleStatus(state) {
|
|
513
1034
|
let spec = null;
|
|
514
1035
|
try {
|
|
515
1036
|
const res = await fetch(`http://localhost:${state.port}/api/server-info`, {
|
|
@@ -541,9 +1062,9 @@ var init_status = __esm(() => {
|
|
|
541
1062
|
// src/commands/reload.ts
|
|
542
1063
|
var exports_reload = {};
|
|
543
1064
|
__export(exports_reload, {
|
|
544
|
-
run: () =>
|
|
1065
|
+
run: () => run5
|
|
545
1066
|
});
|
|
546
|
-
async function
|
|
1067
|
+
async function run5() {
|
|
547
1068
|
const state = await readDaemonState();
|
|
548
1069
|
if (!state || !isProcessAlive(state.pid)) {
|
|
549
1070
|
console.log(`
|
|
@@ -566,6 +1087,7 @@ async function run4() {
|
|
|
566
1087
|
}
|
|
567
1088
|
spinner.stop("\u2713", `Reloaded "${data.spec}" \xB7 ${paint.green(String(data.endpoints ?? 0) + " endpoints")}`, "green");
|
|
568
1089
|
console.log();
|
|
1090
|
+
process.exit(0);
|
|
569
1091
|
} catch (e) {
|
|
570
1092
|
spinner.stop("\u2717", `Failed: ${e instanceof Error ? e.message : String(e)}`, "red");
|
|
571
1093
|
process.exit(1);
|
|
@@ -576,318 +1098,62 @@ var init_reload = __esm(() => {
|
|
|
576
1098
|
init_ui();
|
|
577
1099
|
});
|
|
578
1100
|
|
|
579
|
-
// src/
|
|
580
|
-
var
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
description TEXT NOT NULL DEFAULT '',
|
|
631
|
-
type TEXT NOT NULL DEFAULT 'none',
|
|
632
|
-
config TEXT NOT NULL DEFAULT '{}',
|
|
633
|
-
token_cache TEXT,
|
|
634
|
-
is_active INTEGER NOT NULL DEFAULT 0,
|
|
635
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
636
|
-
);
|
|
637
|
-
|
|
638
|
-
CREATE TABLE IF NOT EXISTS saved_requests (
|
|
639
|
-
id TEXT PRIMARY KEY,
|
|
640
|
-
name TEXT NOT NULL DEFAULT 'Untitled',
|
|
641
|
-
folder TEXT NOT NULL DEFAULT '',
|
|
642
|
-
method TEXT NOT NULL DEFAULT 'GET',
|
|
643
|
-
url TEXT NOT NULL DEFAULT '',
|
|
644
|
-
headers TEXT NOT NULL DEFAULT '[]',
|
|
645
|
-
params TEXT NOT NULL DEFAULT '[]',
|
|
646
|
-
body TEXT NOT NULL DEFAULT '',
|
|
647
|
-
body_type TEXT NOT NULL DEFAULT 'none',
|
|
648
|
-
raw_type TEXT NOT NULL DEFAULT 'text/plain',
|
|
649
|
-
form_rows TEXT NOT NULL DEFAULT '[]',
|
|
650
|
-
auth TEXT NOT NULL DEFAULT '{}',
|
|
651
|
-
notes TEXT NOT NULL DEFAULT '',
|
|
652
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
653
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
654
|
-
);
|
|
655
|
-
CREATE INDEX IF NOT EXISTS idx_saved_folder ON saved_requests(folder, created_at DESC);
|
|
656
|
-
|
|
657
|
-
CREATE TABLE IF NOT EXISTS spec_history (
|
|
658
|
-
id TEXT PRIMARY KEY,
|
|
659
|
-
url TEXT NOT NULL UNIQUE,
|
|
660
|
-
title TEXT,
|
|
661
|
-
version TEXT,
|
|
662
|
-
endpoint_count INTEGER,
|
|
663
|
-
last_used INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
664
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
665
|
-
);
|
|
666
|
-
CREATE INDEX IF NOT EXISTS idx_spec_history_last_used ON spec_history(last_used DESC);
|
|
667
|
-
|
|
668
|
-
CREATE TABLE IF NOT EXISTS workflows (
|
|
669
|
-
id TEXT PRIMARY KEY,
|
|
670
|
-
name TEXT NOT NULL DEFAULT 'Untitled Workflow',
|
|
671
|
-
description TEXT NOT NULL DEFAULT '',
|
|
672
|
-
steps TEXT NOT NULL DEFAULT '[]',
|
|
673
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
|
|
674
|
-
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
675
|
-
);
|
|
676
|
-
CREATE INDEX IF NOT EXISTS idx_workflows_updated ON workflows(updated_at DESC);
|
|
677
|
-
|
|
678
|
-
CREATE TABLE IF NOT EXISTS capture_bins (
|
|
679
|
-
id TEXT PRIMARY KEY,
|
|
680
|
-
name TEXT NOT NULL DEFAULT '',
|
|
681
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
682
|
-
);
|
|
683
|
-
|
|
684
|
-
CREATE TABLE IF NOT EXISTS chat_memory (
|
|
685
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
686
|
-
role TEXT NOT NULL,
|
|
687
|
-
content TEXT NOT NULL,
|
|
688
|
-
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
689
|
-
);
|
|
690
|
-
CREATE INDEX IF NOT EXISTS idx_memory_created ON chat_memory(created_at DESC);
|
|
691
|
-
`;
|
|
692
|
-
|
|
693
|
-
// src/db/index.ts
|
|
694
|
-
import { Database } from "bun:sqlite";
|
|
695
|
-
import { join as join3 } from "path";
|
|
696
|
-
import { mkdirSync, existsSync } from "fs";
|
|
697
|
-
import { homedir as homedir3 } from "os";
|
|
698
|
-
import { randomUUID } from "crypto";
|
|
699
|
-
function resolveDataDir() {
|
|
700
|
-
if (process.env.WASPER_DATA_DIR ?? process.env.OPENAPI_AGENT_DATA_DIR) {
|
|
701
|
-
return process.env.WASPER_DATA_DIR ?? process.env.OPENAPI_AGENT_DATA_DIR;
|
|
702
|
-
}
|
|
703
|
-
try {
|
|
704
|
-
const legacy = join3(import.meta.dir, "../../data");
|
|
705
|
-
if (!Bun.main.includes("$bunfs") && (existsSync(join3(legacy, "wasper.db")) || existsSync(join3(legacy, "openapi-agent.db"))))
|
|
706
|
-
return legacy;
|
|
707
|
-
} catch {}
|
|
708
|
-
const oldDir = join3(homedir3(), ".openapi-agent", "data");
|
|
709
|
-
if (existsSync(oldDir))
|
|
710
|
-
return oldDir;
|
|
711
|
-
return join3(homedir3(), ".wasper", "data");
|
|
712
|
-
}
|
|
713
|
-
var DATA_DIR, DB_PATH, db, hasOldSchema, dbQueries;
|
|
714
|
-
var init_db = __esm(() => {
|
|
715
|
-
DATA_DIR = resolveDataDir();
|
|
716
|
-
mkdirSync(DATA_DIR, { recursive: true });
|
|
717
|
-
DB_PATH = join3(DATA_DIR, existsSync(join3(DATA_DIR, "openapi-agent.db")) && !existsSync(join3(DATA_DIR, "wasper.db")) ? "openapi-agent.db" : "wasper.db");
|
|
718
|
-
db = new Database(DB_PATH, { create: true });
|
|
719
|
-
db.exec("PRAGMA journal_mode = WAL;");
|
|
720
|
-
db.exec("PRAGMA foreign_keys = OFF;");
|
|
721
|
-
hasOldSchema = db.query("SELECT name FROM sqlite_master WHERE type='table' AND name='specs'").get() !== null;
|
|
722
|
-
if (hasOldSchema) {
|
|
723
|
-
db.exec("DROP TABLE IF EXISTS tools; DROP TABLE IF EXISTS specs; DROP TABLE IF EXISTS auth_configs; DROP TABLE IF EXISTS request_logs;");
|
|
724
|
-
}
|
|
725
|
-
db.exec(SCHEMA);
|
|
726
|
-
dbQueries = {
|
|
727
|
-
getAuthConfig: () => db.query("SELECT * FROM auth_config WHERE id = 'default'").get(),
|
|
728
|
-
setAuthConfig: (type, config) => db.query(`INSERT INTO auth_config (id, type, config, updated_at)
|
|
729
|
-
VALUES ('default', ?, ?, unixepoch())
|
|
730
|
-
ON CONFLICT(id) DO UPDATE SET type = excluded.type, config = excluded.config, updated_at = unixepoch()`).run(type, JSON.stringify(config)),
|
|
731
|
-
updateTokenCache: (tokenCache) => db.query("UPDATE auth_config SET token_cache = ? WHERE id = 'default'").run(tokenCache ? JSON.stringify(tokenCache) : null),
|
|
732
|
-
getRecentLogs: (limit = 500) => db.query("SELECT * FROM request_logs ORDER BY created_at DESC LIMIT ?").all(limit),
|
|
733
|
-
insertLog: (data) => db.query(`INSERT INTO request_logs
|
|
734
|
-
(id, source, tool_name, method, url, request_headers, request_body,
|
|
735
|
-
status_code, response_headers, response_body, latency_ms, error)
|
|
736
|
-
VALUES ($id, $source, $tool_name, $method, $url, $request_headers, $request_body,
|
|
737
|
-
$status_code, $response_headers, $response_body, $latency_ms, $error)`).run({
|
|
738
|
-
$id: data.id,
|
|
739
|
-
$source: data.source,
|
|
740
|
-
$tool_name: data.tool_name,
|
|
741
|
-
$method: data.method,
|
|
742
|
-
$url: data.url,
|
|
743
|
-
$request_headers: data.request_headers,
|
|
744
|
-
$request_body: data.request_body,
|
|
745
|
-
$status_code: data.status_code,
|
|
746
|
-
$response_headers: data.response_headers,
|
|
747
|
-
$response_body: data.response_body,
|
|
748
|
-
$latency_ms: data.latency_ms,
|
|
749
|
-
$error: data.error
|
|
750
|
-
}),
|
|
751
|
-
clearLogs: () => db.query("DELETE FROM request_logs").run(),
|
|
752
|
-
getRules: () => db.query("SELECT * FROM intercept_rules ORDER BY sort_order, created_at").all(),
|
|
753
|
-
insertRule: (rule) => db.query(`INSERT INTO intercept_rules (id,enabled,name,sort_order,match_path,match_method,target_host,strip_prefix,add_prefix,add_headers)
|
|
754
|
-
VALUES ($id,$enabled,$name,$sort_order,$match_path,$match_method,$target_host,$strip_prefix,$add_prefix,$add_headers)`).run({
|
|
755
|
-
$id: rule.id,
|
|
756
|
-
$enabled: rule.enabled,
|
|
757
|
-
$name: rule.name,
|
|
758
|
-
$sort_order: rule.sort_order,
|
|
759
|
-
$match_path: rule.match_path,
|
|
760
|
-
$match_method: rule.match_method,
|
|
761
|
-
$target_host: rule.target_host,
|
|
762
|
-
$strip_prefix: rule.strip_prefix,
|
|
763
|
-
$add_prefix: rule.add_prefix,
|
|
764
|
-
$add_headers: rule.add_headers
|
|
765
|
-
}),
|
|
766
|
-
updateRule: (id, patch) => {
|
|
767
|
-
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
768
|
-
const params = { $id: id };
|
|
769
|
-
for (const [k, v] of Object.entries(patch))
|
|
770
|
-
params[`$${k}`] = v;
|
|
771
|
-
db.query(`UPDATE intercept_rules SET ${cols} WHERE id = $id`).run(params);
|
|
772
|
-
},
|
|
773
|
-
deleteRule: (id) => db.query("DELETE FROM intercept_rules WHERE id = ?").run(id),
|
|
774
|
-
getSettings: () => db.query("SELECT value FROM settings WHERE key='app' LIMIT 1").get() ?? null,
|
|
775
|
-
setSettings: (value) => {
|
|
776
|
-
db.run("INSERT INTO settings(key,value) VALUES('app',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", [JSON.stringify(value)]);
|
|
777
|
-
},
|
|
778
|
-
getProfiles: () => db.query("SELECT * FROM auth_profiles ORDER BY name COLLATE NOCASE").all(),
|
|
779
|
-
getActiveProfile: () => db.query("SELECT * FROM auth_profiles WHERE is_active = 1 LIMIT 1").get(),
|
|
780
|
-
insertProfile: (p) => db.query(`INSERT INTO auth_profiles (id,name,description,type,config,token_cache,is_active)
|
|
781
|
-
VALUES ($id,$name,$description,$type,$config,$token_cache,$is_active)`).run({
|
|
782
|
-
$id: p.id,
|
|
783
|
-
$name: p.name,
|
|
784
|
-
$description: p.description,
|
|
785
|
-
$type: p.type,
|
|
786
|
-
$config: p.config,
|
|
787
|
-
$token_cache: p.token_cache,
|
|
788
|
-
$is_active: p.is_active
|
|
789
|
-
}),
|
|
790
|
-
updateProfile: (id, patch) => {
|
|
791
|
-
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
792
|
-
const params = { $id: id };
|
|
793
|
-
for (const [k, v] of Object.entries(patch))
|
|
794
|
-
params[`$${k}`] = v;
|
|
795
|
-
db.query(`UPDATE auth_profiles SET ${cols} WHERE id = $id`).run(params);
|
|
796
|
-
},
|
|
797
|
-
deleteProfile: (id) => db.query("DELETE FROM auth_profiles WHERE id = ?").run(id),
|
|
798
|
-
activateProfile: (id) => {
|
|
799
|
-
const profile = db.query("SELECT * FROM auth_profiles WHERE id = ?").get(id);
|
|
800
|
-
if (!profile)
|
|
801
|
-
return;
|
|
802
|
-
db.query("UPDATE auth_profiles SET is_active = 0").run();
|
|
803
|
-
db.query("UPDATE auth_profiles SET is_active = 1 WHERE id = ?").run(id);
|
|
804
|
-
db.query(`INSERT INTO auth_config (id, type, config, updated_at) VALUES ('default', $type, $config, unixepoch())
|
|
805
|
-
ON CONFLICT(id) DO UPDATE SET type=excluded.type, config=excluded.config, updated_at=unixepoch()`).run({ $type: profile.type, $config: profile.config });
|
|
806
|
-
},
|
|
807
|
-
getSavedRequests: () => db.query("SELECT * FROM saved_requests ORDER BY folder, name COLLATE NOCASE").all(),
|
|
808
|
-
getSavedRequest: (id) => db.query("SELECT * FROM saved_requests WHERE id = ?").get(id),
|
|
809
|
-
insertSavedRequest: (r) => db.query(`INSERT INTO saved_requests
|
|
810
|
-
(id, name, folder, method, url, headers, params, body, body_type, raw_type, form_rows, auth, notes)
|
|
811
|
-
VALUES ($id,$name,$folder,$method,$url,$headers,$params,$body,$body_type,$raw_type,$form_rows,$auth,$notes)`).run({
|
|
812
|
-
$id: r.id,
|
|
813
|
-
$name: r.name,
|
|
814
|
-
$folder: r.folder,
|
|
815
|
-
$method: r.method,
|
|
816
|
-
$url: r.url,
|
|
817
|
-
$headers: r.headers,
|
|
818
|
-
$params: r.params,
|
|
819
|
-
$body: r.body,
|
|
820
|
-
$body_type: r.body_type,
|
|
821
|
-
$raw_type: r.raw_type,
|
|
822
|
-
$form_rows: r.form_rows,
|
|
823
|
-
$auth: r.auth,
|
|
824
|
-
$notes: r.notes
|
|
825
|
-
}),
|
|
826
|
-
updateSavedRequest: (id, patch) => {
|
|
827
|
-
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
828
|
-
const params = { $id: id };
|
|
829
|
-
for (const [k, v] of Object.entries(patch))
|
|
830
|
-
params[`$${k}`] = v;
|
|
831
|
-
db.query(`UPDATE saved_requests SET ${cols}, updated_at = unixepoch() WHERE id = $id`).run(params);
|
|
832
|
-
},
|
|
833
|
-
deleteSavedRequest: (id) => db.query("DELETE FROM saved_requests WHERE id = ?").run(id),
|
|
834
|
-
getSpecHistory: () => db.query("SELECT * FROM spec_history ORDER BY last_used DESC").all(),
|
|
835
|
-
getLastSpec: () => db.query("SELECT * FROM spec_history ORDER BY last_used DESC LIMIT 1").get(),
|
|
836
|
-
upsertSpec: (url, title, version, endpointCount) => {
|
|
837
|
-
const existing = db.query("SELECT id FROM spec_history WHERE url = ?").get(url);
|
|
838
|
-
if (existing) {
|
|
839
|
-
db.query("UPDATE spec_history SET title=?, version=?, endpoint_count=?, last_used=unixepoch() WHERE url=?").run(title, version, endpointCount, url);
|
|
840
|
-
} else {
|
|
841
|
-
db.query(`INSERT INTO spec_history (id, url, title, version, endpoint_count)
|
|
842
|
-
VALUES (?, ?, ?, ?, ?)`).run(randomUUID(), url, title, version, endpointCount);
|
|
843
|
-
}
|
|
844
|
-
},
|
|
845
|
-
deleteSpec: (id) => {
|
|
846
|
-
db.query("DELETE FROM spec_history WHERE id = ?").run(id);
|
|
847
|
-
},
|
|
848
|
-
getWorkflows: () => db.query("SELECT * FROM workflows ORDER BY updated_at DESC").all(),
|
|
849
|
-
getWorkflow: (id) => db.query("SELECT * FROM workflows WHERE id = ?").get(id),
|
|
850
|
-
insertWorkflow: (w) => db.query("INSERT INTO workflows (id, name, description, steps) VALUES ($id, $name, $description, $steps)").run({ $id: w.id, $name: w.name, $description: w.description, $steps: w.steps }),
|
|
851
|
-
updateWorkflow: (id, patch) => {
|
|
852
|
-
const cols = Object.keys(patch).map((k) => `${k} = $${k}`).join(", ");
|
|
853
|
-
const params = { $id: id };
|
|
854
|
-
for (const [k, v] of Object.entries(patch))
|
|
855
|
-
params[`$${k}`] = v;
|
|
856
|
-
db.query(`UPDATE workflows SET ${cols}, updated_at = unixepoch() WHERE id = $id`).run(params);
|
|
857
|
-
},
|
|
858
|
-
deleteWorkflow: (id) => db.query("DELETE FROM workflows WHERE id = ?").run(id),
|
|
859
|
-
getCaptureBins: () => db.query("SELECT * FROM capture_bins ORDER BY created_at DESC").all(),
|
|
860
|
-
getCaptureBin: (id) => db.query("SELECT * FROM capture_bins WHERE id = ?").get(id),
|
|
861
|
-
insertCaptureBin: (id, name) => {
|
|
862
|
-
db.query("INSERT INTO capture_bins (id, name) VALUES (?, ?)").run(id, name);
|
|
863
|
-
},
|
|
864
|
-
deleteCaptureBin: (id) => {
|
|
865
|
-
db.query("DELETE FROM capture_bins WHERE id = ?").run(id);
|
|
866
|
-
},
|
|
867
|
-
getSetting: (key) => (db.query("SELECT value FROM settings WHERE key = ?").get(key) ?? null)?.value ?? null,
|
|
868
|
-
setSetting: (key, value) => {
|
|
869
|
-
db.run("INSERT INTO settings(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value", [key, value]);
|
|
870
|
-
},
|
|
871
|
-
saveMemory: (role, content) => {
|
|
872
|
-
db.query("INSERT INTO chat_memory (role, content) VALUES (?, ?)").run(role, content);
|
|
873
|
-
},
|
|
874
|
-
getMemory: (limit = 20) => {
|
|
875
|
-
const rows = db.query("SELECT role, content FROM chat_memory ORDER BY created_at DESC LIMIT ?").all(limit);
|
|
876
|
-
return rows.reverse();
|
|
877
|
-
},
|
|
878
|
-
clearMemory: () => {
|
|
879
|
-
db.query("DELETE FROM chat_memory").run();
|
|
880
|
-
},
|
|
881
|
-
trimMemory: (keepLast = 40) => {
|
|
882
|
-
db.query("DELETE FROM chat_memory WHERE id NOT IN (SELECT id FROM chat_memory ORDER BY created_at DESC LIMIT ?)").run(keepLast);
|
|
883
|
-
}
|
|
884
|
-
};
|
|
885
|
-
});
|
|
1101
|
+
// src/commands/logs.ts
|
|
1102
|
+
var exports_logs = {};
|
|
1103
|
+
__export(exports_logs, {
|
|
1104
|
+
run: () => run6
|
|
1105
|
+
});
|
|
1106
|
+
async function run6() {
|
|
1107
|
+
const args = process.argv.slice(2).filter((a) => a !== "logs");
|
|
1108
|
+
const follow = args.includes("-f") || args.includes("--follow");
|
|
1109
|
+
const portIdx = args.findIndex((a) => a === "--port" || a === "-p");
|
|
1110
|
+
const portArg = portIdx >= 0 ? parseInt(args[portIdx + 1] ?? "", 10) : undefined;
|
|
1111
|
+
const nIdx = args.findIndex((a) => a === "-n" || a === "--lines");
|
|
1112
|
+
const lines = nIdx >= 0 ? args[nIdx + 1] ?? "100" : "100";
|
|
1113
|
+
let port = portArg;
|
|
1114
|
+
if (!port) {
|
|
1115
|
+
const all = await readAllDaemonStates();
|
|
1116
|
+
if (all.length === 0) {
|
|
1117
|
+
console.log(`
|
|
1118
|
+
${paint.yellow("\u25CB")} wasper is not running ${paint.dim("\u2192 wasper up")}
|
|
1119
|
+
`);
|
|
1120
|
+
process.exit(1);
|
|
1121
|
+
}
|
|
1122
|
+
if (all.length > 1) {
|
|
1123
|
+
console.log(`
|
|
1124
|
+
${paint.yellow("\u25CB")} Multiple instances \u2014 specify --port:
|
|
1125
|
+
`);
|
|
1126
|
+
for (const s of all)
|
|
1127
|
+
console.log(` :${s.port} ${paint.dim(s.specUrl ?? "no spec")}`);
|
|
1128
|
+
console.log(`
|
|
1129
|
+
${paint.dim("wasper logs --port <port> [-f]")}
|
|
1130
|
+
`);
|
|
1131
|
+
process.exit(1);
|
|
1132
|
+
}
|
|
1133
|
+
port = all[0].port;
|
|
1134
|
+
}
|
|
1135
|
+
const path = logFile(port);
|
|
1136
|
+
const file = Bun.file(path);
|
|
1137
|
+
if (!await file.exists()) {
|
|
1138
|
+
console.log(`
|
|
1139
|
+
${paint.yellow("\u25CB")} No log file for :${port}`);
|
|
1140
|
+
console.log(` ${paint.dim("Expected: " + path)}
|
|
1141
|
+
`);
|
|
1142
|
+
process.exit(1);
|
|
1143
|
+
}
|
|
1144
|
+
const tailArgs = follow ? ["tail", "-f", "-n", lines, path] : ["tail", "-n", lines, path];
|
|
1145
|
+
const proc = Bun.spawn(tailArgs, { stdout: "inherit", stderr: "inherit" });
|
|
1146
|
+
await proc.exited;
|
|
1147
|
+
}
|
|
1148
|
+
var init_logs = __esm(() => {
|
|
1149
|
+
init_daemon();
|
|
1150
|
+
init_ui();
|
|
1151
|
+
});
|
|
886
1152
|
|
|
887
1153
|
// src/commands/ls.ts
|
|
888
1154
|
var exports_ls = {};
|
|
889
1155
|
__export(exports_ls, {
|
|
890
|
-
run: () =>
|
|
1156
|
+
run: () => run7
|
|
891
1157
|
});
|
|
892
1158
|
function ago(ts) {
|
|
893
1159
|
const secs = Math.floor(Date.now() / 1000) - ts;
|
|
@@ -901,40 +1167,852 @@ function ago(ts) {
|
|
|
901
1167
|
return `${Math.floor(secs / 86400)}d ago`;
|
|
902
1168
|
return new Date(ts * 1000).toLocaleDateString();
|
|
903
1169
|
}
|
|
904
|
-
async function
|
|
905
|
-
const history = dbQueries.getSpecHistory();
|
|
906
|
-
if (history.length === 0) {
|
|
907
|
-
console.log(`
|
|
908
|
-
No saved specs yet.
|
|
909
|
-
`);
|
|
910
|
-
console.log(` ${paint.dim("Run:")} wasper --url <spec-url>
|
|
911
|
-
`);
|
|
912
|
-
process.exit(0);
|
|
1170
|
+
async function run7() {
|
|
1171
|
+
const history = dbQueries.getSpecHistory();
|
|
1172
|
+
if (history.length === 0) {
|
|
1173
|
+
console.log(`
|
|
1174
|
+
No saved specs yet.
|
|
1175
|
+
`);
|
|
1176
|
+
console.log(` ${paint.dim("Run:")} wasper --url <spec-url>
|
|
1177
|
+
`);
|
|
1178
|
+
process.exit(0);
|
|
1179
|
+
}
|
|
1180
|
+
const COL_URL = isTTY ? 50 : 60;
|
|
1181
|
+
const COL_TITLE = 28;
|
|
1182
|
+
console.log();
|
|
1183
|
+
if (isTTY) {
|
|
1184
|
+
const header = ` ${"#".padEnd(3)} ${"URL".padEnd(COL_URL)} ${"Title".padEnd(COL_TITLE)} ${"Endpoints".padEnd(10)} Last used`;
|
|
1185
|
+
console.log(paint.dim(header));
|
|
1186
|
+
console.log(paint.dim(" " + "\u2500".repeat(header.length - 2)));
|
|
1187
|
+
}
|
|
1188
|
+
history.forEach((row, i) => {
|
|
1189
|
+
const num = String(i + 1).padEnd(3);
|
|
1190
|
+
const url = row.url.length > COL_URL ? row.url.slice(0, COL_URL - 1) + "\u2026" : row.url.padEnd(COL_URL);
|
|
1191
|
+
const title = (row.title ?? "\u2014").slice(0, COL_TITLE).padEnd(COL_TITLE);
|
|
1192
|
+
const eps = (row.endpoint_count != null ? String(row.endpoint_count) : "\u2014").padEnd(10);
|
|
1193
|
+
const time = ago(row.last_used);
|
|
1194
|
+
console.log(` ${paint.cyan(num)} ${url} ${paint.dim(title)} ${eps} ${paint.dim(time)}`);
|
|
1195
|
+
});
|
|
1196
|
+
console.log();
|
|
1197
|
+
console.log(paint.dim(` wasper use <number> \u2014 start server with that spec`));
|
|
1198
|
+
console.log(paint.dim(` wasper rm <number> \u2014 remove a spec from history`));
|
|
1199
|
+
console.log();
|
|
1200
|
+
process.exit(0);
|
|
1201
|
+
}
|
|
1202
|
+
var init_ls = __esm(() => {
|
|
1203
|
+
init_db();
|
|
1204
|
+
init_ui();
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
// src/commands/use.ts
|
|
1208
|
+
var exports_use = {};
|
|
1209
|
+
__export(exports_use, {
|
|
1210
|
+
run: () => run8
|
|
1211
|
+
});
|
|
1212
|
+
async function run8() {
|
|
1213
|
+
const raw = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
1214
|
+
const portIdx = process.argv.findIndex((a) => a === "--port" || a === "-p");
|
|
1215
|
+
const PORT = portIdx >= 0 ? parseInt(process.argv[portIdx + 1] ?? "3388", 10) : parseInt(process.env.WASPER_PORT ?? "3388", 10);
|
|
1216
|
+
const target = raw[1];
|
|
1217
|
+
if (!target) {
|
|
1218
|
+
console.error(`
|
|
1219
|
+
Usage: wasper use <number|url> [--port <port>]
|
|
1220
|
+
`);
|
|
1221
|
+
process.exit(1);
|
|
1222
|
+
}
|
|
1223
|
+
const history = dbQueries.getSpecHistory();
|
|
1224
|
+
let url = null;
|
|
1225
|
+
const num = parseInt(target, 10);
|
|
1226
|
+
if (!isNaN(num) && num >= 1 && num <= history.length) {
|
|
1227
|
+
url = history[num - 1]?.url ?? null;
|
|
1228
|
+
} else if (target.startsWith("http")) {
|
|
1229
|
+
url = target;
|
|
1230
|
+
} else {
|
|
1231
|
+
const match = history.find((r) => r.title?.toLowerCase().includes(target.toLowerCase()));
|
|
1232
|
+
if (match)
|
|
1233
|
+
url = match.url;
|
|
1234
|
+
}
|
|
1235
|
+
if (!url) {
|
|
1236
|
+
console.error(`
|
|
1237
|
+
${paint.red("\u2717")} Spec not found: ${target}`);
|
|
1238
|
+
console.error(` Run ${paint.cyan("wasper ls")} to see saved specs.
|
|
1239
|
+
`);
|
|
1240
|
+
process.exit(1);
|
|
1241
|
+
}
|
|
1242
|
+
const existing = await readDaemonState(PORT);
|
|
1243
|
+
if (existing && isProcessAlive(existing.pid)) {
|
|
1244
|
+
try {
|
|
1245
|
+
process.kill(existing.pid, "SIGTERM");
|
|
1246
|
+
} catch {}
|
|
1247
|
+
await Bun.sleep(400);
|
|
1248
|
+
}
|
|
1249
|
+
const pid = await spawnDaemon(url, PORT, { features: getFeatures() });
|
|
1250
|
+
await Bun.sleep(600);
|
|
1251
|
+
await writeDaemonState({ pid, port: PORT, specUrl: url, startedAt: Date.now() });
|
|
1252
|
+
console.log(`
|
|
1253
|
+
${paint.green("\u2713")} Started on :${PORT} ${paint.dim(`PID ${pid}`)}`);
|
|
1254
|
+
console.log(` ${paint.dim("\u21A9")} ${url}`);
|
|
1255
|
+
console.log(` ${paint.dim("\u279C")} ${paint.cyan(`http://localhost:${PORT}/`)}
|
|
1256
|
+
`);
|
|
1257
|
+
process.exit(0);
|
|
1258
|
+
}
|
|
1259
|
+
var init_use = __esm(() => {
|
|
1260
|
+
init_db();
|
|
1261
|
+
init_daemon();
|
|
1262
|
+
init_config();
|
|
1263
|
+
init_ui();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// src/commands/rm.ts
|
|
1267
|
+
var exports_rm = {};
|
|
1268
|
+
__export(exports_rm, {
|
|
1269
|
+
run: () => run9
|
|
1270
|
+
});
|
|
1271
|
+
async function run9() {
|
|
1272
|
+
const args = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
1273
|
+
const target = args[1];
|
|
1274
|
+
if (!target) {
|
|
1275
|
+
console.error(`
|
|
1276
|
+
Usage: wasper rm <number|url>
|
|
1277
|
+
`);
|
|
1278
|
+
process.exit(1);
|
|
1279
|
+
}
|
|
1280
|
+
const history = dbQueries.getSpecHistory();
|
|
1281
|
+
let id = null;
|
|
1282
|
+
let label = null;
|
|
1283
|
+
const num = parseInt(target, 10);
|
|
1284
|
+
if (!isNaN(num) && num >= 1 && num <= history.length) {
|
|
1285
|
+
const row = history[num - 1];
|
|
1286
|
+
if (row) {
|
|
1287
|
+
id = row.id;
|
|
1288
|
+
label = row.title ?? row.url;
|
|
1289
|
+
}
|
|
1290
|
+
} else {
|
|
1291
|
+
const match = history.find((r) => r.url === target || r.title?.toLowerCase().includes(target.toLowerCase()));
|
|
1292
|
+
if (match) {
|
|
1293
|
+
id = match.id;
|
|
1294
|
+
label = match.title ?? match.url;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
if (!id) {
|
|
1298
|
+
console.error(`
|
|
1299
|
+
${paint.red("\u2717")} Spec not found: ${target}
|
|
1300
|
+
`);
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
dbQueries.deleteSpec(id);
|
|
1304
|
+
console.log(`
|
|
1305
|
+
${paint.green("\u2713")} Removed ${paint.dim(label ?? id)}
|
|
1306
|
+
`);
|
|
1307
|
+
process.exit(0);
|
|
1308
|
+
}
|
|
1309
|
+
var init_rm = __esm(() => {
|
|
1310
|
+
init_db();
|
|
1311
|
+
init_ui();
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
// src/commands/feature.ts
|
|
1315
|
+
var exports_feature = {};
|
|
1316
|
+
__export(exports_feature, {
|
|
1317
|
+
run: () => run10
|
|
1318
|
+
});
|
|
1319
|
+
async function run10() {
|
|
1320
|
+
const args = process.argv.slice(2);
|
|
1321
|
+
const name = args.find((a) => FEATURE_NAMES.includes(a));
|
|
1322
|
+
const value = args.find((a) => a === "on" || a === "off");
|
|
1323
|
+
const portIdx = args.findIndex((a) => a === "--port" || a === "-p");
|
|
1324
|
+
const portArg = portIdx >= 0 ? parseInt(args[portIdx + 1] ?? "", 10) : undefined;
|
|
1325
|
+
const state = portArg ? await readDaemonState(portArg) : await resolveSingleDaemon();
|
|
1326
|
+
if (!state || !isProcessAlive(state.pid)) {
|
|
1327
|
+
const hint = portArg ? `:${portArg}` : "";
|
|
1328
|
+
console.log(`
|
|
1329
|
+
${paint.yellow("\u25CB")} wasper${hint} is not running ${paint.dim("\u2192 wasper up")}
|
|
1330
|
+
`);
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
const authHdr = state.token ? { Authorization: `Bearer ${state.token}` } : {};
|
|
1334
|
+
const base = `http://localhost:${state.port}`;
|
|
1335
|
+
const cur = await fetch(`${base}/api/features`, {
|
|
1336
|
+
headers: authHdr,
|
|
1337
|
+
signal: AbortSignal.timeout(3000)
|
|
1338
|
+
}).then((r) => r.json());
|
|
1339
|
+
const next = value === "on" ? true : value === "off" ? false : !cur[name];
|
|
1340
|
+
await fetch(`${base}/api/features`, {
|
|
1341
|
+
method: "PUT",
|
|
1342
|
+
headers: { "Content-Type": "application/json", ...authHdr },
|
|
1343
|
+
body: JSON.stringify({ [name]: next }),
|
|
1344
|
+
signal: AbortSignal.timeout(3000)
|
|
1345
|
+
});
|
|
1346
|
+
const port = state.port !== 3388 ? ` ${paint.dim(":" + state.port)}` : "";
|
|
1347
|
+
const icon = next ? paint.green("\u2713") : paint.yellow("\u25CB");
|
|
1348
|
+
const status = next ? paint.green("on") : paint.yellow("off");
|
|
1349
|
+
console.log(`
|
|
1350
|
+
${icon} ${LABELS[name]} ${status}${port}
|
|
1351
|
+
`);
|
|
1352
|
+
process.exit(0);
|
|
1353
|
+
}
|
|
1354
|
+
async function resolveSingleDaemon() {
|
|
1355
|
+
const all = await readAllDaemonStates();
|
|
1356
|
+
if (all.length <= 1)
|
|
1357
|
+
return all[0] ?? null;
|
|
1358
|
+
console.log(`
|
|
1359
|
+
${paint.yellow("\u25CB")} Multiple instances running \u2014 specify --port:
|
|
1360
|
+
`);
|
|
1361
|
+
for (const s of all)
|
|
1362
|
+
console.log(` :${s.port} ${paint.dim(s.specUrl ?? "no spec")}`);
|
|
1363
|
+
console.log();
|
|
1364
|
+
process.exit(1);
|
|
1365
|
+
}
|
|
1366
|
+
var FEATURE_NAMES, LABELS;
|
|
1367
|
+
var init_feature = __esm(() => {
|
|
1368
|
+
init_daemon();
|
|
1369
|
+
init_ui();
|
|
1370
|
+
FEATURE_NAMES = ["mcp", "proxy", "ai", "readonly"];
|
|
1371
|
+
LABELS = {
|
|
1372
|
+
mcp: "MCP endpoint",
|
|
1373
|
+
proxy: "HTTP proxy",
|
|
1374
|
+
ai: "AI chat",
|
|
1375
|
+
readonly: "Read-only mode"
|
|
1376
|
+
};
|
|
1377
|
+
});
|
|
1378
|
+
|
|
1379
|
+
// src/commands/auth-cmd.ts
|
|
1380
|
+
var exports_auth_cmd = {};
|
|
1381
|
+
__export(exports_auth_cmd, {
|
|
1382
|
+
run: () => run11
|
|
1383
|
+
});
|
|
1384
|
+
async function run11() {
|
|
1385
|
+
const args = process.argv.slice(2).filter((a) => a !== "auth");
|
|
1386
|
+
const sub = args[0] ?? "list";
|
|
1387
|
+
const name = args.slice(1).join(" ");
|
|
1388
|
+
if (!sub || sub === "list") {
|
|
1389
|
+
const profiles = dbQueries.getProfiles();
|
|
1390
|
+
if (!profiles.length) {
|
|
1391
|
+
console.log(`
|
|
1392
|
+
${paint.dim("\u25CB")} No auth profiles saved`);
|
|
1393
|
+
console.log(` ${paint.dim("Create them in the studio (Authentication tab)")}
|
|
1394
|
+
`);
|
|
1395
|
+
process.exit(0);
|
|
1396
|
+
}
|
|
1397
|
+
console.log();
|
|
1398
|
+
for (const p of profiles) {
|
|
1399
|
+
const mark = p.is_active === 1 ? paint.green("\u25CF") : paint.dim("\u25CB");
|
|
1400
|
+
const desc = p.description ? ` ${paint.dim(p.description)}` : "";
|
|
1401
|
+
console.log(` ${mark} ${paint.bold(p.name)} ${paint.dim(`(${p.type})`)}${desc}`);
|
|
1402
|
+
}
|
|
1403
|
+
console.log(`
|
|
1404
|
+
${paint.dim("wasper auth use <name> \xB7 wasper auth none")}
|
|
1405
|
+
`);
|
|
1406
|
+
process.exit(0);
|
|
1407
|
+
}
|
|
1408
|
+
if (sub === "use") {
|
|
1409
|
+
if (!name) {
|
|
1410
|
+
console.log(`
|
|
1411
|
+
${paint.red("\u2717")} Usage: wasper auth use <name>
|
|
1412
|
+
`);
|
|
1413
|
+
process.exit(1);
|
|
1414
|
+
}
|
|
1415
|
+
const profiles = dbQueries.getProfiles();
|
|
1416
|
+
const target = profiles.find((p) => p.name.toLowerCase() === name.toLowerCase()) ?? profiles.find((p) => p.id === name);
|
|
1417
|
+
if (!target) {
|
|
1418
|
+
console.log(`
|
|
1419
|
+
${paint.red("\u2717")} Profile not found: "${name}"`);
|
|
1420
|
+
console.log(` ${paint.dim("wasper auth \u2014 list profiles")}
|
|
1421
|
+
`);
|
|
1422
|
+
process.exit(1);
|
|
1423
|
+
}
|
|
1424
|
+
dbQueries.activateProfile(target.id);
|
|
1425
|
+
console.log(`
|
|
1426
|
+
${paint.green("\u2713")} Active auth: ${paint.bold(target.name)} ${paint.dim(`(${target.type})`)}
|
|
1427
|
+
`);
|
|
1428
|
+
const state = await readDaemonState();
|
|
1429
|
+
if (state && isProcessAlive(state.pid)) {
|
|
1430
|
+
try {
|
|
1431
|
+
await fetch(`http://localhost:${state.port}/api/auth/profiles/${encodeURIComponent(target.id)}/activate`, {
|
|
1432
|
+
method: "POST",
|
|
1433
|
+
headers: state.token ? { Authorization: `Bearer ${state.token}` } : undefined,
|
|
1434
|
+
signal: AbortSignal.timeout(2000)
|
|
1435
|
+
});
|
|
1436
|
+
} catch {}
|
|
1437
|
+
}
|
|
1438
|
+
process.exit(0);
|
|
1439
|
+
}
|
|
1440
|
+
if (sub === "none") {
|
|
1441
|
+
dbQueries.setAuthConfig("none", {});
|
|
1442
|
+
console.log(`
|
|
1443
|
+
${paint.green("\u2713")} Auth disabled
|
|
1444
|
+
`);
|
|
1445
|
+
const state = await readDaemonState();
|
|
1446
|
+
if (state && isProcessAlive(state.pid)) {
|
|
1447
|
+
try {
|
|
1448
|
+
await fetch(`http://localhost:${state.port}/api/auth`, {
|
|
1449
|
+
method: "PUT",
|
|
1450
|
+
headers: {
|
|
1451
|
+
"Content-Type": "application/json",
|
|
1452
|
+
...state.token ? { Authorization: `Bearer ${state.token}` } : {}
|
|
1453
|
+
},
|
|
1454
|
+
body: JSON.stringify({ type: "none" }),
|
|
1455
|
+
signal: AbortSignal.timeout(2000)
|
|
1456
|
+
});
|
|
1457
|
+
} catch {}
|
|
1458
|
+
}
|
|
1459
|
+
process.exit(0);
|
|
1460
|
+
}
|
|
1461
|
+
console.log(`
|
|
1462
|
+
${paint.red("\u2717")} Unknown: wasper auth ${sub}`);
|
|
1463
|
+
console.log(` ${paint.dim("wasper auth \xB7 wasper auth use <name> \xB7 wasper auth none")}
|
|
1464
|
+
`);
|
|
1465
|
+
process.exit(1);
|
|
1466
|
+
}
|
|
1467
|
+
var init_auth_cmd = __esm(() => {
|
|
1468
|
+
init_daemon();
|
|
1469
|
+
init_db();
|
|
1470
|
+
init_ui();
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
// src/commands/spec-cmd.ts
|
|
1474
|
+
var exports_spec_cmd = {};
|
|
1475
|
+
__export(exports_spec_cmd, {
|
|
1476
|
+
run: () => run12
|
|
1477
|
+
});
|
|
1478
|
+
async function run12() {
|
|
1479
|
+
const args = process.argv.slice(2).filter((a) => a !== "spec");
|
|
1480
|
+
const portIdx = args.findIndex((a) => a === "--port" || a === "-p");
|
|
1481
|
+
const portArg = portIdx >= 0 ? parseInt(args[portIdx + 1] ?? "", 10) : undefined;
|
|
1482
|
+
const url = args.find((a) => !a.startsWith("-") && a !== args[portIdx + 1]);
|
|
1483
|
+
if (!url) {
|
|
1484
|
+
console.log(`
|
|
1485
|
+
${paint.red("\u2717")} Usage: wasper spec <url> [--port <port>]`);
|
|
1486
|
+
console.log(` ${paint.dim("Load a new OpenAPI spec on the running daemon")}
|
|
1487
|
+
`);
|
|
1488
|
+
process.exit(1);
|
|
1489
|
+
}
|
|
1490
|
+
const state = portArg ? await readDaemonState(portArg) : await resolveSingleDaemon2();
|
|
1491
|
+
if (!state || !isProcessAlive(state.pid)) {
|
|
1492
|
+
console.log(`
|
|
1493
|
+
${paint.yellow("\u25CB")} wasper is not running ${paint.dim("\u2192 wasper up --url " + url)}
|
|
1494
|
+
`);
|
|
1495
|
+
process.exit(1);
|
|
1496
|
+
}
|
|
1497
|
+
const port = state.port !== 3388 ? ` ${paint.dim(":" + state.port)}` : "";
|
|
1498
|
+
process.stdout.write(`
|
|
1499
|
+
${paint.dim("\u2192")} Loading ${paint.cyan(url)}...${port}
|
|
1500
|
+
`);
|
|
1501
|
+
try {
|
|
1502
|
+
const res = await fetch(`http://localhost:${state.port}/api/spec/reload-url`, {
|
|
1503
|
+
method: "POST",
|
|
1504
|
+
headers: {
|
|
1505
|
+
"Content-Type": "application/json",
|
|
1506
|
+
...state.token ? { Authorization: `Bearer ${state.token}` } : {}
|
|
1507
|
+
},
|
|
1508
|
+
body: JSON.stringify({ url }),
|
|
1509
|
+
signal: AbortSignal.timeout(30000)
|
|
1510
|
+
});
|
|
1511
|
+
const data = await res.json();
|
|
1512
|
+
if (!res.ok || data.error) {
|
|
1513
|
+
console.log(` ${paint.red("\u2717")} ${data.error ?? "Failed to load spec"}
|
|
1514
|
+
`);
|
|
1515
|
+
process.exit(1);
|
|
1516
|
+
}
|
|
1517
|
+
const title = data.spec?.title ?? url;
|
|
1518
|
+
const ver = data.spec?.version ? ` ${paint.dim("v" + data.spec.version)}` : "";
|
|
1519
|
+
const eps = data.endpointCount != null ? ` ${paint.dim("\xB7")} ${paint.green(data.endpointCount + " endpoints")}` : "";
|
|
1520
|
+
console.log(` ${paint.green("\u2713")} ${paint.bold(title)}${ver}${eps}
|
|
1521
|
+
`);
|
|
1522
|
+
} catch (e) {
|
|
1523
|
+
console.log(` ${paint.red("\u2717")} ${e instanceof Error ? e.message : String(e)}
|
|
1524
|
+
`);
|
|
1525
|
+
process.exit(1);
|
|
1526
|
+
}
|
|
1527
|
+
process.exit(0);
|
|
1528
|
+
}
|
|
1529
|
+
async function resolveSingleDaemon2() {
|
|
1530
|
+
const all = await readAllDaemonStates();
|
|
1531
|
+
if (all.length <= 1)
|
|
1532
|
+
return all[0] ?? null;
|
|
1533
|
+
console.log(`
|
|
1534
|
+
${paint.yellow("\u25CB")} Multiple instances running \u2014 specify --port:
|
|
1535
|
+
`);
|
|
1536
|
+
for (const s of all)
|
|
1537
|
+
console.log(` :${s.port} ${paint.dim(s.specUrl ?? "no spec")}`);
|
|
1538
|
+
console.log();
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
}
|
|
1541
|
+
var init_spec_cmd = __esm(() => {
|
|
1542
|
+
init_daemon();
|
|
1543
|
+
init_ui();
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
// src/commands/service.ts
|
|
1547
|
+
var exports_service = {};
|
|
1548
|
+
__export(exports_service, {
|
|
1549
|
+
run: () => run13
|
|
1550
|
+
});
|
|
1551
|
+
import { join as join3 } from "path";
|
|
1552
|
+
import { homedir as homedir3 } from "os";
|
|
1553
|
+
import { mkdir as mkdir2, writeFile as writeFile2, unlink as unlink2 } from "fs/promises";
|
|
1554
|
+
async function resolveWasperBin() {
|
|
1555
|
+
const exec = process.execPath;
|
|
1556
|
+
if (!exec.endsWith("/bun") && !exec.endsWith("/bun-debug") && !exec.endsWith("/bun-profile")) {
|
|
1557
|
+
return exec;
|
|
1558
|
+
}
|
|
1559
|
+
try {
|
|
1560
|
+
const r = await Bun.$`which wasper`.quiet();
|
|
1561
|
+
const p = r.stdout.toString().trim();
|
|
1562
|
+
if (p)
|
|
1563
|
+
return p;
|
|
1564
|
+
} catch {}
|
|
1565
|
+
return `${exec} ${Bun.main}`;
|
|
1566
|
+
}
|
|
1567
|
+
async function buildSystemdUnit(wasperBin, port, specUrl) {
|
|
1568
|
+
const wasperDir = join3(homedir3(), ".wasper");
|
|
1569
|
+
const logPath = join3(wasperDir, `server-${port}.log`);
|
|
1570
|
+
const envBlock = [`PORT=${port}`, ...specUrl ? [`WASPER_SPEC_URL=${specUrl}`] : []];
|
|
1571
|
+
const execArgs = ["start", "--_daemon", "--port", String(port), ...specUrl ? ["--url", specUrl] : []];
|
|
1572
|
+
return `[Unit]
|
|
1573
|
+
Description=Wasper OpenAPI Agent
|
|
1574
|
+
After=network.target
|
|
1575
|
+
|
|
1576
|
+
[Service]
|
|
1577
|
+
Type=simple
|
|
1578
|
+
ExecStart=${wasperBin} ${execArgs.join(" ")}
|
|
1579
|
+
Restart=on-failure
|
|
1580
|
+
RestartSec=5
|
|
1581
|
+
Environment=${envBlock.join(" ")}
|
|
1582
|
+
StandardOutput=append:${logPath}
|
|
1583
|
+
StandardError=append:${logPath}
|
|
1584
|
+
|
|
1585
|
+
[Install]
|
|
1586
|
+
WantedBy=default.target
|
|
1587
|
+
`;
|
|
1588
|
+
}
|
|
1589
|
+
async function systemctl(...args) {
|
|
1590
|
+
const proc = Bun.spawn(["systemctl", "--user", ...args], {
|
|
1591
|
+
stdout: "inherit",
|
|
1592
|
+
stderr: "inherit"
|
|
1593
|
+
});
|
|
1594
|
+
return proc.exited;
|
|
1595
|
+
}
|
|
1596
|
+
async function installLinux(port, specUrl) {
|
|
1597
|
+
const wasperBin = await resolveWasperBin();
|
|
1598
|
+
await mkdir2(SYSTEMD_UNIT_DIR, { recursive: true });
|
|
1599
|
+
await mkdir2(join3(homedir3(), ".wasper"), { recursive: true });
|
|
1600
|
+
const unit = await buildSystemdUnit(wasperBin, port, specUrl);
|
|
1601
|
+
await writeFile2(SYSTEMD_UNIT_FILE, unit);
|
|
1602
|
+
await systemctl("daemon-reload");
|
|
1603
|
+
await systemctl("enable", SYSTEMD_SERVICE);
|
|
1604
|
+
console.log(`
|
|
1605
|
+
${paint.green("\u2713")} Service installed`);
|
|
1606
|
+
console.log(` ${paint.dim(SYSTEMD_UNIT_FILE)}`);
|
|
1607
|
+
console.log(`
|
|
1608
|
+
${paint.dim("Start now: wasper service start")}`);
|
|
1609
|
+
console.log(` ${paint.dim("Auto-starts on login (systemd user session)")}
|
|
1610
|
+
`);
|
|
1611
|
+
}
|
|
1612
|
+
async function uninstallLinux() {
|
|
1613
|
+
await systemctl("stop", SYSTEMD_SERVICE).catch(() => {});
|
|
1614
|
+
await systemctl("disable", SYSTEMD_SERVICE).catch(() => {});
|
|
1615
|
+
try {
|
|
1616
|
+
await unlink2(SYSTEMD_UNIT_FILE);
|
|
1617
|
+
} catch {}
|
|
1618
|
+
await systemctl("daemon-reload");
|
|
1619
|
+
console.log(`
|
|
1620
|
+
${paint.green("\u2713")} Service uninstalled
|
|
1621
|
+
`);
|
|
1622
|
+
}
|
|
1623
|
+
function buildPlist(wasperBin, port, specUrl) {
|
|
1624
|
+
const logFile2 = join3(homedir3(), ".wasper", `server-${port}.log`);
|
|
1625
|
+
const args = [wasperBin, "start", "--_daemon", "--port", String(port), ...specUrl ? ["--url", specUrl] : []];
|
|
1626
|
+
const argsXml = args.map((a) => ` <string>${a}</string>`).join(`
|
|
1627
|
+
`);
|
|
1628
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
1629
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1630
|
+
<plist version="1.0">
|
|
1631
|
+
<dict>
|
|
1632
|
+
<key>Label</key>
|
|
1633
|
+
<string>${LAUNCH_LABEL}</string>
|
|
1634
|
+
<key>ProgramArguments</key>
|
|
1635
|
+
<array>
|
|
1636
|
+
${argsXml}
|
|
1637
|
+
</array>
|
|
1638
|
+
<key>RunAtLoad</key>
|
|
1639
|
+
<true/>
|
|
1640
|
+
<key>KeepAlive</key>
|
|
1641
|
+
<true/>
|
|
1642
|
+
<key>StandardOutPath</key>
|
|
1643
|
+
<string>${logFile2}</string>
|
|
1644
|
+
<key>StandardErrorPath</key>
|
|
1645
|
+
<string>${logFile2}</string>
|
|
1646
|
+
</dict>
|
|
1647
|
+
</plist>
|
|
1648
|
+
`;
|
|
1649
|
+
}
|
|
1650
|
+
async function launchctl(...args) {
|
|
1651
|
+
const proc = Bun.spawn(["launchctl", ...args], { stdout: "inherit", stderr: "inherit" });
|
|
1652
|
+
return proc.exited;
|
|
1653
|
+
}
|
|
1654
|
+
async function installMac(port, specUrl) {
|
|
1655
|
+
const wasperBin = await resolveWasperBin();
|
|
1656
|
+
await mkdir2(LAUNCH_DIR, { recursive: true });
|
|
1657
|
+
await mkdir2(join3(homedir3(), ".wasper"), { recursive: true });
|
|
1658
|
+
const plist = buildPlist(wasperBin, port, specUrl);
|
|
1659
|
+
await writeFile2(LAUNCH_PLIST, plist);
|
|
1660
|
+
await launchctl("load", "-w", LAUNCH_PLIST);
|
|
1661
|
+
console.log(`
|
|
1662
|
+
${paint.green("\u2713")} Launch Agent installed`);
|
|
1663
|
+
console.log(` ${paint.dim(LAUNCH_PLIST)}`);
|
|
1664
|
+
console.log(`
|
|
1665
|
+
${paint.dim("Auto-starts on login (macOS LaunchAgent)")}
|
|
1666
|
+
`);
|
|
1667
|
+
}
|
|
1668
|
+
async function uninstallMac() {
|
|
1669
|
+
await launchctl("unload", "-w", LAUNCH_PLIST).catch(() => {});
|
|
1670
|
+
try {
|
|
1671
|
+
await unlink2(LAUNCH_PLIST);
|
|
1672
|
+
} catch {}
|
|
1673
|
+
console.log(`
|
|
1674
|
+
${paint.green("\u2713")} Launch Agent uninstalled
|
|
1675
|
+
`);
|
|
1676
|
+
}
|
|
1677
|
+
async function run13() {
|
|
1678
|
+
const rawArgs = process.argv.slice(2).filter((a) => a !== "service");
|
|
1679
|
+
const sub = rawArgs.find((a) => !a.startsWith("-")) ?? "help";
|
|
1680
|
+
const portIdx = rawArgs.findIndex((a) => a === "--port" || a === "-p");
|
|
1681
|
+
const port = portIdx >= 0 ? parseInt(rawArgs[portIdx + 1] ?? "3388", 10) : parseInt(process.env.WASPER_PORT ?? "", 10) || 3388;
|
|
1682
|
+
const urlIdx = rawArgs.findIndex((a) => a === "--url" || a === "-u");
|
|
1683
|
+
const specUrl = urlIdx >= 0 ? rawArgs[urlIdx + 1] : process.env.WASPER_SPEC_URL;
|
|
1684
|
+
if (!IS_LINUX && !IS_MAC) {
|
|
1685
|
+
console.log(`
|
|
1686
|
+
${paint.yellow("\u25CB")} Service management is only supported on Linux (systemd) and macOS (launchd)
|
|
1687
|
+
`);
|
|
1688
|
+
process.exit(1);
|
|
1689
|
+
}
|
|
1690
|
+
const platform = IS_LINUX ? "Linux (systemd --user)" : "macOS (LaunchAgent)";
|
|
1691
|
+
switch (sub) {
|
|
1692
|
+
case "install": {
|
|
1693
|
+
if (IS_LINUX)
|
|
1694
|
+
await installLinux(port, specUrl);
|
|
1695
|
+
else
|
|
1696
|
+
await installMac(port, specUrl);
|
|
1697
|
+
break;
|
|
1698
|
+
}
|
|
1699
|
+
case "uninstall": {
|
|
1700
|
+
if (IS_LINUX)
|
|
1701
|
+
await uninstallLinux();
|
|
1702
|
+
else
|
|
1703
|
+
await uninstallMac();
|
|
1704
|
+
break;
|
|
1705
|
+
}
|
|
1706
|
+
case "start": {
|
|
1707
|
+
if (IS_LINUX) {
|
|
1708
|
+
await systemctl("start", SYSTEMD_SERVICE);
|
|
1709
|
+
} else {
|
|
1710
|
+
await launchctl("load", "-w", LAUNCH_PLIST);
|
|
1711
|
+
}
|
|
1712
|
+
break;
|
|
1713
|
+
}
|
|
1714
|
+
case "stop": {
|
|
1715
|
+
if (IS_LINUX) {
|
|
1716
|
+
await systemctl("stop", SYSTEMD_SERVICE);
|
|
1717
|
+
} else {
|
|
1718
|
+
await launchctl("unload", LAUNCH_PLIST);
|
|
1719
|
+
}
|
|
1720
|
+
break;
|
|
1721
|
+
}
|
|
1722
|
+
case "restart": {
|
|
1723
|
+
if (IS_LINUX) {
|
|
1724
|
+
await systemctl("restart", SYSTEMD_SERVICE);
|
|
1725
|
+
} else {
|
|
1726
|
+
await launchctl("unload", LAUNCH_PLIST).catch(() => {});
|
|
1727
|
+
await Bun.sleep(500);
|
|
1728
|
+
await launchctl("load", "-w", LAUNCH_PLIST);
|
|
1729
|
+
}
|
|
1730
|
+
break;
|
|
1731
|
+
}
|
|
1732
|
+
case "status": {
|
|
1733
|
+
if (IS_LINUX) {
|
|
1734
|
+
await systemctl("status", SYSTEMD_SERVICE);
|
|
1735
|
+
} else {
|
|
1736
|
+
await launchctl("list", LAUNCH_LABEL);
|
|
1737
|
+
}
|
|
1738
|
+
break;
|
|
1739
|
+
}
|
|
1740
|
+
case "enable": {
|
|
1741
|
+
if (IS_LINUX) {
|
|
1742
|
+
await systemctl("enable", SYSTEMD_SERVICE);
|
|
1743
|
+
} else {
|
|
1744
|
+
console.log(`
|
|
1745
|
+
${paint.dim("On macOS, RunAtLoad=true in the plist controls auto-start.")}`);
|
|
1746
|
+
console.log(` ${paint.dim("Use: wasper service install to re-install with auto-start enabled.")}
|
|
1747
|
+
`);
|
|
1748
|
+
}
|
|
1749
|
+
break;
|
|
1750
|
+
}
|
|
1751
|
+
case "disable": {
|
|
1752
|
+
if (IS_LINUX) {
|
|
1753
|
+
await systemctl("disable", SYSTEMD_SERVICE);
|
|
1754
|
+
} else {
|
|
1755
|
+
console.log(`
|
|
1756
|
+
${paint.dim("On macOS, use: wasper service uninstall to remove auto-start.")}
|
|
1757
|
+
`);
|
|
1758
|
+
}
|
|
1759
|
+
break;
|
|
1760
|
+
}
|
|
1761
|
+
case "logs": {
|
|
1762
|
+
if (IS_LINUX) {
|
|
1763
|
+
const proc = Bun.spawn(["journalctl", "--user", "-u", SYSTEMD_SERVICE, "-f", "--no-pager"], {
|
|
1764
|
+
stdout: "inherit",
|
|
1765
|
+
stderr: "inherit"
|
|
1766
|
+
});
|
|
1767
|
+
await proc.exited;
|
|
1768
|
+
} else {
|
|
1769
|
+
const logPath = join3(homedir3(), ".wasper", "server.log");
|
|
1770
|
+
const proc = Bun.spawn(["tail", "-f", logPath], { stdout: "inherit", stderr: "inherit" });
|
|
1771
|
+
await proc.exited;
|
|
1772
|
+
}
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
case "cat": {
|
|
1776
|
+
if (IS_LINUX) {
|
|
1777
|
+
const wasperBin = await resolveWasperBin();
|
|
1778
|
+
process.stdout.write(await buildSystemdUnit(wasperBin, port, specUrl));
|
|
1779
|
+
} else {
|
|
1780
|
+
const wasperBin = await resolveWasperBin();
|
|
1781
|
+
process.stdout.write(buildPlist(wasperBin, port, specUrl));
|
|
1782
|
+
}
|
|
1783
|
+
break;
|
|
1784
|
+
}
|
|
1785
|
+
default: {
|
|
1786
|
+
console.log(`
|
|
1787
|
+
${paint.bold("wasper service")} \u2014 Manage wasper as a system service ${paint.dim(`(${platform})`)}
|
|
1788
|
+
|
|
1789
|
+
${paint.bold("Commands")}
|
|
1790
|
+
wasper service install Install and enable (auto-start on login)
|
|
1791
|
+
wasper service uninstall Remove the service
|
|
1792
|
+
wasper service start Start the service now
|
|
1793
|
+
wasper service stop Stop the service
|
|
1794
|
+
wasper service restart Restart the service
|
|
1795
|
+
wasper service status Show service status
|
|
1796
|
+
wasper service enable Enable auto-start on login ${paint.dim("(Linux)")}
|
|
1797
|
+
wasper service disable Disable auto-start ${paint.dim("(Linux)")}
|
|
1798
|
+
wasper service logs Follow service logs
|
|
1799
|
+
wasper service cat Print the generated unit/plist
|
|
1800
|
+
|
|
1801
|
+
${paint.bold("Options")}
|
|
1802
|
+
--url <spec> OpenAPI spec URL to embed in the service definition
|
|
1803
|
+
--port <port> Port ${paint.dim("(default: 3388)")}
|
|
1804
|
+
`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
var IS_LINUX, IS_MAC, SYSTEMD_SERVICE = "wasper", SYSTEMD_UNIT_DIR, SYSTEMD_UNIT_FILE, LAUNCH_LABEL = "com.wasper.agent", LAUNCH_DIR, LAUNCH_PLIST;
|
|
1809
|
+
var init_service = __esm(() => {
|
|
1810
|
+
init_ui();
|
|
1811
|
+
IS_LINUX = process.platform === "linux";
|
|
1812
|
+
IS_MAC = process.platform === "darwin";
|
|
1813
|
+
SYSTEMD_UNIT_DIR = join3(homedir3(), ".config", "systemd", "user");
|
|
1814
|
+
SYSTEMD_UNIT_FILE = join3(SYSTEMD_UNIT_DIR, `${SYSTEMD_SERVICE}.service`);
|
|
1815
|
+
LAUNCH_DIR = join3(homedir3(), "Library", "LaunchAgents");
|
|
1816
|
+
LAUNCH_PLIST = join3(LAUNCH_DIR, `${LAUNCH_LABEL}.plist`);
|
|
1817
|
+
});
|
|
1818
|
+
|
|
1819
|
+
// src/commands/update.ts
|
|
1820
|
+
var exports_update = {};
|
|
1821
|
+
__export(exports_update, {
|
|
1822
|
+
run: () => run14,
|
|
1823
|
+
printUpdateNotice: () => printUpdateNotice,
|
|
1824
|
+
performUpdate: () => performUpdate,
|
|
1825
|
+
fetchLatestVersion: () => fetchLatestVersion,
|
|
1826
|
+
checkForUpdate: () => checkForUpdate
|
|
1827
|
+
});
|
|
1828
|
+
import { join as join4 } from "path";
|
|
1829
|
+
import { homedir as homedir4 } from "os";
|
|
1830
|
+
import { chmod, rename, unlink as unlink3, mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "fs/promises";
|
|
1831
|
+
async function fetchLatestVersion() {
|
|
1832
|
+
try {
|
|
1833
|
+
const res = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, {
|
|
1834
|
+
signal: AbortSignal.timeout(5000),
|
|
1835
|
+
headers: { Accept: "application/json" }
|
|
1836
|
+
});
|
|
1837
|
+
if (!res.ok)
|
|
1838
|
+
return null;
|
|
1839
|
+
const data = await res.json();
|
|
1840
|
+
return data.version ?? null;
|
|
1841
|
+
} catch {
|
|
1842
|
+
return null;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
async function checkForUpdate() {
|
|
1846
|
+
if (process.env.WASPER_NO_UPDATE_CHECK)
|
|
1847
|
+
return null;
|
|
1848
|
+
let state = null;
|
|
1849
|
+
try {
|
|
1850
|
+
state = JSON.parse(await readFile2(CHECK_FILE, "utf-8"));
|
|
1851
|
+
} catch {}
|
|
1852
|
+
let latest = state?.latest ?? null;
|
|
1853
|
+
if (!state || Date.now() - state.lastCheck > CHECK_INTERVAL) {
|
|
1854
|
+
latest = await fetchLatestVersion();
|
|
1855
|
+
if (latest) {
|
|
1856
|
+
await mkdir3(join4(homedir4(), ".wasper"), { recursive: true }).catch(() => {});
|
|
1857
|
+
await writeFile3(CHECK_FILE, JSON.stringify({ lastCheck: Date.now(), latest }), "utf-8").catch(() => {});
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
if (latest && compareSemver(latest, VERSION) > 0)
|
|
1861
|
+
return latest;
|
|
1862
|
+
return null;
|
|
1863
|
+
}
|
|
1864
|
+
function printUpdateNotice(latest) {
|
|
1865
|
+
console.log(` ${paint.yellow("\u25B2")} Update available ${paint.dim(VERSION)} \u2192 ${paint.green(latest)} ${paint.dim("\xB7")} run ${paint.bold("wasper update")}
|
|
1866
|
+
`);
|
|
1867
|
+
}
|
|
1868
|
+
function binaryAssetName() {
|
|
1869
|
+
const os = process.platform === "darwin" ? "darwin" : process.platform === "win32" ? "windows" : "linux";
|
|
1870
|
+
const arch = process.arch === "arm64" ? "arm64" : "x64";
|
|
1871
|
+
return `wasper-${os}-${arch}${os === "windows" ? ".exe" : ""}`;
|
|
1872
|
+
}
|
|
1873
|
+
async function updateCompiledBinary(latest) {
|
|
1874
|
+
const exe = process.execPath;
|
|
1875
|
+
const asset = binaryAssetName();
|
|
1876
|
+
const url = `https://github.com/${REPO}/releases/download/v${latest}/${asset}`;
|
|
1877
|
+
const res = await fetch(url, { redirect: "follow" });
|
|
1878
|
+
if (!res.ok)
|
|
1879
|
+
throw new Error(`Download failed (HTTP ${res.status}) \u2014 ${url}`);
|
|
1880
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
1881
|
+
if (bytes.byteLength < 1024 * 100)
|
|
1882
|
+
throw new Error("Downloaded file is suspiciously small \u2014 aborting");
|
|
1883
|
+
const tmp = `${exe}.update`;
|
|
1884
|
+
const old = `${exe}.old`;
|
|
1885
|
+
await Bun.write(tmp, bytes);
|
|
1886
|
+
await chmod(tmp, 493);
|
|
1887
|
+
await rename(exe, old);
|
|
1888
|
+
try {
|
|
1889
|
+
await rename(tmp, exe);
|
|
1890
|
+
} catch (e) {
|
|
1891
|
+
await rename(old, exe).catch(() => {});
|
|
1892
|
+
throw e;
|
|
1893
|
+
}
|
|
1894
|
+
await unlink3(old).catch(() => {});
|
|
1895
|
+
}
|
|
1896
|
+
async function updatePackageInstall() {
|
|
1897
|
+
const tryRun = async (cmd) => {
|
|
1898
|
+
try {
|
|
1899
|
+
const p = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
1900
|
+
const code = await p.exited;
|
|
1901
|
+
return code === 0;
|
|
1902
|
+
} catch {
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
};
|
|
1906
|
+
if (await tryRun(["bun", "add", "-g", `${PACKAGE_NAME}@latest`]))
|
|
1907
|
+
return;
|
|
1908
|
+
if (await tryRun(["npm", "install", "-g", `${PACKAGE_NAME}@latest`]))
|
|
1909
|
+
return;
|
|
1910
|
+
throw new Error("Neither `bun add -g` nor `npm install -g` succeeded. Update manually.");
|
|
1911
|
+
}
|
|
1912
|
+
async function performUpdate(opts = {}) {
|
|
1913
|
+
const spinner = new Spinner;
|
|
1914
|
+
if (!opts.quiet)
|
|
1915
|
+
spinner.start("Checking for updates\u2026");
|
|
1916
|
+
const latest = await fetchLatestVersion();
|
|
1917
|
+
if (!latest) {
|
|
1918
|
+
spinner.stop("\u2717", "Could not reach the npm registry", "red");
|
|
1919
|
+
return false;
|
|
913
1920
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
if (isTTY) {
|
|
918
|
-
const header = ` ${"#".padEnd(3)} ${"URL".padEnd(COL_URL)} ${"Title".padEnd(COL_TITLE)} ${"Endpoints".padEnd(10)} Last used`;
|
|
919
|
-
console.log(paint.dim(header));
|
|
920
|
-
console.log(paint.dim(" " + "\u2500".repeat(header.length - 2)));
|
|
1921
|
+
if (compareSemver(latest, VERSION) <= 0) {
|
|
1922
|
+
spinner.stop("\u2713", `Already up to date ${paint.dim("v" + VERSION)}`, "green");
|
|
1923
|
+
return false;
|
|
921
1924
|
}
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1925
|
+
spinner.stop();
|
|
1926
|
+
if (!opts.quiet)
|
|
1927
|
+
spinner.start(`Updating ${paint.dim(VERSION)} \u2192 ${paint.green(latest)}\u2026`);
|
|
1928
|
+
try {
|
|
1929
|
+
if (isCompiledBinary())
|
|
1930
|
+
await updateCompiledBinary(latest);
|
|
1931
|
+
else
|
|
1932
|
+
await updatePackageInstall();
|
|
1933
|
+
spinner.stop("\u2713", `Updated to ${paint.bold("v" + latest)} ${paint.dim("\u2014 restart any running servers to use it")}`, "green");
|
|
1934
|
+
await writeFile3(CHECK_FILE, JSON.stringify({ lastCheck: Date.now(), latest }), "utf-8").catch(() => {});
|
|
1935
|
+
return true;
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
spinner.stop("\u2717", `Update failed: ${e instanceof Error ? e.message : String(e)}`, "red");
|
|
1938
|
+
return false;
|
|
1939
|
+
}
|
|
1940
|
+
}
|
|
1941
|
+
async function run14() {
|
|
930
1942
|
console.log();
|
|
931
|
-
|
|
932
|
-
console.log(paint.dim(` wasper rm <number> \u2014 remove a spec from history`));
|
|
1943
|
+
await performUpdate();
|
|
933
1944
|
console.log();
|
|
934
1945
|
}
|
|
935
|
-
var
|
|
936
|
-
|
|
1946
|
+
var CHECK_FILE, CHECK_INTERVAL;
|
|
1947
|
+
var init_update = __esm(() => {
|
|
1948
|
+
init_version();
|
|
1949
|
+
init_ui();
|
|
1950
|
+
CHECK_FILE = join4(homedir4(), ".wasper", "update-check.json");
|
|
1951
|
+
CHECK_INTERVAL = 24 * 60 * 60 * 1000;
|
|
1952
|
+
});
|
|
1953
|
+
|
|
1954
|
+
// src/commands/help.ts
|
|
1955
|
+
var exports_help = {};
|
|
1956
|
+
__export(exports_help, {
|
|
1957
|
+
run: () => run15
|
|
1958
|
+
});
|
|
1959
|
+
async function run15() {
|
|
1960
|
+
const b = (s) => paint.bold(s);
|
|
1961
|
+
const d = (s) => paint.dim(s);
|
|
1962
|
+
const c = (s) => paint.cyan(s);
|
|
1963
|
+
console.log(`
|
|
1964
|
+
${b("wasper")} ${d("v" + VERSION)}
|
|
1965
|
+
|
|
1966
|
+
${b("Daemon")}
|
|
1967
|
+
${c("wasper up")} ${d("[--url <spec>] [--port <port>]")}
|
|
1968
|
+
Start daemon in background (default port: 3388)
|
|
1969
|
+
${c("wasper up")} ${d("--url <spec2> --port 3389")}
|
|
1970
|
+
Run a second instance on a different port
|
|
1971
|
+
${c("wasper down")} ${d("[--port <port>]")} Stop one instance
|
|
1972
|
+
${c("wasper down --all")} Stop all instances
|
|
1973
|
+
${c("wasper ps")} List all running instances
|
|
1974
|
+
${c("wasper status")} ${d("[--port <p>]")} Status of one or all instances
|
|
1975
|
+
${c("wasper logs")} ${d("[-f] [--port <p>]")} Tail server logs
|
|
1976
|
+
|
|
1977
|
+
${b("Spec")}
|
|
1978
|
+
${c("wasper spec")} ${d("<url> [--port <p>]")} Load a new spec on the running daemon
|
|
1979
|
+
${c("wasper reload")} ${d("[--port <p>]")} Hot-reload current spec
|
|
1980
|
+
${c("wasper ls")} List saved spec history
|
|
1981
|
+
${c("wasper use")} ${d("<n|url> [--port <p>]")} Restart with a saved spec
|
|
1982
|
+
${c("wasper rm")} ${d("<n|url>")} Remove spec from history
|
|
1983
|
+
|
|
1984
|
+
${b("Features")} ${d("toggle on the running daemon")}
|
|
1985
|
+
${c("wasper mcp")} ${d("[on|off] [--port <p>]")}
|
|
1986
|
+
${c("wasper proxy")} ${d("[on|off] [--port <p>]")}
|
|
1987
|
+
${c("wasper ai")} ${d("[on|off] [--port <p>]")}
|
|
1988
|
+
${c("wasper readonly")} ${d("[on|off] [--port <p>]")}
|
|
1989
|
+
|
|
1990
|
+
${b("Auth")}
|
|
1991
|
+
${c("wasper auth")} List saved auth profiles
|
|
1992
|
+
${c("wasper auth use")} ${d("<name>")} Switch active profile
|
|
1993
|
+
${c("wasper auth none")} Disable auth
|
|
1994
|
+
|
|
1995
|
+
${b("Service")} ${d("auto-start on login")}
|
|
1996
|
+
${c("wasper service install")} ${d("[--port <p>] [--url <spec>]")}
|
|
1997
|
+
${c("wasper service uninstall")}
|
|
1998
|
+
${c("wasper service start")} ${d("|")} ${c("stop")} ${d("|")} ${c("status")} ${d("|")} ${c("logs")}
|
|
1999
|
+
|
|
2000
|
+
${b("Other")}
|
|
2001
|
+
${c("wasper update")} Update to latest version
|
|
2002
|
+
${c("wasper --version")} Print version
|
|
2003
|
+
|
|
2004
|
+
${d("Multi-instance example:")}
|
|
2005
|
+
${d(" wasper up --url https://api1.com/openapi.json --port 3388")}
|
|
2006
|
+
${d(" wasper up --url https://api2.com/openapi.json --port 3389")}
|
|
2007
|
+
${d(" wasper ps")}
|
|
2008
|
+
${d(" wasper mcp off --port 3389")}
|
|
2009
|
+
${d(" wasper down --all")}
|
|
2010
|
+
`);
|
|
2011
|
+
process.exit(0);
|
|
2012
|
+
}
|
|
2013
|
+
var init_help = __esm(() => {
|
|
937
2014
|
init_ui();
|
|
2015
|
+
init_version();
|
|
938
2016
|
});
|
|
939
2017
|
|
|
940
2018
|
// ../../node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -3708,6 +4786,11 @@ function parseSpecText(text, url, name) {
|
|
|
3708
4786
|
const info = doc.info ?? {};
|
|
3709
4787
|
const servers = doc.servers ?? [];
|
|
3710
4788
|
let baseUrl = servers[0]?.url ?? "";
|
|
4789
|
+
if (!baseUrl && doc.swagger && doc.host) {
|
|
4790
|
+
const scheme = doc.schemes?.[0] ?? "https";
|
|
4791
|
+
const basePath = typeof doc.basePath === "string" ? doc.basePath.replace(/\/$/, "") : "";
|
|
4792
|
+
baseUrl = `${scheme}://${doc.host}${basePath}`;
|
|
4793
|
+
}
|
|
3711
4794
|
if (!baseUrl && url) {
|
|
3712
4795
|
try {
|
|
3713
4796
|
baseUrl = new URL(url).origin;
|
|
@@ -4100,8 +5183,8 @@ async function applyAuth(url, headers, authConfig) {
|
|
|
4100
5183
|
function cacheKey(c) {
|
|
4101
5184
|
return `${c.tokenUrl ?? ""}|${c.clientId ?? ""}|${c.scope ?? ""}`;
|
|
4102
5185
|
}
|
|
4103
|
-
function getCachedToken(
|
|
4104
|
-
const key = cacheKey(
|
|
5186
|
+
function getCachedToken(config2) {
|
|
5187
|
+
const key = cacheKey(config2);
|
|
4105
5188
|
const mem = memCaches.get(key);
|
|
4106
5189
|
if (mem && Date.now() < mem.expires_at - 30000)
|
|
4107
5190
|
return mem.access_token;
|
|
@@ -4120,21 +5203,21 @@ function getCachedToken(config) {
|
|
|
4120
5203
|
}
|
|
4121
5204
|
return null;
|
|
4122
5205
|
}
|
|
4123
|
-
async function getOrRefreshOAuthToken(
|
|
4124
|
-
const cached = getCachedToken(
|
|
5206
|
+
async function getOrRefreshOAuthToken(config2) {
|
|
5207
|
+
const cached = getCachedToken(config2);
|
|
4125
5208
|
if (cached)
|
|
4126
5209
|
return cached;
|
|
4127
|
-
if (!
|
|
5210
|
+
if (!config2.tokenUrl || !config2.clientId)
|
|
4128
5211
|
return null;
|
|
4129
5212
|
const params = new URLSearchParams({
|
|
4130
5213
|
grant_type: "client_credentials",
|
|
4131
|
-
client_id:
|
|
4132
|
-
client_secret:
|
|
5214
|
+
client_id: config2.clientId,
|
|
5215
|
+
client_secret: config2.clientSecret ?? ""
|
|
4133
5216
|
});
|
|
4134
|
-
if (
|
|
4135
|
-
params.set("scope",
|
|
5217
|
+
if (config2.scope)
|
|
5218
|
+
params.set("scope", config2.scope);
|
|
4136
5219
|
try {
|
|
4137
|
-
const res = await fetch(
|
|
5220
|
+
const res = await fetch(config2.tokenUrl, {
|
|
4138
5221
|
method: "POST",
|
|
4139
5222
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4140
5223
|
body: params.toString()
|
|
@@ -4145,7 +5228,7 @@ async function getOrRefreshOAuthToken(config) {
|
|
|
4145
5228
|
if (!data.access_token)
|
|
4146
5229
|
return null;
|
|
4147
5230
|
const cache = { access_token: data.access_token, expires_at: Date.now() + (data.expires_in ?? 3600) * 1000 };
|
|
4148
|
-
memCaches.set(cacheKey(
|
|
5231
|
+
memCaches.set(cacheKey(config2), cache);
|
|
4149
5232
|
dbQueries.updateTokenCache(cache);
|
|
4150
5233
|
return cache.access_token;
|
|
4151
5234
|
} catch {
|
|
@@ -4169,53 +5252,6 @@ var init_engine = __esm(() => {
|
|
|
4169
5252
|
memCaches = new Map;
|
|
4170
5253
|
});
|
|
4171
5254
|
|
|
4172
|
-
// src/config.ts
|
|
4173
|
-
function setServerConfig(c) {
|
|
4174
|
-
config = c;
|
|
4175
|
-
}
|
|
4176
|
-
function getServerConfig() {
|
|
4177
|
-
return config;
|
|
4178
|
-
}
|
|
4179
|
-
function updateServerConfig(patch) {
|
|
4180
|
-
config = { ...config, ...patch };
|
|
4181
|
-
}
|
|
4182
|
-
function getFeatures() {
|
|
4183
|
-
return features;
|
|
4184
|
-
}
|
|
4185
|
-
function setFeatures(patch) {
|
|
4186
|
-
features = { ...features, ...patch };
|
|
4187
|
-
}
|
|
4188
|
-
function readonlyViolation(method) {
|
|
4189
|
-
if (!features.readonly)
|
|
4190
|
-
return null;
|
|
4191
|
-
if (SAFE_METHODS.has(method.toUpperCase()))
|
|
4192
|
-
return null;
|
|
4193
|
-
return `Read-only mode is enabled \u2014 ${method.toUpperCase()} requests are blocked. Ask the operator to run /readonly off.`;
|
|
4194
|
-
}
|
|
4195
|
-
function isAuthorized(req) {
|
|
4196
|
-
if (!config.token)
|
|
4197
|
-
return true;
|
|
4198
|
-
const auth = req.headers.get("authorization");
|
|
4199
|
-
if (auth === `Bearer ${config.token}`)
|
|
4200
|
-
return true;
|
|
4201
|
-
try {
|
|
4202
|
-
return new URL(req.url).searchParams.get("token") === config.token;
|
|
4203
|
-
} catch {
|
|
4204
|
-
return false;
|
|
4205
|
-
}
|
|
4206
|
-
}
|
|
4207
|
-
var config, features, SAFE_METHODS;
|
|
4208
|
-
var init_config = __esm(() => {
|
|
4209
|
-
config = {
|
|
4210
|
-
port: 3388,
|
|
4211
|
-
host: "0.0.0.0",
|
|
4212
|
-
origin: null,
|
|
4213
|
-
token: null
|
|
4214
|
-
};
|
|
4215
|
-
features = { mcp: true, proxy: true, ai: true, readonly: false };
|
|
4216
|
-
SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS"]);
|
|
4217
|
-
});
|
|
4218
|
-
|
|
4219
5255
|
// src/mcp/server.ts
|
|
4220
5256
|
function summarizeAuth(type, config2) {
|
|
4221
5257
|
switch (type) {
|
|
@@ -5838,6 +6874,7 @@ async function handleSpecUpload(req) {
|
|
|
5838
6874
|
try {
|
|
5839
6875
|
const state = loadSpecFromText(content, filename);
|
|
5840
6876
|
const suggestedVars = extractSuggestedVars(content, state.spec.baseUrl);
|
|
6877
|
+
logBus.broadcastServerEvent({ kind: "spec_changed" });
|
|
5841
6878
|
return json({
|
|
5842
6879
|
ok: true,
|
|
5843
6880
|
spec: { title: state.spec.title, version: state.spec.version, baseUrl: state.spec.baseUrl },
|
|
@@ -5860,6 +6897,7 @@ async function handleSpecReloadUrl(req) {
|
|
|
5860
6897
|
try {
|
|
5861
6898
|
const state = await loadSpec(body.url);
|
|
5862
6899
|
const suggestedVars = extractSuggestedVars(state.spec.raw, state.spec.baseUrl);
|
|
6900
|
+
logBus.broadcastServerEvent({ kind: "spec_changed" });
|
|
5863
6901
|
return json({
|
|
5864
6902
|
ok: true,
|
|
5865
6903
|
spec: { title: state.spec.title, version: state.spec.version, baseUrl: state.spec.baseUrl },
|
|
@@ -5871,7 +6909,8 @@ async function handleSpecReloadUrl(req) {
|
|
|
5871
6909
|
}
|
|
5872
6910
|
}
|
|
5873
6911
|
function handleGetLogs(searchParams) {
|
|
5874
|
-
const
|
|
6912
|
+
const raw = parseInt(searchParams.get("limit") ?? "500", 10);
|
|
6913
|
+
const limit = Math.min(Number.isFinite(raw) ? raw : 500, 2000);
|
|
5875
6914
|
return json(dbQueries.getRecentLogs(limit));
|
|
5876
6915
|
}
|
|
5877
6916
|
function handleClearLogs() {
|
|
@@ -5893,6 +6932,8 @@ async function handleSetAuth(req) {
|
|
|
5893
6932
|
return json({ type: body.type, config: body.config });
|
|
5894
6933
|
}
|
|
5895
6934
|
async function handleTestAuth() {
|
|
6935
|
+
if (!hasState())
|
|
6936
|
+
return badRequest("No spec loaded");
|
|
5896
6937
|
const { spec } = getState();
|
|
5897
6938
|
const authRow = dbQueries.getAuthConfig();
|
|
5898
6939
|
const authConfig = authRow ? JSON.parse(authRow.config) : { type: "none" };
|
|
@@ -5906,6 +6947,8 @@ async function handleTestAuth() {
|
|
|
5906
6947
|
}
|
|
5907
6948
|
}
|
|
5908
6949
|
function handleGetEndpoints() {
|
|
6950
|
+
if (!hasState())
|
|
6951
|
+
return json([]);
|
|
5909
6952
|
return json(getState().operations);
|
|
5910
6953
|
}
|
|
5911
6954
|
function handleGetSettings() {
|
|
@@ -5927,6 +6970,8 @@ async function handleSetSettings(req) {
|
|
|
5927
6970
|
return json(body);
|
|
5928
6971
|
}
|
|
5929
6972
|
async function executeTool(name, args, cache = new Map) {
|
|
6973
|
+
if (!hasState())
|
|
6974
|
+
return { text: "No spec loaded.", isError: true };
|
|
5930
6975
|
const { operations, spec } = getState();
|
|
5931
6976
|
if (name === "search_endpoints") {
|
|
5932
6977
|
const cacheKey2 = `search:${String(args.query ?? "").toLowerCase()}`;
|
|
@@ -6586,10 +7631,11 @@ async function handleExplorerRequest(req) {
|
|
|
6586
7631
|
};
|
|
6587
7632
|
if (timeoutMs > 0)
|
|
6588
7633
|
fetchOpts.signal = AbortSignal.timeout(timeoutMs);
|
|
7634
|
+
const parsedUrl = new URL(authedUrl);
|
|
6589
7635
|
let dnsMs = 0;
|
|
6590
7636
|
let resolvedAddr = "";
|
|
6591
7637
|
try {
|
|
6592
|
-
const u =
|
|
7638
|
+
const u = parsedUrl;
|
|
6593
7639
|
const h = u.hostname;
|
|
6594
7640
|
const defaultPort = u.protocol === "https:" ? 443 : 80;
|
|
6595
7641
|
const port = u.port ? Number(u.port) : defaultPort;
|
|
@@ -6608,7 +7654,7 @@ async function handleExplorerRequest(req) {
|
|
|
6608
7654
|
const waitMs = Math.round(performance.now() - fetchStart);
|
|
6609
7655
|
const resHeaders = Object.fromEntries(res.headers.entries());
|
|
6610
7656
|
const ct = res.headers.get("content-type") ?? "";
|
|
6611
|
-
const u =
|
|
7657
|
+
const u = parsedUrl;
|
|
6612
7658
|
const networkInfo = {
|
|
6613
7659
|
scheme: u.protocol.replace(":", ""),
|
|
6614
7660
|
host: u.host,
|
|
@@ -7489,15 +8535,15 @@ var init_repl = __esm(() => {
|
|
|
7489
8535
|
// src/commands/start.ts
|
|
7490
8536
|
var exports_start = {};
|
|
7491
8537
|
__export(exports_start, {
|
|
7492
|
-
run: () =>
|
|
8538
|
+
run: () => run16
|
|
7493
8539
|
});
|
|
7494
|
-
import { parseArgs } from "util";
|
|
8540
|
+
import { parseArgs as parseArgs2 } from "util";
|
|
7495
8541
|
import { createInterface } from "readline";
|
|
7496
|
-
import { homedir as
|
|
7497
|
-
import { join as
|
|
8542
|
+
import { homedir as homedir5 } from "os";
|
|
8543
|
+
import { join as join5, dirname } from "path";
|
|
7498
8544
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
7499
|
-
async function
|
|
7500
|
-
const { values } =
|
|
8545
|
+
async function run16(overrideOpts) {
|
|
8546
|
+
const { values } = parseArgs2({
|
|
7501
8547
|
args: process.argv.slice(2).filter((a) => a !== "start"),
|
|
7502
8548
|
options: {
|
|
7503
8549
|
url: { type: "string" },
|
|
@@ -7517,7 +8563,7 @@ async function run6(overrideOpts) {
|
|
|
7517
8563
|
strict: false
|
|
7518
8564
|
});
|
|
7519
8565
|
if (values.help) {
|
|
7520
|
-
|
|
8566
|
+
printHelp2();
|
|
7521
8567
|
process.exit(0);
|
|
7522
8568
|
}
|
|
7523
8569
|
let specUrl = overrideOpts?.url ?? (values.url ? String(values.url) : null) ?? process.env.WASPER_SPEC_URL ?? null;
|
|
@@ -7682,7 +8728,7 @@ async function run6(overrideOpts) {
|
|
|
7682
8728
|
${paint.dim("shutting down")}
|
|
7683
8729
|
|
|
7684
8730
|
`);
|
|
7685
|
-
clearDaemonState().finally(() => {
|
|
8731
|
+
clearDaemonState(PORT).finally(() => {
|
|
7686
8732
|
db.close();
|
|
7687
8733
|
server.stop();
|
|
7688
8734
|
process.exit(0);
|
|
@@ -8008,18 +9054,21 @@ function printInteractiveHelp() {
|
|
|
8008
9054
|
${k("/status")} ${k("/reload")} ${k("/help")} ${k("/quit")}
|
|
8009
9055
|
`);
|
|
8010
9056
|
}
|
|
8011
|
-
function
|
|
9057
|
+
function printHelp2() {
|
|
8012
9058
|
console.log(`
|
|
8013
|
-
Usage: wasper
|
|
9059
|
+
Usage: wasper start [options]
|
|
9060
|
+
|
|
9061
|
+
Starts wasper in the foreground with an interactive REPL.
|
|
9062
|
+
For background (daemon) mode \u2014 the default \u2014 use: wasper up
|
|
8014
9063
|
|
|
8015
|
-
wasper [--url <spec
|
|
8016
|
-
wasper start --
|
|
8017
|
-
wasper
|
|
8018
|
-
wasper status Show
|
|
9064
|
+
wasper up [--url <spec>] Start daemon in background (default)
|
|
9065
|
+
wasper start [--url <spec>] Start in foreground with REPL
|
|
9066
|
+
wasper down Stop the daemon
|
|
9067
|
+
wasper status Show daemon status
|
|
9068
|
+
wasper logs [-f] Tail server logs
|
|
9069
|
+
wasper service install Install as system service (auto-start)
|
|
8019
9070
|
wasper reload Hot-reload the spec
|
|
8020
9071
|
wasper ls List saved specs (history)
|
|
8021
|
-
wasper use <number|url> Start with a saved spec
|
|
8022
|
-
wasper rm <number|url> Remove a spec from history
|
|
8023
9072
|
|
|
8024
9073
|
Options:
|
|
8025
9074
|
--url, -u OpenAPI spec URL or local path
|
|
@@ -8034,17 +9083,17 @@ Options:
|
|
|
8034
9083
|
--no-proxy Start with the HTTP proxy disabled
|
|
8035
9084
|
--no-ai Start with the AI chat endpoint disabled
|
|
8036
9085
|
--readonly Block all non-GET upstream requests (agent guardrail)
|
|
8037
|
-
--background, -b Start detached in background
|
|
9086
|
+
--background, -b Start detached in background (same as wasper up)
|
|
8038
9087
|
--daemon, -d Same as --background
|
|
8039
9088
|
-h, --help Show this help
|
|
8040
9089
|
|
|
8041
|
-
Interactive
|
|
9090
|
+
Interactive REPL slash commands (foreground mode):
|
|
8042
9091
|
/mcp on|off \xB7 /proxy on|off \xB7 /ai on|off \xB7 /readonly on|off
|
|
8043
9092
|
/auth use <role> \xB7 /token new \xB7 /spec <url> \xB7 /tail \xB7 /help
|
|
8044
9093
|
|
|
8045
9094
|
Self-hosting:
|
|
8046
|
-
wasper
|
|
8047
|
-
|
|
9095
|
+
wasper up --url <spec> --origin https://api.example.com --token <secret>
|
|
9096
|
+
wasper service install --url <spec> --port 3388
|
|
8048
9097
|
`);
|
|
8049
9098
|
}
|
|
8050
9099
|
function buildScalarHtml(title, req) {
|
|
@@ -8079,10 +9128,10 @@ function promptYN(question, defaultYes = true) {
|
|
|
8079
9128
|
function claudeDesktopConfigPath() {
|
|
8080
9129
|
const p = process.platform;
|
|
8081
9130
|
if (p === "darwin")
|
|
8082
|
-
return
|
|
9131
|
+
return join5(homedir5(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
8083
9132
|
if (p === "win32")
|
|
8084
|
-
return
|
|
8085
|
-
return
|
|
9133
|
+
return join5(process.env.APPDATA ?? join5(homedir5(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
9134
|
+
return join5(homedir5(), ".config", "Claude", "claude_desktop_config.json");
|
|
8086
9135
|
}
|
|
8087
9136
|
async function configureClaudeDesktop(mcpUrl) {
|
|
8088
9137
|
const cfgPath = claudeDesktopConfigPath();
|
|
@@ -8156,98 +9205,6 @@ var init_start = __esm(() => {
|
|
|
8156
9205
|
SPIN_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8157
9206
|
});
|
|
8158
9207
|
|
|
8159
|
-
// src/commands/use.ts
|
|
8160
|
-
var exports_use = {};
|
|
8161
|
-
__export(exports_use, {
|
|
8162
|
-
run: () => run7
|
|
8163
|
-
});
|
|
8164
|
-
async function run7() {
|
|
8165
|
-
const args = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
8166
|
-
const target = args[1];
|
|
8167
|
-
if (!target) {
|
|
8168
|
-
console.error(`
|
|
8169
|
-
Usage: wasper use <number|url>
|
|
8170
|
-
`);
|
|
8171
|
-
process.exit(1);
|
|
8172
|
-
}
|
|
8173
|
-
const history = dbQueries.getSpecHistory();
|
|
8174
|
-
let url = null;
|
|
8175
|
-
const num = parseInt(target, 10);
|
|
8176
|
-
if (!isNaN(num) && num >= 1 && num <= history.length) {
|
|
8177
|
-
url = history[num - 1]?.url ?? null;
|
|
8178
|
-
} else if (target.startsWith("http")) {
|
|
8179
|
-
url = target;
|
|
8180
|
-
} else {
|
|
8181
|
-
const match = history.find((r) => r.title?.toLowerCase().includes(target.toLowerCase()));
|
|
8182
|
-
if (match)
|
|
8183
|
-
url = match.url;
|
|
8184
|
-
}
|
|
8185
|
-
if (!url) {
|
|
8186
|
-
console.error(`
|
|
8187
|
-
${paint.red("\u2717")} Spec not found: ${target}
|
|
8188
|
-
`);
|
|
8189
|
-
console.error(` Run ${paint.cyan("wasper ls")} to see saved specs.
|
|
8190
|
-
`);
|
|
8191
|
-
process.exit(1);
|
|
8192
|
-
}
|
|
8193
|
-
console.log(`
|
|
8194
|
-
${paint.dim("\u2192")} Starting with ${paint.cyan(url)}
|
|
8195
|
-
`);
|
|
8196
|
-
const { run: startRun } = await Promise.resolve().then(() => (init_start(), exports_start));
|
|
8197
|
-
await startRun({ url });
|
|
8198
|
-
}
|
|
8199
|
-
var init_use = __esm(() => {
|
|
8200
|
-
init_db();
|
|
8201
|
-
init_ui();
|
|
8202
|
-
});
|
|
8203
|
-
|
|
8204
|
-
// src/commands/rm.ts
|
|
8205
|
-
var exports_rm = {};
|
|
8206
|
-
__export(exports_rm, {
|
|
8207
|
-
run: () => run8
|
|
8208
|
-
});
|
|
8209
|
-
async function run8() {
|
|
8210
|
-
const args = process.argv.slice(2).filter((a) => !a.startsWith("-"));
|
|
8211
|
-
const target = args[1];
|
|
8212
|
-
if (!target) {
|
|
8213
|
-
console.error(`
|
|
8214
|
-
Usage: wasper rm <number|url>
|
|
8215
|
-
`);
|
|
8216
|
-
process.exit(1);
|
|
8217
|
-
}
|
|
8218
|
-
const history = dbQueries.getSpecHistory();
|
|
8219
|
-
let id = null;
|
|
8220
|
-
let label = null;
|
|
8221
|
-
const num = parseInt(target, 10);
|
|
8222
|
-
if (!isNaN(num) && num >= 1 && num <= history.length) {
|
|
8223
|
-
const row = history[num - 1];
|
|
8224
|
-
if (row) {
|
|
8225
|
-
id = row.id;
|
|
8226
|
-
label = row.title ?? row.url;
|
|
8227
|
-
}
|
|
8228
|
-
} else {
|
|
8229
|
-
const match = history.find((r) => r.url === target || r.title?.toLowerCase().includes(target.toLowerCase()));
|
|
8230
|
-
if (match) {
|
|
8231
|
-
id = match.id;
|
|
8232
|
-
label = match.title ?? match.url;
|
|
8233
|
-
}
|
|
8234
|
-
}
|
|
8235
|
-
if (!id) {
|
|
8236
|
-
console.error(`
|
|
8237
|
-
${paint.red("\u2717")} Spec not found: ${target}
|
|
8238
|
-
`);
|
|
8239
|
-
process.exit(1);
|
|
8240
|
-
}
|
|
8241
|
-
dbQueries.deleteSpec(id);
|
|
8242
|
-
console.log(`
|
|
8243
|
-
${paint.green("\u2713")} Removed ${paint.dim(label ?? id)}
|
|
8244
|
-
`);
|
|
8245
|
-
}
|
|
8246
|
-
var init_rm = __esm(() => {
|
|
8247
|
-
init_db();
|
|
8248
|
-
init_ui();
|
|
8249
|
-
});
|
|
8250
|
-
|
|
8251
9208
|
// cli.ts
|
|
8252
9209
|
var rawArgs = process.argv.slice(2);
|
|
8253
9210
|
if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
|
|
@@ -8255,20 +9212,53 @@ if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
|
|
|
8255
9212
|
console.log(VERSION2);
|
|
8256
9213
|
process.exit(0);
|
|
8257
9214
|
}
|
|
8258
|
-
var
|
|
9215
|
+
var IS_DAEMON_CHILD = rawArgs.includes("--_daemon");
|
|
9216
|
+
var SUBCOMMANDS = new Set([
|
|
9217
|
+
"up",
|
|
9218
|
+
"down",
|
|
9219
|
+
"stop",
|
|
9220
|
+
"status",
|
|
9221
|
+
"ps",
|
|
9222
|
+
"reload",
|
|
9223
|
+
"logs",
|
|
9224
|
+
"ls",
|
|
9225
|
+
"list",
|
|
9226
|
+
"use",
|
|
9227
|
+
"rm",
|
|
9228
|
+
"remove",
|
|
9229
|
+
"mcp",
|
|
9230
|
+
"proxy",
|
|
9231
|
+
"ai",
|
|
9232
|
+
"readonly",
|
|
9233
|
+
"auth",
|
|
9234
|
+
"spec",
|
|
9235
|
+
"service",
|
|
9236
|
+
"update",
|
|
9237
|
+
"start",
|
|
9238
|
+
"help"
|
|
9239
|
+
]);
|
|
9240
|
+
var firstPositional = rawArgs.find((a) => !a.startsWith("-"));
|
|
9241
|
+
var subcommand = firstPositional && SUBCOMMANDS.has(firstPositional) ? firstPositional : IS_DAEMON_CHILD ? "start" : "up";
|
|
8259
9242
|
switch (subcommand) {
|
|
9243
|
+
case "up":
|
|
9244
|
+
await Promise.resolve().then(() => (init_up(), exports_up)).then((m) => m.run());
|
|
9245
|
+
break;
|
|
9246
|
+
case "ps":
|
|
9247
|
+
await Promise.resolve().then(() => (init_ps(), exports_ps)).then((m) => m.run());
|
|
9248
|
+
break;
|
|
9249
|
+
case "down":
|
|
8260
9250
|
case "stop":
|
|
8261
9251
|
await Promise.resolve().then(() => (init_stop(), exports_stop)).then((m) => m.run());
|
|
8262
9252
|
break;
|
|
8263
|
-
case "update":
|
|
8264
|
-
await Promise.resolve().then(() => (init_update(), exports_update)).then((m) => m.run());
|
|
8265
|
-
break;
|
|
8266
9253
|
case "status":
|
|
8267
9254
|
await Promise.resolve().then(() => (init_status(), exports_status)).then((m) => m.run());
|
|
8268
9255
|
break;
|
|
8269
9256
|
case "reload":
|
|
8270
9257
|
await Promise.resolve().then(() => (init_reload(), exports_reload)).then((m) => m.run());
|
|
8271
9258
|
break;
|
|
9259
|
+
case "logs":
|
|
9260
|
+
await Promise.resolve().then(() => (init_logs(), exports_logs)).then((m) => m.run());
|
|
9261
|
+
break;
|
|
8272
9262
|
case "ls":
|
|
8273
9263
|
case "list":
|
|
8274
9264
|
await Promise.resolve().then(() => (init_ls(), exports_ls)).then((m) => m.run());
|
|
@@ -8280,6 +9270,27 @@ switch (subcommand) {
|
|
|
8280
9270
|
case "remove":
|
|
8281
9271
|
await Promise.resolve().then(() => (init_rm(), exports_rm)).then((m) => m.run());
|
|
8282
9272
|
break;
|
|
9273
|
+
case "mcp":
|
|
9274
|
+
case "proxy":
|
|
9275
|
+
case "ai":
|
|
9276
|
+
case "readonly":
|
|
9277
|
+
await Promise.resolve().then(() => (init_feature(), exports_feature)).then((m) => m.run());
|
|
9278
|
+
break;
|
|
9279
|
+
case "auth":
|
|
9280
|
+
await Promise.resolve().then(() => (init_auth_cmd(), exports_auth_cmd)).then((m) => m.run());
|
|
9281
|
+
break;
|
|
9282
|
+
case "spec":
|
|
9283
|
+
await Promise.resolve().then(() => (init_spec_cmd(), exports_spec_cmd)).then((m) => m.run());
|
|
9284
|
+
break;
|
|
9285
|
+
case "service":
|
|
9286
|
+
await Promise.resolve().then(() => (init_service(), exports_service)).then((m) => m.run());
|
|
9287
|
+
break;
|
|
9288
|
+
case "update":
|
|
9289
|
+
await Promise.resolve().then(() => (init_update(), exports_update)).then((m) => m.run());
|
|
9290
|
+
break;
|
|
9291
|
+
case "help":
|
|
9292
|
+
await Promise.resolve().then(() => (init_help(), exports_help)).then((m) => m.run());
|
|
9293
|
+
break;
|
|
8283
9294
|
case "start":
|
|
8284
9295
|
default:
|
|
8285
9296
|
await Promise.resolve().then(() => (init_start(), exports_start)).then((m) => m.run());
|