wasper-cli 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -83
- package/dist/cli.js +1829 -833
- package/dist/index.js +72 -28
- 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.2",
|
|
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,365 +1098,921 @@ 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
|
-
);
|
|
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
|
+
});
|
|
637
1152
|
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
1153
|
+
// src/commands/ls.ts
|
|
1154
|
+
var exports_ls = {};
|
|
1155
|
+
__export(exports_ls, {
|
|
1156
|
+
run: () => run7
|
|
1157
|
+
});
|
|
1158
|
+
function ago(ts) {
|
|
1159
|
+
const secs = Math.floor(Date.now() / 1000) - ts;
|
|
1160
|
+
if (secs < 60)
|
|
1161
|
+
return "just now";
|
|
1162
|
+
if (secs < 3600)
|
|
1163
|
+
return `${Math.floor(secs / 60)}m ago`;
|
|
1164
|
+
if (secs < 86400)
|
|
1165
|
+
return `${Math.floor(secs / 3600)}h ago`;
|
|
1166
|
+
if (secs < 86400 * 30)
|
|
1167
|
+
return `${Math.floor(secs / 86400)}d ago`;
|
|
1168
|
+
return new Date(ts * 1000).toLocaleDateString();
|
|
1169
|
+
}
|
|
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
|
+
});
|
|
656
1206
|
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
);
|
|
666
|
-
|
|
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
|
+
});
|
|
667
1265
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
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
|
+
});
|
|
677
1313
|
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
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
|
+
});
|
|
683
1378
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
|
691
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
|
+
});
|
|
692
1818
|
|
|
693
|
-
// src/
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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;
|
|
702
1843
|
}
|
|
1844
|
+
}
|
|
1845
|
+
async function checkForUpdate() {
|
|
1846
|
+
if (process.env.WASPER_NO_UPDATE_CHECK)
|
|
1847
|
+
return null;
|
|
1848
|
+
let state = null;
|
|
703
1849
|
try {
|
|
704
|
-
|
|
705
|
-
if (!Bun.main.includes("$bunfs") && (existsSync(join3(legacy, "wasper.db")) || existsSync(join3(legacy, "openapi-agent.db"))))
|
|
706
|
-
return legacy;
|
|
1850
|
+
state = JSON.parse(await readFile2(CHECK_FILE, "utf-8"));
|
|
707
1851
|
} catch {}
|
|
708
|
-
|
|
709
|
-
if (
|
|
710
|
-
|
|
711
|
-
|
|
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;
|
|
712
1863
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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;
|
|
724
1893
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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);
|
|
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;
|
|
883
1904
|
}
|
|
884
1905
|
};
|
|
885
|
-
})
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
run: () => run5
|
|
891
|
-
});
|
|
892
|
-
function ago(ts) {
|
|
893
|
-
const secs = Math.floor(Date.now() / 1000) - ts;
|
|
894
|
-
if (secs < 60)
|
|
895
|
-
return "just now";
|
|
896
|
-
if (secs < 3600)
|
|
897
|
-
return `${Math.floor(secs / 60)}m ago`;
|
|
898
|
-
if (secs < 86400)
|
|
899
|
-
return `${Math.floor(secs / 3600)}h ago`;
|
|
900
|
-
if (secs < 86400 * 30)
|
|
901
|
-
return `${Math.floor(secs / 86400)}d ago`;
|
|
902
|
-
return new Date(ts * 1000).toLocaleDateString();
|
|
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.");
|
|
903
1911
|
}
|
|
904
|
-
async function
|
|
905
|
-
const
|
|
906
|
-
if (
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
process.exit(0);
|
|
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
|
|
@@ -4100,8 +5178,8 @@ async function applyAuth(url, headers, authConfig) {
|
|
|
4100
5178
|
function cacheKey(c) {
|
|
4101
5179
|
return `${c.tokenUrl ?? ""}|${c.clientId ?? ""}|${c.scope ?? ""}`;
|
|
4102
5180
|
}
|
|
4103
|
-
function getCachedToken(
|
|
4104
|
-
const key = cacheKey(
|
|
5181
|
+
function getCachedToken(config2) {
|
|
5182
|
+
const key = cacheKey(config2);
|
|
4105
5183
|
const mem = memCaches.get(key);
|
|
4106
5184
|
if (mem && Date.now() < mem.expires_at - 30000)
|
|
4107
5185
|
return mem.access_token;
|
|
@@ -4120,21 +5198,21 @@ function getCachedToken(config) {
|
|
|
4120
5198
|
}
|
|
4121
5199
|
return null;
|
|
4122
5200
|
}
|
|
4123
|
-
async function getOrRefreshOAuthToken(
|
|
4124
|
-
const cached = getCachedToken(
|
|
5201
|
+
async function getOrRefreshOAuthToken(config2) {
|
|
5202
|
+
const cached = getCachedToken(config2);
|
|
4125
5203
|
if (cached)
|
|
4126
5204
|
return cached;
|
|
4127
|
-
if (!
|
|
5205
|
+
if (!config2.tokenUrl || !config2.clientId)
|
|
4128
5206
|
return null;
|
|
4129
5207
|
const params = new URLSearchParams({
|
|
4130
5208
|
grant_type: "client_credentials",
|
|
4131
|
-
client_id:
|
|
4132
|
-
client_secret:
|
|
5209
|
+
client_id: config2.clientId,
|
|
5210
|
+
client_secret: config2.clientSecret ?? ""
|
|
4133
5211
|
});
|
|
4134
|
-
if (
|
|
4135
|
-
params.set("scope",
|
|
5212
|
+
if (config2.scope)
|
|
5213
|
+
params.set("scope", config2.scope);
|
|
4136
5214
|
try {
|
|
4137
|
-
const res = await fetch(
|
|
5215
|
+
const res = await fetch(config2.tokenUrl, {
|
|
4138
5216
|
method: "POST",
|
|
4139
5217
|
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
4140
5218
|
body: params.toString()
|
|
@@ -4145,7 +5223,7 @@ async function getOrRefreshOAuthToken(config) {
|
|
|
4145
5223
|
if (!data.access_token)
|
|
4146
5224
|
return null;
|
|
4147
5225
|
const cache = { access_token: data.access_token, expires_at: Date.now() + (data.expires_in ?? 3600) * 1000 };
|
|
4148
|
-
memCaches.set(cacheKey(
|
|
5226
|
+
memCaches.set(cacheKey(config2), cache);
|
|
4149
5227
|
dbQueries.updateTokenCache(cache);
|
|
4150
5228
|
return cache.access_token;
|
|
4151
5229
|
} catch {
|
|
@@ -4169,53 +5247,6 @@ var init_engine = __esm(() => {
|
|
|
4169
5247
|
memCaches = new Map;
|
|
4170
5248
|
});
|
|
4171
5249
|
|
|
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
5250
|
// src/mcp/server.ts
|
|
4220
5251
|
function summarizeAuth(type, config2) {
|
|
4221
5252
|
switch (type) {
|
|
@@ -7489,15 +8520,15 @@ var init_repl = __esm(() => {
|
|
|
7489
8520
|
// src/commands/start.ts
|
|
7490
8521
|
var exports_start = {};
|
|
7491
8522
|
__export(exports_start, {
|
|
7492
|
-
run: () =>
|
|
8523
|
+
run: () => run16
|
|
7493
8524
|
});
|
|
7494
|
-
import { parseArgs } from "util";
|
|
8525
|
+
import { parseArgs as parseArgs2 } from "util";
|
|
7495
8526
|
import { createInterface } from "readline";
|
|
7496
|
-
import { homedir as
|
|
7497
|
-
import { join as
|
|
8527
|
+
import { homedir as homedir5 } from "os";
|
|
8528
|
+
import { join as join5, dirname } from "path";
|
|
7498
8529
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
7499
|
-
async function
|
|
7500
|
-
const { values } =
|
|
8530
|
+
async function run16(overrideOpts) {
|
|
8531
|
+
const { values } = parseArgs2({
|
|
7501
8532
|
args: process.argv.slice(2).filter((a) => a !== "start"),
|
|
7502
8533
|
options: {
|
|
7503
8534
|
url: { type: "string" },
|
|
@@ -7517,7 +8548,7 @@ async function run6(overrideOpts) {
|
|
|
7517
8548
|
strict: false
|
|
7518
8549
|
});
|
|
7519
8550
|
if (values.help) {
|
|
7520
|
-
|
|
8551
|
+
printHelp2();
|
|
7521
8552
|
process.exit(0);
|
|
7522
8553
|
}
|
|
7523
8554
|
let specUrl = overrideOpts?.url ?? (values.url ? String(values.url) : null) ?? process.env.WASPER_SPEC_URL ?? null;
|
|
@@ -7682,7 +8713,7 @@ async function run6(overrideOpts) {
|
|
|
7682
8713
|
${paint.dim("shutting down")}
|
|
7683
8714
|
|
|
7684
8715
|
`);
|
|
7685
|
-
clearDaemonState().finally(() => {
|
|
8716
|
+
clearDaemonState(PORT).finally(() => {
|
|
7686
8717
|
db.close();
|
|
7687
8718
|
server.stop();
|
|
7688
8719
|
process.exit(0);
|
|
@@ -8008,18 +9039,21 @@ function printInteractiveHelp() {
|
|
|
8008
9039
|
${k("/status")} ${k("/reload")} ${k("/help")} ${k("/quit")}
|
|
8009
9040
|
`);
|
|
8010
9041
|
}
|
|
8011
|
-
function
|
|
9042
|
+
function printHelp2() {
|
|
8012
9043
|
console.log(`
|
|
8013
|
-
Usage: wasper
|
|
9044
|
+
Usage: wasper start [options]
|
|
9045
|
+
|
|
9046
|
+
Starts wasper in the foreground with an interactive REPL.
|
|
9047
|
+
For background (daemon) mode \u2014 the default \u2014 use: wasper up
|
|
8014
9048
|
|
|
8015
|
-
wasper [--url <spec
|
|
8016
|
-
wasper start --
|
|
8017
|
-
wasper
|
|
8018
|
-
wasper status Show
|
|
9049
|
+
wasper up [--url <spec>] Start daemon in background (default)
|
|
9050
|
+
wasper start [--url <spec>] Start in foreground with REPL
|
|
9051
|
+
wasper down Stop the daemon
|
|
9052
|
+
wasper status Show daemon status
|
|
9053
|
+
wasper logs [-f] Tail server logs
|
|
9054
|
+
wasper service install Install as system service (auto-start)
|
|
8019
9055
|
wasper reload Hot-reload the spec
|
|
8020
9056
|
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
9057
|
|
|
8024
9058
|
Options:
|
|
8025
9059
|
--url, -u OpenAPI spec URL or local path
|
|
@@ -8034,17 +9068,17 @@ Options:
|
|
|
8034
9068
|
--no-proxy Start with the HTTP proxy disabled
|
|
8035
9069
|
--no-ai Start with the AI chat endpoint disabled
|
|
8036
9070
|
--readonly Block all non-GET upstream requests (agent guardrail)
|
|
8037
|
-
--background, -b Start detached in background
|
|
9071
|
+
--background, -b Start detached in background (same as wasper up)
|
|
8038
9072
|
--daemon, -d Same as --background
|
|
8039
9073
|
-h, --help Show this help
|
|
8040
9074
|
|
|
8041
|
-
Interactive
|
|
9075
|
+
Interactive REPL slash commands (foreground mode):
|
|
8042
9076
|
/mcp on|off \xB7 /proxy on|off \xB7 /ai on|off \xB7 /readonly on|off
|
|
8043
9077
|
/auth use <role> \xB7 /token new \xB7 /spec <url> \xB7 /tail \xB7 /help
|
|
8044
9078
|
|
|
8045
9079
|
Self-hosting:
|
|
8046
|
-
wasper
|
|
8047
|
-
|
|
9080
|
+
wasper up --url <spec> --origin https://api.example.com --token <secret>
|
|
9081
|
+
wasper service install --url <spec> --port 3388
|
|
8048
9082
|
`);
|
|
8049
9083
|
}
|
|
8050
9084
|
function buildScalarHtml(title, req) {
|
|
@@ -8079,10 +9113,10 @@ function promptYN(question, defaultYes = true) {
|
|
|
8079
9113
|
function claudeDesktopConfigPath() {
|
|
8080
9114
|
const p = process.platform;
|
|
8081
9115
|
if (p === "darwin")
|
|
8082
|
-
return
|
|
9116
|
+
return join5(homedir5(), "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
8083
9117
|
if (p === "win32")
|
|
8084
|
-
return
|
|
8085
|
-
return
|
|
9118
|
+
return join5(process.env.APPDATA ?? join5(homedir5(), "AppData", "Roaming"), "Claude", "claude_desktop_config.json");
|
|
9119
|
+
return join5(homedir5(), ".config", "Claude", "claude_desktop_config.json");
|
|
8086
9120
|
}
|
|
8087
9121
|
async function configureClaudeDesktop(mcpUrl) {
|
|
8088
9122
|
const cfgPath = claudeDesktopConfigPath();
|
|
@@ -8156,98 +9190,6 @@ var init_start = __esm(() => {
|
|
|
8156
9190
|
SPIN_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
8157
9191
|
});
|
|
8158
9192
|
|
|
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
9193
|
// cli.ts
|
|
8252
9194
|
var rawArgs = process.argv.slice(2);
|
|
8253
9195
|
if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
|
|
@@ -8255,20 +9197,53 @@ if (rawArgs.includes("--version") || rawArgs.includes("-v")) {
|
|
|
8255
9197
|
console.log(VERSION2);
|
|
8256
9198
|
process.exit(0);
|
|
8257
9199
|
}
|
|
8258
|
-
var
|
|
9200
|
+
var IS_DAEMON_CHILD = rawArgs.includes("--_daemon");
|
|
9201
|
+
var SUBCOMMANDS = new Set([
|
|
9202
|
+
"up",
|
|
9203
|
+
"down",
|
|
9204
|
+
"stop",
|
|
9205
|
+
"status",
|
|
9206
|
+
"ps",
|
|
9207
|
+
"reload",
|
|
9208
|
+
"logs",
|
|
9209
|
+
"ls",
|
|
9210
|
+
"list",
|
|
9211
|
+
"use",
|
|
9212
|
+
"rm",
|
|
9213
|
+
"remove",
|
|
9214
|
+
"mcp",
|
|
9215
|
+
"proxy",
|
|
9216
|
+
"ai",
|
|
9217
|
+
"readonly",
|
|
9218
|
+
"auth",
|
|
9219
|
+
"spec",
|
|
9220
|
+
"service",
|
|
9221
|
+
"update",
|
|
9222
|
+
"start",
|
|
9223
|
+
"help"
|
|
9224
|
+
]);
|
|
9225
|
+
var firstPositional = rawArgs.find((a) => !a.startsWith("-"));
|
|
9226
|
+
var subcommand = firstPositional && SUBCOMMANDS.has(firstPositional) ? firstPositional : IS_DAEMON_CHILD ? "start" : "up";
|
|
8259
9227
|
switch (subcommand) {
|
|
9228
|
+
case "up":
|
|
9229
|
+
await Promise.resolve().then(() => (init_up(), exports_up)).then((m) => m.run());
|
|
9230
|
+
break;
|
|
9231
|
+
case "ps":
|
|
9232
|
+
await Promise.resolve().then(() => (init_ps(), exports_ps)).then((m) => m.run());
|
|
9233
|
+
break;
|
|
9234
|
+
case "down":
|
|
8260
9235
|
case "stop":
|
|
8261
9236
|
await Promise.resolve().then(() => (init_stop(), exports_stop)).then((m) => m.run());
|
|
8262
9237
|
break;
|
|
8263
|
-
case "update":
|
|
8264
|
-
await Promise.resolve().then(() => (init_update(), exports_update)).then((m) => m.run());
|
|
8265
|
-
break;
|
|
8266
9238
|
case "status":
|
|
8267
9239
|
await Promise.resolve().then(() => (init_status(), exports_status)).then((m) => m.run());
|
|
8268
9240
|
break;
|
|
8269
9241
|
case "reload":
|
|
8270
9242
|
await Promise.resolve().then(() => (init_reload(), exports_reload)).then((m) => m.run());
|
|
8271
9243
|
break;
|
|
9244
|
+
case "logs":
|
|
9245
|
+
await Promise.resolve().then(() => (init_logs(), exports_logs)).then((m) => m.run());
|
|
9246
|
+
break;
|
|
8272
9247
|
case "ls":
|
|
8273
9248
|
case "list":
|
|
8274
9249
|
await Promise.resolve().then(() => (init_ls(), exports_ls)).then((m) => m.run());
|
|
@@ -8280,6 +9255,27 @@ switch (subcommand) {
|
|
|
8280
9255
|
case "remove":
|
|
8281
9256
|
await Promise.resolve().then(() => (init_rm(), exports_rm)).then((m) => m.run());
|
|
8282
9257
|
break;
|
|
9258
|
+
case "mcp":
|
|
9259
|
+
case "proxy":
|
|
9260
|
+
case "ai":
|
|
9261
|
+
case "readonly":
|
|
9262
|
+
await Promise.resolve().then(() => (init_feature(), exports_feature)).then((m) => m.run());
|
|
9263
|
+
break;
|
|
9264
|
+
case "auth":
|
|
9265
|
+
await Promise.resolve().then(() => (init_auth_cmd(), exports_auth_cmd)).then((m) => m.run());
|
|
9266
|
+
break;
|
|
9267
|
+
case "spec":
|
|
9268
|
+
await Promise.resolve().then(() => (init_spec_cmd(), exports_spec_cmd)).then((m) => m.run());
|
|
9269
|
+
break;
|
|
9270
|
+
case "service":
|
|
9271
|
+
await Promise.resolve().then(() => (init_service(), exports_service)).then((m) => m.run());
|
|
9272
|
+
break;
|
|
9273
|
+
case "update":
|
|
9274
|
+
await Promise.resolve().then(() => (init_update(), exports_update)).then((m) => m.run());
|
|
9275
|
+
break;
|
|
9276
|
+
case "help":
|
|
9277
|
+
await Promise.resolve().then(() => (init_help(), exports_help)).then((m) => m.run());
|
|
9278
|
+
break;
|
|
8283
9279
|
case "start":
|
|
8284
9280
|
default:
|
|
8285
9281
|
await Promise.resolve().then(() => (init_start(), exports_start)).then((m) => m.run());
|