apiblaze 0.20.31 → 0.20.34

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 +100 -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.31";
1027
+ var version = "0.20.33";
1028
1028
 
1029
1029
  // src/index.ts
1030
1030
  init_types();
@@ -2102,6 +2102,7 @@ function suggestTenantName(projectSlug, taken) {
2102
2102
  var fs8 = __toESM(require("fs"));
2103
2103
  var path6 = __toESM(require("path"));
2104
2104
  var crypto2 = __toESM(require("crypto"));
2105
+ var import_child_process3 = require("child_process");
2105
2106
  var import_chalk14 = __toESM(require("chalk"));
2106
2107
  var import_ora4 = __toESM(require("ora"));
2107
2108
  var import_yaml2 = require("yaml");
@@ -2575,6 +2576,67 @@ var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
2575
2576
  function directedPrompt(spec2, question) {
2576
2577
  return `Use the "${spec2.name}" MCP server (it exposes this API's tools) to answer: ${question}`;
2577
2578
  }
2579
+ function agentInstructionFile(cli, dir = process.cwd()) {
2580
+ return path4.join(dir, cli === "claude" ? "CLAUDE.md" : "AGENTS.md");
2581
+ }
2582
+ var marker = (name) => ({
2583
+ start: `<!-- apiblaze:mcp:${name} -->`,
2584
+ end: `<!-- /apiblaze:mcp:${name} -->`
2585
+ });
2586
+ function stripMarkedBlock(text, name) {
2587
+ const m = marker(name);
2588
+ for (; ; ) {
2589
+ const i = text.indexOf(m.start);
2590
+ if (i < 0) return text;
2591
+ const j = text.indexOf(m.end, i);
2592
+ if (j < 0) return text.slice(0, i);
2593
+ let k = j + m.end.length;
2594
+ if (text[k] === "\n") k++;
2595
+ text = text.slice(0, i) + text.slice(k);
2596
+ }
2597
+ }
2598
+ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
2599
+ try {
2600
+ const file = agentInstructionFile(cli, dir);
2601
+ const m = marker(spec2.name);
2602
+ const block = [
2603
+ m.start,
2604
+ `## ${spec2.projectLabel} API \u2014 use the "${spec2.name}" MCP server`,
2605
+ "",
2606
+ `This directory is connected to the \`${spec2.name}\` MCP server, which exposes the`,
2607
+ `${spec2.projectLabel} API as tools. For any question about this API \u2014 its data, its`,
2608
+ "records, what it can do \u2014 call those tools rather than answering from memory or",
2609
+ "asking for data the API can provide.",
2610
+ "",
2611
+ `_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze apichat ${spec2.name} --remove-mcp ${cli}\`._`,
2612
+ m.end,
2613
+ ""
2614
+ ].join("\n");
2615
+ let text = "";
2616
+ try {
2617
+ text = fs5.readFileSync(file, "utf-8");
2618
+ } catch {
2619
+ }
2620
+ const cleaned = stripMarkedBlock(text, spec2.name);
2621
+ const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
2622
+ fs5.writeFileSync(file, cleaned + sep + block, "utf-8");
2623
+ return file;
2624
+ } catch {
2625
+ return null;
2626
+ }
2627
+ }
2628
+ function removeAgentInstruction(cli, name, dir = process.cwd()) {
2629
+ try {
2630
+ const file = agentInstructionFile(cli, dir);
2631
+ const text = fs5.readFileSync(file, "utf-8");
2632
+ const cleaned = stripMarkedBlock(text, name);
2633
+ if (cleaned === text) return false;
2634
+ fs5.writeFileSync(file, cleaned.replace(/\n{3,}/g, "\n\n"), "utf-8");
2635
+ return true;
2636
+ } catch {
2637
+ return false;
2638
+ }
2639
+ }
2578
2640
  function claudeSystemHint(spec2) {
2579
2641
  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
2642
  }
@@ -2658,8 +2720,20 @@ async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
2658
2720
  if (ans.status !== 0) log(import_chalk12.default.yellow(`
2659
2721
  ${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
2660
2722
  }
2661
- log(`
2662
- ${import_chalk12.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
2723
+ const instructionFile = writeAgentInstruction(cli.kind, spec2);
2724
+ const off = `npx apiblaze apichat ${spec2.name} --remove-mcp ${cli.kind}`;
2725
+ const mdName = cli.kind === "claude" ? "CLAUDE.md" : "AGENTS.md";
2726
+ log("");
2727
+ log(import_chalk12.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
2728
+ log(import_chalk12.default.red.bold(` Every ${cli.label} session in this directory can call your API's tools.`));
2729
+ if (instructionFile) {
2730
+ log(import_chalk12.default.red(` We added a "use this MCP" block to your ${import_chalk12.default.bold(mdName)} \u2014 that is what makes it stick:`));
2731
+ log(import_chalk12.default.red(` ${instructionFile}`));
2732
+ } else {
2733
+ log(import_chalk12.default.red(` (Could not write ${mdName} here, so this applies only to sessions apichat starts.)`));
2734
+ }
2735
+ log(import_chalk12.default.red(` Turn it off (removes the MCP server AND the ${mdName} block):`));
2736
+ log(import_chalk12.default.red(` ${import_chalk12.default.bold(off)}`));
2663
2737
  return true;
2664
2738
  }
2665
2739
 
@@ -4334,6 +4408,23 @@ async function startChat(p, messages, opts) {
4334
4408
  }
4335
4409
  await runRepl(p, messages);
4336
4410
  }
4411
+ async function removeExternalMcp(projectId, cliKind) {
4412
+ const kind = cliKind.toLowerCase();
4413
+ if (kind !== "claude" && kind !== "codex") fail3(`--remove-mcp takes "claude" or "codex", not "${cliKind}".`);
4414
+ const label3 = kind === "claude" ? "Claude CLI" : "Codex CLI";
4415
+ let removed = false;
4416
+ if (kind === "claude") {
4417
+ const r = (0, import_child_process3.spawnSync)("claude", ["mcp", "remove", projectId], { encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], timeout: 15e3 });
4418
+ removed = r.status === 0;
4419
+ } else {
4420
+ removed = removeCodexServer(projectId);
4421
+ }
4422
+ console.log(removed ? ` ${import_chalk14.default.green("\u2714")} Removed the ${import_chalk14.default.bold(projectId)} MCP server from ${label3}.` : import_chalk14.default.dim(` ${label3} had no ${projectId} MCP server configured.`));
4423
+ const hadNote = removeAgentInstruction(kind, projectId);
4424
+ console.log(hadNote ? ` ${import_chalk14.default.green("\u2714")} Removed the standing instruction from ${agentInstructionFile(kind)}.` : import_chalk14.default.dim(` No standing instruction found in ${agentInstructionFile(kind)}.`));
4425
+ console.log(import_chalk14.default.dim(`
4426
+ ${label3} will no longer launch with your API's tools in this directory.`));
4427
+ }
4337
4428
  async function runApichat(opts) {
4338
4429
  setVerbose(opts.verbose === true);
4339
4430
  console.log(import_chalk14.default.bold("\napichat \u2014 turn any API into a chat\n"));
@@ -4346,6 +4437,11 @@ async function runApichat(opts) {
4346
4437
  opts.target = void 0;
4347
4438
  }
4348
4439
  }
4440
+ if (opts.removeMcp) {
4441
+ if (!opts.project) fail3("Name the proxy: apiblaze apichat <project> --remove-mcp claude|codex");
4442
+ await removeExternalMcp(opts.project, opts.removeMcp);
4443
+ return;
4444
+ }
4349
4445
  if (opts.project) {
4350
4446
  const opened = await openDirectProject(opts.project, opts);
4351
4447
  if (await maybeInstallExternalCli(opened.p, opts)) return;
@@ -11695,7 +11791,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
11695
11791
  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)));
11696
11792
  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)));
11697
11793
  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)));
11698
- 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 })));
11794
+ 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.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
11699
11795
  var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
11700
11796
  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)));
11701
11797
  llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.31",
3
+ "version": "0.20.34",
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",