wasper-cli 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -83
- package/dist/cli.js +1845 -834
- package/dist/index.js +90 -31
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2786,6 +2786,11 @@ function parseSpecText(text, url, name) {
|
|
|
2786
2786
|
const info = doc.info ?? {};
|
|
2787
2787
|
const servers = doc.servers ?? [];
|
|
2788
2788
|
let baseUrl = servers[0]?.url ?? "";
|
|
2789
|
+
if (!baseUrl && doc.swagger && doc.host) {
|
|
2790
|
+
const scheme = doc.schemes?.[0] ?? "https";
|
|
2791
|
+
const basePath = typeof doc.basePath === "string" ? doc.basePath.replace(/\/$/, "") : "";
|
|
2792
|
+
baseUrl = `${scheme}://${doc.host}${basePath}`;
|
|
2793
|
+
}
|
|
2789
2794
|
if (!baseUrl && url) {
|
|
2790
2795
|
try {
|
|
2791
2796
|
baseUrl = new URL(url).origin;
|
|
@@ -3607,7 +3612,7 @@ var package_default;
|
|
|
3607
3612
|
var init_package = __esm(() => {
|
|
3608
3613
|
package_default = {
|
|
3609
3614
|
name: "wasper-cli",
|
|
3610
|
-
version: "0.3.
|
|
3615
|
+
version: "0.3.3",
|
|
3611
3616
|
description: "Host an MCP server + API proxy from any OpenAPI spec. Like Drizzle Studio, but for APIs.",
|
|
3612
3617
|
type: "module",
|
|
3613
3618
|
homepage: "https://wasper.site",
|
|
@@ -5316,6 +5321,7 @@ async function handleSpecUpload(req) {
|
|
|
5316
5321
|
try {
|
|
5317
5322
|
const state = loadSpecFromText(content, filename);
|
|
5318
5323
|
const suggestedVars = extractSuggestedVars(content, state.spec.baseUrl);
|
|
5324
|
+
logBus.broadcastServerEvent({ kind: "spec_changed" });
|
|
5319
5325
|
return json({
|
|
5320
5326
|
ok: true,
|
|
5321
5327
|
spec: { title: state.spec.title, version: state.spec.version, baseUrl: state.spec.baseUrl },
|
|
@@ -5338,6 +5344,7 @@ async function handleSpecReloadUrl(req) {
|
|
|
5338
5344
|
try {
|
|
5339
5345
|
const state = await loadSpec(body.url);
|
|
5340
5346
|
const suggestedVars = extractSuggestedVars(state.spec.raw, state.spec.baseUrl);
|
|
5347
|
+
logBus.broadcastServerEvent({ kind: "spec_changed" });
|
|
5341
5348
|
return json({
|
|
5342
5349
|
ok: true,
|
|
5343
5350
|
spec: { title: state.spec.title, version: state.spec.version, baseUrl: state.spec.baseUrl },
|
|
@@ -5349,7 +5356,8 @@ async function handleSpecReloadUrl(req) {
|
|
|
5349
5356
|
}
|
|
5350
5357
|
}
|
|
5351
5358
|
function handleGetLogs(searchParams) {
|
|
5352
|
-
const
|
|
5359
|
+
const raw = parseInt(searchParams.get("limit") ?? "500", 10);
|
|
5360
|
+
const limit = Math.min(Number.isFinite(raw) ? raw : 500, 2000);
|
|
5353
5361
|
return json(dbQueries.getRecentLogs(limit));
|
|
5354
5362
|
}
|
|
5355
5363
|
function handleClearLogs() {
|
|
@@ -5371,6 +5379,8 @@ async function handleSetAuth(req) {
|
|
|
5371
5379
|
return json({ type: body.type, config: body.config });
|
|
5372
5380
|
}
|
|
5373
5381
|
async function handleTestAuth() {
|
|
5382
|
+
if (!hasState())
|
|
5383
|
+
return badRequest("No spec loaded");
|
|
5374
5384
|
const { spec } = getState();
|
|
5375
5385
|
const authRow = dbQueries.getAuthConfig();
|
|
5376
5386
|
const authConfig = authRow ? JSON.parse(authRow.config) : { type: "none" };
|
|
@@ -5384,6 +5394,8 @@ async function handleTestAuth() {
|
|
|
5384
5394
|
}
|
|
5385
5395
|
}
|
|
5386
5396
|
function handleGetEndpoints() {
|
|
5397
|
+
if (!hasState())
|
|
5398
|
+
return json([]);
|
|
5387
5399
|
return json(getState().operations);
|
|
5388
5400
|
}
|
|
5389
5401
|
function handleGetSettings() {
|
|
@@ -5405,6 +5417,8 @@ async function handleSetSettings(req) {
|
|
|
5405
5417
|
return json(body);
|
|
5406
5418
|
}
|
|
5407
5419
|
async function executeTool(name, args, cache = new Map) {
|
|
5420
|
+
if (!hasState())
|
|
5421
|
+
return { text: "No spec loaded.", isError: true };
|
|
5408
5422
|
const { operations, spec } = getState();
|
|
5409
5423
|
if (name === "search_endpoints") {
|
|
5410
5424
|
const cacheKey2 = `search:${String(args.query ?? "").toLowerCase()}`;
|
|
@@ -6064,10 +6078,11 @@ async function handleExplorerRequest(req) {
|
|
|
6064
6078
|
};
|
|
6065
6079
|
if (timeoutMs > 0)
|
|
6066
6080
|
fetchOpts.signal = AbortSignal.timeout(timeoutMs);
|
|
6081
|
+
const parsedUrl = new URL(authedUrl);
|
|
6067
6082
|
let dnsMs = 0;
|
|
6068
6083
|
let resolvedAddr = "";
|
|
6069
6084
|
try {
|
|
6070
|
-
const u =
|
|
6085
|
+
const u = parsedUrl;
|
|
6071
6086
|
const h = u.hostname;
|
|
6072
6087
|
const defaultPort = u.protocol === "https:" ? 443 : 80;
|
|
6073
6088
|
const port = u.port ? Number(u.port) : defaultPort;
|
|
@@ -6086,7 +6101,7 @@ async function handleExplorerRequest(req) {
|
|
|
6086
6101
|
const waitMs = Math.round(performance.now() - fetchStart);
|
|
6087
6102
|
const resHeaders = Object.fromEntries(res.headers.entries());
|
|
6088
6103
|
const ct = res.headers.get("content-type") ?? "";
|
|
6089
|
-
const u =
|
|
6104
|
+
const u = parsedUrl;
|
|
6090
6105
|
const networkInfo = {
|
|
6091
6106
|
scheme: u.protocol.replace(":", ""),
|
|
6092
6107
|
host: u.host,
|
|
@@ -6581,25 +6596,69 @@ var init_routes = __esm(() => {
|
|
|
6581
6596
|
// src/daemon.ts
|
|
6582
6597
|
import { join as join2 } from "path";
|
|
6583
6598
|
import { homedir as homedir2 } from "os";
|
|
6584
|
-
import { mkdir, readFile, writeFile, unlink } from "fs/promises";
|
|
6599
|
+
import { mkdir, readFile, readdir, writeFile, unlink } from "fs/promises";
|
|
6585
6600
|
async function ensureDir() {
|
|
6586
|
-
await mkdir(
|
|
6601
|
+
await mkdir(WASPER_DIR, { recursive: true });
|
|
6602
|
+
}
|
|
6603
|
+
function stateFile(port) {
|
|
6604
|
+
return join2(WASPER_DIR, `server-${port}.json`);
|
|
6605
|
+
}
|
|
6606
|
+
function logFile(port) {
|
|
6607
|
+
return join2(WASPER_DIR, `server-${port}.log`);
|
|
6587
6608
|
}
|
|
6588
6609
|
async function writeDaemonState(s) {
|
|
6589
6610
|
await ensureDir();
|
|
6590
|
-
await writeFile(
|
|
6611
|
+
await writeFile(stateFile(s.port), JSON.stringify(s, null, 2), "utf-8");
|
|
6591
6612
|
}
|
|
6592
|
-
async function
|
|
6613
|
+
async function readAllDaemonStates() {
|
|
6593
6614
|
try {
|
|
6594
|
-
const
|
|
6595
|
-
|
|
6615
|
+
const files = await readdir(WASPER_DIR);
|
|
6616
|
+
const states = [];
|
|
6617
|
+
for (const f of files) {
|
|
6618
|
+
if (!f.match(/^server(-\d+)?\.json$/))
|
|
6619
|
+
continue;
|
|
6620
|
+
const filePath = join2(WASPER_DIR, f);
|
|
6621
|
+
try {
|
|
6622
|
+
const raw = await readFile(filePath, "utf-8");
|
|
6623
|
+
const state = JSON.parse(raw);
|
|
6624
|
+
if (isProcessAlive(state.pid)) {
|
|
6625
|
+
states.push(state);
|
|
6626
|
+
} else {
|
|
6627
|
+
await unlink(filePath).catch(() => {});
|
|
6628
|
+
}
|
|
6629
|
+
} catch {}
|
|
6630
|
+
}
|
|
6631
|
+
return states.sort((a, b) => a.port - b.port);
|
|
6596
6632
|
} catch {
|
|
6597
|
-
return
|
|
6633
|
+
return [];
|
|
6634
|
+
}
|
|
6635
|
+
}
|
|
6636
|
+
async function readDaemonState(port) {
|
|
6637
|
+
if (port !== undefined) {
|
|
6638
|
+
try {
|
|
6639
|
+
const raw = await readFile(stateFile(port), "utf-8");
|
|
6640
|
+
const state = JSON.parse(raw);
|
|
6641
|
+
return isProcessAlive(state.pid) ? state : null;
|
|
6642
|
+
} catch {
|
|
6643
|
+
return null;
|
|
6644
|
+
}
|
|
6598
6645
|
}
|
|
6646
|
+
const all = await readAllDaemonStates();
|
|
6647
|
+
if (all.length === 0)
|
|
6648
|
+
return null;
|
|
6649
|
+
if (all.length === 1)
|
|
6650
|
+
return all[0];
|
|
6651
|
+
return all.find((s) => s.port === DEFAULT_PORT) ?? all[0];
|
|
6599
6652
|
}
|
|
6600
|
-
async function clearDaemonState() {
|
|
6653
|
+
async function clearDaemonState(port) {
|
|
6601
6654
|
try {
|
|
6602
|
-
await unlink(
|
|
6655
|
+
await unlink(stateFile(port));
|
|
6656
|
+
} catch {}
|
|
6657
|
+
try {
|
|
6658
|
+
const raw = await readFile(join2(WASPER_DIR, "server.json"), "utf-8");
|
|
6659
|
+
const state = JSON.parse(raw);
|
|
6660
|
+
if (state.port === port)
|
|
6661
|
+
await unlink(join2(WASPER_DIR, "server.json")).catch(() => {});
|
|
6603
6662
|
} catch {}
|
|
6604
6663
|
}
|
|
6605
6664
|
function isProcessAlive(pid) {
|
|
@@ -6633,22 +6692,19 @@ async function spawnDaemon(specUrl, port, opts = {}) {
|
|
|
6633
6692
|
args.push("--readonly");
|
|
6634
6693
|
}
|
|
6635
6694
|
args.push("--_daemon");
|
|
6636
|
-
const logDir = DIR;
|
|
6637
6695
|
await ensureDir();
|
|
6638
|
-
const logPath = join2(logDir, "server.log");
|
|
6639
6696
|
const child = Bun.spawn([process.execPath, Bun.main, ...args], {
|
|
6640
6697
|
detached: true,
|
|
6641
6698
|
cwd: process.cwd(),
|
|
6642
6699
|
env: { ...process.env },
|
|
6643
|
-
stdio: ["ignore", Bun.file(
|
|
6700
|
+
stdio: ["ignore", Bun.file(logFile(port)), Bun.file(logFile(port))]
|
|
6644
6701
|
});
|
|
6645
6702
|
child.unref();
|
|
6646
6703
|
return child.pid;
|
|
6647
6704
|
}
|
|
6648
|
-
var
|
|
6705
|
+
var WASPER_DIR, DEFAULT_PORT = 3388;
|
|
6649
6706
|
var init_daemon = __esm(() => {
|
|
6650
|
-
|
|
6651
|
-
STATE_FILE = join2(DIR, "server.json");
|
|
6707
|
+
WASPER_DIR = join2(homedir2(), ".wasper");
|
|
6652
6708
|
});
|
|
6653
6709
|
|
|
6654
6710
|
// src/ui.ts
|
|
@@ -7508,7 +7564,7 @@ async function run2(overrideOpts) {
|
|
|
7508
7564
|
${paint.dim("shutting down")}
|
|
7509
7565
|
|
|
7510
7566
|
`);
|
|
7511
|
-
clearDaemonState().finally(() => {
|
|
7567
|
+
clearDaemonState(PORT).finally(() => {
|
|
7512
7568
|
db.close();
|
|
7513
7569
|
server.stop();
|
|
7514
7570
|
process.exit(0);
|
|
@@ -7836,16 +7892,19 @@ function printInteractiveHelp() {
|
|
|
7836
7892
|
}
|
|
7837
7893
|
function printHelp() {
|
|
7838
7894
|
console.log(`
|
|
7839
|
-
Usage: wasper
|
|
7895
|
+
Usage: wasper start [options]
|
|
7896
|
+
|
|
7897
|
+
Starts wasper in the foreground with an interactive REPL.
|
|
7898
|
+
For background (daemon) mode \u2014 the default \u2014 use: wasper up
|
|
7840
7899
|
|
|
7841
|
-
wasper [--url <spec
|
|
7842
|
-
wasper start --
|
|
7843
|
-
wasper
|
|
7844
|
-
wasper status Show
|
|
7900
|
+
wasper up [--url <spec>] Start daemon in background (default)
|
|
7901
|
+
wasper start [--url <spec>] Start in foreground with REPL
|
|
7902
|
+
wasper down Stop the daemon
|
|
7903
|
+
wasper status Show daemon status
|
|
7904
|
+
wasper logs [-f] Tail server logs
|
|
7905
|
+
wasper service install Install as system service (auto-start)
|
|
7845
7906
|
wasper reload Hot-reload the spec
|
|
7846
7907
|
wasper ls List saved specs (history)
|
|
7847
|
-
wasper use <number|url> Start with a saved spec
|
|
7848
|
-
wasper rm <number|url> Remove a spec from history
|
|
7849
7908
|
|
|
7850
7909
|
Options:
|
|
7851
7910
|
--url, -u OpenAPI spec URL or local path
|
|
@@ -7860,17 +7919,17 @@ Options:
|
|
|
7860
7919
|
--no-proxy Start with the HTTP proxy disabled
|
|
7861
7920
|
--no-ai Start with the AI chat endpoint disabled
|
|
7862
7921
|
--readonly Block all non-GET upstream requests (agent guardrail)
|
|
7863
|
-
--background, -b Start detached in background
|
|
7922
|
+
--background, -b Start detached in background (same as wasper up)
|
|
7864
7923
|
--daemon, -d Same as --background
|
|
7865
7924
|
-h, --help Show this help
|
|
7866
7925
|
|
|
7867
|
-
Interactive
|
|
7926
|
+
Interactive REPL slash commands (foreground mode):
|
|
7868
7927
|
/mcp on|off \xB7 /proxy on|off \xB7 /ai on|off \xB7 /readonly on|off
|
|
7869
7928
|
/auth use <role> \xB7 /token new \xB7 /spec <url> \xB7 /tail \xB7 /help
|
|
7870
7929
|
|
|
7871
7930
|
Self-hosting:
|
|
7872
|
-
wasper
|
|
7873
|
-
|
|
7931
|
+
wasper up --url <spec> --origin https://api.example.com --token <secret>
|
|
7932
|
+
wasper service install --url <spec> --port 3388
|
|
7874
7933
|
`);
|
|
7875
7934
|
}
|
|
7876
7935
|
function buildScalarHtml(title, req) {
|