apiblaze 0.20.28 → 0.20.31

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 +115 -4
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1024,7 +1024,7 @@ var import_commander = require("commander");
1024
1024
  var import_chalk54 = __toESM(require("chalk"));
1025
1025
 
1026
1026
  // package.json
1027
- var version = "0.20.28";
1027
+ var version = "0.20.31";
1028
1028
 
1029
1029
  // src/index.ts
1030
1030
  init_types();
@@ -2414,7 +2414,7 @@ function claudeOneShot(spec2, prompt) {
2414
2414
  return { argv, status: r.status };
2415
2415
  }
2416
2416
  function claudeInteractive(spec2, prompt) {
2417
- const argv = ["claude", prompt, "--allowedTools", `mcp__${spec2.name}__*`];
2417
+ const argv = ["claude", prompt, "--allowedTools", `mcp__${spec2.name}__*`, "--append-system-prompt", claudeSystemHint(spec2)];
2418
2418
  const r = run(argv[0], argv.slice(1), { interactive: true });
2419
2419
  return { argv, status: r.status };
2420
2420
  }
@@ -2492,6 +2492,75 @@ function installIntoCodex(spec2) {
2492
2492
  return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
2493
2493
  }
2494
2494
  }
2495
+ function codexSiblingApiblazeServers(keepName) {
2496
+ try {
2497
+ const text = fs5.readFileSync(codexConfigPath(), "utf-8");
2498
+ const lines = text.split("\n");
2499
+ const out = [];
2500
+ const header = /^\[mcp_servers\.(?:"([^"]+)"|([^\]]+))\]\s*(#.*)?$/;
2501
+ const anyHeader = /^\s*\[[^\]]+\]\s*(#.*)?$/;
2502
+ for (let i = 0; i < lines.length; i++) {
2503
+ const m = lines[i].match(header);
2504
+ if (!m) continue;
2505
+ const name = m[1] ?? m[2];
2506
+ let end = lines.length;
2507
+ for (let j = i + 1; j < lines.length; j++) {
2508
+ if (anyHeader.test(lines[j])) {
2509
+ end = j;
2510
+ break;
2511
+ }
2512
+ }
2513
+ const body = lines.slice(i, end).join("\n");
2514
+ const url = body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1];
2515
+ if (name && name !== keepName && url && OURS.test(url)) out.push(name);
2516
+ i = end - 1;
2517
+ }
2518
+ return out;
2519
+ } catch {
2520
+ return [];
2521
+ }
2522
+ }
2523
+ async function mcpEndpointIsDead(url) {
2524
+ try {
2525
+ const res = await fetch(url, {
2526
+ method: "POST",
2527
+ headers: { "Content-Type": "application/json" },
2528
+ body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list" }),
2529
+ signal: AbortSignal.timeout(8e3)
2530
+ });
2531
+ if (res.status === 401 || res.status === 403) return false;
2532
+ if (res.status === 404 || res.status >= 500) return true;
2533
+ return false;
2534
+ } catch {
2535
+ return false;
2536
+ }
2537
+ }
2538
+ function codexServerUrl(name) {
2539
+ try {
2540
+ const lines = fs5.readFileSync(codexConfigPath(), "utf-8").split("\n");
2541
+ const ranges = codexSectionRanges(lines, name);
2542
+ if (!ranges.length) return null;
2543
+ const body = lines.slice(ranges[0].start, ranges[0].end).join("\n");
2544
+ return body.match(/^\s*url\s*=\s*"([^"]*)"/m)?.[1] ?? null;
2545
+ } catch {
2546
+ return null;
2547
+ }
2548
+ }
2549
+ function removeCodexServer(name) {
2550
+ try {
2551
+ const file = codexConfigPath();
2552
+ const lines = fs5.readFileSync(file, "utf-8").split("\n");
2553
+ const ranges = codexSectionRanges(lines, name);
2554
+ if (!ranges.length) return false;
2555
+ for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
2556
+ const tmp = `${file}.tmp-${process.pid}`;
2557
+ fs5.writeFileSync(tmp, lines.join("\n"), { encoding: "utf-8", mode: 384 });
2558
+ fs5.renameSync(tmp, file);
2559
+ return true;
2560
+ } catch {
2561
+ return false;
2562
+ }
2563
+ }
2495
2564
  function codexOneShot(_spec, prompt) {
2496
2565
  const argv = ["codex", "exec", "--skip-git-repo-check", prompt];
2497
2566
  const r = run(argv[0], argv.slice(1), { inherit: true });
@@ -2503,6 +2572,12 @@ function codexInteractive(_spec, prompt) {
2503
2572
  return { argv, status: r.status };
2504
2573
  }
2505
2574
  var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
2575
+ function directedPrompt(spec2, question) {
2576
+ return `Use the "${spec2.name}" MCP server (it exposes this API's tools) to answer: ${question}`;
2577
+ }
2578
+ function claudeSystemHint(spec2) {
2579
+ return `You are connected to the "${spec2.name}" MCP server, which exposes the ${spec2.projectLabel} API as tools (mcp__${spec2.name}__*). For any question about this API, call those tools rather than answering from memory or asking the user for data the API can provide.`;
2580
+ }
2506
2581
  function renderCommand(argv) {
2507
2582
  return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
2508
2583
  }
@@ -2575,10 +2650,11 @@ async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
2575
2650
  const ask = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
2576
2651
  log(`
2577
2652
  ${import_chalk12.default.dim(handoff ? `Opening ${cli.label} with your question \u2014 you'll stay in that session:` : `Your question, through ${cli.label}:`)}`);
2578
- const shown = cli.kind === "claude" ? handoff ? ["claude", question, "--allowedTools", `mcp__${spec2.name}__*`] : ["claude", "-p", question, "--allowedTools", `mcp__${spec2.name}__*`] : handoff ? ["codex", question] : ["codex", "exec", "--skip-git-repo-check", question];
2653
+ const directed = directedPrompt(spec2, question);
2654
+ const shown = cli.kind === "claude" ? handoff ? ["claude", directed, "--allowedTools", `mcp__${spec2.name}__*`, "--append-system-prompt", "\u2026"] : ["claude", "-p", directed, "--allowedTools", `mcp__${spec2.name}__*`] : handoff ? ["codex", directed] : ["codex", "exec", "--skip-git-repo-check", directed];
2579
2655
  log(` ${import_chalk12.default.dim("$")} ${renderCommand(shown)}
2580
2656
  `);
2581
- const ans = ask(spec2, question);
2657
+ const ans = ask(spec2, directedPrompt(spec2, question));
2582
2658
  if (ans.status !== 0) log(import_chalk12.default.yellow(`
2583
2659
  ${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
2584
2660
  }
@@ -4160,12 +4236,47 @@ async function maybeInstallExternalCli(p, opts) {
4160
4236
  const ran = await installAndDemo(pick2, buildInstallSpec(p), () => resolveQuestion(opts));
4161
4237
  if (ran) {
4162
4238
  rememberCliOffer(p, pick2.kind, "installed");
4239
+ if (pick2.kind === "codex") await offerToPruneCodexSiblings(p.projectId);
4163
4240
  if (p.anon) {
4164
4241
  console.log(import_chalk14.default.dim(` Anonymous workspace \u2014 run \`apiblaze apichat ${p.projectId}\` and /claim to keep it (and this MCP) beyond 30 days.`));
4165
4242
  }
4166
4243
  }
4167
4244
  return ran;
4168
4245
  }
4246
+ async function offerToPruneCodexSiblings(keepName) {
4247
+ const siblings = codexSiblingApiblazeServers(keepName);
4248
+ if (siblings.length === 0) return;
4249
+ const alive = [];
4250
+ for (const name of siblings) {
4251
+ const url = codexServerUrl(name);
4252
+ if (url && await mcpEndpointIsDead(url)) {
4253
+ if (removeCodexServer(name)) {
4254
+ console.log(import_chalk14.default.dim(` Removed ${import_chalk14.default.bold(name)} from ~/.codex/config.toml \u2014 that proxy no longer exists.`));
4255
+ continue;
4256
+ }
4257
+ }
4258
+ alive.push(name);
4259
+ }
4260
+ if (alive.length === 0) return;
4261
+ const siblingsLabel = alive;
4262
+ console.log(import_chalk14.default.yellow(`
4263
+ Codex also has ${siblingsLabel.length} other APIblaze MCP server${siblingsLabel.length > 1 ? "s" : ""} configured: ${import_chalk14.default.bold(siblingsLabel.join(", "))}.`));
4264
+ console.log(import_chalk14.default.dim(' Codex tries every one at startup and reports "MCP startup incomplete" for any that is not signed in.'));
4265
+ if (!process.stdin.isTTY) {
4266
+ console.log(import_chalk14.default.dim(` Remove the ones you don't use: apiblaze apichat <name> --install-mcp codex (or edit ~/.codex/config.toml)`));
4267
+ return;
4268
+ }
4269
+ const { default: inquirer3 } = await import("inquirer");
4270
+ const { drop } = await inquirer3.prompt([{
4271
+ type: "checkbox",
4272
+ name: "drop",
4273
+ message: "Remove any you no longer use? (space to select, enter to confirm)",
4274
+ choices: siblingsLabel.map((n) => ({ name: n, value: n }))
4275
+ }]);
4276
+ for (const n of drop ?? []) {
4277
+ console.log(removeCodexServer(n) ? ` ${import_chalk14.default.green("\u2714")} Removed ${import_chalk14.default.bold(n)} from ~/.codex/config.toml.` : import_chalk14.default.red(` Could not remove ${n}.`));
4278
+ }
4279
+ }
4169
4280
  async function authorizeClientRegistration(p, cliLabel) {
4170
4281
  if (!p.consumerAuth) return;
4171
4282
  const teamId = p.teamId ?? loadCredentials()?.teamId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.28",
3
+ "version": "0.20.31",
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",