apiblaze 0.20.11 → 0.20.14
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/dist/index.js +297 -69
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1024,7 +1024,7 @@ var import_commander = require("commander");
|
|
|
1024
1024
|
var import_chalk53 = __toESM(require("chalk"));
|
|
1025
1025
|
|
|
1026
1026
|
// package.json
|
|
1027
|
-
var version = "0.20.
|
|
1027
|
+
var version = "0.20.14";
|
|
1028
1028
|
|
|
1029
1029
|
// src/index.ts
|
|
1030
1030
|
init_types();
|
|
@@ -2251,7 +2251,7 @@ async function runCreate(opts = {}) {
|
|
|
2251
2251
|
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
2252
2252
|
const c = await classifyTargetInput2(opts.target, fail);
|
|
2253
2253
|
if (c.kind === "spec") {
|
|
2254
|
-
console.log(import_chalk11.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 creating FROM the spec.`));
|
|
2254
|
+
if (!opts.json) console.log(import_chalk11.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 creating FROM the spec.`));
|
|
2255
2255
|
opts.openapi = opts.target;
|
|
2256
2256
|
opts.target = void 0;
|
|
2257
2257
|
}
|
|
@@ -2261,7 +2261,7 @@ async function runCreate(opts = {}) {
|
|
|
2261
2261
|
await runAnonymousCreate(opts);
|
|
2262
2262
|
return;
|
|
2263
2263
|
}
|
|
2264
|
-
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
2264
|
+
const interactive = !!process.stdin.isTTY && !opts.json && !opts.auto;
|
|
2265
2265
|
if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
|
|
2266
2266
|
const oauthPlan = parseOauthFlag(opts.oauth);
|
|
2267
2267
|
const auth = (oauthPlan ? oauthPlan.auth : opts.apikey ? "api_key" : opts.auth ?? "api_key").toLowerCase();
|
|
@@ -2323,8 +2323,12 @@ async function runCreate(opts = {}) {
|
|
|
2323
2323
|
`);
|
|
2324
2324
|
break;
|
|
2325
2325
|
}
|
|
2326
|
+
} else if (opts.auto) {
|
|
2327
|
+
name = await pickGeneratedName(opts, teamId);
|
|
2328
|
+
if (!opts.json) console.log(`${import_chalk11.default.cyan("\u2192")} Your API will live at ${import_chalk11.default.bold(`https://${name}.abz.run`)}
|
|
2329
|
+
`);
|
|
2326
2330
|
} else {
|
|
2327
|
-
fail("--name is required in non-interactive mode.");
|
|
2331
|
+
fail("--name is required in non-interactive mode.", "Or pass --auto to have one generated for you.");
|
|
2328
2332
|
}
|
|
2329
2333
|
let openapiContent = null;
|
|
2330
2334
|
if (opts.openapi !== void 0) {
|
|
@@ -2354,7 +2358,7 @@ async function runCreate(opts = {}) {
|
|
|
2354
2358
|
} else {
|
|
2355
2359
|
fail("--target is required in non-interactive mode (a server base URL, a local OpenAPI file, or a remote OpenAPI URL).");
|
|
2356
2360
|
}
|
|
2357
|
-
if (interactive && !opts.yes) {
|
|
2361
|
+
if (interactive && !opts.yes && !opts.auto) {
|
|
2358
2362
|
const { default: inquirer3 } = await import("inquirer");
|
|
2359
2363
|
console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : oauthPlan ? ` \u2014 ${oauthPlan.summary}` : ""}`);
|
|
2360
2364
|
const { ok } = await inquirer3.prompt([{
|
|
@@ -2370,7 +2374,7 @@ async function runCreate(opts = {}) {
|
|
|
2370
2374
|
}
|
|
2371
2375
|
const chosenTenant = await chooseTenantForCreate(teamId, name, {
|
|
2372
2376
|
tenant: opts.tenant,
|
|
2373
|
-
yes: opts.yes,
|
|
2377
|
+
yes: opts.yes || opts.auto,
|
|
2374
2378
|
json: opts.json
|
|
2375
2379
|
});
|
|
2376
2380
|
const spinner = !opts.json ? (0, import_ora5.default)("Creating proxy (tenant, keys, dev portal)...").start() : null;
|
|
@@ -2432,8 +2436,29 @@ async function applyCreateToggles(name, opts) {
|
|
|
2432
2436
|
if (opts.identified) await runIdentifiedToggle2(name, "require", toggleOpts);
|
|
2433
2437
|
if (opts.iam) await runIamToggle2(name, "on", toggleOpts);
|
|
2434
2438
|
}
|
|
2439
|
+
function deriveNameFrom(source) {
|
|
2440
|
+
if (!source) return null;
|
|
2441
|
+
try {
|
|
2442
|
+
const host = new URL(source).hostname;
|
|
2443
|
+
let base2 = normalizeName(host.split(".")[0]);
|
|
2444
|
+
if (base2.length < 3) base2 = normalizeName(host);
|
|
2445
|
+
if (base2.length < 3) return null;
|
|
2446
|
+
return `${base2}${Math.random().toString(36).slice(2, 6)}`;
|
|
2447
|
+
} catch {
|
|
2448
|
+
return null;
|
|
2449
|
+
}
|
|
2450
|
+
}
|
|
2451
|
+
async function pickGeneratedName(opts, teamId) {
|
|
2452
|
+
const source = opts.target ?? opts.openapi;
|
|
2453
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
2454
|
+
const candidate = (attempt === 0 ? deriveNameFrom(source) : null) ?? randomProxyName();
|
|
2455
|
+
const check = await checkProxyName(candidate, teamId, opts.apiversion).catch(() => null);
|
|
2456
|
+
if (!check || check.canUseProjectName && check.canUseApiVersion) return candidate;
|
|
2457
|
+
}
|
|
2458
|
+
fail("Could not find a free generated proxy name. Pass --name explicitly.");
|
|
2459
|
+
}
|
|
2435
2460
|
async function runAnonymousCreate(opts) {
|
|
2436
|
-
const interactive = !!process.stdin.isTTY && !opts.json;
|
|
2461
|
+
const interactive = !!process.stdin.isTTY && !opts.json && !opts.auto;
|
|
2437
2462
|
if (!opts.json) {
|
|
2438
2463
|
console.log(import_chalk11.default.bold("\nCreate an API proxy"));
|
|
2439
2464
|
console.log(import_chalk11.default.dim("Not logged in \u2014 creating an anonymous proxy. You can claim it to your account within 30 days.\n"));
|
|
@@ -2495,6 +2520,10 @@ async function runAnonymousCreate(opts) {
|
|
|
2495
2520
|
fail("A source is required: pass --target, or target/openapi/github in --config.");
|
|
2496
2521
|
}
|
|
2497
2522
|
}
|
|
2523
|
+
if (name === void 0 && opts.auto) {
|
|
2524
|
+
const nameSource = target || (typeof opts.openapi === "string" ? opts.openapi : void 0);
|
|
2525
|
+
name = deriveNameFrom(nameSource) ?? randomProxyName();
|
|
2526
|
+
}
|
|
2498
2527
|
if (target) {
|
|
2499
2528
|
body.target = target;
|
|
2500
2529
|
body.target_url = target;
|
|
@@ -8360,14 +8389,17 @@ var os4 = __toESM(require("os"));
|
|
|
8360
8389
|
var path6 = __toESM(require("path"));
|
|
8361
8390
|
var import_child_process2 = require("child_process");
|
|
8362
8391
|
var import_chalk46 = __toESM(require("chalk"));
|
|
8363
|
-
var
|
|
8392
|
+
var useShell = process.platform === "win32";
|
|
8393
|
+
var winQuote = (a) => /^[A-Za-z0-9_\-.:/\\=]+$/.test(a) ? a : `"${a.replace(/"/g, '""')}"`;
|
|
8394
|
+
var run = (cmd, args, opts = {}) => (0, import_child_process2.spawnSync)(cmd, useShell ? args.map(winQuote) : args, {
|
|
8364
8395
|
encoding: "utf-8",
|
|
8365
|
-
stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
|
|
8366
|
-
timeout: opts.inherit ? void 0 : 15e3,
|
|
8367
|
-
shell:
|
|
8368
|
-
// .cmd shims on Windows
|
|
8396
|
+
stdio: opts.interactive ? ["inherit", "inherit", "inherit"] : opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
|
|
8397
|
+
timeout: opts.inherit || opts.interactive ? void 0 : 15e3,
|
|
8398
|
+
shell: useShell
|
|
8369
8399
|
});
|
|
8400
|
+
var detected = null;
|
|
8370
8401
|
function detectExternalClis() {
|
|
8402
|
+
if (detected) return detected;
|
|
8371
8403
|
const found = [];
|
|
8372
8404
|
for (const [kind, label3] of [["claude", "Claude CLI"], ["codex", "Codex CLI"]]) {
|
|
8373
8405
|
try {
|
|
@@ -8376,15 +8408,50 @@ function detectExternalClis() {
|
|
|
8376
8408
|
} catch {
|
|
8377
8409
|
}
|
|
8378
8410
|
}
|
|
8411
|
+
detected = found;
|
|
8379
8412
|
return found;
|
|
8380
8413
|
}
|
|
8381
|
-
|
|
8414
|
+
var OURS = /\.mcp\.(abz\.run|tryabz\.run|apiblaze\.com)\b/;
|
|
8415
|
+
var mask = (k) => k.length > 14 ? `${k.slice(0, 10)}\u2026${k.slice(-4)}` : "\u2022\u2022\u2022";
|
|
8416
|
+
async function verifyMcpEndpoint(spec2) {
|
|
8417
|
+
try {
|
|
8418
|
+
const headers = { "Content-Type": "application/json" };
|
|
8419
|
+
if (spec2.apiKey) headers["X-API-Key"] = spec2.apiKey;
|
|
8420
|
+
if (spec2.endUserId) headers["X-End-User-Id"] = spec2.endUserId;
|
|
8421
|
+
const res = await fetch(spec2.url, {
|
|
8422
|
+
method: "POST",
|
|
8423
|
+
headers,
|
|
8424
|
+
body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
|
|
8425
|
+
signal: AbortSignal.timeout(1e4)
|
|
8426
|
+
});
|
|
8427
|
+
if (!res.ok) return { ok: false, status: res.status, tools: 0 };
|
|
8428
|
+
const body = await res.json().catch(() => null);
|
|
8429
|
+
if (!spec2.apiKey) return { ok: true, status: res.status, tools: body?.result?.tools?.length ?? 0 };
|
|
8430
|
+
if (body?.error) return { ok: false, status: res.status, tools: 0 };
|
|
8431
|
+
return { ok: true, status: res.status, tools: body?.result?.tools?.length ?? 0 };
|
|
8432
|
+
} catch {
|
|
8433
|
+
return { ok: false, status: 0, tools: 0 };
|
|
8434
|
+
}
|
|
8435
|
+
}
|
|
8436
|
+
function claudeInstallArgs(spec2, masked = false) {
|
|
8382
8437
|
const args = ["mcp", "add", "--transport", "http", spec2.name, spec2.url];
|
|
8383
|
-
if (spec2.apiKey) args.push("--header", `X-API-Key: ${spec2.apiKey}`);
|
|
8438
|
+
if (spec2.apiKey) args.push("--header", `X-API-Key: ${masked ? mask(spec2.apiKey) : spec2.apiKey}`);
|
|
8439
|
+
if (spec2.endUserId) args.push("--header", `X-End-User-Id: ${spec2.endUserId}`);
|
|
8384
8440
|
return args;
|
|
8385
8441
|
}
|
|
8386
8442
|
function installIntoClaude(spec2) {
|
|
8387
|
-
run("claude", ["mcp", "
|
|
8443
|
+
const existing = run("claude", ["mcp", "get", spec2.name]);
|
|
8444
|
+
if (existing.status === 0) {
|
|
8445
|
+
const desc = existing.stdout || "";
|
|
8446
|
+
if (!OURS.test(desc)) {
|
|
8447
|
+
return {
|
|
8448
|
+
ok: false,
|
|
8449
|
+
conflict: true,
|
|
8450
|
+
error: `Claude CLI already has an MCP server named "${spec2.name}" that is not an APIblaze proxy \u2014 not touching it. Remove or rename it (claude mcp remove ${spec2.name}) and re-run.`
|
|
8451
|
+
};
|
|
8452
|
+
}
|
|
8453
|
+
run("claude", ["mcp", "remove", spec2.name]);
|
|
8454
|
+
}
|
|
8388
8455
|
const r = run("claude", claudeInstallArgs(spec2));
|
|
8389
8456
|
if (r.status === 0) return { ok: true };
|
|
8390
8457
|
return { ok: false, error: (r.stderr || r.stdout || `exit ${r.status}`).trim().slice(0, 400) };
|
|
@@ -8400,23 +8467,69 @@ function codexConfigPath() {
|
|
|
8400
8467
|
var tomlStr = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
8401
8468
|
function codexServerBlock(spec2) {
|
|
8402
8469
|
const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
|
|
8403
|
-
|
|
8470
|
+
const headers = [];
|
|
8471
|
+
if (spec2.apiKey) headers.push(`"X-API-Key" = ${tomlStr(spec2.apiKey)}`);
|
|
8472
|
+
if (spec2.endUserId) headers.push(`"X-End-User-Id" = ${tomlStr(spec2.endUserId)}`);
|
|
8473
|
+
if (headers.length) lines.push(`http_headers = { ${headers.join(", ")} }`);
|
|
8404
8474
|
return lines.join("\n") + "\n";
|
|
8405
8475
|
}
|
|
8476
|
+
function codexSectionRanges(lines, name) {
|
|
8477
|
+
const esc = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8478
|
+
const header = new RegExp(`^\\[mcp_servers\\.(?:"${esc}"|${esc})\\]\\s*(#.*)?$`);
|
|
8479
|
+
const anyHeader = /^\s*\[[^\]]+\]\s*(#.*)?$/;
|
|
8480
|
+
const ranges = [];
|
|
8481
|
+
for (let i = 0; i < lines.length; i++) {
|
|
8482
|
+
if (!header.test(lines[i])) continue;
|
|
8483
|
+
let end = lines.length;
|
|
8484
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
8485
|
+
if (anyHeader.test(lines[j])) {
|
|
8486
|
+
end = j;
|
|
8487
|
+
break;
|
|
8488
|
+
}
|
|
8489
|
+
}
|
|
8490
|
+
ranges.push({ start: i, end });
|
|
8491
|
+
i = end - 1;
|
|
8492
|
+
}
|
|
8493
|
+
return ranges;
|
|
8494
|
+
}
|
|
8406
8495
|
function installIntoCodex(spec2) {
|
|
8407
8496
|
const file = codexConfigPath();
|
|
8408
8497
|
try {
|
|
8409
8498
|
fs10.mkdirSync(path6.dirname(file), { recursive: true });
|
|
8410
8499
|
let text = "";
|
|
8500
|
+
let existed = true;
|
|
8411
8501
|
try {
|
|
8412
8502
|
text = fs10.readFileSync(file, "utf-8");
|
|
8413
8503
|
} catch {
|
|
8504
|
+
existed = false;
|
|
8505
|
+
}
|
|
8506
|
+
const lines = text.split("\n");
|
|
8507
|
+
const ranges = codexSectionRanges(lines, spec2.name);
|
|
8508
|
+
for (const r of ranges) {
|
|
8509
|
+
const body = lines.slice(r.start, r.end).join("\n");
|
|
8510
|
+
const url = body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1];
|
|
8511
|
+
if (url && !OURS.test(url) || !url && /^\s*command\s*=/m.test(body)) {
|
|
8512
|
+
return {
|
|
8513
|
+
ok: false,
|
|
8514
|
+
conflict: true,
|
|
8515
|
+
path: file,
|
|
8516
|
+
error: `~/.codex/config.toml already has an MCP server named "${spec2.name}" that is not an APIblaze proxy \u2014 not touching it. Rename or remove that block and re-run.`
|
|
8517
|
+
};
|
|
8518
|
+
}
|
|
8519
|
+
}
|
|
8520
|
+
for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
|
|
8521
|
+
let cleaned = lines.join("\n");
|
|
8522
|
+
if (cleaned.length && !cleaned.endsWith("\n")) cleaned += "\n";
|
|
8523
|
+
if (cleaned.length && !cleaned.endsWith("\n\n")) cleaned += "\n";
|
|
8524
|
+
const tmp = `${file}.tmp-${process.pid}`;
|
|
8525
|
+
fs10.writeFileSync(tmp, cleaned + codexServerBlock(spec2), { encoding: "utf-8", mode: 384 });
|
|
8526
|
+
fs10.renameSync(tmp, file);
|
|
8527
|
+
if (!existed) {
|
|
8528
|
+
try {
|
|
8529
|
+
fs10.chmodSync(file, 384);
|
|
8530
|
+
} catch {
|
|
8531
|
+
}
|
|
8414
8532
|
}
|
|
8415
|
-
const esc = spec2.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8416
|
-
const section = new RegExp(`(^|\\n)\\[mcp_servers\\.(?:"${esc}"|${esc})\\][^\\[]*`, "g");
|
|
8417
|
-
const cleaned = text.replace(section, "$1");
|
|
8418
|
-
const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
|
|
8419
|
-
fs10.writeFileSync(file, cleaned + sep + codexServerBlock(spec2), "utf-8");
|
|
8420
8533
|
return { ok: true, path: file };
|
|
8421
8534
|
} catch (err) {
|
|
8422
8535
|
return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
|
|
@@ -8431,44 +8544,64 @@ var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
|
8431
8544
|
function renderCommand(argv) {
|
|
8432
8545
|
return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
|
|
8433
8546
|
}
|
|
8434
|
-
function
|
|
8547
|
+
function refreshInstall(cli, spec2) {
|
|
8548
|
+
const r = cli === "claude" ? installIntoClaude(spec2) : installIntoCodex(spec2);
|
|
8549
|
+
return r.ok;
|
|
8550
|
+
}
|
|
8551
|
+
async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
|
|
8435
8552
|
if (cli.kind === "claude") {
|
|
8436
8553
|
log(`
|
|
8437
|
-
${import_chalk46.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2)])}`);
|
|
8554
|
+
${import_chalk46.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2, true)])}`);
|
|
8438
8555
|
const r = installIntoClaude(spec2);
|
|
8439
8556
|
if (!r.ok) {
|
|
8440
|
-
log(import_chalk46.default.red(` Install failed: ${r.error}`));
|
|
8557
|
+
log(import_chalk46.default.red(` ${r.conflict ? "" : "Install failed: "}${r.error}`));
|
|
8441
8558
|
return false;
|
|
8442
8559
|
}
|
|
8443
8560
|
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
|
|
8561
|
+
if (spec2.apiKey) log(import_chalk46.default.dim(" The API key is stored in Claude's local MCP config for this directory."));
|
|
8444
8562
|
} else {
|
|
8445
8563
|
const r = installIntoCodex(spec2);
|
|
8446
8564
|
if (!r.ok) {
|
|
8447
|
-
log(import_chalk46.default.red(` Could not write ${r.path}: ${r.error}`));
|
|
8565
|
+
log(import_chalk46.default.red(` ${r.conflict ? "" : `Could not write ${r.path}: `}${r.error}`));
|
|
8448
8566
|
return false;
|
|
8449
8567
|
}
|
|
8450
8568
|
log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to ${r.path}.`);
|
|
8451
8569
|
}
|
|
8452
8570
|
if (!spec2.apiKey) {
|
|
8453
|
-
|
|
8571
|
+
const loginArgv = cli.kind === "claude" ? ["claude", "mcp", "login", spec2.name] : ["codex", "mcp", "login", spec2.name];
|
|
8454
8572
|
log(`
|
|
8455
|
-
${import_chalk46.default.
|
|
8456
|
-
|
|
8573
|
+
${import_chalk46.default.dim("This proxy authenticates by login \u2014 signing you in:")}`);
|
|
8574
|
+
log(` ${import_chalk46.default.dim("$")} ${renderCommand(loginArgv)}`);
|
|
8575
|
+
const r = run(loginArgv[0], loginArgv.slice(1), { interactive: true });
|
|
8576
|
+
if (r.status !== 0) {
|
|
8577
|
+
log(import_chalk46.default.yellow(` Sign-in didn't complete (exit ${r.status ?? "?"}). Run \`${renderCommand(loginArgv)}\` yourself, then chat away.`));
|
|
8578
|
+
return true;
|
|
8579
|
+
}
|
|
8580
|
+
log(` ${import_chalk46.default.green("\u2714")} Signed in.`);
|
|
8457
8581
|
}
|
|
8458
8582
|
const oneShot = cli.kind === "claude" ? claudeOneShot : codexOneShot;
|
|
8459
8583
|
const abilities = `What are the tool abilities of the MCP server "${spec2.name}"? List them briefly.`;
|
|
8460
8584
|
log(`
|
|
8461
|
-
${import_chalk46.default.dim("Checking what the
|
|
8585
|
+
${import_chalk46.default.dim("Checking what the API can do\u2026")}`);
|
|
8462
8586
|
log(` ${import_chalk46.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", abilities])}
|
|
8463
8587
|
`);
|
|
8464
|
-
oneShot(spec2, abilities);
|
|
8588
|
+
const check = oneShot(spec2, abilities);
|
|
8589
|
+
if (check.status !== 0) {
|
|
8590
|
+
log(import_chalk46.default.yellow(`
|
|
8591
|
+
${cli.label} exited with ${check.status ?? "no status"} \u2014 the MCP is installed, but the demo call failed.`));
|
|
8592
|
+
log(import_chalk46.default.yellow(` Open ${cli.label} and try it there${cli.kind === "claude" ? " (use /mcp to inspect the connection)" : ""}.`));
|
|
8593
|
+
return true;
|
|
8594
|
+
}
|
|
8595
|
+
const question = await getQuestion();
|
|
8465
8596
|
if (question) {
|
|
8466
8597
|
log(`
|
|
8467
8598
|
${import_chalk46.default.dim("Your question, through " + cli.label + ":")}`);
|
|
8468
8599
|
const shown = cli.kind === "claude" ? ["claude", "-p", question, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", question];
|
|
8469
8600
|
log(` ${import_chalk46.default.dim("$")} ${renderCommand(shown)}
|
|
8470
8601
|
`);
|
|
8471
|
-
oneShot(spec2, question);
|
|
8602
|
+
const ans = oneShot(spec2, question);
|
|
8603
|
+
if (ans.status !== 0) log(import_chalk46.default.yellow(`
|
|
8604
|
+
${cli.label} exited with ${ans.status ?? "no status"} on that one \u2014 the MCP stays installed.`));
|
|
8472
8605
|
}
|
|
8473
8606
|
log(`
|
|
8474
8607
|
${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
|
|
@@ -9694,6 +9827,14 @@ async function runRepl(p, initialMessages) {
|
|
|
9694
9827
|
p.anon = false;
|
|
9695
9828
|
claimApichat(p, loadCredentials()?.apiblazeUserId);
|
|
9696
9829
|
console.log(import_chalk47.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
|
|
9830
|
+
const entry = loadApichats().find((a) => apichatKey(a) === apichatKey(p));
|
|
9831
|
+
for (const [cli, state] of Object.entries(entry?.cliOffer ?? {})) {
|
|
9832
|
+
if (state === "installed" && (cli === "claude" || cli === "codex")) {
|
|
9833
|
+
if (refreshInstall(cli, buildInstallSpec(p))) {
|
|
9834
|
+
console.log(import_chalk47.default.dim(` ${cli === "claude" ? "Claude" : "Codex"} CLI's MCP entry updated to the new host.`));
|
|
9835
|
+
}
|
|
9836
|
+
}
|
|
9837
|
+
}
|
|
9697
9838
|
}
|
|
9698
9839
|
} catch (err) {
|
|
9699
9840
|
console.log(import_chalk47.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
@@ -9714,61 +9855,137 @@ async function runRepl(p, initialMessages) {
|
|
|
9714
9855
|
}
|
|
9715
9856
|
console.log(import_chalk47.default.dim("\nBye."));
|
|
9716
9857
|
}
|
|
9717
|
-
function
|
|
9858
|
+
function offerKey(p) {
|
|
9859
|
+
return apichatKey(p);
|
|
9860
|
+
}
|
|
9861
|
+
function rememberCliOffer(p, cli, state) {
|
|
9718
9862
|
const list = loadApichats();
|
|
9719
|
-
const i = list.findIndex((a) => a
|
|
9863
|
+
const i = list.findIndex((a) => apichatKey(a) === offerKey(p));
|
|
9720
9864
|
if (i < 0) return;
|
|
9721
9865
|
list[i].cliOffer = { ...list[i].cliOffer ?? {}, [cli]: state };
|
|
9722
9866
|
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9723
9867
|
writeApichats(list);
|
|
9724
9868
|
}
|
|
9725
|
-
|
|
9726
|
-
|
|
9869
|
+
function buildInstallSpec(p) {
|
|
9870
|
+
return {
|
|
9727
9871
|
name: p.projectId,
|
|
9728
9872
|
url: `https://${p.mcpHost}/${p.version}/${p.environment}`,
|
|
9729
9873
|
// consumerAuth means the door is a login — install bare, the CLI signs in.
|
|
9730
9874
|
apiKey: p.consumerAuth ? void 0 : p.dpKey,
|
|
9875
|
+
endUserId: p.endUserId,
|
|
9731
9876
|
projectLabel: p.projectId
|
|
9732
9877
|
};
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9878
|
+
}
|
|
9879
|
+
async function verifyAndHealMcpHost(p) {
|
|
9880
|
+
const ok = (await verifyMcpEndpoint(buildInstallSpec(p))).ok;
|
|
9881
|
+
if (ok) return true;
|
|
9882
|
+
const flipped = p.mcpHost.includes(".mcp.tryabz.run") ? p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run") : p.mcpHost.replace(".mcp.abz.run", ".mcp.tryabz.run");
|
|
9883
|
+
if (flipped === p.mcpHost) return false;
|
|
9884
|
+
const prev = p.mcpHost;
|
|
9885
|
+
p.mcpHost = flipped;
|
|
9886
|
+
if ((await verifyMcpEndpoint(buildInstallSpec(p))).ok) {
|
|
9887
|
+
const list = loadApichats();
|
|
9888
|
+
const i = list.findIndex((a) => apichatKey(a) === offerKey(p));
|
|
9889
|
+
if (i >= 0) {
|
|
9890
|
+
list[i].mcpHost = flipped;
|
|
9891
|
+
list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
9892
|
+
writeApichats(list);
|
|
9893
|
+
}
|
|
9894
|
+
return true;
|
|
9895
|
+
}
|
|
9896
|
+
p.mcpHost = prev;
|
|
9897
|
+
return false;
|
|
9898
|
+
}
|
|
9899
|
+
async function maybeInstallExternalCli(p, opts) {
|
|
9900
|
+
const forced = (opts.installMcp ?? "").toLowerCase();
|
|
9901
|
+
if (forced && forced !== "claude" && forced !== "codex") {
|
|
9902
|
+
fail4(`--install-mcp takes "claude" or "codex", not "${opts.installMcp}".`);
|
|
9903
|
+
}
|
|
9904
|
+
if (!forced && !process.stdin.isTTY) return false;
|
|
9905
|
+
const offer = loadApichats().find((a) => apichatKey(a) === offerKey(p))?.cliOffer ?? {};
|
|
9906
|
+
if (forced) {
|
|
9907
|
+
const cli = detectExternalClis().find((c) => c.kind === forced);
|
|
9738
9908
|
if (!cli) fail4(
|
|
9739
|
-
`${
|
|
9740
|
-
|
|
9909
|
+
`${forced === "claude" ? "Claude" : "Codex"} CLI not found on this machine.`,
|
|
9910
|
+
forced === "claude" ? "Install it: npm install -g @anthropic-ai/claude-code" : "Install it: npm install -g @openai/codex"
|
|
9741
9911
|
);
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9912
|
+
if (!await ensureInstallableDoor(p, opts)) return false;
|
|
9913
|
+
if (!await verifyAndHealMcpHost(p)) {
|
|
9914
|
+
fail4(`The MCP endpoint https://${p.mcpHost}/${p.version}/${p.environment} is not answering \u2014 not installing it into ${cli.label}.`);
|
|
9915
|
+
}
|
|
9916
|
+
const ran2 = await installAndDemo(cli, buildInstallSpec(p), () => resolveQuestion(opts));
|
|
9917
|
+
if (ran2) rememberCliOffer(p, cli.kind, "installed");
|
|
9745
9918
|
return ran2;
|
|
9746
9919
|
}
|
|
9747
|
-
|
|
9748
|
-
|
|
9920
|
+
const clis = detectExternalClis();
|
|
9921
|
+
if (clis.length === 0) return false;
|
|
9922
|
+
for (const c of clis) {
|
|
9923
|
+
if (offer[c.kind] === "installed" && await verifyAndHealMcpHost(p)) {
|
|
9924
|
+
refreshInstall(c.kind, buildInstallSpec(p));
|
|
9925
|
+
}
|
|
9926
|
+
}
|
|
9749
9927
|
const fresh = clis.filter((c) => !offer[c.kind]);
|
|
9750
9928
|
if (fresh.length === 0) return false;
|
|
9751
9929
|
const { default: inquirer3 } = await import("inquirer");
|
|
9752
9930
|
const names = fresh.map((c) => c.label).join(" and ");
|
|
9931
|
+
const targetName = fresh.length > 1 ? "one of them" : fresh[0].label;
|
|
9753
9932
|
const { pick: pick2 } = await inquirer3.prompt([{
|
|
9754
9933
|
type: "list",
|
|
9755
9934
|
name: "pick",
|
|
9756
|
-
message: `I see ${names} ${fresh.length > 1 ? "are" : "is"} installed on this computer.
|
|
9935
|
+
message: `I see ${names} ${fresh.length > 1 ? "are" : "is"} installed on this computer. Do you want to add the MCP for this proxy to ${targetName} so you can chat with your API from there directly, or chat here directly?`,
|
|
9757
9936
|
choices: [
|
|
9758
|
-
...fresh.map((c) => ({ name:
|
|
9759
|
-
{ name: "
|
|
9937
|
+
...fresh.map((c) => ({ name: c.label, value: c })),
|
|
9938
|
+
{ name: "Chat here directly", value: "here" }
|
|
9760
9939
|
]
|
|
9761
9940
|
}]);
|
|
9762
|
-
if (pick2 === "
|
|
9763
|
-
for (const c of fresh) rememberCliOffer(p
|
|
9941
|
+
if (pick2 === "here") {
|
|
9942
|
+
for (const c of fresh) rememberCliOffer(p, c.kind, "declined");
|
|
9943
|
+
if (p.consumerAuth && p.teamId && p.tenant) {
|
|
9944
|
+
try {
|
|
9945
|
+
await ensureConsumerLogin(p.teamId, p.tenant, p.version);
|
|
9946
|
+
} catch (err) {
|
|
9947
|
+
console.log(import_chalk47.default.yellow(` Sign-in didn't complete (${err instanceof Error ? err.message : String(err)}) \u2014 the first chat turn will retry it.`));
|
|
9948
|
+
}
|
|
9949
|
+
}
|
|
9950
|
+
return false;
|
|
9951
|
+
}
|
|
9952
|
+
if (!await ensureInstallableDoor(p, opts)) return false;
|
|
9953
|
+
if (!await verifyAndHealMcpHost(p)) {
|
|
9954
|
+
console.log(import_chalk47.default.red(` The MCP endpoint https://${p.mcpHost}/${p.version}/${p.environment} is not answering \u2014 not installing it into ${pick2.label}. Chat here instead.`));
|
|
9764
9955
|
return false;
|
|
9765
9956
|
}
|
|
9766
|
-
const
|
|
9767
|
-
|
|
9768
|
-
|
|
9957
|
+
const ran = await installAndDemo(pick2, buildInstallSpec(p), () => resolveQuestion(opts));
|
|
9958
|
+
if (ran) {
|
|
9959
|
+
rememberCliOffer(p, pick2.kind, "installed");
|
|
9960
|
+
if (p.anon) {
|
|
9961
|
+
console.log(import_chalk47.default.dim(` Anonymous workspace \u2014 run \`apiblaze apichat ${p.projectId}\` and /claim to keep it (and this MCP) beyond 30 days.`));
|
|
9962
|
+
}
|
|
9963
|
+
}
|
|
9769
9964
|
return ran;
|
|
9770
9965
|
}
|
|
9771
|
-
async function
|
|
9966
|
+
async function ensureInstallableDoor(p, opts) {
|
|
9967
|
+
if (p.dpKey || p.consumerAuth) return true;
|
|
9968
|
+
if (!process.stdin.isTTY) {
|
|
9969
|
+
fail4(
|
|
9970
|
+
`Can't tell how "${p.projectId}" authenticates (no key on file).`,
|
|
9971
|
+
"Pass --apikey <key> for a key-door proxy, or open it interactively once first."
|
|
9972
|
+
);
|
|
9973
|
+
}
|
|
9974
|
+
const { default: inquirer3 } = await import("inquirer");
|
|
9975
|
+
const { key } = await inquirer3.prompt([{
|
|
9976
|
+
type: "password",
|
|
9977
|
+
name: "key",
|
|
9978
|
+
mask: "*",
|
|
9979
|
+
message: `API key for ${p.projectId} (leave empty if it uses a login):`
|
|
9980
|
+
}]);
|
|
9981
|
+
if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
|
|
9982
|
+
else p.consumerAuth = true;
|
|
9983
|
+
void opts;
|
|
9984
|
+
return true;
|
|
9985
|
+
}
|
|
9986
|
+
async function resolveQuestion(opts) {
|
|
9987
|
+
if (opts.prompt) return opts.prompt;
|
|
9988
|
+
if (!process.stdin.isTTY) return void 0;
|
|
9772
9989
|
const { default: inquirer3 } = await import("inquirer");
|
|
9773
9990
|
const { q } = await inquirer3.prompt([{
|
|
9774
9991
|
type: "input",
|
|
@@ -9778,8 +9995,18 @@ async function askApiQuestion() {
|
|
|
9778
9995
|
const t = (q ?? "").trim();
|
|
9779
9996
|
return t || void 0;
|
|
9780
9997
|
}
|
|
9998
|
+
async function startChat(p, messages, opts) {
|
|
9999
|
+
if (opts.prompt) {
|
|
10000
|
+
await replTurn(p, messages, opts.prompt);
|
|
10001
|
+
saveTranscript(p, messages);
|
|
10002
|
+
if (!process.stdin.isTTY) return;
|
|
10003
|
+
} else if (!process.stdin.isTTY) {
|
|
10004
|
+
fail4('Interactive chat needs a terminal. Pass -p "<question>" for a one-shot answer.');
|
|
10005
|
+
}
|
|
10006
|
+
await runRepl(p, messages);
|
|
10007
|
+
}
|
|
9781
10008
|
async function runApichat(opts) {
|
|
9782
|
-
setVerbose(opts.verbose
|
|
10009
|
+
setVerbose(opts.verbose === true);
|
|
9783
10010
|
console.log(import_chalk47.default.bold("\napichat \u2014 turn any API into a chat\n"));
|
|
9784
10011
|
if (opts.target && !opts.openapispec) {
|
|
9785
10012
|
const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
|
|
@@ -9793,7 +10020,7 @@ async function runApichat(opts) {
|
|
|
9793
10020
|
if (opts.project) {
|
|
9794
10021
|
const opened = await openDirectProject(opts.project, opts);
|
|
9795
10022
|
if (await maybeInstallExternalCli(opened.p, opts)) return;
|
|
9796
|
-
await
|
|
10023
|
+
await startChat(opened.p, opened.messages, opts);
|
|
9797
10024
|
return;
|
|
9798
10025
|
}
|
|
9799
10026
|
if (!opts.openapispec && !opts.target) {
|
|
@@ -9803,7 +10030,7 @@ async function runApichat(opts) {
|
|
|
9803
10030
|
const resumed = await noArgsMenu(opts);
|
|
9804
10031
|
if (resumed) {
|
|
9805
10032
|
if (await maybeInstallExternalCli(resumed.p, opts)) return;
|
|
9806
|
-
await
|
|
10033
|
+
await startChat(resumed.p, resumed.messages, opts);
|
|
9807
10034
|
return;
|
|
9808
10035
|
}
|
|
9809
10036
|
}
|
|
@@ -9857,8 +10084,8 @@ async function runApichat(opts) {
|
|
|
9857
10084
|
if (p.anon) {
|
|
9858
10085
|
console.log(import_chalk47.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
|
|
9859
10086
|
}
|
|
9860
|
-
if (await maybeInstallExternalCli(p, opts)) return;
|
|
9861
|
-
await
|
|
10087
|
+
if (mcpUrl && await maybeInstallExternalCli(p, opts)) return;
|
|
10088
|
+
await startChat(p, [], opts);
|
|
9862
10089
|
}
|
|
9863
10090
|
|
|
9864
10091
|
// src/commands/consumer.ts
|
|
@@ -10243,14 +10470,14 @@ async function runAnonymousInit(root, router, opts) {
|
|
|
10243
10470
|
}
|
|
10244
10471
|
async function runSidecar(opts) {
|
|
10245
10472
|
const root = path8.resolve(opts.dir ?? process.cwd());
|
|
10246
|
-
const
|
|
10247
|
-
if (!
|
|
10473
|
+
const detected2 = detectNextProject(root);
|
|
10474
|
+
if (!detected2.found) {
|
|
10248
10475
|
console.log(import_chalk49.default.yellow(`No Next.js project detected in ${root}.`));
|
|
10249
10476
|
console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
|
|
10250
10477
|
return;
|
|
10251
10478
|
}
|
|
10252
10479
|
if (!loadCredentials() && !readEnvKey(root)) {
|
|
10253
|
-
await runAnonymousInit(root,
|
|
10480
|
+
await runAnonymousInit(root, detected2.router, opts);
|
|
10254
10481
|
return;
|
|
10255
10482
|
}
|
|
10256
10483
|
if (!loadCredentials()) {
|
|
@@ -10292,7 +10519,7 @@ async function runSidecar(opts) {
|
|
|
10292
10519
|
installSidecarPackage(root);
|
|
10293
10520
|
let inspectorPath = null;
|
|
10294
10521
|
if (!opts.noInspector) {
|
|
10295
|
-
inspectorPath = generateInspector(root,
|
|
10522
|
+
inspectorPath = generateInspector(root, detected2.router);
|
|
10296
10523
|
if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
|
|
10297
10524
|
}
|
|
10298
10525
|
console.log("");
|
|
@@ -11066,7 +11293,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
|
|
|
11066
11293
|
process.exit(1);
|
|
11067
11294
|
}
|
|
11068
11295
|
});
|
|
11069
|
-
program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apikey", "Protect with API keys and print the bootstrap keys (the default, made explicit). Consumers send X-API-Key.").option("--oauth [config]", `Login door. Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0).`).option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
|
|
11296
|
+
program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apikey", "Protect with API keys and print the bootstrap keys (the default, made explicit). Consumers send X-API-Key.").option("--oauth [config]", `Login door. Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0).`).option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--auto", "Zero prompts, start to finish: generates the proxy name when --name is omitted (from the target host, salted), takes every confirmation as yes, auto-picks the tenant. Implies --yes.").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
|
|
11070
11297
|
try {
|
|
11071
11298
|
await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
|
|
11072
11299
|
} catch (err) {
|
|
@@ -11079,7 +11306,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
|
|
|
11079
11306
|
program.command("rule").description("Author an object-level access rule in plain English, in one shot (billed per turn)").argument("<rule>", 'The rule in plain English, e.g. "users see only their own rows"').argument("<project>", "Project name or id").option("--enforce", "Turn enforcement on immediately (default: shadow-publish only)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((rule, project, opts) => runRule(rule, project, opts)));
|
|
11080
11307
|
agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
|
|
11081
11308
|
agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
|
|
11082
|
-
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--
|
|
11309
|
+
program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
|
|
11083
11310
|
var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
|
|
11084
11311
|
llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
|
|
11085
11312
|
llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
|
|
@@ -11244,14 +11471,15 @@ ${groupedCommandHelp()}
|
|
|
11244
11471
|
Tips:
|
|
11245
11472
|
\u2022 \`apiblaze config <project>\` browses EVERY setting & feature (works logged-out to explore).
|
|
11246
11473
|
\u2022 Add --verbose to any command to see the equivalent API calls.
|
|
11474
|
+
\u2022 Add --auto to \`create\` for a scriptable run: no prompts, no TTY needed, proxy name generated.
|
|
11247
11475
|
\u2022 Full API reference: https://api.apiblaze.com/openapi.json
|
|
11248
11476
|
\u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
|
|
11249
11477
|
|
|
11250
11478
|
Examples:
|
|
11251
|
-
$ npx apiblaze apichat --
|
|
11479
|
+
$ npx apiblaze apichat --target https://pokeapi.co/openapi.yaml # chat with any API
|
|
11252
11480
|
$ npx apiblaze agent # just chat
|
|
11253
11481
|
$ npx apiblaze create --target https://api.example.com # one-line API
|
|
11254
|
-
$ npx apiblaze create --
|
|
11482
|
+
$ npx apiblaze create --target https://pokeapi.co/openapi.yaml --auto # zero prompts, name generated
|
|
11255
11483
|
$ npx apiblaze search gmail # find a recipe (no login needed)
|
|
11256
11484
|
$ npx apiblaze install @julien/gmail # someone's whole working setup, your credentials
|
|
11257
11485
|
$ npx apiblaze publish mygmail # share yours back, as @yourgithubhandle/mygmail
|