apiblaze 0.21.5 → 0.21.6

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 +754 -226
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -40,9 +40,9 @@ var init_types = __esm({
40
40
  "src/types.ts"() {
41
41
  "use strict";
42
42
  ApiError = class extends Error {
43
- constructor(status, message, body) {
43
+ constructor(status2, message, body) {
44
44
  super(message);
45
- this.status = status;
45
+ this.status = status2;
46
46
  this.body = body;
47
47
  this.name = "ApiError";
48
48
  }
@@ -81,6 +81,8 @@ function loadCredentials() {
81
81
  }
82
82
  }
83
83
  function getAccessToken() {
84
+ const fromEnv = process.env.APIBLAZE_TOKEN;
85
+ if (fromEnv && fromEnv.trim()) return fromEnv.trim();
84
86
  const creds = loadCredentials();
85
87
  if (!creds) {
86
88
  throw new Error("Not authenticated. Run `apiblaze login` first.");
@@ -545,19 +547,19 @@ var init_trace = __esm({
545
547
  });
546
548
 
547
549
  // src/lib/admin.ts
548
- async function admin(call) {
550
+ async function admin(call2) {
549
551
  const token = getAccessToken();
550
552
  const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
551
553
  method: "POST",
552
554
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
553
- body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
555
+ body: JSON.stringify({ path: call2.path, method: call2.method, body: call2.body })
554
556
  });
555
557
  let data = null;
556
558
  try {
557
559
  data = await res.json();
558
560
  } catch {
559
561
  }
560
- recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
562
+ recordCall({ method: call2.method, path: call2.path, body: call2.body, status: res.status, summary: call2.summary });
561
563
  maybePrintBilling(data);
562
564
  if (!res.ok) {
563
565
  const msg = data?.details ?? data?.error ?? res.statusText;
@@ -650,13 +652,13 @@ async function resolveActingTeam(caller, optTeam) {
650
652
  }
651
653
  return resolveTeam(optTeam);
652
654
  }
653
- async function producer(caller, call) {
654
- if (caller.mode === "session") return admin(call);
655
- const out = await cpFetch(caller.cpKey, call.path, {
656
- method: call.method,
657
- ...call.body !== void 0 ? { body: JSON.stringify(call.body) } : {}
655
+ async function producer(caller, call2) {
656
+ if (caller.mode === "session") return admin(call2);
657
+ const out = await cpFetch(caller.cpKey, call2.path, {
658
+ method: call2.method,
659
+ ...call2.body !== void 0 ? { body: JSON.stringify(call2.body) } : {}
658
660
  });
659
- recordCall({ method: call.method, path: call.path, body: call.body, status: 200, summary: call.summary });
661
+ recordCall({ method: call2.method, path: call2.path, body: call2.body, status: 200, summary: call2.summary });
660
662
  return out;
661
663
  }
662
664
  async function resolveProjectVia(caller, teamId, nameOrId, version2) {
@@ -1332,10 +1334,10 @@ var init_tenant_pick = __esm({
1332
1334
  // src/index.ts
1333
1335
  var import_fs5 = __toESM(require("fs"));
1334
1336
  var import_commander = require("commander");
1335
- var import_chalk59 = __toESM(require("chalk"));
1337
+ var import_chalk62 = __toESM(require("chalk"));
1336
1338
 
1337
1339
  // package.json
1338
- var version = "0.21.5";
1340
+ var version = "0.21.6";
1339
1341
 
1340
1342
  // src/index.ts
1341
1343
  init_types();
@@ -1523,11 +1525,11 @@ function colorMethod(method) {
1523
1525
  const colorFn = METHOD_COLORS[method.toUpperCase()] ?? import_chalk3.default.white;
1524
1526
  return colorFn(method.padEnd(7));
1525
1527
  }
1526
- function colorStatus(status) {
1527
- const s = String(status);
1528
- if (status >= 500) return import_chalk3.default.red(s);
1529
- if (status >= 400) return import_chalk3.default.yellow(s);
1530
- if (status >= 300) return import_chalk3.default.cyan(s);
1528
+ function colorStatus(status2) {
1529
+ const s = String(status2);
1530
+ if (status2 >= 500) return import_chalk3.default.red(s);
1531
+ if (status2 >= 400) return import_chalk3.default.yellow(s);
1532
+ if (status2 >= 300) return import_chalk3.default.cyan(s);
1531
1533
  return import_chalk3.default.green(s);
1532
1534
  }
1533
1535
  function colorLatency(latency) {
@@ -2041,8 +2043,8 @@ function startTunnelClient(opts) {
2041
2043
  inflight.delete(id);
2042
2044
  void forward(socket, id, f);
2043
2045
  }
2044
- function sendResponse(socket, id, status, headers, body) {
2045
- send(socket, { id, type: "res", status, headers, bodyLen: body.length });
2046
+ function sendResponse(socket, id, status2, headers, body) {
2047
+ send(socket, { id, type: "res", status: status2, headers, bodyLen: body.length });
2046
2048
  if (body.length === 0) {
2047
2049
  send(socket, { id, type: "chunk", seq: 0, data: "", final: true });
2048
2050
  return;
@@ -2052,7 +2054,7 @@ function startTunnelClient(opts) {
2052
2054
  send(socket, { id, type: "chunk", seq: seq++, data: slice.toString("base64"), final: off + CHUNK_BYTES >= body.length });
2053
2055
  }
2054
2056
  }
2055
- function record(f, reqBody, status, captured, resHeaders, resBody) {
2057
+ function record(f, reqBody, status2, captured, resHeaders, resBody) {
2056
2058
  if (!opts.onRecord) return;
2057
2059
  const req = encodeBody(reqBody);
2058
2060
  const res = resBody ? encodeBody(resBody) : void 0;
@@ -2065,7 +2067,7 @@ function startTunnelClient(opts) {
2065
2067
  requestHeaders: f.headers,
2066
2068
  requestBody: reqBody.length ? req.value : void 0,
2067
2069
  requestBodyEncoding: reqBody.length ? req.encoding : void 0,
2068
- status,
2070
+ status: status2,
2069
2071
  responseHeaders: resHeaders,
2070
2072
  responseBody: res?.value,
2071
2073
  responseBodyEncoding: res?.encoding,
@@ -2078,7 +2080,7 @@ function startTunnelClient(opts) {
2078
2080
  if (body.length) init.body = body;
2079
2081
  try {
2080
2082
  const resp = await fetch(target + f.path, init);
2081
- const status = resp.status;
2083
+ const status2 = resp.status;
2082
2084
  const buf = Buffer.from(await resp.arrayBuffer());
2083
2085
  const headers = {};
2084
2086
  resp.headers.forEach((value, key) => {
@@ -2088,9 +2090,9 @@ function startTunnelClient(opts) {
2088
2090
  capturing = false;
2089
2091
  opts.onResume?.();
2090
2092
  }
2091
- sendResponse(socket, id, status, headers, buf);
2092
- opts.onEntry({ method: f.method, path: f.path, status, latency: Date.now() - f.start });
2093
- record(f, body, status, false, headers, buf);
2093
+ sendResponse(socket, id, status2, headers, buf);
2094
+ opts.onEntry({ method: f.method, path: f.path, status: status2, latency: Date.now() - f.start });
2095
+ record(f, body, status2, false, headers, buf);
2094
2096
  } catch (err) {
2095
2097
  const code = err?.cause?.code;
2096
2098
  if (OFFLINE_CODES.has(code)) {
@@ -2731,101 +2733,101 @@ function refreshInstall(cli, spec2) {
2731
2733
  const r = cli === "claude" ? installIntoClaude(spec2) : installIntoCodex(spec2);
2732
2734
  return r.ok;
2733
2735
  }
2734
- async function installAndDemo(cli, spec2, getQuestion, log = console.log) {
2736
+ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
2735
2737
  if (cli.kind === "claude") {
2736
- log(`
2738
+ log4(`
2737
2739
  ${import_chalk10.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2, true)])}`);
2738
2740
  const r = installIntoClaude(spec2);
2739
2741
  if (!r.ok) {
2740
- log(import_chalk10.default.red(` ${r.conflict ? "" : "Install failed: "}${r.error}`));
2742
+ log4(import_chalk10.default.red(` ${r.conflict ? "" : "Install failed: "}${r.error}`));
2741
2743
  return false;
2742
2744
  }
2743
- log(` ${import_chalk10.default.green("\u2714")} MCP ${import_chalk10.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
2744
- if (spec2.apiKey) log(import_chalk10.default.dim(" The API key is stored in Claude's local MCP config for this directory."));
2745
+ log4(` ${import_chalk10.default.green("\u2714")} MCP ${import_chalk10.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
2746
+ if (spec2.apiKey) log4(import_chalk10.default.dim(" The API key is stored in Claude's local MCP config for this directory."));
2745
2747
  } else {
2746
2748
  const r = installIntoCodex(spec2);
2747
2749
  if (!r.ok) {
2748
- log(import_chalk10.default.red(` ${r.conflict ? "" : `Could not write ${r.path}: `}${r.error}`));
2750
+ log4(import_chalk10.default.red(` ${r.conflict ? "" : `Could not write ${r.path}: `}${r.error}`));
2749
2751
  return false;
2750
2752
  }
2751
- log(` ${import_chalk10.default.green("\u2714")} MCP ${import_chalk10.default.bold(spec2.name)} added to ${r.path}.`);
2753
+ log4(` ${import_chalk10.default.green("\u2714")} MCP ${import_chalk10.default.bold(spec2.name)} added to ${r.path}.`);
2752
2754
  }
2753
2755
  if (!spec2.apiKey) {
2754
2756
  if (!supportsMcpLogin(cli.kind)) {
2755
- log(`
2757
+ log4(`
2756
2758
  ${import_chalk10.default.dim("This proxy authenticates by login.")}`);
2757
- log(import_chalk10.default.yellow(` Your ${cli.label}${cli.version ? ` (${cli.version})` : ""} can't start that sign-in from the shell.`));
2758
- log(import_chalk10.default.dim(` ${manualAuthHint(cli.kind, spec2.name)}`));
2759
- log(import_chalk10.default.dim(" (Upgrading the CLI lets apichat do this for you.)"));
2759
+ log4(import_chalk10.default.yellow(` Your ${cli.label}${cli.version ? ` (${cli.version})` : ""} can't start that sign-in from the shell.`));
2760
+ log4(import_chalk10.default.dim(` ${manualAuthHint(cli.kind, spec2.name)}`));
2761
+ log4(import_chalk10.default.dim(" (Upgrading the CLI lets apichat do this for you.)"));
2760
2762
  } else {
2761
2763
  const loginArgv = [cli.kind, "mcp", "login", spec2.name];
2762
- log(`
2764
+ log4(`
2763
2765
  ${import_chalk10.default.dim("This proxy authenticates by login \u2014 signing you in:")}`);
2764
- log(` ${import_chalk10.default.dim("$")} ${renderCommand(loginArgv)}`);
2766
+ log4(` ${import_chalk10.default.dim("$")} ${renderCommand(loginArgv)}`);
2765
2767
  const r = run(loginArgv[0], loginArgv.slice(1), { interactive: true });
2766
2768
  if (r.status !== 0) {
2767
- log(import_chalk10.default.yellow(` Sign-in didn't complete (exit ${r.status ?? "?"}). Run \`${renderCommand(loginArgv)}\` yourself to finish it.`));
2769
+ log4(import_chalk10.default.yellow(` Sign-in didn't complete (exit ${r.status ?? "?"}). Run \`${renderCommand(loginArgv)}\` yourself to finish it.`));
2768
2770
  } else {
2769
- log(` ${import_chalk10.default.green("\u2714")} Signed in.`);
2771
+ log4(` ${import_chalk10.default.green("\u2714")} Signed in.`);
2770
2772
  }
2771
2773
  }
2772
2774
  }
2773
2775
  if (cli.kind === "claude") {
2774
- const status = waitForClaudeConnection(spec2.name);
2775
- if (status !== "connected") {
2776
- log(import_chalk10.default.yellow(`
2777
- ${cli.label} has the MCP configured, but reports: ${status === "needs-auth" ? "needs authentication" : status === "failed" ? "failed to connect" : "unknown status"}.`));
2778
- if (status === "needs-auth") {
2779
- log(import_chalk10.default.dim(supportsMcpLogin("claude") ? ` Finish the sign-in with: claude mcp login ${spec2.name}` : ` ${manualAuthHint("claude", spec2.name)}`));
2776
+ const status2 = waitForClaudeConnection(spec2.name);
2777
+ if (status2 !== "connected") {
2778
+ log4(import_chalk10.default.yellow(`
2779
+ ${cli.label} has the MCP configured, but reports: ${status2 === "needs-auth" ? "needs authentication" : status2 === "failed" ? "failed to connect" : "unknown status"}.`));
2780
+ if (status2 === "needs-auth") {
2781
+ log4(import_chalk10.default.dim(supportsMcpLogin("claude") ? ` Finish the sign-in with: claude mcp login ${spec2.name}` : ` ${manualAuthHint("claude", spec2.name)}`));
2780
2782
  } else {
2781
- log(import_chalk10.default.dim(` Check it with: claude mcp get ${spec2.name} (or /mcp inside Claude Code)`));
2783
+ log4(import_chalk10.default.dim(` Check it with: claude mcp get ${spec2.name} (or /mcp inside Claude Code)`));
2782
2784
  }
2783
- log(import_chalk10.default.dim(" Chatting here instead so you still get an answer."));
2785
+ log4(import_chalk10.default.dim(" Chatting here instead so you still get an answer."));
2784
2786
  return false;
2785
2787
  }
2786
- log(` ${import_chalk10.default.green("\u2714")} ${cli.label} reports the server connected.`);
2788
+ log4(` ${import_chalk10.default.green("\u2714")} ${cli.label} reports the server connected.`);
2787
2789
  }
2788
2790
  const oneShot = cli.kind === "claude" ? claudeOneShot : codexOneShot;
2789
2791
  const abilities = `What are the tool abilities of the MCP server "${spec2.name}"? List them briefly.`;
2790
- log(`
2792
+ log4(`
2791
2793
  ${import_chalk10.default.dim("Checking what the API can do\u2026")}`);
2792
- log(` ${import_chalk10.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", "--skip-git-repo-check", abilities])}
2794
+ log4(` ${import_chalk10.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", "--skip-git-repo-check", abilities])}
2793
2795
  `);
2794
2796
  const check = oneShot(spec2, abilities);
2795
2797
  if (check.status !== 0) {
2796
- log(import_chalk10.default.yellow(`
2798
+ log4(import_chalk10.default.yellow(`
2797
2799
  ${cli.label} exited with ${check.status ?? "no status"} \u2014 the MCP is installed, but the demo call failed.`));
2798
- log(import_chalk10.default.yellow(` Open ${cli.label} and try it there${cli.kind === "claude" ? " (use /mcp to inspect the connection)" : ""}.`));
2800
+ log4(import_chalk10.default.yellow(` Open ${cli.label} and try it there${cli.kind === "claude" ? " (use /mcp to inspect the connection)" : ""}.`));
2799
2801
  return true;
2800
2802
  }
2801
2803
  const question = await getQuestion();
2802
2804
  if (question) {
2803
2805
  const handoff = !!process.stdin.isTTY;
2804
2806
  const ask = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
2805
- log(`
2807
+ log4(`
2806
2808
  ${import_chalk10.default.dim(handoff ? `Opening ${cli.label} with your question \u2014 you'll stay in that session:` : `Your question, through ${cli.label}:`)}`);
2807
2809
  const directed = directedPrompt(spec2, question);
2808
2810
  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];
2809
- log(` ${import_chalk10.default.dim("$")} ${renderCommand(shown)}
2811
+ log4(` ${import_chalk10.default.dim("$")} ${renderCommand(shown)}
2810
2812
  `);
2811
2813
  const ans = ask(spec2, directedPrompt(spec2, question));
2812
- if (ans.status !== 0) log(import_chalk10.default.yellow(`
2814
+ if (ans.status !== 0) log4(import_chalk10.default.yellow(`
2813
2815
  ${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
2814
2816
  }
2815
2817
  const instructionFile = writeAgentInstruction(cli.kind, spec2);
2816
2818
  const off = `npx apiblaze apichat ${spec2.name} --remove-mcp ${cli.kind}`;
2817
2819
  const mdName = cli.kind === "claude" ? "CLAUDE.md" : "AGENTS.md";
2818
- log("");
2819
- log(import_chalk10.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
2820
- log(import_chalk10.default.red.bold(` Every ${cli.label} session in this directory can call your API's tools.`));
2820
+ log4("");
2821
+ log4(import_chalk10.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
2822
+ log4(import_chalk10.default.red.bold(` Every ${cli.label} session in this directory can call your API's tools.`));
2821
2823
  if (instructionFile) {
2822
- log(import_chalk10.default.red(` We added a "use this MCP" block to your ${import_chalk10.default.bold(mdName)} \u2014 that is what makes it stick:`));
2823
- log(import_chalk10.default.red(` ${instructionFile}`));
2824
+ log4(import_chalk10.default.red(` We added a "use this MCP" block to your ${import_chalk10.default.bold(mdName)} \u2014 that is what makes it stick:`));
2825
+ log4(import_chalk10.default.red(` ${instructionFile}`));
2824
2826
  } else {
2825
- log(import_chalk10.default.red(` (Could not write ${mdName} here, so this applies only to sessions apichat starts.)`));
2827
+ log4(import_chalk10.default.red(` (Could not write ${mdName} here, so this applies only to sessions apichat starts.)`));
2826
2828
  }
2827
- log(import_chalk10.default.red(` Turn it off (removes the MCP server AND the ${mdName} block):`));
2828
- log(import_chalk10.default.red(` ${import_chalk10.default.bold(off)}`));
2829
+ log4(import_chalk10.default.red(` Turn it off (removes the MCP server AND the ${mdName} block):`));
2830
+ log4(import_chalk10.default.red(` ${import_chalk10.default.bold(off)}`));
2829
2831
  return true;
2830
2832
  }
2831
2833
 
@@ -3301,13 +3303,13 @@ async function provision(spec2, target, opts) {
3301
3303
  spinner.succeed(`Proxy provisioned${name !== base2 ? ` as "${name}"` : ""}.`);
3302
3304
  break;
3303
3305
  } catch (err) {
3304
- const status = err instanceof ApiError ? err.status : void 0;
3306
+ const status2 = err instanceof ApiError ? err.status : void 0;
3305
3307
  const collision = err instanceof ApiError && (err.status === 409 || /exist|taken|available/i.test(err.message));
3306
3308
  if (collision && attempt < 3) {
3307
3309
  name = `${base2}${salt()}`;
3308
3310
  continue;
3309
3311
  }
3310
- if (status === 401) {
3312
+ if (status2 === 401) {
3311
3313
  if (!loggedIn && attempt < 3) {
3312
3314
  clearAnonCred();
3313
3315
  continue;
@@ -3682,9 +3684,9 @@ async function replTurn(p, messages, userText) {
3682
3684
  const id = String(ev.toolCallId ?? "");
3683
3685
  const meta = toolMeta.get(id);
3684
3686
  const ok = ev.type === "tool-output-available";
3685
- const ms = meta ? Date.now() - meta.startedAt : void 0;
3687
+ const ms2 = meta ? Date.now() - meta.startedAt : void 0;
3686
3688
  const mark = ok ? import_chalk12.default.green("\u2713") : import_chalk12.default.red("\u2717");
3687
- console.log(` ${mark} ${import_chalk12.default.cyan(meta?.name ?? "tool")} ${import_chalk12.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
3689
+ console.log(` ${mark} ${import_chalk12.default.cyan(meta?.name ?? "tool")} ${import_chalk12.default.dim(`(${ok ? "ok" : "error"}${ms2 != null ? `, ${ms2}ms` : ""})`)}`);
3688
3690
  const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
3689
3691
  if (meta) {
3690
3692
  Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
@@ -5084,10 +5086,10 @@ async function chooseSecure(args) {
5084
5086
  return null;
5085
5087
  }
5086
5088
  const spinner = quiet ? null : (0, import_ora6.default)("Reading your OpenAPI spec\u2026").start();
5087
- const { status, data } = await proposeFromSpec(openapi).catch((err) => ({ status: 0, data: { error: err.message } }));
5088
- if (status !== 200 || !data || !Array.isArray(data.families)) {
5089
+ const { status: status2, data } = await proposeFromSpec(openapi).catch((err) => ({ status: 0, data: { error: err.message } }));
5090
+ if (status2 !== 200 || !data || !Array.isArray(data.families)) {
5089
5091
  spinner?.stop();
5090
- const why = data?.details ?? data?.error ?? (status ? `HTTP ${status}` : "network error");
5092
+ const why = data?.details ?? data?.error ?? (status2 ? `HTTP ${status2}` : "network error");
5091
5093
  say(import_chalk16.default.yellow(` \u26A0 Could not work out what to protect (${why}) \u2014 creating without rules. Later: npx ${B.cli} rule ${name}`));
5092
5094
  return null;
5093
5095
  }
@@ -6227,7 +6229,7 @@ async function runDev(options) {
6227
6229
  projectId,
6228
6230
  localPort: options.port,
6229
6231
  onEntry: (entry) => say(formatLogLine(entry)),
6230
- onStatus: (status) => say(import_chalk17.default.gray(`[${projectId}] ${status}`)),
6232
+ onStatus: (status2) => say(import_chalk17.default.gray(`[${projectId}] ${status2}`)),
6231
6233
  onCapture: (req, note) => say(formatCapturedRequest(req, note)),
6232
6234
  onCaptureStart: () => say(import_chalk17.default.magenta(`
6233
6235
  \u26B2 No local server on port ${options.port} yet \u2014 capturing requests below. Start your server and they'll forward automatically.
@@ -7080,14 +7082,14 @@ async function runAgentChatRepl(opts) {
7080
7082
  async function turn(content) {
7081
7083
  messages.push({ role: "user", content });
7082
7084
  process.stdout.write(import_chalk26.default.dim(" \u2026thinking\n"));
7083
- const { status, data } = await agentCall(endpoint, "POST", { messages, ...buildBody(messages) });
7084
- if (status === 402) {
7085
+ const { status: status2, data } = await agentCall(endpoint, "POST", { messages, ...buildBody(messages) });
7086
+ if (status2 === 402) {
7085
7087
  console.log(import_chalk26.default.red(" Insufficient credits \u2014 top up to keep using the agents.\n"));
7086
7088
  messages.pop();
7087
7089
  return;
7088
7090
  }
7089
- if (status >= 400 || !data) {
7090
- console.log(import_chalk26.default.red(` Error (${status}): ${(data && data.error) ?? "request failed"}
7091
+ if (status2 >= 400 || !data) {
7092
+ console.log(import_chalk26.default.red(` Error (${status2}): ${(data && data.error) ?? "request failed"}
7091
7093
  `));
7092
7094
  messages.pop();
7093
7095
  return;
@@ -7188,17 +7190,17 @@ function maybeWarnNoTraffic(total, projectArg) {
7188
7190
  `));
7189
7191
  }
7190
7192
  async function applyProposal(opts) {
7191
- const log = opts.log ?? ((s) => console.log(s));
7193
+ const log4 = opts.log ?? ((s) => console.log(s));
7192
7194
  const { projectId, apiVersion, tenant: tenant2, proposal, enable } = opts;
7193
7195
  {
7194
7196
  const m = await agentCall(`/projects/${projectId}/${apiVersion}/policies/model?tenantId=${encodeURIComponent(tenant2)}`, "POST", proposal.model);
7195
7197
  if (m.status === 404) {
7196
- log(import_chalk27.default.red(` Project ${projectId} ${apiVersion} not found on the authorization service \u2014 check \`apiblaze projects\`.
7198
+ log4(import_chalk27.default.red(` Project ${projectId} ${apiVersion} not found on the authorization service \u2014 check \`apiblaze projects\`.
7197
7199
  `));
7198
7200
  return { ok: 0, fail: 0, enabled: false, aborted: true };
7199
7201
  }
7200
7202
  if (m.status >= 400) {
7201
- log(import_chalk27.default.red(` Model save failed (${m.status}): ${m.data?.error ?? ""}
7203
+ log4(import_chalk27.default.red(` Model save failed (${m.status}): ${m.data?.error ?? ""}
7202
7204
  `));
7203
7205
  return { ok: 0, fail: 0, enabled: false, aborted: true };
7204
7206
  }
@@ -7230,17 +7232,17 @@ async function applyProposal(opts) {
7230
7232
  }
7231
7233
  }
7232
7234
  if (fail6 > 0) {
7233
- log(import_chalk27.default.red(` ${fail6} of ${proposal.routes.length} route rule(s) could not be saved:`));
7234
- for (const f of failed) log(import_chalk27.default.red(` ${f}`));
7235
+ log4(import_chalk27.default.red(` ${fail6} of ${proposal.routes.length} route rule(s) could not be saved:`));
7236
+ for (const f of failed) log4(import_chalk27.default.red(` ${f}`));
7235
7237
  if (enable) {
7236
- log(import_chalk27.default.red(" Enforcement NOT turned on \u2014 a route without a saved rule is left open by the gateway. Fix the rule(s) above and re-run with --enforce.\n"));
7238
+ log4(import_chalk27.default.red(" Enforcement NOT turned on \u2014 a route without a saved rule is left open by the gateway. Fix the rule(s) above and re-run with --enforce.\n"));
7237
7239
  }
7238
7240
  return { ok, fail: fail6, enabled: false, aborted: true, failed };
7239
7241
  }
7240
7242
  if (enable) {
7241
7243
  const e = await agentCall(`/projects/${projectId}/${apiVersion}`, "PATCH", { authorization: { enforce_authorization: true }, tenant: tenant2 });
7242
7244
  if (e.status >= 400) {
7243
- log(import_chalk27.default.red(` Enforced ${ok} route(s) but turning on the project-level switch failed (${e.status}). Toggle "Enforce Authorization" on in the dashboard.
7245
+ log4(import_chalk27.default.red(` Enforced ${ok} route(s) but turning on the project-level switch failed (${e.status}). Toggle "Enforce Authorization" on in the dashboard.
7244
7246
  `));
7245
7247
  return { ok, fail: fail6, enabled: false, failed };
7246
7248
  }
@@ -7330,13 +7332,13 @@ async function runRule(projectArg, ruleText, opts) {
7330
7332
  role: "user",
7331
7333
  content: `Author this authorization rule and generate the COMPLETE proposal (OpenFGA model + per-route rules) NOW for every affected route. Do not ask clarifying questions; make reasonable assumptions and state them briefly. Rule: "${rule}"`
7332
7334
  }];
7333
- const { status, data } = await agentCall(
7335
+ const { status: status2, data } = await agentCall(
7334
7336
  `/projects/${projectId}/${apiVersion}/authz/chat`,
7335
7337
  "POST",
7336
7338
  { messages, included_sample_ids: sampleIds, existing_model: null, existing_routes: [] }
7337
7339
  );
7338
- if (status >= 400) {
7339
- spinner.fail(`Authorization agent error (${status}): ${data?.error ?? ""}`);
7340
+ if (status2 >= 400) {
7341
+ spinner.fail(`Authorization agent error (${status2}): ${data?.error ?? ""}`);
7340
7342
  return;
7341
7343
  }
7342
7344
  const proposal = data?.proposal;
@@ -7759,16 +7761,16 @@ async function runExport(projectArg, versionArg, opts) {
7759
7761
  });
7760
7762
  if (!job?.job_id) throw new Error("Failed to queue export.");
7761
7763
  const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
7762
- let status = "queued";
7764
+ let status2 = "queued";
7763
7765
  const started = Date.now();
7764
7766
  process.stdout.write(import_chalk35.default.dim("Building bundle"));
7765
- while (status !== "ready" && status !== "failed") {
7767
+ while (status2 !== "ready" && status2 !== "failed") {
7766
7768
  if (Date.now() - started > 10 * 6e4) throw new Error("Export timed out after 10 minutes.");
7767
7769
  await new Promise((r) => setTimeout(r, 3e3));
7768
7770
  process.stdout.write(import_chalk35.default.dim("."));
7769
7771
  const st = await admin({ path: statusPath, method: "GET", summary: "poll export" });
7770
- status = st?.status ?? "queued";
7771
- if (status === "failed") {
7772
+ status2 = st?.status ?? "queued";
7773
+ if (status2 === "failed") {
7772
7774
  process.stdout.write("\n");
7773
7775
  throw new Error(`Export failed: ${st?.error ?? "unknown error"}`);
7774
7776
  }
@@ -7854,38 +7856,38 @@ async function publicFetch(path11) {
7854
7856
  }
7855
7857
  return { status: res.status, data, json };
7856
7858
  }
7857
- function registryDown(status) {
7859
+ function registryDown(status2) {
7858
7860
  return new Error(
7859
- `The recipe registry at ${RECIPES_BASE}/api/recipes did not answer with JSON (HTTP ${status}).
7861
+ `The recipe registry at ${RECIPES_BASE}/api/recipes did not answer with JSON (HTTP ${status2}).
7860
7862
  Set APIBLAZE_RECIPES_BASE if you are pointing at a different deployment.`
7861
7863
  );
7862
7864
  }
7863
7865
  async function searchRecipes(query, limit) {
7864
7866
  const q = `?q=${encodeURIComponent(query ?? "")}${limit ? `&limit=${limit}` : ""}`;
7865
- const { status, data, json } = await publicFetch(`/search${q}`);
7866
- if (!json) throw registryDown(status);
7867
- if (status >= 400) throw new ApiError(status, data?.error ?? "Search failed.");
7867
+ const { status: status2, data, json } = await publicFetch(`/search${q}`);
7868
+ if (!json) throw registryDown(status2);
7869
+ if (status2 >= 400) throw new ApiError(status2, data?.error ?? "Search failed.");
7868
7870
  return data?.recipes ?? [];
7869
7871
  }
7870
7872
  async function getRecipeHead(ref) {
7871
- const { status, data, json } = await publicFetch(`/${ref.handle}/${ref.slug}`);
7872
- if (!json) throw registryDown(status);
7873
- if (status === 404) return null;
7874
- if (status >= 400) throw new ApiError(status, data?.error ?? "Lookup failed.");
7873
+ const { status: status2, data, json } = await publicFetch(`/${ref.handle}/${ref.slug}`);
7874
+ if (!json) throw registryDown(status2);
7875
+ if (status2 === 404) return null;
7876
+ if (status2 >= 400) throw new ApiError(status2, data?.error ?? "Lookup failed.");
7875
7877
  return data;
7876
7878
  }
7877
7879
  async function fetchRecipeFile(ref, caller) {
7878
7880
  const head = ref.revision !== void 0 ? null : await getRecipeHead(ref);
7879
7881
  const revision = ref.revision ?? head?.revision;
7880
7882
  if (revision !== void 0) {
7881
- const { status, data, json } = await publicFetch(
7883
+ const { status: status2, data, json } = await publicFetch(
7882
7884
  `/${ref.handle}/${ref.slug}/${revision}`
7883
7885
  );
7884
- if (!json) throw registryDown(status);
7885
- if (status === 410) {
7886
+ if (!json) throw registryDown(status2);
7887
+ if (status2 === 410) {
7886
7888
  throw new Error(`${ref.name}@${revision} was withdrawn by its publisher. Try a different revision.`);
7887
7889
  }
7888
- if (status < 400 && data) return data;
7890
+ if (status2 < 400 && data) return data;
7889
7891
  }
7890
7892
  if (!caller) {
7891
7893
  throw new Error(
@@ -7901,8 +7903,8 @@ async function fetchRecipeFile(ref, caller) {
7901
7903
  summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
7902
7904
  });
7903
7905
  } catch (err) {
7904
- const status = err instanceof ApiError ? err.status : 0;
7905
- if (status === 404 || status === 400) {
7906
+ const status2 = err instanceof ApiError ? err.status : 0;
7907
+ if (status2 === 404 || status2 === 400) {
7906
7908
  throw new Error(
7907
7909
  `${ref.name} does not exist, or it is private and your team does not own it.
7908
7910
  (admin-api exposes no authorized read for a private recipe file yet \u2014 only GET /recipes/public/* is registered, so private install cannot resolve.)`
@@ -9124,10 +9126,10 @@ init_resolve();
9124
9126
  // src/lib/log-view.ts
9125
9127
  var import_chalk39 = __toESM(require("chalk"));
9126
9128
  var readline2 = __toESM(require("readline"));
9127
- function statusColor(status) {
9128
- if (status < 300) return import_chalk39.default.green;
9129
- if (status < 400) return import_chalk39.default.blue;
9130
- if (status < 500) return import_chalk39.default.yellow;
9129
+ function statusColor(status2) {
9130
+ if (status2 < 300) return import_chalk39.default.green;
9131
+ if (status2 < 400) return import_chalk39.default.blue;
9132
+ if (status2 < 500) return import_chalk39.default.yellow;
9131
9133
  return import_chalk39.default.red;
9132
9134
  }
9133
9135
  function methodColor(method) {
@@ -9157,10 +9159,10 @@ function formatRow(r) {
9157
9159
  const time = import_chalk39.default.dim(clock(r.ts));
9158
9160
  const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
9159
9161
  const route = routeCell(String(r.route || "\u2014"));
9160
- const status = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
9161
- const ms = import_chalk39.default.dim(`${r.duration_ms}ms`.padStart(8));
9162
+ const status2 = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
9163
+ const ms2 = import_chalk39.default.dim(`${r.duration_ms}ms`.padStart(8));
9162
9164
  const step = r.error_step ? import_chalk39.default.dim(` ${r.error_step}`) : "";
9163
- return `${time} ${method} ${route} ${status} ${ms}${step}`;
9165
+ return `${time} ${method} ${route} ${status2} ${ms2}${step}`;
9164
9166
  }
9165
9167
  function printRow(r, json) {
9166
9168
  console.log(json ? JSON.stringify(r) : formatRow(r));
@@ -13026,10 +13028,33 @@ async function runOriginsRemove(origin, opts) {
13026
13028
  }
13027
13029
 
13028
13030
  // src/commands/op.ts
13029
- var import_chalk58 = __toESM(require("chalk"));
13031
+ var import_chalk61 = __toESM(require("chalk"));
13032
+ init_auth();
13033
+
13034
+ // src/lib/op-call.ts
13030
13035
  init_auth();
13031
13036
  init_trace();
13032
13037
  init_types();
13038
+ var DASHBOARD_BASE8 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
13039
+ async function opCall(call2) {
13040
+ const token = getAccessToken();
13041
+ const res = await fetch(`${DASHBOARD_BASE8}/api/cli/op`, {
13042
+ method: "POST",
13043
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
13044
+ body: JSON.stringify({ path: call2.path, method: call2.method, body: call2.body })
13045
+ });
13046
+ let data = null;
13047
+ try {
13048
+ data = await res.json();
13049
+ } catch {
13050
+ }
13051
+ recordCall({ method: call2.method, path: call2.path, body: call2.body, status: res.status, summary: call2.summary });
13052
+ if (!res.ok) {
13053
+ const msg = data?.details ?? data?.error ?? res.statusText;
13054
+ throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
13055
+ }
13056
+ return data;
13057
+ }
13033
13058
 
13034
13059
  // src/commands/op-billing.ts
13035
13060
  var import_chalk57 = __toESM(require("chalk"));
@@ -13043,7 +13068,7 @@ var SANDBOX = {
13043
13068
  };
13044
13069
  var dpHost = `${SANDBOX.project}.abz.run`;
13045
13070
  var ZONE_COUNT = 3;
13046
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
13071
+ var sleep = (ms2) => new Promise((r) => setTimeout(r, ms2));
13047
13072
  async function readQuiet(read, tries = 8) {
13048
13073
  let prev = await read();
13049
13074
  for (let i = 0; i < tries; i++) {
@@ -13261,32 +13286,502 @@ function printChecks(checks) {
13261
13286
  console.log("");
13262
13287
  }
13263
13288
 
13264
- // src/commands/op.ts
13265
- var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
13266
- var DASHBOARD_BASE8 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
13267
- function isOperatorLogin() {
13268
- const email = loadCredentials()?.email?.toLowerCase();
13269
- return !!email && OPERATOR_EMAILS.has(email);
13270
- }
13271
- async function opCall(call) {
13289
+ // src/commands/op-test.ts
13290
+ var import_chalk58 = __toESM(require("chalk"));
13291
+ init_auth();
13292
+ init_types();
13293
+ var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
13294
+ async function runnerCall(body, timeoutS) {
13272
13295
  const token = getAccessToken();
13273
- const res = await fetch(`${DASHBOARD_BASE8}/api/cli/op`, {
13296
+ const res = await fetch(`${DASHBOARD_BASE9}/api/cli/op/test`, {
13274
13297
  method: "POST",
13275
13298
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
13276
- body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
13299
+ body: JSON.stringify(body),
13300
+ signal: AbortSignal.timeout(timeoutS * 1e3)
13277
13301
  });
13278
- let data = null;
13279
- try {
13280
- data = await res.json();
13281
- } catch {
13282
- }
13283
- recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
13302
+ const data = await res.json().catch(() => ({}));
13284
13303
  if (!res.ok) {
13285
- const msg = data?.details ?? data?.error ?? res.statusText;
13304
+ const msg = data.error ?? res.statusText;
13286
13305
  throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
13287
13306
  }
13288
13307
  return data;
13289
13308
  }
13309
+ var call = runnerCall;
13310
+ var matches = (id, group2, o) => {
13311
+ if (o.only) {
13312
+ const wanted = o.only.split(",").map((s) => s.trim()).filter(Boolean);
13313
+ if (!wanted.some((w) => id === w || id.startsWith(w))) return false;
13314
+ }
13315
+ if (o.group && group2 && !group2.toLowerCase().includes(o.group.toLowerCase())) return false;
13316
+ if (o.group && !group2) return false;
13317
+ return true;
13318
+ };
13319
+ var ms = (n) => n >= 1e3 ? `${(n / 1e3).toFixed(1)}s` : `${n}ms`;
13320
+ var log = (s) => process.stderr.write(s + "\n");
13321
+ async function runOpTest(o = {}) {
13322
+ const timeoutS = o.timeout && o.timeout > 0 ? o.timeout : 150;
13323
+ const listing = await call({ action: "list" }, 60);
13324
+ const specs = o.gutsOnly ? [] : listing.specs.filter((s) => matches(s.id, s.group, o));
13325
+ const guts = o.skipGuts ? [] : listing.guts.filter((g) => matches(g.id, void 0, { ...o, group: void 0 }));
13326
+ const total = specs.length + guts.length;
13327
+ if (total === 0) {
13328
+ log(import_chalk58.default.yellow("nothing matched"));
13329
+ process.exitCode = 2;
13330
+ return;
13331
+ }
13332
+ log(import_chalk58.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
13333
+ const results = [];
13334
+ let failed = 0;
13335
+ const started = Date.now();
13336
+ const record = (r) => {
13337
+ results.push(r);
13338
+ if (!r.ok) failed += 1;
13339
+ const mark = r.ok ? import_chalk58.default.green("PASS") : import_chalk58.default.red("FAIL");
13340
+ log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
13341
+ if (!r.ok) {
13342
+ if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk58.default.red(` \u2717 ${c.name}: ${c.detail}`));
13343
+ else if (r.error) log(import_chalk58.default.red(` \u2717 ${r.error}`));
13344
+ else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(import_chalk58.default.red(` \u2717 ${x.name ?? ""}: ${x.detail ?? JSON.stringify(x)}`));
13345
+ }
13346
+ };
13347
+ for (const s of specs) {
13348
+ let r;
13349
+ try {
13350
+ r = await call({ action: "spec", id: s.id }, timeoutS);
13351
+ } catch (e) {
13352
+ r = { kind: "spec", id: s.id, label: s.label, group: s.group, ok: false, cases: [{ name: "runner", ok: false, detail: e instanceof Error ? e.message : String(e) }], duration_ms: 0 };
13353
+ }
13354
+ record(r);
13355
+ if (!r.ok && o.failFast) break;
13356
+ }
13357
+ if (!(o.failFast && failed)) {
13358
+ for (const g of guts) {
13359
+ let r;
13360
+ try {
13361
+ r = await call({ action: "gut", id: g.id }, timeoutS);
13362
+ } catch (e) {
13363
+ r = { kind: "gut", id: g.id, label: g.label, ok: false, results: [], error: e instanceof Error ? e.message : String(e), duration_ms: 0 };
13364
+ }
13365
+ record(r);
13366
+ if (!r.ok && o.failFast) break;
13367
+ }
13368
+ }
13369
+ const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
13370
+ log("");
13371
+ log(failed ? import_chalk58.default.red(`${summary.passed}/${summary.ran} passed, ${failed} failed (${ms(summary.duration_ms)})`) : import_chalk58.default.green(`${summary.passed}/${summary.ran} passed (${ms(summary.duration_ms)})`));
13372
+ if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
13373
+ if (failed) process.exitCode = 1;
13374
+ }
13375
+
13376
+ // src/commands/op-consumer.ts
13377
+ var import_chalk59 = __toESM(require("chalk"));
13378
+ var SANDBOX_TENANT = "portalteststenant";
13379
+ var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
13380
+ function proxyUrlFor(spec2, path11 = "/get") {
13381
+ const [name, version2 = "1.0.0"] = spec2.split("@");
13382
+ return `https://${name}.abz.run/${version2}/prod${path11.startsWith("/") ? path11 : `/${path11}`}`;
13383
+ }
13384
+ function isPortalAudience(aud) {
13385
+ return /^https:\/\/[a-z0-9-]+\.portal\.apiblaze\.com\b/i.test(aud);
13386
+ }
13387
+ var log2 = (s) => process.stderr.write(s + "\n");
13388
+ function probeUrlFor(audience, path11 = "/") {
13389
+ const base2 = audience.replace(/\/+$/, "");
13390
+ return `${base2}/prod${path11.startsWith("/") ? path11 : `/${path11}`}`;
13391
+ }
13392
+ function tamper(token) {
13393
+ const parts = token.split(".");
13394
+ if (parts.length !== 3) return token + "x";
13395
+ const sig = parts[2];
13396
+ const i = Math.floor(sig.length / 2);
13397
+ const c = sig[i] === "A" ? "B" : "A";
13398
+ parts[2] = sig.slice(0, i) + c + sig.slice(i + 1);
13399
+ return parts.join(".");
13400
+ }
13401
+ function audienceOf(token) {
13402
+ try {
13403
+ const payload = JSON.parse(Buffer.from(token.split(".")[1].replace(/-/g, "+").replace(/_/g, "/"), "base64").toString());
13404
+ return typeof payload.aud === "string" ? payload.aud : Array.isArray(payload.aud) ? String(payload.aud[0]) : null;
13405
+ } catch {
13406
+ return null;
13407
+ }
13408
+ }
13409
+ async function status(url, headers) {
13410
+ const res = await fetch(url, { headers, signal: AbortSignal.timeout(15e3), redirect: "manual" });
13411
+ const body = (await res.text().catch(() => "")).slice(0, 300);
13412
+ return { status: res.status, body };
13413
+ }
13414
+ async function runOpConsumer(o = {}) {
13415
+ const t0 = Date.now();
13416
+ const creds = loadConsumer();
13417
+ const done = (r) => {
13418
+ const out = { kind: "consumer", id: "consumer-door", label: "Consumer door: the stored consumer token opens its tenant's OAuth-door proxy", duration_ms: Date.now() - t0, ...r };
13419
+ const mark = out.skipped ? import_chalk59.default.yellow("SKIP") : out.ok ? import_chalk59.default.green("PASS") : import_chalk59.default.red("FAIL");
13420
+ log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
13421
+ for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ? import_chalk59.default.dim : import_chalk59.default.red)(` ${c.ok ? "ok " : "\u2717"} ${c.name}: ${c.detail}`));
13422
+ if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
13423
+ if (!out.ok && !out.skipped) process.exitCode = 1;
13424
+ if (out.skipped) process.exitCode = 2;
13425
+ return out;
13426
+ };
13427
+ if (!creds) {
13428
+ return done({ ok: false, skipped: true, cases: [{ name: "consumer session", ok: false, detail: "none stored \u2014 run `apiblaze consumer login --team <team> --tenant <tenant>` first" }] });
13429
+ }
13430
+ if (Date.now() >= creds.expiresAt) {
13431
+ return done({ ok: false, skipped: true, tenant: creds.tenant, cases: [{ name: "consumer session", ok: false, detail: `expired ${new Date(creds.expiresAt).toISOString()} \u2014 run \`apiblaze consumer login --tenant ${creds.tenant}\`` }] });
13432
+ }
13433
+ const audience = audienceOf(creds.accessToken);
13434
+ if (!audience) {
13435
+ return done({ ok: false, tenant: creds.tenant, cases: [{ name: "token audience", ok: false, detail: "the stored access token carries no aud claim" }] });
13436
+ }
13437
+ let url = null;
13438
+ if (o.url) url = o.url;
13439
+ else if (o.proxy) url = proxyUrlFor(o.proxy, o.path ?? "/get");
13440
+ else if (creds.tenant === SANDBOX_TENANT) url = proxyUrlFor(SANDBOX_PROXY, o.path ?? "/get");
13441
+ else if (!isPortalAudience(audience)) url = probeUrlFor(audience, o.path ?? "/");
13442
+ if (!url) {
13443
+ return done({ ok: false, skipped: true, tenant: creds.tenant, audience, cases: [{ name: "target", ok: false, detail: `the session's audience is the tenant portal (${audience}); pass --proxy <name@version> (an OAuth-door proxy of tenant ${creds.tenant}) or --url` }] });
13444
+ }
13445
+ const cases = [];
13446
+ try {
13447
+ const noAuth = await status(url, {});
13448
+ cases.push({ name: "no token \u2192 401", ok: noAuth.status === 401, detail: `${noAuth.status} ${noAuth.body}` });
13449
+ const withAuth = await status(url, { Authorization: `Bearer ${creds.accessToken}` });
13450
+ const opened = withAuth.status !== 401 && withAuth.status !== 403;
13451
+ cases.push({ name: "valid token \u2192 door opens (not 401/403)", ok: opened, detail: `${withAuth.status} ${withAuth.body}` });
13452
+ const bad = await status(url, { Authorization: `Bearer ${tamper(creds.accessToken)}` });
13453
+ cases.push({ name: "tampered signature \u2192 401", ok: bad.status === 401, detail: `${bad.status} ${bad.body}` });
13454
+ } catch (e) {
13455
+ cases.push({ name: "probe", ok: false, detail: e instanceof Error ? e.message : String(e) });
13456
+ }
13457
+ return done({ ok: cases.every((c) => c.ok), tenant: creds.tenant, audience, url, cases });
13458
+ }
13459
+
13460
+ // src/commands/op-perf.ts
13461
+ var import_chalk60 = __toESM(require("chalk"));
13462
+ var RPS_DEFAULT = 100;
13463
+ var RPS_CAP = 300;
13464
+ var SECONDS_DEFAULT = 60;
13465
+ var SECONDS_CAP = 300;
13466
+ var REQUEST_TIMEOUT_MS = 1e4;
13467
+ var PROGRESS_EVERY_MS = 1e4;
13468
+ var GATEWAY_WAIT_MS = 9e4;
13469
+ var GATEWAY_POLL_MS = 1e4;
13470
+ var GATEWAY_MIN_SHARE = 0.8;
13471
+ var DEFAULT_THRESHOLDS = {
13472
+ p50_ms: 250,
13473
+ p95_ms: 800,
13474
+ error_rate_pct: 0.5,
13475
+ gateway_p50_ms: 60,
13476
+ gateway_p95_ms: 250
13477
+ };
13478
+ function percentile(values, p) {
13479
+ if (values.length === 0) return 0;
13480
+ const sorted = [...values].sort((a, b) => a - b);
13481
+ const rank = Math.ceil(p * sorted.length) - 1;
13482
+ return sorted[Math.min(sorted.length - 1, Math.max(0, rank))];
13483
+ }
13484
+ function clampRate(rps, seconds) {
13485
+ const r = Number.isFinite(rps) && rps > 0 ? Math.min(RPS_CAP, rps) : RPS_DEFAULT;
13486
+ const s = Number.isFinite(seconds) && seconds > 0 ? Math.min(SECONDS_CAP, seconds) : SECONDS_DEFAULT;
13487
+ return { rps: r, seconds: s };
13488
+ }
13489
+ function thresholdsFrom(o) {
13490
+ const pick2 = (v, d) => Number.isFinite(v) && v >= 0 ? v : d;
13491
+ return {
13492
+ p50_ms: pick2(o.maxP50, DEFAULT_THRESHOLDS.p50_ms),
13493
+ p95_ms: pick2(o.maxP95, DEFAULT_THRESHOLDS.p95_ms),
13494
+ error_rate_pct: pick2(o.maxErrorRate, DEFAULT_THRESHOLDS.error_rate_pct),
13495
+ gateway_p50_ms: pick2(o.maxGatewayP50, DEFAULT_THRESHOLDS.gateway_p50_ms),
13496
+ gateway_p95_ms: pick2(o.maxGatewayP95, DEFAULT_THRESHOLDS.gateway_p95_ms)
13497
+ };
13498
+ }
13499
+ function perfOptionsFrom(opts) {
13500
+ const num = (v) => {
13501
+ if (v === void 0 || v === null || v === "") return void 0;
13502
+ const n = Number(v);
13503
+ return Number.isFinite(n) ? n : void 0;
13504
+ };
13505
+ const str = (v) => typeof v === "string" && v.trim() ? v.trim() : void 0;
13506
+ return {
13507
+ json: !!opts.json,
13508
+ rps: num(opts.rps),
13509
+ seconds: num(opts.seconds),
13510
+ url: str(opts.url),
13511
+ key: str(opts.key),
13512
+ maxP50: num(opts.maxP50),
13513
+ maxP95: num(opts.maxP95),
13514
+ maxErrorRate: num(opts.maxErrorRate),
13515
+ maxGatewayP50: num(opts.maxGatewayP50),
13516
+ maxGatewayP95: num(opts.maxGatewayP95)
13517
+ };
13518
+ }
13519
+ function summarize(samples, elapsedMs) {
13520
+ let ok = 0, non2xx = 0, timeouts = 0, network = 0;
13521
+ for (const s of samples) {
13522
+ if (s.error === "timeout") timeouts += 1;
13523
+ else if (s.error === "network") network += 1;
13524
+ else if (s.status >= 200 && s.status < 300) ok += 1;
13525
+ else non2xx += 1;
13526
+ }
13527
+ const sent = samples.length;
13528
+ const errors = non2xx + timeouts + network;
13529
+ const ms2 = samples.map((s) => s.ms);
13530
+ const r1 = (n) => Math.round(n * 10) / 10;
13531
+ return {
13532
+ sent,
13533
+ ok,
13534
+ errors,
13535
+ non2xx,
13536
+ timeouts,
13537
+ network,
13538
+ error_rate_pct: sent ? Math.round(errors / sent * 1e4) / 100 : 0,
13539
+ p50: r1(percentile(ms2, 0.5)),
13540
+ p95: r1(percentile(ms2, 0.95)),
13541
+ p99: r1(percentile(ms2, 0.99)),
13542
+ rps_achieved: elapsedMs > 0 ? r1(sent / (elapsedMs / 1e3)) : 0
13543
+ };
13544
+ }
13545
+ function evaluate(client, gateway, t) {
13546
+ const out = [];
13547
+ if (client.p50 > t.p50_ms) out.push({ metric: "client_p50", value: client.p50, limit: t.p50_ms });
13548
+ if (client.p95 > t.p95_ms) out.push({ metric: "client_p95", value: client.p95, limit: t.p95_ms });
13549
+ if (client.error_rate_pct > t.error_rate_pct) out.push({ metric: "error_rate", value: client.error_rate_pct, limit: t.error_rate_pct });
13550
+ if (gateway) {
13551
+ if (gateway.p50 > t.gateway_p50_ms) out.push({ metric: "gateway_p50", value: gateway.p50, limit: t.gateway_p50_ms });
13552
+ if (gateway.p95 > t.gateway_p95_ms) out.push({ metric: "gateway_p95", value: gateway.p95, limit: t.gateway_p95_ms });
13553
+ }
13554
+ return out;
13555
+ }
13556
+ function schedule(opts) {
13557
+ return new Promise((resolve3) => {
13558
+ const start = Date.now();
13559
+ let sent = 0;
13560
+ let timer;
13561
+ const finish = () => {
13562
+ if (timer) clearTimeout(timer);
13563
+ opts.signal?.removeEventListener("abort", finish);
13564
+ resolve3({ sent, elapsedMs: Date.now() - start });
13565
+ };
13566
+ opts.signal?.addEventListener("abort", finish, { once: true });
13567
+ const tick = () => {
13568
+ if (opts.signal?.aborted) return;
13569
+ const due = Math.min(opts.total, Math.floor((Date.now() - start) / opts.intervalMs) + 1);
13570
+ while (sent < due) {
13571
+ opts.fire(sent);
13572
+ sent += 1;
13573
+ }
13574
+ if (sent >= opts.total) return finish();
13575
+ const next = start + sent * opts.intervalMs;
13576
+ timer = setTimeout(tick, Math.max(0, next - Date.now()));
13577
+ };
13578
+ if (opts.total <= 0 || opts.signal?.aborted) return finish();
13579
+ tick();
13580
+ });
13581
+ }
13582
+ function proxyFromUrl(url) {
13583
+ return new URL(url).hostname.split(".")[0];
13584
+ }
13585
+ var log3 = (s) => process.stderr.write(s + "\n");
13586
+ var fmtMs = (n) => `${Math.round(n)}ms`;
13587
+ var sleep2 = (ms2, signal) => new Promise((resolve3) => {
13588
+ const t = setTimeout(done, ms2);
13589
+ function done() {
13590
+ clearTimeout(t);
13591
+ signal?.removeEventListener("abort", done);
13592
+ resolve3();
13593
+ }
13594
+ signal?.addEventListener("abort", done, { once: true });
13595
+ });
13596
+ async function fireRequest(url, key, samples) {
13597
+ const t0 = performance.now();
13598
+ try {
13599
+ const res = await fetch(url, {
13600
+ method: "GET",
13601
+ headers: { "X-API-Key": key, "User-Agent": "apiblaze-cli op-perf" },
13602
+ keepalive: true,
13603
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
13604
+ });
13605
+ await res.arrayBuffer();
13606
+ samples.push({ status: res.status, ms: performance.now() - t0 });
13607
+ } catch (e) {
13608
+ const name = e?.name;
13609
+ const timeout = name === "TimeoutError" || name === "AbortError";
13610
+ samples.push({ status: 0, ms: performance.now() - t0, error: timeout ? "timeout" : "network" });
13611
+ }
13612
+ }
13613
+ async function readGateway(proxy, sent, signal) {
13614
+ const q = `?hours=1&project=${encodeURIComponent(proxy)}`;
13615
+ const deadline = Date.now() + GATEWAY_WAIT_MS;
13616
+ const need = Math.ceil(sent * GATEWAY_MIN_SHARE);
13617
+ let lastSeen = 0;
13618
+ let lastErr = "";
13619
+ for (; ; ) {
13620
+ if (signal.aborted) return null;
13621
+ try {
13622
+ const s = await opCall({ method: "GET", path: `/operator/latency/summary${q}`, summary: "perf gate: gateway share" });
13623
+ lastSeen = Number(s?.requests ?? 0);
13624
+ if (lastSeen >= need) {
13625
+ const ov = s?.apiblaze_overhead_ms ?? {};
13626
+ const num = (v) => typeof v === "number" && Number.isFinite(v) ? v : void 0;
13627
+ const g = { p50: Number(ov.p50 ?? 0), p95: Number(ov.p95 ?? 0), p99: num(ov.p99), requests: lastSeen };
13628
+ const kv = num(s?.kv_reads_per_request ?? s?.kv_per_req);
13629
+ const redis = num(s?.redis_commands_per_request ?? s?.redis_per_req);
13630
+ if (kv !== void 0) g.kv_per_req = kv;
13631
+ if (redis !== void 0) g.redis_per_req = redis;
13632
+ try {
13633
+ const blame = await opCall({ method: "GET", path: `/operator/latency${q}`, summary: "perf gate: blame table" });
13634
+ const rows = blame?.blame ?? [];
13635
+ if (rows.length) g.blame = rows.slice(0, 3).map((r) => ({ feature: String(r.feature), dep: String(r.dep), share: Number(r.share ?? 0), p95_ms: Number(r.p95_ms ?? 0) }));
13636
+ } catch {
13637
+ }
13638
+ return g;
13639
+ }
13640
+ } catch (e) {
13641
+ lastErr = e instanceof Error ? e.message : String(e);
13642
+ }
13643
+ const left = deadline - Date.now();
13644
+ if (left <= 0) {
13645
+ log3(import_chalk60.default.yellow(` gateway: not available \u2014 Analytics Engine had ${lastSeen}/${sent} rows for ${proxy} after ${GATEWAY_WAIT_MS / 1e3}s${lastErr ? ` (last error: ${lastErr})` : ""}`));
13646
+ return null;
13647
+ }
13648
+ log3(import_chalk60.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
13649
+ await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
13650
+ }
13651
+ }
13652
+ async function runOpPerf(o = {}) {
13653
+ const { rps, seconds } = clampRate(o.rps, o.seconds);
13654
+ const thresholds = thresholdsFrom(o);
13655
+ const ctrl = new AbortController();
13656
+ const onSigint = () => {
13657
+ log3(import_chalk60.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
13658
+ ctrl.abort();
13659
+ };
13660
+ process.once("SIGINT", onSigint);
13661
+ let fixture;
13662
+ if (o.url) {
13663
+ if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
13664
+ fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
13665
+ } else {
13666
+ log3(import_chalk60.default.dim("provisioning the perf fixture through the runner\u2026"));
13667
+ const f = await runnerCall({ action: "perf-fixture" }, 120);
13668
+ if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
13669
+ fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
13670
+ }
13671
+ try {
13672
+ const total = Math.round(rps * seconds);
13673
+ const intervalMs = 1e3 / rps;
13674
+ log3(import_chalk60.default.bold(`apiblaze op test --perf \u2014 ${rps} rps \xD7 ${seconds}s (${total} requests) against ${fixture.url}${fixture.team ? ` (sandbox ${fixture.team})` : ""}`));
13675
+ const samples = [];
13676
+ const inflight = /* @__PURE__ */ new Set();
13677
+ let fired = 0;
13678
+ const t0 = Date.now();
13679
+ const progress = setInterval(() => {
13680
+ const m = summarize(samples, Date.now() - t0);
13681
+ const secs = (Date.now() - t0) / 1e3;
13682
+ log3(import_chalk60.default.dim(` ${Math.round(secs)}s sent ${fired} ok ${m.ok} err ${m.errors} p50 ${fmtMs(m.p50)} p95 ${fmtMs(m.p95)} (${(fired / secs).toFixed(1)} rps)`));
13683
+ }, PROGRESS_EVERY_MS);
13684
+ const { sent, elapsedMs } = await schedule({
13685
+ total,
13686
+ intervalMs,
13687
+ signal: ctrl.signal,
13688
+ fire: () => {
13689
+ fired += 1;
13690
+ const p = fireRequest(fixture.url, fixture.key, samples);
13691
+ inflight.add(p);
13692
+ p.finally(() => inflight.delete(p));
13693
+ }
13694
+ });
13695
+ clearInterval(progress);
13696
+ if (inflight.size) log3(import_chalk60.default.dim(` load sent; waiting for ${inflight.size} in-flight response(s) (\u2264 ${REQUEST_TIMEOUT_MS / 1e3}s)\u2026`));
13697
+ await Promise.all([...inflight]);
13698
+ const client = summarize(samples, elapsedMs);
13699
+ log3("");
13700
+ log3(` ${import_chalk60.default.bold("client ")} p50 ${fmtMs(client.p50).padStart(7)} p95 ${fmtMs(client.p95).padStart(7)} p99 ${fmtMs(client.p99).padStart(7)} errors ${client.error_rate_pct.toFixed(2)}% (${client.errors}/${client.sent}: ${client.non2xx} non-2xx, ${client.timeouts} timeouts, ${client.network} network) achieved ${client.rps_achieved} rps of ${rps}`);
13701
+ const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
13702
+ if (gateway) {
13703
+ const extras = [
13704
+ gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
13705
+ gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
13706
+ ].filter(Boolean).join(" ");
13707
+ log3(` ${import_chalk60.default.bold("gateway")} p50 ${fmtMs(gateway.p50).padStart(7)} p95 ${fmtMs(gateway.p95).padStart(7)}${gateway.p99 !== void 0 ? ` p99 ${fmtMs(gateway.p99).padStart(7)}` : ""} ${import_chalk60.default.dim(`(${gateway.requests} ledger rows${extras ? `, ${extras}` : ""})`)}`);
13708
+ for (const b of gateway.blame ?? []) log3(import_chalk60.default.dim(` ${(b.share * 100).toFixed(1).padStart(5)}% p95 ${fmtMs(b.p95_ms).padStart(6)} ${b.feature} \u2192 ${b.dep}`));
13709
+ } else {
13710
+ log3(` ${import_chalk60.default.bold("gateway")} ${import_chalk60.default.yellow("not available")} ${import_chalk60.default.dim("(gateway thresholds not evaluated)")}`);
13711
+ }
13712
+ const breaches = evaluate(client, gateway, thresholds);
13713
+ const aborted = ctrl.signal.aborted;
13714
+ const ok = breaches.length === 0 && !aborted;
13715
+ log3("");
13716
+ const row = (metric, value, limit, unit) => {
13717
+ const hit = breaches.find((b) => b.metric === metric);
13718
+ log3(`[${hit ? import_chalk60.default.red("FAIL") : import_chalk60.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
13719
+ };
13720
+ row("client_p50", client.p50, thresholds.p50_ms, "ms");
13721
+ row("client_p95", client.p95, thresholds.p95_ms, "ms");
13722
+ row("error_rate", client.error_rate_pct, thresholds.error_rate_pct, "%");
13723
+ if (gateway) {
13724
+ row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
13725
+ row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
13726
+ } else {
13727
+ log3(`[${import_chalk60.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
13728
+ }
13729
+ log3("");
13730
+ if (aborted) log3(import_chalk60.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
13731
+ else if (ok) log3(import_chalk60.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
13732
+ else log3(import_chalk60.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
13733
+ if (o.json) {
13734
+ const doc = {
13735
+ kind: "perf",
13736
+ fixture: { url: fixture.url, proxy: fixture.proxy, team: fixture.team ?? null, provisioned: fixture.provisioned },
13737
+ rps_target: rps,
13738
+ seconds,
13739
+ rps_achieved: client.rps_achieved,
13740
+ sent: client.sent,
13741
+ // 2xx count. The plan's shape spells both this and the verdict `ok`; a
13742
+ // fixing loop reads the verdict, so that keeps the name (last field).
13743
+ succeeded: client.ok,
13744
+ errors: client.errors,
13745
+ error_rate_pct: client.error_rate_pct,
13746
+ error_detail: { non2xx: client.non2xx, timeouts: client.timeouts, network: client.network },
13747
+ client: { p50: client.p50, p95: client.p95, p99: client.p99 },
13748
+ gateway: gateway ? {
13749
+ p50: gateway.p50,
13750
+ p95: gateway.p95,
13751
+ ...gateway.p99 !== void 0 ? { p99: gateway.p99 } : {},
13752
+ ...gateway.kv_per_req !== void 0 ? { kv_per_req: gateway.kv_per_req } : {},
13753
+ ...gateway.redis_per_req !== void 0 ? { redis_per_req: gateway.redis_per_req } : {},
13754
+ requests: gateway.requests,
13755
+ ...gateway.blame ? { blame: gateway.blame } : {}
13756
+ } : null,
13757
+ thresholds,
13758
+ breaches,
13759
+ ...aborted ? { interrupted: true } : {},
13760
+ ok
13761
+ };
13762
+ process.stdout.write(JSON.stringify(doc, null, 2) + "\n");
13763
+ }
13764
+ if (!ok) process.exitCode = 1;
13765
+ } finally {
13766
+ process.off("SIGINT", onSigint);
13767
+ if (fixture.provisioned) {
13768
+ try {
13769
+ await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
13770
+ log3(import_chalk60.default.dim(`fixture ${fixture.proxy} torn down`));
13771
+ } catch (e) {
13772
+ log3(import_chalk60.default.red(`teardown of ${fixture.proxy} failed: ${e instanceof Error ? e.message : String(e)} \u2014 run \`apiblaze op residue\` / clean the sandbox`));
13773
+ process.exitCode = 1;
13774
+ }
13775
+ }
13776
+ }
13777
+ }
13778
+
13779
+ // src/commands/op.ts
13780
+ var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
13781
+ function isOperatorLogin() {
13782
+ const email = loadCredentials()?.email?.toLowerCase();
13783
+ return !!email && OPERATOR_EMAILS.has(email);
13784
+ }
13290
13785
  var OP_COMMANDS = [
13291
13786
  { cmd: "op latency", blurb: "which FEATURE is slow, and what inside it", extra: "--hours N --team T --project P" },
13292
13787
  { cmd: "op latency slow", blurb: "slowest requests, each with a request id you can look up", extra: "--min-ms N" },
@@ -13298,102 +13793,135 @@ var OP_COMMANDS = [
13298
13793
  { cmd: "op billing", blurb: "billing conformance: every door metered, 1 request = 1 charge", extra: "--key K --count N" },
13299
13794
  { cmd: "op billing doors", blurb: "every live route + workers.dev URL, and whether it passes the meter (free, read-only)" },
13300
13795
  { cmd: "op billing meter", blurb: "send known traffic, assert the ledger moved by exactly that much", extra: "spends a few tenths of a cent" },
13796
+ { cmd: "op test", blurb: "run the operator test suite against production (sandbox team only): contract specs + gut checks", extra: "--json --only <ids> --group <g> --skip-guts --guts-only --fail-fast" },
13797
+ { cmd: "op test --perf", blurb: "performance gate: fixed-rate load on a sandbox proxy, client + gateway latency vs thresholds (alias: op perf)", extra: "--rps 100 --seconds 60 --json --max-p50 --max-p95 --max-error-rate --max-gateway-p50 --max-gateway-p95 --url --key" },
13798
+ { cmd: "op test --consumer", blurb: "the data plane as a consumer: the stored `consumer login` token opens exactly its own proxy (401 without, 401 tampered)", extra: "--path /get --json" },
13301
13799
  { cmd: "op residue", blurb: "external-store residue report (Upstash + Neon/OpenFGA) \u2014 dry run, deletes nothing" },
13302
13800
  { cmd: "op sweep", blurb: "delete the orphans the residue report found", extra: "asks first; -y to skip" }
13303
13801
  ];
13304
13802
  function renderOpCommands() {
13305
13803
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
13306
13804
  const lines = OP_COMMANDS.map((c) => {
13307
- const left = ` ${import_chalk58.default.cyan(`apiblaze ${c.cmd}`)}`;
13805
+ const left = ` ${import_chalk61.default.cyan(`apiblaze ${c.cmd}`)}`;
13308
13806
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
13309
- return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk58.default.dim(`(${c.extra})`) : ""}`;
13807
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk61.default.dim(`(${c.extra})`) : ""}`;
13310
13808
  });
13311
13809
  return [
13312
- import_chalk58.default.bold("Operator commands"),
13810
+ import_chalk61.default.bold("Operator commands"),
13313
13811
  ...lines,
13314
13812
  "",
13315
- import_chalk58.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
13316
- import_chalk58.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
13317
- import_chalk58.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
13813
+ import_chalk61.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
13814
+ import_chalk61.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
13815
+ import_chalk61.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
13318
13816
  "",
13319
- import_chalk58.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
13320
- import_chalk58.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
13321
- import_chalk58.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
13817
+ import_chalk61.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
13818
+ import_chalk61.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
13819
+ import_chalk61.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
13322
13820
  ].join("\n");
13323
13821
  }
13324
13822
  function printResidue(report, applied) {
13325
13823
  const up = report?.upstash ?? {};
13326
13824
  const fga = report?.fga ?? {};
13327
13825
  const ghosts = report?.ghosts ?? {};
13328
- console.log(import_chalk58.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
13329
- console.log(import_chalk58.default.bold("\n Upstash"));
13826
+ console.log(import_chalk61.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
13827
+ console.log(import_chalk61.default.bold("\n Upstash"));
13330
13828
  const orphans = up.orphans ?? [];
13331
- if (orphans.length === 0) console.log(import_chalk58.default.green(" no orphaned keys"));
13332
- for (const o of orphans) console.log(` ${import_chalk58.default.yellow(o.key)} ${import_chalk58.default.dim(`\u2014 ${o.reason}`)}`);
13333
- console.log(import_chalk58.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
13829
+ if (orphans.length === 0) console.log(import_chalk61.default.green(" no orphaned keys"));
13830
+ for (const o of orphans) console.log(` ${import_chalk61.default.yellow(o.key)} ${import_chalk61.default.dim(`\u2014 ${o.reason}`)}`);
13831
+ console.log(import_chalk61.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
13334
13832
  if (up.anon_wallet_detail) {
13335
13833
  const d = up.anon_wallet_detail;
13336
- console.log(import_chalk58.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
13834
+ console.log(import_chalk61.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
13337
13835
  }
13338
13836
  if (up.keyspace_census) {
13339
13837
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
13340
- console.log(import_chalk58.default.dim(` keyspace: ${census}`));
13838
+ console.log(import_chalk61.default.dim(` keyspace: ${census}`));
13341
13839
  }
13342
- if (up.unknown?.length) console.log(import_chalk58.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
13343
- if (applied) console.log(` ${import_chalk58.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
13344
- for (const e of up.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
13345
- console.log(import_chalk58.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
13840
+ if (up.unknown?.length) console.log(import_chalk61.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
13841
+ if (applied) console.log(` ${import_chalk61.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
13842
+ for (const e of up.errors ?? []) console.log(import_chalk61.default.red(` error: ${e}`));
13843
+ console.log(import_chalk61.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
13346
13844
  if (applied) {
13347
13845
  const swept = fga?.swept ?? [];
13348
- if (swept.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
13846
+ if (swept.length === 0) console.log(import_chalk61.default.green(" no orphaned stores"));
13349
13847
  for (const s of swept) {
13350
13848
  console.log(
13351
- ` ${import_chalk58.default.yellow(s.store_id)} ${import_chalk58.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
13849
+ ` ${import_chalk61.default.yellow(s.store_id)} ${import_chalk61.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
13352
13850
  );
13353
13851
  }
13354
- if (fga?.remaining) console.log(import_chalk58.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
13852
+ if (fga?.remaining) console.log(import_chalk61.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
13355
13853
  const st = fga?.side_tables;
13356
- if (st) console.log(import_chalk58.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
13854
+ if (st) console.log(import_chalk61.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
13357
13855
  } else {
13358
13856
  const fgaOrphans = fga?.orphans ?? [];
13359
- if (fgaOrphans.length === 0) console.log(import_chalk58.default.green(" no orphaned stores"));
13857
+ if (fgaOrphans.length === 0) console.log(import_chalk61.default.green(" no orphaned stores"));
13360
13858
  for (const s of fgaOrphans) {
13361
13859
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
13362
- console.log(` ${import_chalk58.default.yellow(s.store_id)} ${import_chalk58.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
13860
+ console.log(` ${import_chalk61.default.yellow(s.store_id)} ${import_chalk61.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
13363
13861
  }
13364
- console.log(import_chalk58.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
13862
+ console.log(import_chalk61.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
13365
13863
  const st = fga?.side_tables;
13366
- if (st) console.log(import_chalk58.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
13864
+ if (st) console.log(import_chalk61.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
13367
13865
  }
13368
- for (const e of fga?.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
13369
- console.log(import_chalk58.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
13866
+ for (const e of fga?.errors ?? []) console.log(import_chalk61.default.red(` error: ${e}`));
13867
+ console.log(import_chalk61.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
13370
13868
  if (applied) {
13371
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk58.default.green(" no ghost tuples"));
13372
- else console.log(` ${import_chalk58.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk58.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
13869
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk61.default.green(" no ghost tuples"));
13870
+ else console.log(` ${import_chalk61.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk61.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
13373
13871
  } else {
13374
13872
  const n = ghosts?.ghost_count ?? 0;
13375
- if (n === 0) console.log(import_chalk58.default.green(` no ghost tuples ${import_chalk58.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
13873
+ if (n === 0) console.log(import_chalk61.default.green(` no ghost tuples ${import_chalk61.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
13376
13874
  else {
13377
- console.log(import_chalk58.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
13875
+ console.log(import_chalk61.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
13378
13876
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
13379
- console.log(import_chalk58.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
13877
+ console.log(import_chalk61.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
13380
13878
  }
13381
- if (n > 20) console.log(import_chalk58.default.dim(` \u2026 and ${n - 20} more`));
13879
+ if (n > 20) console.log(import_chalk61.default.dim(` \u2026 and ${n - 20} more`));
13382
13880
  }
13383
13881
  }
13384
- for (const e of ghosts?.errors ?? []) console.log(import_chalk58.default.red(` error: ${e}`));
13882
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk61.default.red(` error: ${e}`));
13385
13883
  console.log();
13386
13884
  }
13387
13885
  async function runOp(sub, opts = {}, view) {
13388
13886
  if (!loadCredentials()) {
13389
- console.log(import_chalk58.default.dim("Not logged in. Run `apiblaze login`."));
13887
+ console.log(import_chalk61.default.dim("Not logged in. Run `apiblaze login`."));
13390
13888
  return;
13391
13889
  }
13392
13890
  if (!isOperatorLogin()) {
13393
- console.log(import_chalk58.default.dim("`apiblaze op` is only available to platform operators."));
13891
+ console.log(import_chalk61.default.dim("`apiblaze op` is only available to platform operators."));
13394
13892
  return;
13395
13893
  }
13396
13894
  switch (sub) {
13895
+ // `op test --perf` and `op perf` are the same thing: the load gate (plan row
13896
+ // 6) instead of the suite. `--perf` is perf ONLY — the suite is not run first.
13897
+ case "perf": {
13898
+ await runOpPerf(perfOptionsFrom(opts));
13899
+ return;
13900
+ }
13901
+ case "consumer": {
13902
+ await runOpConsumer({ json: !!opts.json, path: typeof opts.path === "string" ? opts.path : void 0, proxy: typeof opts.proxy === "string" ? opts.proxy : void 0, url: typeof opts.url === "string" ? opts.url : void 0 });
13903
+ return;
13904
+ }
13905
+ case "test": {
13906
+ if (opts.consumer) {
13907
+ await runOpConsumer({ json: !!opts.json, path: typeof opts.path === "string" ? opts.path : void 0, proxy: typeof opts.proxy === "string" ? opts.proxy : void 0, url: typeof opts.url === "string" ? opts.url : void 0 });
13908
+ return;
13909
+ }
13910
+ if (opts.perf) {
13911
+ await runOpPerf(perfOptionsFrom(opts));
13912
+ return;
13913
+ }
13914
+ await runOpTest({
13915
+ json: !!opts.json,
13916
+ only: typeof opts.only === "string" ? opts.only : void 0,
13917
+ group: typeof opts.group === "string" ? opts.group : void 0,
13918
+ skipGuts: !!opts.skipGuts,
13919
+ gutsOnly: !!opts.gutsOnly,
13920
+ failFast: !!opts.failFast,
13921
+ timeout: typeof opts.timeout === "string" ? Number(opts.timeout) : void 0
13922
+ });
13923
+ return;
13924
+ }
13397
13925
  case void 0:
13398
13926
  case "menu": {
13399
13927
  console.log("");
@@ -13416,17 +13944,17 @@ async function runOp(sub, opts = {}, view) {
13416
13944
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
13417
13945
  printResidue(report, false);
13418
13946
  if (nUp + nFga + nGhost + nSide === 0) {
13419
- console.log(import_chalk58.default.green("Nothing to sweep."));
13947
+ console.log(import_chalk61.default.green("Nothing to sweep."));
13420
13948
  return;
13421
13949
  }
13422
13950
  if (!opts.yes) {
13423
13951
  const readline3 = await import("readline/promises");
13424
13952
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
13425
13953
  const answer = await rl.question(
13426
- import_chalk58.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
13954
+ import_chalk61.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
13427
13955
  );
13428
13956
  rl.close();
13429
- if (answer.trim() !== "sweep") return void console.log(import_chalk58.default.dim("Aborted."));
13957
+ if (answer.trim() !== "sweep") return void console.log(import_chalk61.default.dim("Aborted."));
13430
13958
  }
13431
13959
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
13432
13960
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -13436,25 +13964,25 @@ async function runOp(sub, opts = {}, view) {
13436
13964
  case "mark": {
13437
13965
  const label3 = (view ?? "").trim();
13438
13966
  if (!label3) {
13439
- console.log(import_chalk58.default.red("Give the change a name:") + import_chalk58.default.cyan(' apiblaze op mark "cached tenant count"'));
13967
+ console.log(import_chalk61.default.red("Give the change a name:") + import_chalk61.default.cyan(' apiblaze op mark "cached tenant count"'));
13440
13968
  return;
13441
13969
  }
13442
13970
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
13443
13971
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
13444
- console.log(import_chalk58.default.green(`
13445
- Marked: `) + import_chalk58.default.bold(label3));
13446
- console.log(import_chalk58.default.dim(` ${ts}`));
13447
- console.log(import_chalk58.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk58.default.cyan("apiblaze op latency compare") + "\n");
13972
+ console.log(import_chalk61.default.green(`
13973
+ Marked: `) + import_chalk61.default.bold(label3));
13974
+ console.log(import_chalk61.default.dim(` ${ts}`));
13975
+ console.log(import_chalk61.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk61.default.cyan("apiblaze op latency compare") + "\n");
13448
13976
  return;
13449
13977
  }
13450
13978
  case "credits": {
13451
13979
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
13452
13980
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
13453
13981
  const accounts = data?.accounts ?? [];
13454
- if (accounts.length === 0) return void console.log(import_chalk58.default.dim("No credit wallets."));
13982
+ if (accounts.length === 0) return void console.log(import_chalk61.default.dim("No credit wallets."));
13455
13983
  for (const a of accounts) {
13456
13984
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
13457
- console.log(` ${import_chalk58.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk58.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk58.default.dim(" \u2014 anon") : ""}`);
13985
+ console.log(` ${import_chalk61.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk61.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk61.default.dim(" \u2014 anon") : ""}`);
13458
13986
  }
13459
13987
  return;
13460
13988
  }
@@ -13465,7 +13993,7 @@ async function runOp(sub, opts = {}, view) {
13465
13993
  case "billing": {
13466
13994
  const which = (view ?? "").trim().toLowerCase();
13467
13995
  if (which && which !== "doors" && which !== "meter") {
13468
- return void console.log(import_chalk58.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
13996
+ return void console.log(import_chalk61.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
13469
13997
  }
13470
13998
  const checks = [];
13471
13999
  let doorsData = null;
@@ -13534,29 +14062,29 @@ async function runOp(sub, opts = {}, view) {
13534
14062
  const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
13535
14063
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
13536
14064
  const t = data.thresholds_ms;
13537
- console.log(import_chalk58.default.bold("\nHow good was apiblaze itself?") + import_chalk58.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
13538
- console.log(import_chalk58.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
14065
+ console.log(import_chalk61.default.bold("\nHow good was apiblaze itself?") + import_chalk61.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
14066
+ console.log(import_chalk61.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
13539
14067
  `));
13540
- console.log(import_chalk58.default.dim(" date reqs excellent okay bad terrible"));
14068
+ console.log(import_chalk61.default.dim(" date reqs excellent okay bad terrible"));
13541
14069
  for (const d of data.days ?? []) {
13542
14070
  const p = d.pct;
13543
- const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk58.default.dim(`${String(v).padStart(5)}%`);
14071
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk61.default.dim(`${String(v).padStart(5)}%`);
13544
14072
  console.log(
13545
- ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk58.default.green)} ${cell(p.okay, import_chalk58.default.cyan)} ${cell(p.bad, import_chalk58.default.yellow)} ${cell(p.terrible, import_chalk58.default.red)}`
14073
+ ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk61.default.green)} ${cell(p.okay, import_chalk61.default.cyan)} ${cell(p.bad, import_chalk61.default.yellow)} ${cell(p.terrible, import_chalk61.default.red)}`
13546
14074
  );
13547
14075
  }
13548
14076
  const cul = data.culprits ?? [];
13549
14077
  if (cul.length) {
13550
- console.log(import_chalk58.default.bold("\n Who caused the bad and terrible ones\n"));
13551
- console.log(import_chalk58.default.dim(" bad terrible feature \u2192 dependency"));
14078
+ console.log(import_chalk61.default.bold("\n Who caused the bad and terrible ones\n"));
14079
+ console.log(import_chalk61.default.dim(" bad terrible feature \u2192 dependency"));
13552
14080
  for (const r of cul.slice(0, 12)) {
13553
14081
  if (!r.bad && !r.terrible) continue;
13554
14082
  console.log(
13555
- ` ${String(r.bad).padStart(6)} ${import_chalk58.default.red(String(r.terrible).padStart(8))} ${import_chalk58.default.yellow(r.feature)} ${import_chalk58.default.dim("\u2192")} ${import_chalk58.default.cyan(r.dep)}`
14083
+ ` ${String(r.bad).padStart(6)} ${import_chalk61.default.red(String(r.terrible).padStart(8))} ${import_chalk61.default.yellow(r.feature)} ${import_chalk61.default.dim("\u2192")} ${import_chalk61.default.cyan(r.dep)}`
13556
14084
  );
13557
14085
  }
13558
14086
  }
13559
- if (data.caveat) console.log(import_chalk58.default.dim(`
14087
+ if (data.caveat) console.log(import_chalk61.default.dim(`
13560
14088
  \u26A0 ${data.caveat}
13561
14089
  `));
13562
14090
  return;
@@ -13565,24 +14093,24 @@ async function runOp(sub, opts = {}, view) {
13565
14093
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
13566
14094
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
13567
14095
  const b = data.before, a = data.after, d = data.delta;
13568
- console.log(import_chalk58.default.bold(`
13569
- Before vs after: `) + import_chalk58.default.cyan(data.marker.label));
13570
- console.log(import_chalk58.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
14096
+ console.log(import_chalk61.default.bold(`
14097
+ Before vs after: `) + import_chalk61.default.cyan(data.marker.label));
14098
+ console.log(import_chalk61.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
13571
14099
  `));
13572
14100
  const row = (name, before, after, delta) => {
13573
14101
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
13574
14102
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
13575
- console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk58.default.green(txt) : import_chalk58.default.red(txt) : import_chalk58.default.dim(txt)}`);
14103
+ console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk61.default.green(txt) : import_chalk61.default.red(txt) : import_chalk61.default.dim(txt)}`);
13576
14104
  };
13577
- console.log(import_chalk58.default.dim(" metric before after change"));
14105
+ console.log(import_chalk61.default.dim(" metric before after change"));
13578
14106
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
13579
14107
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
13580
14108
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
13581
14109
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
13582
- console.log(import_chalk58.default.dim(`
14110
+ console.log(import_chalk61.default.dim(`
13583
14111
  requests: ${b.requests} before \xB7 ${a.requests} after`));
13584
14112
  for (const w of data.warnings ?? []) {
13585
- console.log((data.trustworthy ? import_chalk58.default.dim : import_chalk58.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
14113
+ console.log((data.trustworthy ? import_chalk61.default.dim : import_chalk61.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
13586
14114
  }
13587
14115
  console.log("");
13588
14116
  return;
@@ -13591,19 +14119,19 @@ Before vs after: `) + import_chalk58.default.cyan(data.marker.label));
13591
14119
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
13592
14120
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
13593
14121
  const rows2 = data?.rows ?? [];
13594
- if (!rows2.length) return void console.log(import_chalk58.default.dim("No requests over the threshold in that window."));
13595
- console.log(import_chalk58.default.bold(`
14122
+ if (!rows2.length) return void console.log(import_chalk61.default.dim("No requests over the threshold in that window."));
14123
+ console.log(import_chalk61.default.bold(`
13596
14124
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
13597
14125
  `));
13598
- console.log(import_chalk58.default.dim(" total ours theirs blame request id"));
14126
+ console.log(import_chalk61.default.dim(" total ours theirs blame request id"));
13599
14127
  for (const r of rows2.slice(0, 30)) {
13600
14128
  console.log(
13601
- ` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk58.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk58.default.dim(r.request_id || "")}`
14129
+ ` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk61.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk61.default.dim(r.request_id || "")}`
13602
14130
  );
13603
14131
  }
13604
- console.log(import_chalk58.default.dim(`
14132
+ console.log(import_chalk61.default.dim(`
13605
14133
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
13606
- console.log(import_chalk58.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
14134
+ console.log(import_chalk61.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
13607
14135
  `));
13608
14136
  return;
13609
14137
  }
@@ -13611,18 +14139,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
13611
14139
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
13612
14140
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
13613
14141
  const rows2 = data?.rows ?? [];
13614
- if (!rows2.length) return void console.log(import_chalk58.default.dim("No LLM traffic in that window."));
13615
- console.log(import_chalk58.default.bold(`
14142
+ if (!rows2.length) return void console.log(import_chalk61.default.dim("No LLM traffic in that window."));
14143
+ console.log(import_chalk61.default.bold(`
13616
14144
  LLM timing \u2014 last ${data.window_hours}h
13617
14145
  `));
13618
- console.log(import_chalk58.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
14146
+ console.log(import_chalk61.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
13619
14147
  for (const r of rows2) {
13620
14148
  const n = r.turns ?? r.requests ?? 0;
13621
14149
  console.log(
13622
14150
  ` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
13623
14151
  );
13624
14152
  }
13625
- console.log(import_chalk58.default.dim(`
14153
+ console.log(import_chalk61.default.dim(`
13626
14154
  ${data.note}
13627
14155
  `));
13628
14156
  return;
@@ -13633,26 +14161,26 @@ LLM timing \u2014 last ${data.window_hours}h
13633
14161
  ]);
13634
14162
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
13635
14163
  const ov = summary?.apiblaze_overhead_ms ?? {};
13636
- console.log(import_chalk58.default.bold(`
14164
+ console.log(import_chalk61.default.bold(`
13637
14165
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
13638
14166
  `));
13639
- console.log(` ${import_chalk58.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk58.default.dim("\u2190 ours")}`);
13640
- console.log(` ${import_chalk58.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk58.default.dim("\u2190 theirs")}`);
13641
- console.log(import_chalk58.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
14167
+ console.log(` ${import_chalk61.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk61.default.dim("\u2190 ours")}`);
14168
+ console.log(` ${import_chalk61.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk61.default.dim("\u2190 theirs")}`);
14169
+ console.log(import_chalk61.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
13642
14170
  `));
13643
14171
  const rows = blame?.blame ?? [];
13644
- if (!rows.length) return void console.log(import_chalk58.default.dim("No latency rows in that window."));
13645
- console.log(import_chalk58.default.bold(" Which feature ate the time, and what inside it\n"));
13646
- console.log(import_chalk58.default.dim(" share p95 feature \u2192 dependency"));
14172
+ if (!rows.length) return void console.log(import_chalk61.default.dim("No latency rows in that window."));
14173
+ console.log(import_chalk61.default.bold(" Which feature ate the time, and what inside it\n"));
14174
+ console.log(import_chalk61.default.dim(" share p95 feature \u2192 dependency"));
13647
14175
  for (const r of rows.slice(0, 15)) {
13648
14176
  const share = `${(r.share * 100).toFixed(1)}%`;
13649
- console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk58.default.yellow(r.feature)} ${import_chalk58.default.dim("\u2192")} ${import_chalk58.default.cyan(r.dep)}`);
14177
+ console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk61.default.yellow(r.feature)} ${import_chalk61.default.dim("\u2192")} ${import_chalk61.default.cyan(r.dep)}`);
13650
14178
  }
13651
14179
  console.log("");
13652
14180
  return;
13653
14181
  }
13654
14182
  default:
13655
- console.log(import_chalk58.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
14183
+ console.log(import_chalk61.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
13656
14184
  }
13657
14185
  }
13658
14186
 
@@ -13722,7 +14250,7 @@ program.command("dev").description(`Put the code running on this machine behind
13722
14250
  try {
13723
14251
  const resolved = parseInt(port ?? opts.port, 10);
13724
14252
  if (Number.isNaN(resolved)) {
13725
- console.error(import_chalk59.default.red(`Invalid port: ${port ?? opts.port}`));
14253
+ console.error(import_chalk62.default.red(`Invalid port: ${port ?? opts.port}`));
13726
14254
  process.exit(1);
13727
14255
  }
13728
14256
  await runDev({ ...opts, port: resolved });
@@ -13826,7 +14354,7 @@ apikeys.command("list").description("List control-plane API keys in your team").
13826
14354
  apikeys.command("mint").description("Create a control-plane API key (secret shown once) \u2014 or, with --tenant, a consumer key in that tenant").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--tenant <slug>", "Mint a CONSUMER key in this tenant instead of a control-plane key").option("--for <email|id>", "Issue this key to a named person \u2014 calls made with it count as that person, and ownership rules trust it. Needs --tenant.").option("--env <name>", "Environment the consumer key is valid for (with --tenant; default dev)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
13827
14355
  apikeys.command("revoke").description("Revoke a control-plane API key").argument("<keyId>", "Key id (see `apikeys list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts, cmd) => runKeyRevoke(keyId, { ...cmd.parent?.opts(), ...opts })));
13828
14356
  program.addCommand(apikeys, { hidden: true });
13829
- var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | billing | mark | credits | residue | sweep (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare | grades \xB7 for `billing`: doors | meter \xB7 for `mark`: the change name, quoted").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").option("--hours <n>", "Look-back window in hours (default 1)").option("--team <id>", "Filter to one team").option("--project <name>", "Filter to one proxy").option("--api-version <v>", "Filter to one API version").option("--tenant <name>", "Filter to one tenant").option("--route <path>", "Filter to one route").option("--environment <env>", "Filter to one environment").option("--status-class <c>", "Filter to a status class (2xx/4xx/5xx)").option("--min-ms <n>", "For `latency slow`: minimum duration (default 1000)").option("--days <n>", "For `latency grades`: how many days back (default 7)").option("--excellent <ms>", "Grade threshold: under this is excellent (default 250)").option("--okay <ms>", "Grade threshold: under this is okay (default 500)").option("--bad <ms>", "Grade threshold: under this is bad, at/over is terrible (default 1000)").option("--key <key>", "For `billing meter`: a data-plane API key for the sandbox proxy (without it the served-request checks are SKIPPED)").option("--count <n>", "For `billing meter`: requests per check (default 3, max 10)").option("--mcp-host <host>", "For `billing meter`: an MCP host to check tools/call is charged once").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
14357
+ var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | billing | mark | credits | residue | sweep | test | perf (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare | grades \xB7 for `billing`: doors | meter \xB7 for `mark`: the change name, quoted").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").option("--hours <n>", "Look-back window in hours (default 1)").option("--team <id>", "Filter to one team").option("--only <ids>", "for `test`: comma-separated spec/gut ids or prefixes").option("--group <name>", "for `test`: only specs whose group matches").option("--skip-guts", "for `test`: contract specs only").option("--guts-only", "for `test`: server-side gut checks only").option("--fail-fast", "for `test`: stop at the first failure").option("--timeout <s>", "for `test`: seconds to wait per check (default 150)").option("--perf", "for `test`: run the performance gate instead of the suite (fixed-rate load on a sandbox proxy)").option("--consumer", "for `test`: probe the data plane with the stored `consumer login` token instead of the suite").option("--path <p>", "for `test --consumer`: request path on the proxy (default /get)").option("--proxy <name@version>", "for `test --consumer`: the OAuth-door proxy to knock on (default: the sandbox proxy for a sandbox-tenant session)").option("--rps <n>", "for `test --perf`: requests per second (default 100, max 300)").option("--seconds <n>", "for `test --perf`: how long to send for (default 60, max 300)").option("--url <url>", "for `test --perf`: load this fixture URL instead of provisioning one (with --key; nothing is torn down)").option("--max-p50 <ms>", "for `test --perf`: client p50 threshold (default 250)").option("--max-p95 <ms>", "for `test --perf`: client p95 threshold (default 800)").option("--max-error-rate <pct>", "for `test --perf`: error-rate threshold in percent (default 0.5)").option("--max-gateway-p50 <ms>", "for `test --perf`: apiblaze-overhead p50 threshold (default 60)").option("--max-gateway-p95 <ms>", "for `test --perf`: apiblaze-overhead p95 threshold (default 250)").option("--project <name>", "Filter to one proxy").option("--api-version <v>", "Filter to one API version").option("--tenant <name>", "Filter to one tenant").option("--route <path>", "Filter to one route").option("--environment <env>", "Filter to one environment").option("--status-class <c>", "Filter to a status class (2xx/4xx/5xx)").option("--min-ms <n>", "For `latency slow`: minimum duration (default 1000)").option("--days <n>", "For `latency grades`: how many days back (default 7)").option("--excellent <ms>", "Grade threshold: under this is excellent (default 250)").option("--okay <ms>", "Grade threshold: under this is okay (default 500)").option("--bad <ms>", "Grade threshold: under this is bad, at/over is terrible (default 1000)").option("--key <key>", "For `billing meter`: a data-plane API key for the sandbox proxy (without it the served-request checks are SKIPPED) \xB7 for `test --perf --url`: the fixture's API key").option("--count <n>", "For `billing meter`: requests per check (default 3, max 10)").option("--mcp-host <host>", "For `billing meter`: an MCP host to check tools/call is charged once").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
13830
14358
  ${renderOpCommands()}
13831
14359
  `);
13832
14360
  program.addCommand(op, { hidden: true });
@@ -13865,7 +14393,7 @@ function groupedCommandHelp() {
13865
14393
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
13866
14394
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
13867
14395
  }).filter(Boolean).join("\n");
13868
- return `${import_chalk59.default.bold(g.title)}
14396
+ return `${import_chalk62.default.bold(g.title)}
13869
14397
  ${rows}`;
13870
14398
  }).join("\n\n");
13871
14399
  }
@@ -13876,7 +14404,7 @@ program.configureHelp({
13876
14404
  }
13877
14405
  });
13878
14406
  program.addHelpText("after", () => `
13879
- ${import_chalk59.default.bold("Don't like reading docs?")} Run ${import_chalk59.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk59.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
14407
+ ${import_chalk62.default.bold("Don't like reading docs?")} Run ${import_chalk62.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk62.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
13880
14408
 
13881
14409
  ${groupedCommandHelp()}
13882
14410
 
@@ -13912,14 +14440,14 @@ async function recoverStaleTeam() {
13912
14440
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
13913
14441
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
13914
14442
  if (!linked) {
13915
- console.error(import_chalk59.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
14443
+ console.error(import_chalk62.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
13916
14444
  return;
13917
14445
  }
13918
14446
  if (linked.teamId === creds.teamId) return;
13919
14447
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
13920
14448
  delete next.activeTenant;
13921
14449
  saveCredentials(next);
13922
- console.error(import_chalk59.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk59.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
14450
+ console.error(import_chalk62.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk62.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
13923
14451
  } catch {
13924
14452
  }
13925
14453
  }
@@ -13927,16 +14455,16 @@ async function printError(err) {
13927
14455
  if (err instanceof ApiError) {
13928
14456
  const data = err.body;
13929
14457
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
13930
- console.error(import_chalk59.default.red(`
14458
+ console.error(import_chalk62.default.red(`
13931
14459
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
13932
14460
  if (err.status === 403 || err.status === 404) {
13933
14461
  await recoverStaleTeam();
13934
14462
  }
13935
14463
  } else if (err instanceof Error) {
13936
- console.error(import_chalk59.default.red(`
14464
+ console.error(import_chalk62.default.red(`
13937
14465
  Error: ${err.message}`));
13938
14466
  } else {
13939
- console.error(import_chalk59.default.red("\nUnknown error"));
14467
+ console.error(import_chalk62.default.red("\nUnknown error"));
13940
14468
  }
13941
14469
  }
13942
14470
  program.parse(process.argv);