apiblaze 0.21.15 → 0.21.17

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 +275 -33
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1347,12 +1347,12 @@ var init_tenant_pick = __esm({
1347
1347
  });
1348
1348
 
1349
1349
  // src/index.ts
1350
- var import_fs5 = __toESM(require("fs"));
1350
+ var import_fs6 = __toESM(require("fs"));
1351
1351
  var import_commander = require("commander");
1352
1352
  var import_chalk67 = __toESM(require("chalk"));
1353
1353
 
1354
1354
  // package.json
1355
- var version = "0.21.15";
1355
+ var version = "0.21.17";
1356
1356
 
1357
1357
  // src/index.ts
1358
1358
  init_types();
@@ -6736,6 +6736,7 @@ var CP_TOOLS = [
6736
6736
  function skillMarkdown() {
6737
6737
  const P = B.product;
6738
6738
  const cli = B.cli;
6739
+ const LIB = "apiblaze";
6739
6740
  const other = BRAND === "mcpblaze" ? "APIblaze" : "MCPblaze";
6740
6741
  const doors = BRAND === "mcpblaze" ? `${P} ships your code to agents (an MCP server); ${other} ships the same code to users (API keys + sign-in).` : `${P} ships your code to users (API keys + sign-in); ${other} ships the same code to agents (an MCP server).`;
6741
6742
  return `---
@@ -6807,7 +6808,7 @@ ship; \`/\` is the same thing behind ${P}. Narrate each step in one line and sho
6807
6808
  \`npx ${cli}\` command \u2014 the commands are the product. Everything goes in \`${cli}-demo/\`.
6808
6809
 
6809
6810
  1. **Scaffold.** \`npx create-next-app@latest ${cli}-demo\` (TypeScript, App Router). Then
6810
- \`npm i hono ${cli}\`.
6811
+ \`npm i hono ${LIB}\`.
6811
6812
  2. **The backend.** Hono mounted at \`app/api/[[...route]]/route.ts\` (\`import { handle } from
6812
6813
  'hono/vercel'\`). Three resources \u2014 restaurants, tables, reservations \u2014 in an in-memory Map,
6813
6814
  every row stamped with \`owner\` = the \`x-abz-user-id\` header (${P} sets it on every call it
@@ -6835,8 +6836,8 @@ ship; \`/\` is the same thing behind ${P}. Narrate each step in one line and sho
6835
6836
  6. **/.** \`app/page.tsx\`: the same UI, plus a **user picker** (ana \xB7 ben \xB7 admin \u2014 a select, no
6836
6837
  real login; the demo is about authorization, not building sign-in). Its server route calls
6837
6838
  \`process.env.APIBLAZE_URL\` with \`X-API-Key: APIBLAZE_SERVER_KEY\` and \`X-End-User-Id: <the
6838
- picked user>\`. Add the three widgets from \`${cli}/react\`, each with its server route:
6839
- - \`<ApiKeyWidget />\` + \`app/api/apiblaze/keys/route.ts\` (\`createApiblazeKeys\` from \`${cli}/server\`)
6839
+ picked user>\`. Add the three widgets from \`${LIB}/react\`, each with its server route:
6840
+ - \`<ApiKeyWidget />\` + \`app/api/apiblaze/keys/route.ts\` (\`createApiblazeKeys\` from \`${LIB}/server\`)
6840
6841
  - \`<UsersGroupsWidget />\` + \`app/api/apiblaze/groups/route.ts\` (\`createApiblazeGroups\` \u2014
6841
6842
  the keys route with one word changed)
6842
6843
  - \`<ChatWidget />\` + \`app/api/apiblaze/chat/route.ts\` (\`createApiblazeChat\`)
@@ -7919,6 +7920,7 @@ async function runOpenapi(projectArg, apiVersionArg) {
7919
7920
  }
7920
7921
 
7921
7922
  // src/commands/mcp.ts
7923
+ var import_fs4 = __toESM(require("fs"));
7922
7924
  var import_chalk33 = __toESM(require("chalk"));
7923
7925
  init_auth();
7924
7926
  init_api();
@@ -7960,6 +7962,234 @@ async function runMcp(projectArg, apiVersionArg, opts) {
7960
7962
  ]
7961
7963
  });
7962
7964
  }
7965
+ var PROMPT_NAME_RE = /^[a-z0-9_-]{1,64}$/;
7966
+ function derivedBehavior(method) {
7967
+ const m = (method ?? "").toUpperCase();
7968
+ const destructive = m === "DELETE";
7969
+ return {
7970
+ readOnly: m === "GET" || m === "HEAD" || m === "OPTIONS",
7971
+ destructive,
7972
+ idempotent: m === "PUT" || m === "DELETE",
7973
+ confirm: destructive,
7974
+ alwaysAsync: false
7975
+ };
7976
+ }
7977
+ function effectiveBehavior(method, o) {
7978
+ const d = derivedBehavior(method);
7979
+ const readOnly = o?.readOnly ?? d.readOnly;
7980
+ const destructive = readOnly ? false : o?.destructive ?? d.destructive;
7981
+ return {
7982
+ readOnly,
7983
+ destructive,
7984
+ idempotent: o?.idempotent ?? d.idempotent,
7985
+ confirm: o?.confirm ?? destructive,
7986
+ alwaysAsync: o?.alwaysAsync ?? false
7987
+ };
7988
+ }
7989
+ function uiResourceUri(projectId, toolName) {
7990
+ return `ui://${projectId}/${toolName}`;
7991
+ }
7992
+ function normalizePrompt(name, raw) {
7993
+ if (!PROMPT_NAME_RE.test(name)) throw new Error(`Prompt name "${name}" must match ${PROMPT_NAME_RE} (lowercase letters, digits, _ and -).`);
7994
+ const p = raw ?? {};
7995
+ if (typeof p !== "object" || Array.isArray(p)) throw new Error("The prompt file must hold one JSON object.");
7996
+ let messages = p.messages;
7997
+ if (!Array.isArray(messages) && typeof p.text === "string") messages = [{ role: "user", text: p.text }];
7998
+ if (!Array.isArray(messages) || messages.length === 0) throw new Error('The prompt needs "messages": [{ "role": "user", "text": "\u2026" }] (or a "text" shorthand).');
7999
+ for (const m of messages) {
8000
+ if (!m || m.role !== "user" && m.role !== "assistant" || typeof m.text !== "string" || !m.text) {
8001
+ throw new Error('Each message needs role "user" | "assistant" and a non-empty "text".');
8002
+ }
8003
+ }
8004
+ if (p.arguments !== void 0 && (!Array.isArray(p.arguments) || p.arguments.some((a) => !a || typeof a.name !== "string"))) {
8005
+ throw new Error('"arguments" must be [{ "name": "\u2026", "description"?: "\u2026", "required"?: true }].');
8006
+ }
8007
+ return {
8008
+ name,
8009
+ ...typeof p.title === "string" ? { title: p.title } : {},
8010
+ ...typeof p.description === "string" ? { description: p.description } : {},
8011
+ ...Array.isArray(p.arguments) ? { arguments: p.arguments } : {},
8012
+ messages
8013
+ };
8014
+ }
8015
+ function withPrompt(cat, prompt) {
8016
+ const prompts = [...cat.prompts ?? []];
8017
+ const i = prompts.findIndex((p) => p.name === prompt.name);
8018
+ if (i >= 0) prompts[i] = prompt;
8019
+ else prompts.push(prompt);
8020
+ return { ...cat, prompts };
8021
+ }
8022
+ function withoutPrompt(cat, name) {
8023
+ const prompts = cat.prompts ?? [];
8024
+ if (!prompts.some((p) => p.name === name)) throw new Error(`No prompt named "${name}". See \`${B.cli} mcp prompts list\`.`);
8025
+ return { ...cat, prompts: prompts.filter((p) => p.name !== name) };
8026
+ }
8027
+ function requireTool(cat, tool) {
8028
+ if (!cat.tools.some((t) => t.name === tool)) {
8029
+ const names = cat.tools.slice(0, 10).map((t) => t.name).join(", ");
8030
+ throw new Error(`No tool named "${tool}" in the published catalogue${names ? ` (tools: ${names}${cat.tools.length > 10 ? ", \u2026" : ""})` : ""}.`);
8031
+ }
8032
+ }
8033
+ function withOverride(cat, tool, flags) {
8034
+ requireTool(cat, tool);
8035
+ const patch = {};
8036
+ for (const k of ["readOnly", "destructive", "idempotent", "confirm", "alwaysAsync"]) {
8037
+ if (typeof flags[k] === "boolean") patch[k] = flags[k];
8038
+ }
8039
+ if (Object.keys(patch).length === 0) {
8040
+ throw new Error("Nothing to set. Pass --read-only, --destructive, --idempotent, --confirm, --always-async (or their --no-\u2026 forms).");
8041
+ }
8042
+ return { ...cat, overrides: { ...cat.overrides ?? {}, [tool]: { ...cat.overrides?.[tool] ?? {}, ...patch } } };
8043
+ }
8044
+ function withoutOverride(cat, tool) {
8045
+ requireTool(cat, tool);
8046
+ const overrides = { ...cat.overrides ?? {} };
8047
+ delete overrides[tool];
8048
+ return { ...cat, overrides };
8049
+ }
8050
+ function flagsLine(b) {
8051
+ const f = [];
8052
+ if (b.readOnly) f.push("read-only");
8053
+ if (b.idempotent) f.push("idempotent");
8054
+ if (b.destructive) f.push("destructive");
8055
+ if (b.confirm) f.push("asks to confirm");
8056
+ if (b.alwaysAsync) f.push("always async");
8057
+ return f.length ? f.join(", ") : "\u2014";
8058
+ }
8059
+ function renderOps(cat) {
8060
+ const rows = cat.tools.map((t) => {
8061
+ const method = t["x-route"]?.method?.toUpperCase() ?? "";
8062
+ const o = cat.overrides?.[t.name];
8063
+ return { name: t.name, route: `${method} ${t["x-route"]?.path ?? ""}`.trim(), eff: flagsLine(effectiveBehavior(method, o)), custom: !!o && Object.keys(o).length > 0 };
8064
+ });
8065
+ const w = Math.min(40, Math.max(4, ...rows.map((r) => r.name.length)));
8066
+ const rw = Math.min(40, Math.max(5, ...rows.map((r) => r.route.length)));
8067
+ return rows.map((r) => ` ${r.name.padEnd(w)} ${r.route.padEnd(rw)} ${r.eff}${r.custom ? import_chalk33.default.yellow(" (override)") : ""}`);
8068
+ }
8069
+ async function resolveMcpTarget(projectArg, opts) {
8070
+ const creds = loadCredentials();
8071
+ if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
8072
+ const projects = await getProjects(creds.teamId ?? "");
8073
+ const matches2 = projects.filter((p) => p.projectId === projectArg || p.projectName === projectArg);
8074
+ const wanted = opts.apiversion ?? opts.version;
8075
+ const match = (wanted ? matches2.find((p) => p.apiVersion === wanted) : matches2[0]) ?? null;
8076
+ if (!match) throw new Error(`Project "${projectArg}"${wanted ? ` version ${wanted}` : ""} not found in your active team. Run \`${B.cli} projects\`.`);
8077
+ return { projectId: match.projectId, apiVersion: wanted || match.apiVersion, environment: opts.environment || "prod" };
8078
+ }
8079
+ async function loadCatalogue(t) {
8080
+ const r = await agentCall(`/projects/${t.projectId}/${t.apiVersion}/mcp/spec?environment=${encodeURIComponent(t.environment)}`, "GET");
8081
+ if (r.status >= 400) throw new Error(`Could not read the MCP catalogue (${r.status}): ${r.data?.error ?? ""}`);
8082
+ const spec2 = r.data?.spec;
8083
+ if (!spec2 || !Array.isArray(spec2.tools)) {
8084
+ throw new Error(`No MCP catalogue published for ${t.projectId} ${t.apiVersion} (${t.environment}). Try --environment, or build one with \`${B.cli} agent mcp ${t.projectId}\`.`);
8085
+ }
8086
+ return spec2;
8087
+ }
8088
+ async function saveCatalogue(t, spec2) {
8089
+ const r = await agentCall(`/projects/${t.projectId}/${t.apiVersion}/mcp/spec`, "PUT", { environment: t.environment, spec: spec2 });
8090
+ if (r.status >= 400) {
8091
+ const d = r.data;
8092
+ throw new Error(`Save failed (${r.status}): ${d?.error ?? ""}${d?.details ? ` \u2014 ${typeof d.details === "string" ? d.details : JSON.stringify(d.details)}` : ""}`);
8093
+ }
8094
+ }
8095
+ async function runMcpPrompts(action2, project, name, opts) {
8096
+ const t = await resolveMcpTarget(project, opts);
8097
+ const cat = await loadCatalogue(t);
8098
+ const prompts = cat.prompts ?? [];
8099
+ if (action2 === "list") {
8100
+ if (opts.json) return void console.log(JSON.stringify({ environment: t.environment, prompts }, null, 2));
8101
+ if (!prompts.length) {
8102
+ console.log(import_chalk33.default.dim(`
8103
+ No prompts on ${t.projectId} (${t.environment}). Prompts are drafted by the MCP agent (\`${B.cli} agent mcp ${t.projectId}\`) or added with \`${B.cli} mcp prompts set\`.
8104
+ `));
8105
+ return;
8106
+ }
8107
+ console.log("");
8108
+ for (const p of prompts) {
8109
+ const args = (p.arguments ?? []).map((a) => a.required ? a.name : `${a.name}?`).join(", ");
8110
+ console.log(` ${import_chalk33.default.bold(p.name)}${args ? import_chalk33.default.dim(`(${args})`) : ""}${p.title || p.description ? ` ${p.title ?? p.description}` : ""}`);
8111
+ }
8112
+ console.log("");
8113
+ return;
8114
+ }
8115
+ if (!name) throw new Error(`Usage: ${B.cli} mcp prompts ${action2} <project> <name>`);
8116
+ if (action2 === "show") {
8117
+ const p = prompts.find((x) => x.name === name);
8118
+ if (!p) throw new Error(`No prompt named "${name}". See \`${B.cli} mcp prompts list ${project}\`.`);
8119
+ if (opts.json) return void console.log(JSON.stringify(p, null, 2));
8120
+ console.log(`
8121
+ ${import_chalk33.default.bold(p.name)}${p.title ? ` \u2014 ${p.title}` : ""}`);
8122
+ if (p.description) console.log(` ${p.description}`);
8123
+ for (const a of p.arguments ?? []) console.log(import_chalk33.default.dim(` ${a.name}${a.required ? " (required)" : ""}${a.description ? `: ${a.description}` : ""}`));
8124
+ for (const m of p.messages) console.log(`
8125
+ [${m.role}] ${m.text}`);
8126
+ console.log("");
8127
+ return;
8128
+ }
8129
+ let next;
8130
+ if (action2 === "set") {
8131
+ if (!opts.file) throw new Error(`Usage: ${B.cli} mcp prompts set <project> <name> --file prompt.json`);
8132
+ let raw;
8133
+ try {
8134
+ raw = JSON.parse(import_fs4.default.readFileSync(opts.file, "utf8"));
8135
+ } catch (e) {
8136
+ throw new Error(`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`);
8137
+ }
8138
+ next = withPrompt(cat, normalizePrompt(name, raw));
8139
+ } else {
8140
+ next = withoutPrompt(cat, name);
8141
+ }
8142
+ await saveCatalogue(t, next);
8143
+ if (opts.json) return void console.log(JSON.stringify({ saved: true, environment: t.environment, prompts: next.prompts }, null, 2));
8144
+ console.log(import_chalk33.default.green(` \u2713 ${action2 === "set" ? "Saved" : "Removed"} prompt "${name}" on ${t.projectId} (${t.environment}).`));
8145
+ }
8146
+ async function runMcpOps(action2, project, tool, opts) {
8147
+ const t = await resolveMcpTarget(project, opts);
8148
+ const cat = await loadCatalogue(t);
8149
+ if (action2 === "list") {
8150
+ if (opts.json) {
8151
+ const tools = cat.tools.map((x) => {
8152
+ const method2 = x["x-route"]?.method?.toUpperCase();
8153
+ return {
8154
+ name: x.name,
8155
+ method: method2,
8156
+ path: x["x-route"]?.path,
8157
+ default: derivedBehavior(method2),
8158
+ override: cat.overrides?.[x.name] ?? null,
8159
+ effective: effectiveBehavior(method2, cat.overrides?.[x.name])
8160
+ };
8161
+ });
8162
+ return void console.log(JSON.stringify({ environment: t.environment, tools }, null, 2));
8163
+ }
8164
+ console.log(import_chalk33.default.dim(`
8165
+ ${cat.tools.length} tool(s) on ${t.projectId} (${t.environment}). Defaults come from the HTTP method: GET/HEAD/OPTIONS read-only, PUT/DELETE idempotent, DELETE destructive + asks to confirm.
8166
+ `));
8167
+ for (const l of renderOps(cat)) console.log(l);
8168
+ console.log(import_chalk33.default.dim(`
8169
+ Override: ${B.cli} mcp ops set ${project} <tool> --read-only | --destructive | --confirm | --no-confirm | --always-async \xB7 Reset: ${B.cli} mcp ops reset ${project} <tool>
8170
+ `));
8171
+ return;
8172
+ }
8173
+ if (!tool) throw new Error(`Usage: ${B.cli} mcp ops ${action2} <project> <tool>`);
8174
+ const next = action2 === "set" ? withOverride(cat, tool, opts) : withoutOverride(cat, tool);
8175
+ await saveCatalogue(t, next);
8176
+ const method = cat.tools.find((x) => x.name === tool)?.["x-route"]?.method;
8177
+ const eff = effectiveBehavior(method, next.overrides?.[tool]);
8178
+ if (opts.json) return void console.log(JSON.stringify({ saved: true, tool, override: next.overrides?.[tool] ?? null, effective: eff }, null, 2));
8179
+ console.log(import_chalk33.default.green(` \u2713 ${tool}: ${flagsLine(eff)}${action2 === "reset" ? " (automatic default)" : ""}`));
8180
+ }
8181
+ async function runMcpUi(project, tool, opts) {
8182
+ const t = await resolveMcpTarget(project, opts);
8183
+ const cat = await loadCatalogue(t);
8184
+ requireTool(cat, tool);
8185
+ const uri = uiResourceUri(t.projectId, tool);
8186
+ if (opts.json) return void console.log(JSON.stringify({ tool, uri, kind: "automatic" }, null, 2));
8187
+ console.log(`
8188
+ ${import_chalk33.default.bold(uri)}
8189
+ `);
8190
+ console.log(import_chalk33.default.dim(" This is the automatic viewer: MCP Apps clients render the tool's result with a generic UI built"));
8191
+ console.log(import_chalk33.default.dim(" from its response schema \u2014 nothing to configure. Uploading your own HTML for it comes later.\n"));
8192
+ }
7963
8193
 
7964
8194
  // src/commands/delete.ts
7965
8195
  var import_chalk35 = __toESM(require("chalk"));
@@ -8157,7 +8387,7 @@ async function runJobStatus(jobId, opts) {
8157
8387
 
8158
8388
  // src/commands/export.ts
8159
8389
  var import_chalk37 = __toESM(require("chalk"));
8160
- var import_fs4 = require("fs");
8390
+ var import_fs5 = require("fs");
8161
8391
  init_admin();
8162
8392
  init_auth();
8163
8393
  init_resolve();
@@ -8206,7 +8436,7 @@ async function runExport(projectArg, versionArg, opts) {
8206
8436
  });
8207
8437
  if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
8208
8438
  const buf = Buffer.from(await res.arrayBuffer());
8209
- (0, import_fs4.writeFileSync)(outFile, buf);
8439
+ (0, import_fs5.writeFileSync)(outFile, buf);
8210
8440
  console.log(import_chalk37.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
8211
8441
  if (job.decryption_key) console.log(import_chalk37.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
8212
8442
  console.log(import_chalk37.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
@@ -11913,7 +12143,7 @@ async function providersMenu(cBase, clientLabel) {
11913
12143
  }
11914
12144
 
11915
12145
  // src/commands/spec.ts
11916
- var fs12 = __toESM(require("fs"));
12146
+ var fs13 = __toESM(require("fs"));
11917
12147
  var import_chalk53 = __toESM(require("chalk"));
11918
12148
  var import_ora24 = __toESM(require("ora"));
11919
12149
  init_admin();
@@ -11936,7 +12166,7 @@ async function runSpecSet(project, opts) {
11936
12166
  }
11937
12167
  let specContent;
11938
12168
  try {
11939
- specContent = fs12.readFileSync(opts.file, "utf-8");
12169
+ specContent = fs13.readFileSync(opts.file, "utf-8");
11940
12170
  } catch {
11941
12171
  console.error(import_chalk53.default.red(`Cannot read file: ${opts.file}`));
11942
12172
  process.exit(1);
@@ -13787,28 +14017,28 @@ async function runConsumerApikeys(opts) {
13787
14017
  // src/commands/sidecar.ts
13788
14018
  var import_chalk60 = __toESM(require("chalk"));
13789
14019
  var import_ora30 = __toESM(require("ora"));
13790
- var fs13 = __toESM(require("fs"));
14020
+ var fs14 = __toESM(require("fs"));
13791
14021
  var path10 = __toESM(require("path"));
13792
14022
  init_admin();
13793
14023
  init_resolve();
13794
14024
  init_auth();
13795
14025
  function detectNextProject(root) {
13796
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs13.existsSync(path10.join(root, f)));
14026
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs14.existsSync(path10.join(root, f)));
13797
14027
  let hasDep = false;
13798
14028
  try {
13799
- const pkg = JSON.parse(fs13.readFileSync(path10.join(root, "package.json"), "utf8"));
14029
+ const pkg = JSON.parse(fs14.readFileSync(path10.join(root, "package.json"), "utf8"));
13800
14030
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
13801
14031
  } catch {
13802
14032
  }
13803
- const appDir = fs13.existsSync(path10.join(root, "app")) || fs13.existsSync(path10.join(root, "src", "app"));
13804
- const pagesDir = fs13.existsSync(path10.join(root, "pages")) || fs13.existsSync(path10.join(root, "src", "pages"));
14033
+ const appDir = fs14.existsSync(path10.join(root, "app")) || fs14.existsSync(path10.join(root, "src", "app"));
14034
+ const pagesDir = fs14.existsSync(path10.join(root, "pages")) || fs14.existsSync(path10.join(root, "src", "pages"));
13805
14035
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
13806
14036
  }
13807
14037
  function upsertEnvLocal(root, token) {
13808
14038
  const p = path10.join(root, ".env.local");
13809
14039
  let existing = "";
13810
14040
  try {
13811
- existing = fs13.readFileSync(p, "utf8");
14041
+ existing = fs14.readFileSync(p, "utf8");
13812
14042
  } catch {
13813
14043
  }
13814
14044
  const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
@@ -13823,15 +14053,15 @@ function upsertEnvLocal(root, token) {
13823
14053
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
13824
14054
  `;
13825
14055
  }
13826
- fs13.writeFileSync(p, next);
14056
+ fs14.writeFileSync(p, next);
13827
14057
  return had ? "rotated" : "created";
13828
14058
  }
13829
14059
  function installSidecarPackage(root) {
13830
- if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
14060
+ if (fs14.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
13831
14061
  console.log(` ${import_chalk60.default.green("\u2713")} apiblaze package already installed`);
13832
14062
  return;
13833
14063
  }
13834
- const has = (f) => fs13.existsSync(path10.join(root, f));
14064
+ const has = (f) => fs14.existsSync(path10.join(root, f));
13835
14065
  const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
13836
14066
  const spinner = (0, import_ora30.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
13837
14067
  try {
@@ -13844,7 +14074,7 @@ function installSidecarPackage(root) {
13844
14074
  }
13845
14075
  function readEnvKey(root) {
13846
14076
  try {
13847
- const s = fs13.readFileSync(path10.join(root, ".env.local"), "utf8");
14077
+ const s = fs14.readFileSync(path10.join(root, ".env.local"), "utf8");
13848
14078
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
13849
14079
  return m ? m[1].trim() : null;
13850
14080
  } catch {
@@ -13855,13 +14085,13 @@ function ensureGitignored(root) {
13855
14085
  const p = path10.join(root, ".gitignore");
13856
14086
  let c = "";
13857
14087
  try {
13858
- c = fs13.readFileSync(p, "utf8");
14088
+ c = fs14.readFileSync(p, "utf8");
13859
14089
  } catch {
13860
14090
  }
13861
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs13.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
14091
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs14.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
13862
14092
  }
13863
14093
  function wireInstrumentation(root) {
13864
- const existing = ["instrumentation.ts", "instrumentation.js", path10.join("src", "instrumentation.ts")].map((c) => path10.join(root, c)).find((f) => fs13.existsSync(f));
14094
+ const existing = ["instrumentation.ts", "instrumentation.js", path10.join("src", "instrumentation.ts")].map((c) => path10.join(root, c)).find((f) => fs14.existsSync(f));
13865
14095
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
13866
14096
 
13867
14097
  export function register() {
@@ -13869,18 +14099,18 @@ export function register() {
13869
14099
  }
13870
14100
  `;
13871
14101
  if (!existing) {
13872
- fs13.writeFileSync(path10.join(root, "instrumentation.ts"), body);
14102
+ fs14.writeFileSync(path10.join(root, "instrumentation.ts"), body);
13873
14103
  return "created";
13874
14104
  }
13875
- const cur = fs13.readFileSync(existing, "utf8");
14105
+ const cur = fs14.readFileSync(existing, "utf8");
13876
14106
  if (cur.includes("apiblaze/sidecar")) return "present";
13877
14107
  if (/export\s+function\s+register\s*\(/.test(cur)) {
13878
- fs13.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
14108
+ fs14.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
13879
14109
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
13880
14110
  apiblaze();`));
13881
14111
  return "patched";
13882
14112
  }
13883
- fs13.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
14113
+ fs14.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
13884
14114
  ${cur}
13885
14115
  // call apiblaze() inside your register() export.
13886
14116
  `);
@@ -13963,16 +14193,16 @@ export default async function Page() {
13963
14193
  function generateInspector(root, router) {
13964
14194
  try {
13965
14195
  if (router === "pages") {
13966
- const dir2 = fs13.existsSync(path10.join(root, "src", "pages")) ? path10.join(root, "src", "pages") : path10.join(root, "pages");
14196
+ const dir2 = fs14.existsSync(path10.join(root, "src", "pages")) ? path10.join(root, "src", "pages") : path10.join(root, "pages");
13967
14197
  const f2 = path10.join(dir2, "abz-inspector.tsx");
13968
- fs13.writeFileSync(f2, INSPECTOR_PAGE);
14198
+ fs14.writeFileSync(f2, INSPECTOR_PAGE);
13969
14199
  return path10.relative(root, f2);
13970
14200
  }
13971
- const base2 = fs13.existsSync(path10.join(root, "src", "app")) ? path10.join(root, "src", "app") : path10.join(root, "app");
14201
+ const base2 = fs14.existsSync(path10.join(root, "src", "app")) ? path10.join(root, "src", "app") : path10.join(root, "app");
13972
14202
  const dir = path10.join(base2, "abz-inspector");
13973
- fs13.mkdirSync(dir, { recursive: true });
14203
+ fs14.mkdirSync(dir, { recursive: true });
13974
14204
  const f = path10.join(dir, "page.tsx");
13975
- fs13.writeFileSync(f, INSPECTOR_PAGE);
14205
+ fs14.writeFileSync(f, INSPECTOR_PAGE);
13976
14206
  return path10.relative(root, f);
13977
14207
  } catch {
13978
14208
  return null;
@@ -15415,8 +15645,20 @@ agent.command("authz").description("Chat to design and turn on access rules for
15415
15645
  program.command("rule").description("Lock resources to the person who created them \u2014 or describe any access rule in plain English (that one is billed per turn)").argument("<project>", "Project name or id").argument("[sentence]", 'Optional rule in plain English, e.g. "users see only their own rows". Omit to pick from what the spec can protect.').option("--enforce", "Turn enforcement on right after saving (default: save in shadow mode, then ask)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((project, sentence, opts) => runRule(project, sentence, opts)));
15416
15646
  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)));
15417
15647
  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)));
15648
+ var mcpCmd = program.command("mcp").description("Optional tweaks to a proxy's MCP server: prompts, per-tool behavior (read-only / destructive / confirm / always-async), UI resource");
15649
+ var mcpCommon = (c) => c.option("--apiversion <version>", "API version (defaults to the project's)").option("--environment <env>", "Catalogue environment (default: prod)").option("--json", "Output machine-readable JSON");
15650
+ var mcpPrompts = mcpCmd.command("prompts").description("Prompts the MCP server offers (drafted by the MCP agent; editable here)");
15651
+ mcpCommon(mcpPrompts.command("list").description("List the prompts").argument("<project>", "Project name or id")).action(action((project, opts) => runMcpPrompts("list", project, void 0, opts)));
15652
+ mcpCommon(mcpPrompts.command("show").description("Show one prompt").argument("<project>", "Project name or id").argument("<name>", "Prompt name")).action(action((project, name, opts) => runMcpPrompts("show", project, name, opts)));
15653
+ mcpCommon(mcpPrompts.command("set").description("Add or replace a prompt from a JSON file ({ title?, description?, arguments?, messages: [{ role, text }] }, {{arg}} placeholders)").argument("<project>", "Project name or id").argument("<name>", "Prompt name (a-z 0-9 _ -)").requiredOption("--file <path>", "prompt.json")).action(action((project, name, opts) => runMcpPrompts("set", project, name, opts)));
15654
+ mcpCommon(mcpPrompts.command("rm").description("Remove a prompt").argument("<project>", "Project name or id").argument("<name>", "Prompt name")).action(action((project, name, opts) => runMcpPrompts("rm", project, name, opts)));
15655
+ var mcpOps = mcpCmd.command("ops").description("Per-tool behavior. Default from the HTTP method: GET/HEAD/OPTIONS read-only, PUT/DELETE idempotent, DELETE destructive + asks to confirm");
15656
+ mcpCommon(mcpOps.command("list").description("Every tool with its effective behavior (overrides marked)").argument("<project>", "Project name or id")).action(action((project, opts) => runMcpOps("list", project, void 0, opts)));
15657
+ mcpCommon(mcpOps.command("set").description("Override one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name").option("--read-only", "Safe to call, changes nothing (e.g. a POST search)").option("--no-read-only", "Not read-only").option("--destructive", "Changes or deletes data (e.g. a GET with side effects)").option("--no-destructive", "Not destructive").option("--idempotent", "Repeating it has no extra effect").option("--no-idempotent", "Not idempotent").option("--confirm", "Ask the user to confirm before running").option("--no-confirm", "Never ask to confirm").option("--always-async", "Return a task handle immediately (long jobs)").option("--no-always-async", "Wait for the result (default)")).action(action((project, tool, opts) => runMcpOps("set", project, tool, opts)));
15658
+ mcpCommon(mcpOps.command("reset").description("Back to the automatic default for one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpOps("reset", project, tool, opts)));
15659
+ mcpCommon(mcpCmd.command("ui").description("Print a tool's MCP Apps UI resource (ui://{project}/{tool}) \u2014 the automatic viewer").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpUi(project, tool, opts)));
15418
15660
  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) => {
15419
- const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
15661
+ const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs6.default.existsSync(project));
15420
15662
  const target2 = opts.target ?? (looksLikeSource ? project : void 0);
15421
15663
  return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
15422
15664
  }));
@@ -15580,7 +15822,7 @@ var HELP_GROUPS = [
15580
15822
  { title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
15581
15823
  { title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
15582
15824
  { title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
15583
- { title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "apikeys", "iam", "identified", "preapprove", "allowoauthregistration", "rule", "domain", "delete", "job", "target", "limits", "billing", "transform", "rename", "spec", "export"] },
15825
+ { title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "apikeys", "iam", "identified", "preapprove", "allowoauthregistration", "rule", "mcp", "domain", "delete", "job", "target", "limits", "billing", "transform", "rename", "spec", "export"] },
15584
15826
  { title: "Data plane commands", commands: [
15585
15827
  { parent: "consumer", sub: "login" },
15586
15828
  { parent: "consumer", sub: "apikeys" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.21.15",
3
+ "version": "0.21.17",
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",