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.
Files changed (2) hide show
  1. package/dist/index.js +297 -69
  2. 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.11";
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 run = (cmd, args, opts = {}) => (0, import_child_process2.spawnSync)(cmd, args, {
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: process.platform === "win32"
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
- function claudeInstallArgs(spec2) {
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", "remove", spec2.name]);
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
- if (spec2.apiKey) lines.push(`http_headers = { "X-API-Key" = ${tomlStr(spec2.apiKey)} }`);
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 installAndDemo(cli, spec2, question, log = console.log) {
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
- log(import_chalk46.default.dim(` This proxy authenticates by login: the first call from ${cli.label} will open its sign-in.`));
8571
+ const loginArgv = cli.kind === "claude" ? ["claude", "mcp", "login", spec2.name] : ["codex", "mcp", "login", spec2.name];
8454
8572
  log(`
8455
- ${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
8456
- return true;
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 MCP exposes\u2026")}`);
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 rememberCliOffer(projectId, cli, state) {
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.projectId === projectId);
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
- async function maybeInstallExternalCli(p, opts) {
9726
- const spec2 = {
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
- const clis = detectExternalClis();
9734
- if (opts.installMcp) {
9735
- const want = opts.installMcp.toLowerCase();
9736
- if (want !== "claude" && want !== "codex") fail4(`--install-mcp takes "claude" or "codex", not "${opts.installMcp}".`);
9737
- const cli = clis.find((c) => c.kind === want);
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
- `${want === "claude" ? "Claude" : "Codex"} CLI not found on this machine.`,
9740
- want === "claude" ? "Install it: npm install -g @anthropic-ai/claude-code" : "Install it: npm install -g @openai/codex"
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
- const question2 = opts.prompt ?? (process.stdin.isTTY ? await askApiQuestion() : void 0);
9743
- const ran2 = installAndDemo(cli, spec2, question2);
9744
- if (ran2) rememberCliOffer(p.projectId, cli.kind, "installed");
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
- if (!process.stdin.isTTY || clis.length === 0) return false;
9748
- const offer = loadApichats().find((a) => a.projectId === p.projectId)?.cliOffer ?? {};
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. Add the MCP for this proxy so you can chat with your API from there directly?`,
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: `Yes \u2014 add it to ${c.label}`, value: c })),
9759
- { name: "No \u2014 chat here instead", value: "no" }
9937
+ ...fresh.map((c) => ({ name: c.label, value: c })),
9938
+ { name: "Chat here directly", value: "here" }
9760
9939
  ]
9761
9940
  }]);
9762
- if (pick2 === "no") {
9763
- for (const c of fresh) rememberCliOffer(p.projectId, c.kind, "declined");
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 question = opts.prompt ?? await askApiQuestion();
9767
- const ran = installAndDemo(pick2, spec2, question);
9768
- if (ran) rememberCliOffer(p.projectId, pick2.kind, "installed");
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 askApiQuestion() {
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 !== false);
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 runRepl(opened.p, opened.messages);
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 runRepl(resumed.p, resumed.messages);
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 runRepl(p);
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 detected = detectNextProject(root);
10247
- if (!detected.found) {
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, detected.router, opts);
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, detected.router);
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("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").option("-p, --prompt <question>", "One-shot question piped through the external agent CLI after the MCP install (used with --install-mcp or the install offer)").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 })));
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 --openapi https://pokeapi.co/openapi.yaml # chat with any API
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 --openapi https://pokeapi.co/openapi.yaml # build it from a spec URL
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.11",
3
+ "version": "0.20.14",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",