apiblaze 0.20.6 → 0.20.9

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 +365 -95
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -929,10 +929,10 @@ var init_tenant_pick = __esm({
929
929
 
930
930
  // src/index.ts
931
931
  var import_commander = require("commander");
932
- var import_chalk51 = __toESM(require("chalk"));
932
+ var import_chalk52 = __toESM(require("chalk"));
933
933
 
934
934
  // package.json
935
- var version = "0.20.6";
935
+ var version = "0.20.9";
936
936
 
937
937
  // src/index.ts
938
938
  init_types();
@@ -9801,10 +9801,242 @@ async function runOriginsRemove(origin, opts) {
9801
9801
  }
9802
9802
 
9803
9803
  // src/commands/op.ts
9804
- var import_chalk50 = __toESM(require("chalk"));
9804
+ var import_chalk51 = __toESM(require("chalk"));
9805
9805
  init_auth();
9806
9806
  init_trace();
9807
9807
  init_types();
9808
+
9809
+ // src/commands/op-billing.ts
9810
+ var import_chalk50 = __toESM(require("chalk"));
9811
+ init_admin();
9812
+ var SANDBOX = {
9813
+ teamId: "team_1782844865835_zujrf",
9814
+ project: "portaltestproxypreapproved",
9815
+ tenant: "portalteststenant",
9816
+ version: "2.7.0",
9817
+ environment: "prod"
9818
+ };
9819
+ var dpHost = `${SANDBOX.project}.abz.run`;
9820
+ var ZONE_COUNT = 3;
9821
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
9822
+ async function readQuiet(read, tries = 8) {
9823
+ let prev = await read();
9824
+ for (let i = 0; i < tries; i++) {
9825
+ await sleep(1200);
9826
+ const now = await read();
9827
+ if (now.band_count === prev.band_count && now.balance_microcents === prev.balance_microcents) return now;
9828
+ prev = now;
9829
+ }
9830
+ return prev;
9831
+ }
9832
+ async function fire(url, n, headers = {}) {
9833
+ const out = [];
9834
+ for (let i = 0; i < n; i++) {
9835
+ try {
9836
+ const res = await fetch(url, { headers });
9837
+ const ray = (res.headers.get("cf-ray") || "").split("-")[0];
9838
+ out.push({ ray, status: res.status });
9839
+ await res.text().catch(() => "");
9840
+ } catch {
9841
+ out.push({ ray: "", status: 0 });
9842
+ }
9843
+ }
9844
+ return out;
9845
+ }
9846
+ async function rowsForRays(project, version2, tenant2, rays) {
9847
+ const want = new Set(rays.filter(Boolean));
9848
+ const found = /* @__PURE__ */ new Map();
9849
+ for (let attempt = 0; attempt < 6 && found.size < want.size; attempt++) {
9850
+ if (attempt) await sleep(2e3);
9851
+ const q = new URLSearchParams({ tenant: tenant2, limit: "200" });
9852
+ const res = await admin({
9853
+ path: `/projects/${project}/${version2}/logs/window?${q.toString()}`,
9854
+ method: "GET",
9855
+ summary: "read request log for billing assertion"
9856
+ }).catch(() => ({ rows: [] }));
9857
+ found.clear();
9858
+ for (const r of res.rows ?? []) {
9859
+ if (!want.has(r.request_id)) continue;
9860
+ const list = found.get(r.request_id) ?? [];
9861
+ list.push({ status: r.status, cost_cents: r.cost_cents });
9862
+ found.set(r.request_id, list);
9863
+ }
9864
+ }
9865
+ return found;
9866
+ }
9867
+ function printDoors(data) {
9868
+ const checks = [];
9869
+ console.log(import_chalk50.default.bold("\n Doors \u2014 is every way in metered?\n"));
9870
+ const doors = data?.doors ?? [];
9871
+ const metered = doors.filter((d) => d.verdict === "metered");
9872
+ const allowed = doors.filter((d) => d.verdict === "allowed-free");
9873
+ const known = doors.filter((d) => d.verdict === "known-open");
9874
+ const stray = data?.stray_routes ?? [];
9875
+ const errs = data?.errors ?? [];
9876
+ const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
9877
+ const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
9878
+ console.log(import_chalk50.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
9879
+ for (const d of metered) console.log(import_chalk50.default.green(` \u2713 ${d.pattern}`));
9880
+ console.log(import_chalk50.default.dim(`
9881
+ ${allowed.length} route(s) free ON PURPOSE`));
9882
+ for (const d of allowed) {
9883
+ console.log(import_chalk50.default.cyan(` \u2022 ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9884
+ console.log(import_chalk50.default.dim(` ${d.why}`));
9885
+ }
9886
+ if (known.length) {
9887
+ console.log(import_chalk50.default.yellow(`
9888
+ ${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
9889
+ for (const d of known) {
9890
+ console.log(import_chalk50.default.yellow(` ! ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9891
+ console.log(import_chalk50.default.dim(` ${d.why}`));
9892
+ }
9893
+ checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
9894
+ }
9895
+ if (stray.length) {
9896
+ console.log(import_chalk50.default.red(`
9897
+ ${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
9898
+ for (const d of stray) {
9899
+ console.log(import_chalk50.default.red(` \u2717 ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9900
+ console.log(import_chalk50.default.dim(` ${d.why}`));
9901
+ }
9902
+ checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
9903
+ } else if (routeAuditBroke) {
9904
+ checks.push({ name: "no stray routes", status: "SKIP", detail: `route enumeration failed for ${errs.filter((e) => e.startsWith("zone ")).length} zone(s) \u2014 NOT a pass, the routes were never read. Needs a CF token with Workers Routes:Read.` });
9905
+ } else {
9906
+ checks.push({ name: "no stray routes", status: "PASS", detail: `all ${doors.length} live route(s) across ${ZONE_COUNT} zone(s) are main-proxy or allowlisted` });
9907
+ }
9908
+ const wd = data?.workers_dev ?? {};
9909
+ const open = wd.enabled ?? [];
9910
+ if (open.length) {
9911
+ console.log(import_chalk50.default.red(`
9912
+ ${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
9913
+ for (const s of open) console.log(import_chalk50.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk50.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
9914
+ console.log(import_chalk50.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
9915
+ checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
9916
+ } else if (devAuditBroke || !wd.total) {
9917
+ checks.push({ name: "no workers.dev doors", status: "SKIP", detail: "script enumeration failed \u2014 NOT a pass, no subdomain was ever read. Needs a CF token with Workers Scripts:Read." });
9918
+ } else {
9919
+ console.log(import_chalk50.default.green(`
9920
+ \u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
9921
+ checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
9922
+ }
9923
+ if (data?.how_to_fix) console.log(import_chalk50.default.yellow(`
9924
+ ${data.reason}`) + import_chalk50.default.dim(`
9925
+ ${data.how_to_fix}`));
9926
+ else for (const e of errs) console.log(import_chalk50.default.red(`
9927
+ audit error: ${e}`));
9928
+ if (errs.length) {
9929
+ checks.push({ name: "audit completeness", status: "FAIL", detail: `${errs.length} part(s) of the audit could not run \u2014 coverage is INCOMPLETE, and the checks they would have covered are SKIP above` });
9930
+ }
9931
+ return checks;
9932
+ }
9933
+ async function runMeter(readLedger, opts) {
9934
+ const checks = [];
9935
+ const N = Math.max(1, Math.min(10, opts.count ?? 3));
9936
+ console.log(import_chalk50.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
9937
+ const snap = await readLedger();
9938
+ console.log(import_chalk50.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
9939
+ `));
9940
+ const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
9941
+ const headers = opts.key ? { "X-API-Key": opts.key } : {};
9942
+ const sent = await fire(url, N, headers);
9943
+ const rays = sent.map((r) => r.ray).filter(Boolean);
9944
+ if (rays.length !== N) {
9945
+ checks.push({ name: "1 request = 1 charge", status: "SKIP", detail: `only ${rays.length}/${N} responses carried a cf-ray \u2014 cannot attribute charges without a request id.` });
9946
+ } else {
9947
+ const rows = await rowsForRays(SANDBOX.project, SANDBOX.version, SANDBOX.tenant, rays);
9948
+ const missing = rays.filter((r) => !rows.has(r));
9949
+ const doubled = rays.filter((r) => (rows.get(r)?.length ?? 0) > 1);
9950
+ if (missing.length === rays.length) {
9951
+ checks.push({ name: "1 request = 1 charge", status: "SKIP", detail: `none of the ${N} request(s) appeared in ${SANDBOX.project}'s log within ~12s (statuses ${sent.map((x) => x.status).join(",")}). Either request logging is off for this proxy or the requests never reached it \u2014 nothing was verified.` });
9952
+ } else if (doubled.length) {
9953
+ checks.push({ name: "1 request = 1 charge", status: "FAIL", detail: `DOUBLE CHARGE: ${doubled.length} of ${N} request(s) produced more than one billed log row (${doubled.map((r) => `${r}\xD7${rows.get(r).length}`).join(", ")}).` });
9954
+ } else if (missing.length) {
9955
+ checks.push({ name: "1 request = 1 charge", status: "FAIL", detail: `${rays.length - missing.length}/${N} request(s) logged exactly once, but ${missing.length} never appeared \u2014 those requests were served and not recorded.` });
9956
+ } else {
9957
+ const costs = rays.map((r) => rows.get(r)[0].cost_cents ?? 0);
9958
+ const statuses = sent.map((x) => x.status).join(",");
9959
+ checks.push({ name: "1 request = 1 charge", status: "PASS", detail: `${N} request(s) (${statuses}) \u2192 exactly ${N} billed row(s), one per request id, at ${costs.join("/")}\xA2 each` });
9960
+ const served = sent.every((x) => x.status > 0 && x.status < 400);
9961
+ const want = served ? snap.next_request_cents : 0;
9962
+ const label3 = served ? "served request charged the catalog rate" : "refused request is not billed";
9963
+ const wrong = want === null ? [] : costs.filter((cst) => Math.abs(cst - want) > 1e-9);
9964
+ if (want === null) {
9965
+ checks.push({ name: label3, status: "SKIP", detail: "no request_bands row \u2014 banded pricing is off, so there is no catalog rate to check against." });
9966
+ } else {
9967
+ checks.push(wrong.length ? { name: label3, status: "FAIL", detail: served ? `expected ${want}\xA2 per request (band ${snap.band_count + 1}, thresholds ${snap.bands?.b1_to}/${snap.bands?.b2_to}); saw ${costs.join(", ")}\xA2` : `our own rejection (status ${sent[0].status}) should cost the caller nothing; the log shows ${costs.join(", ")}\xA2 billed` } : { name: label3, status: "PASS", detail: served ? `every request charged ${want}\xA2, matching the live request_bands row` : `${N} request(s) refused at the gate (status ${sent[0].status}) \u2192 0\xA2 billed, one row each` });
9968
+ }
9969
+ }
9970
+ }
9971
+ const shared = snap.wallet_shared_with ?? [];
9972
+ if (shared.length) {
9973
+ checks.push({
9974
+ name: "pre-upstream reject is refunded",
9975
+ status: "SKIP",
9976
+ detail: `needs a wallet nothing else spends. ${snap.billing_account_id} also pays for ${shared.length} other team(s): ${shared.slice(0, 3).join(", ")}${shared.length > 3 ? " \u2026" : ""} \u2014 including this command's own op-lane calls. A refund is a wallet event with no per-request row, so it cannot be attributed here. NOT VERIFIED.`
9977
+ });
9978
+ } else {
9979
+ const before = await readQuiet(readLedger);
9980
+ const rejects = await fire(`https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/__billing_probe`, N);
9981
+ const after = await readQuiet(readLedger);
9982
+ const dBand = after.band_count - before.band_count;
9983
+ const dMoney = (before.balance_microcents ?? 0) - (after.balance_microcents ?? 0);
9984
+ checks.push(dBand === 0 && dMoney === 0 ? { name: "pre-upstream reject is refunded", status: "PASS", detail: `${N} unauthenticated request(s) (${rejects.map((r) => r.status).join(",")}) \u2192 charged then fully refunded: band +0, money +0` } : { name: "pre-upstream reject is refunded", status: "FAIL", detail: `${N} rejected request(s) left band +${dBand} and ${dMoney}\xB5\xA2 charged; expected 0 and 0 \u2014 refund-on-reject is ${dMoney > 0 ? "not firing" : "over-refunding"}.` });
9985
+ }
9986
+ if (!opts.key) {
9987
+ checks.push({ name: "management planes bill correctly", status: "SKIP", detail: "needs --key <key> to call the iam/apikeys planes. NOT COVERED: whether a direct IAM call is billed, and whether the apikeys plane stayed free." });
9988
+ } else {
9989
+ for (const plane of [
9990
+ { name: "iam plane bills 1", project: "iam", url: "https://iam.apiblaze.com/1.0.0/prod/groups", want: 1 },
9991
+ { name: "apikeys plane is free", project: "apikeys", url: "https://apikeys.apiblaze.com/1.0.0/prod/apikeys", want: 0 }
9992
+ ]) {
9993
+ const b = await readQuiet(readLedger);
9994
+ const st = await fire(plane.url, 1, { "X-API-Key": opts.key });
9995
+ const aft = await readQuiet(readLedger);
9996
+ const d = aft.band_count - b.band_count;
9997
+ if (shared.length) {
9998
+ checks.push({ name: plane.name, status: "SKIP", detail: `measured band +${d}, but the wallet is shared so that number is not attributable. NOT VERIFIED.` });
9999
+ } else {
10000
+ checks.push(d === plane.want ? { name: plane.name, status: "PASS", detail: `1 call (${st.map((x) => x.status).join(",")}) \u2192 band +${d}` } : { name: plane.name, status: "FAIL", detail: `1 call (${st.map((x) => x.status).join(",")}) \u2192 band +${d}, expected +${plane.want}. ${d > plane.want ? "Charged more than once for one call." : "Free lane \u2014 this plane is usable without paying."}` });
10001
+ }
10002
+ }
10003
+ }
10004
+ if (!opts.mcpHost || !opts.key) {
10005
+ checks.push({ name: "MCP tools/call = 1 charge", status: "SKIP", detail: "needs --mcp-host <host> --key <key> to send a SERVED tool call. Fixed in code and deployed, but NOT verified live: an unauthenticated probe bills 0\xA2 either way, so it would prove nothing." });
10006
+ } else {
10007
+ const sentMcp = await fire(`https://${opts.mcpHost}/${SANDBOX.version}/${SANDBOX.environment}`, 1, { "X-API-Key": opts.key });
10008
+ const ray = sentMcp[0]?.ray;
10009
+ if (!ray || sentMcp[0].status >= 400) {
10010
+ checks.push({ name: "MCP tools/call = 1 charge", status: "SKIP", detail: `the MCP call returned ${sentMcp[0]?.status} \u2014 only a SERVED tool call can tell one charge from two.` });
10011
+ } else {
10012
+ const rows = await rowsForRays(SANDBOX.project, SANDBOX.version, SANDBOX.tenant, [ray]);
10013
+ const n = rows.get(ray)?.length ?? 0;
10014
+ checks.push(n === 1 ? { name: "MCP tools/call = 1 charge", status: "PASS", detail: "1 tool call \u2192 1 billed row (was 2 before 2026-08-11)" } : { name: "MCP tools/call = 1 charge", status: "FAIL", detail: `1 tool call \u2192 ${n} billed row(s); expected 1. The edge hop is billing again.` });
10015
+ }
10016
+ }
10017
+ return checks;
10018
+ }
10019
+ function printChecks(checks) {
10020
+ console.log(import_chalk50.default.bold("\n Results\n"));
10021
+ const mark = { PASS: import_chalk50.default.green(" PASS"), FAIL: import_chalk50.default.red(" FAIL"), SKIP: import_chalk50.default.dim(" SKIP"), KNOWN: import_chalk50.default.yellow(" KNOWN") };
10022
+ for (const ch of checks) {
10023
+ console.log(` ${mark[ch.status]} ${import_chalk50.default.bold(ch.name)}`);
10024
+ console.log(import_chalk50.default.dim(` ${ch.detail}`));
10025
+ }
10026
+ const fails = checks.filter((c) => c.status === "FAIL").length;
10027
+ const skips = checks.filter((c) => c.status === "SKIP").length;
10028
+ const known = checks.filter((c) => c.status === "KNOWN").length;
10029
+ console.log("");
10030
+ const passes = checks.filter((c) => c.status === "PASS").length;
10031
+ if (fails) console.log(import_chalk50.default.red(` ${fails} check(s) FAILED.`));
10032
+ else if (passes) console.log(import_chalk50.default.green(` ${passes} check(s) passed, 0 failed.`));
10033
+ else console.log(import_chalk50.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
10034
+ if (known) console.log(import_chalk50.default.yellow(` ${known} known-open issue(s) still outstanding.`));
10035
+ if (skips) console.log(import_chalk50.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
10036
+ console.log("");
10037
+ }
10038
+
10039
+ // src/commands/op.ts
9808
10040
  var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
9809
10041
  var DASHBOARD_BASE8 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
9810
10042
  function isOperatorLogin() {
@@ -9838,99 +10070,102 @@ var OP_COMMANDS = [
9838
10070
  { cmd: "op mark", blurb: 'record "I changed something significant just now"', extra: 'apiblaze op mark "cached tenant count"' },
9839
10071
  { cmd: "op latency compare", blurb: "latency before vs after the last mark, equal windows", extra: "--hours N" },
9840
10072
  { cmd: "op credits", blurb: "list credit wallets across all teams" },
10073
+ { cmd: "op billing", blurb: "billing conformance: every door metered, 1 request = 1 charge", extra: "--key K --count N" },
10074
+ { cmd: "op billing doors", blurb: "every live route + workers.dev URL, and whether it passes the meter (free, read-only)" },
10075
+ { cmd: "op billing meter", blurb: "send known traffic, assert the ledger moved by exactly that much", extra: "spends a few tenths of a cent" },
9841
10076
  { cmd: "op residue", blurb: "external-store residue report (Upstash + Neon/OpenFGA) \u2014 dry run, deletes nothing" },
9842
10077
  { cmd: "op sweep", blurb: "delete the orphans the residue report found", extra: "asks first; -y to skip" }
9843
10078
  ];
9844
10079
  function renderOpCommands() {
9845
10080
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
9846
10081
  const lines = OP_COMMANDS.map((c) => {
9847
- const left = ` ${import_chalk50.default.cyan(`apiblaze ${c.cmd}`)}`;
10082
+ const left = ` ${import_chalk51.default.cyan(`apiblaze ${c.cmd}`)}`;
9848
10083
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
9849
- return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk50.default.dim(`(${c.extra})`) : ""}`;
10084
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk51.default.dim(`(${c.extra})`) : ""}`;
9850
10085
  });
9851
10086
  return [
9852
- import_chalk50.default.bold("Operator commands"),
10087
+ import_chalk51.default.bold("Operator commands"),
9853
10088
  ...lines,
9854
10089
  "",
9855
- import_chalk50.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
9856
- import_chalk50.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
9857
- import_chalk50.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
10090
+ import_chalk51.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
10091
+ import_chalk51.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
10092
+ import_chalk51.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
9858
10093
  "",
9859
- import_chalk50.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
9860
- import_chalk50.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
9861
- import_chalk50.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
10094
+ import_chalk51.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
10095
+ import_chalk51.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
10096
+ import_chalk51.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
9862
10097
  ].join("\n");
9863
10098
  }
9864
10099
  function printResidue(report, applied) {
9865
10100
  const up = report?.upstash ?? {};
9866
10101
  const fga = report?.fga ?? {};
9867
10102
  const ghosts = report?.ghosts ?? {};
9868
- console.log(import_chalk50.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
9869
- console.log(import_chalk50.default.bold("\n Upstash"));
10103
+ console.log(import_chalk51.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
10104
+ console.log(import_chalk51.default.bold("\n Upstash"));
9870
10105
  const orphans = up.orphans ?? [];
9871
- if (orphans.length === 0) console.log(import_chalk50.default.green(" no orphaned keys"));
9872
- for (const o of orphans) console.log(` ${import_chalk50.default.yellow(o.key)} ${import_chalk50.default.dim(`\u2014 ${o.reason}`)}`);
9873
- console.log(import_chalk50.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
10106
+ if (orphans.length === 0) console.log(import_chalk51.default.green(" no orphaned keys"));
10107
+ for (const o of orphans) console.log(` ${import_chalk51.default.yellow(o.key)} ${import_chalk51.default.dim(`\u2014 ${o.reason}`)}`);
10108
+ console.log(import_chalk51.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
9874
10109
  if (up.anon_wallet_detail) {
9875
10110
  const d = up.anon_wallet_detail;
9876
- console.log(import_chalk50.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)"}`));
10111
+ console.log(import_chalk51.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)"}`));
9877
10112
  }
9878
10113
  if (up.keyspace_census) {
9879
10114
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
9880
- console.log(import_chalk50.default.dim(` keyspace: ${census}`));
10115
+ console.log(import_chalk51.default.dim(` keyspace: ${census}`));
9881
10116
  }
9882
- if (up.unknown?.length) console.log(import_chalk50.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
9883
- if (applied) console.log(` ${import_chalk50.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
9884
- for (const e of up.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
9885
- console.log(import_chalk50.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
10117
+ if (up.unknown?.length) console.log(import_chalk51.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
10118
+ if (applied) console.log(` ${import_chalk51.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
10119
+ for (const e of up.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
10120
+ console.log(import_chalk51.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
9886
10121
  if (applied) {
9887
10122
  const swept = fga?.swept ?? [];
9888
- if (swept.length === 0) console.log(import_chalk50.default.green(" no orphaned stores"));
10123
+ if (swept.length === 0) console.log(import_chalk51.default.green(" no orphaned stores"));
9889
10124
  for (const s of swept) {
9890
10125
  console.log(
9891
- ` ${import_chalk50.default.yellow(s.store_id)} ${import_chalk50.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
10126
+ ` ${import_chalk51.default.yellow(s.store_id)} ${import_chalk51.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
9892
10127
  );
9893
10128
  }
9894
- if (fga?.remaining) console.log(import_chalk50.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
10129
+ if (fga?.remaining) console.log(import_chalk51.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
9895
10130
  const st = fga?.side_tables;
9896
- if (st) console.log(import_chalk50.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})` : ""}`));
10131
+ if (st) console.log(import_chalk51.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})` : ""}`));
9897
10132
  } else {
9898
10133
  const fgaOrphans = fga?.orphans ?? [];
9899
- if (fgaOrphans.length === 0) console.log(import_chalk50.default.green(" no orphaned stores"));
10134
+ if (fgaOrphans.length === 0) console.log(import_chalk51.default.green(" no orphaned stores"));
9900
10135
  for (const s of fgaOrphans) {
9901
10136
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
9902
- console.log(` ${import_chalk50.default.yellow(s.store_id)} ${import_chalk50.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
10137
+ console.log(` ${import_chalk51.default.yellow(s.store_id)} ${import_chalk51.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
9903
10138
  }
9904
- console.log(import_chalk50.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
10139
+ console.log(import_chalk51.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
9905
10140
  const st = fga?.side_tables;
9906
- if (st) console.log(import_chalk50.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`));
10141
+ if (st) console.log(import_chalk51.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`));
9907
10142
  }
9908
- for (const e of fga?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
9909
- console.log(import_chalk50.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
10143
+ for (const e of fga?.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
10144
+ console.log(import_chalk51.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
9910
10145
  if (applied) {
9911
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk50.default.green(" no ghost tuples"));
9912
- else console.log(` ${import_chalk50.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk50.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
10146
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk51.default.green(" no ghost tuples"));
10147
+ else console.log(` ${import_chalk51.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk51.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
9913
10148
  } else {
9914
10149
  const n = ghosts?.ghost_count ?? 0;
9915
- if (n === 0) console.log(import_chalk50.default.green(` no ghost tuples ${import_chalk50.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
10150
+ if (n === 0) console.log(import_chalk51.default.green(` no ghost tuples ${import_chalk51.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
9916
10151
  else {
9917
- console.log(import_chalk50.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
10152
+ console.log(import_chalk51.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
9918
10153
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
9919
- console.log(import_chalk50.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
10154
+ console.log(import_chalk51.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
9920
10155
  }
9921
- if (n > 20) console.log(import_chalk50.default.dim(` \u2026 and ${n - 20} more`));
10156
+ if (n > 20) console.log(import_chalk51.default.dim(` \u2026 and ${n - 20} more`));
9922
10157
  }
9923
10158
  }
9924
- for (const e of ghosts?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
10159
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
9925
10160
  console.log();
9926
10161
  }
9927
10162
  async function runOp(sub, opts = {}, view) {
9928
10163
  if (!loadCredentials()) {
9929
- console.log(import_chalk50.default.dim("Not logged in. Run `apiblaze login`."));
10164
+ console.log(import_chalk51.default.dim("Not logged in. Run `apiblaze login`."));
9930
10165
  return;
9931
10166
  }
9932
10167
  if (!isOperatorLogin()) {
9933
- console.log(import_chalk50.default.dim("`apiblaze op` is only available to platform operators."));
10168
+ console.log(import_chalk51.default.dim("`apiblaze op` is only available to platform operators."));
9934
10169
  return;
9935
10170
  }
9936
10171
  switch (sub) {
@@ -9956,17 +10191,17 @@ async function runOp(sub, opts = {}, view) {
9956
10191
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
9957
10192
  printResidue(report, false);
9958
10193
  if (nUp + nFga + nGhost + nSide === 0) {
9959
- console.log(import_chalk50.default.green("Nothing to sweep."));
10194
+ console.log(import_chalk51.default.green("Nothing to sweep."));
9960
10195
  return;
9961
10196
  }
9962
10197
  if (!opts.yes) {
9963
10198
  const readline3 = await import("readline/promises");
9964
10199
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
9965
10200
  const answer = await rl.question(
9966
- import_chalk50.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: `)
10201
+ import_chalk51.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: `)
9967
10202
  );
9968
10203
  rl.close();
9969
- if (answer.trim() !== "sweep") return void console.log(import_chalk50.default.dim("Aborted."));
10204
+ if (answer.trim() !== "sweep") return void console.log(import_chalk51.default.dim("Aborted."));
9970
10205
  }
9971
10206
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
9972
10207
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -9976,26 +10211,61 @@ async function runOp(sub, opts = {}, view) {
9976
10211
  case "mark": {
9977
10212
  const label3 = (view ?? "").trim();
9978
10213
  if (!label3) {
9979
- console.log(import_chalk50.default.red("Give the change a name:") + import_chalk50.default.cyan(' apiblaze op mark "cached tenant count"'));
10214
+ console.log(import_chalk51.default.red("Give the change a name:") + import_chalk51.default.cyan(' apiblaze op mark "cached tenant count"'));
9980
10215
  return;
9981
10216
  }
9982
10217
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
9983
10218
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
9984
- console.log(import_chalk50.default.green(`
9985
- Marked: `) + import_chalk50.default.bold(label3));
9986
- console.log(import_chalk50.default.dim(` ${ts}`));
9987
- console.log(import_chalk50.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk50.default.cyan("apiblaze op latency compare") + "\n");
10219
+ console.log(import_chalk51.default.green(`
10220
+ Marked: `) + import_chalk51.default.bold(label3));
10221
+ console.log(import_chalk51.default.dim(` ${ts}`));
10222
+ console.log(import_chalk51.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk51.default.cyan("apiblaze op latency compare") + "\n");
9988
10223
  return;
9989
10224
  }
9990
10225
  case "credits": {
9991
10226
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
9992
10227
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
9993
10228
  const accounts = data?.accounts ?? [];
9994
- if (accounts.length === 0) return void console.log(import_chalk50.default.dim("No credit wallets."));
10229
+ if (accounts.length === 0) return void console.log(import_chalk51.default.dim("No credit wallets."));
9995
10230
  for (const a of accounts) {
9996
10231
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
9997
- console.log(` ${import_chalk50.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk50.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk50.default.dim(" \u2014 anon") : ""}`);
10232
+ console.log(` ${import_chalk51.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk51.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk51.default.dim(" \u2014 anon") : ""}`);
10233
+ }
10234
+ return;
10235
+ }
10236
+ // ── BILLING (specs/billing/callable_outside_a_request.md) ─────────────
10237
+ // Two invariants: every door is metered (so nobody runs their RBAC/OpenFGA on
10238
+ // us for free), and one request is charged one request (so closing a free lane
10239
+ // by billing it can never turn into a double charge).
10240
+ case "billing": {
10241
+ const which = (view ?? "").trim().toLowerCase();
10242
+ if (which && which !== "doors" && which !== "meter") {
10243
+ return void console.log(import_chalk51.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
10244
+ }
10245
+ const checks = [];
10246
+ let doorsData = null;
10247
+ if (which !== "meter") {
10248
+ doorsData = await opCall({ method: "GET", path: "/operator/billing/doors", summary: "billing door audit" });
10249
+ if (!opts.json) checks.push(...printDoors(doorsData));
10250
+ }
10251
+ if (which !== "doors") {
10252
+ const teamId = opts.team || void 0;
10253
+ const readLedger = async () => {
10254
+ const q = teamId ? `?team_id=${encodeURIComponent(String(teamId))}` : "";
10255
+ return await opCall({
10256
+ method: "GET",
10257
+ path: `/operator/billing/ledger${q || "?team_id=team_1782844865835_zujrf"}`,
10258
+ summary: "billing ledger snapshot"
10259
+ });
10260
+ };
10261
+ checks.push(...await runMeter(readLedger, {
10262
+ key: opts.key,
10263
+ mcpHost: opts.mcpHost,
10264
+ count: opts.count ? Number(opts.count) : void 0
10265
+ }));
9998
10266
  }
10267
+ if (opts.json) return void console.log(JSON.stringify({ doors: doorsData, checks }, null, 2));
10268
+ printChecks(checks);
9999
10269
  return;
10000
10270
  }
10001
10271
  // ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
@@ -10031,29 +10301,29 @@ async function runOp(sub, opts = {}, view) {
10031
10301
  const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
10032
10302
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10033
10303
  const t = data.thresholds_ms;
10034
- console.log(import_chalk50.default.bold("\nHow good was apiblaze itself?") + import_chalk50.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
10035
- console.log(import_chalk50.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
10304
+ console.log(import_chalk51.default.bold("\nHow good was apiblaze itself?") + import_chalk51.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
10305
+ console.log(import_chalk51.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
10036
10306
  `));
10037
- console.log(import_chalk50.default.dim(" date reqs excellent okay bad terrible"));
10307
+ console.log(import_chalk51.default.dim(" date reqs excellent okay bad terrible"));
10038
10308
  for (const d of data.days ?? []) {
10039
10309
  const p = d.pct;
10040
- const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk50.default.dim(`${String(v).padStart(5)}%`);
10310
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk51.default.dim(`${String(v).padStart(5)}%`);
10041
10311
  console.log(
10042
- ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk50.default.green)} ${cell(p.okay, import_chalk50.default.cyan)} ${cell(p.bad, import_chalk50.default.yellow)} ${cell(p.terrible, import_chalk50.default.red)}`
10312
+ ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk51.default.green)} ${cell(p.okay, import_chalk51.default.cyan)} ${cell(p.bad, import_chalk51.default.yellow)} ${cell(p.terrible, import_chalk51.default.red)}`
10043
10313
  );
10044
10314
  }
10045
10315
  const cul = data.culprits ?? [];
10046
10316
  if (cul.length) {
10047
- console.log(import_chalk50.default.bold("\n Who caused the bad and terrible ones\n"));
10048
- console.log(import_chalk50.default.dim(" bad terrible feature \u2192 dependency"));
10317
+ console.log(import_chalk51.default.bold("\n Who caused the bad and terrible ones\n"));
10318
+ console.log(import_chalk51.default.dim(" bad terrible feature \u2192 dependency"));
10049
10319
  for (const r of cul.slice(0, 12)) {
10050
10320
  if (!r.bad && !r.terrible) continue;
10051
10321
  console.log(
10052
- ` ${String(r.bad).padStart(6)} ${import_chalk50.default.red(String(r.terrible).padStart(8))} ${import_chalk50.default.yellow(r.feature)} ${import_chalk50.default.dim("\u2192")} ${import_chalk50.default.cyan(r.dep)}`
10322
+ ` ${String(r.bad).padStart(6)} ${import_chalk51.default.red(String(r.terrible).padStart(8))} ${import_chalk51.default.yellow(r.feature)} ${import_chalk51.default.dim("\u2192")} ${import_chalk51.default.cyan(r.dep)}`
10053
10323
  );
10054
10324
  }
10055
10325
  }
10056
- if (data.caveat) console.log(import_chalk50.default.dim(`
10326
+ if (data.caveat) console.log(import_chalk51.default.dim(`
10057
10327
  \u26A0 ${data.caveat}
10058
10328
  `));
10059
10329
  return;
@@ -10062,24 +10332,24 @@ async function runOp(sub, opts = {}, view) {
10062
10332
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
10063
10333
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10064
10334
  const b = data.before, a = data.after, d = data.delta;
10065
- console.log(import_chalk50.default.bold(`
10066
- Before vs after: `) + import_chalk50.default.cyan(data.marker.label));
10067
- console.log(import_chalk50.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
10335
+ console.log(import_chalk51.default.bold(`
10336
+ Before vs after: `) + import_chalk51.default.cyan(data.marker.label));
10337
+ console.log(import_chalk51.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
10068
10338
  `));
10069
10339
  const row = (name, before, after, delta) => {
10070
10340
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
10071
10341
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
10072
- console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk50.default.green(txt) : import_chalk50.default.red(txt) : import_chalk50.default.dim(txt)}`);
10342
+ console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk51.default.green(txt) : import_chalk51.default.red(txt) : import_chalk51.default.dim(txt)}`);
10073
10343
  };
10074
- console.log(import_chalk50.default.dim(" metric before after change"));
10344
+ console.log(import_chalk51.default.dim(" metric before after change"));
10075
10345
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
10076
10346
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
10077
10347
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
10078
10348
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
10079
- console.log(import_chalk50.default.dim(`
10349
+ console.log(import_chalk51.default.dim(`
10080
10350
  requests: ${b.requests} before \xB7 ${a.requests} after`));
10081
10351
  for (const w of data.warnings ?? []) {
10082
- console.log((data.trustworthy ? import_chalk50.default.dim : import_chalk50.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
10352
+ console.log((data.trustworthy ? import_chalk51.default.dim : import_chalk51.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
10083
10353
  }
10084
10354
  console.log("");
10085
10355
  return;
@@ -10088,19 +10358,19 @@ Before vs after: `) + import_chalk50.default.cyan(data.marker.label));
10088
10358
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
10089
10359
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10090
10360
  const rows2 = data?.rows ?? [];
10091
- if (!rows2.length) return void console.log(import_chalk50.default.dim("No requests over the threshold in that window."));
10092
- console.log(import_chalk50.default.bold(`
10361
+ if (!rows2.length) return void console.log(import_chalk51.default.dim("No requests over the threshold in that window."));
10362
+ console.log(import_chalk51.default.bold(`
10093
10363
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10094
10364
  `));
10095
- console.log(import_chalk50.default.dim(" total ours theirs blame request id"));
10365
+ console.log(import_chalk51.default.dim(" total ours theirs blame request id"));
10096
10366
  for (const r of rows2.slice(0, 30)) {
10097
10367
  console.log(
10098
- ` ${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_chalk50.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk50.default.dim(r.request_id || "")}`
10368
+ ` ${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_chalk51.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk51.default.dim(r.request_id || "")}`
10099
10369
  );
10100
10370
  }
10101
- console.log(import_chalk50.default.dim(`
10371
+ console.log(import_chalk51.default.dim(`
10102
10372
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
10103
- console.log(import_chalk50.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
10373
+ console.log(import_chalk51.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
10104
10374
  `));
10105
10375
  return;
10106
10376
  }
@@ -10108,17 +10378,17 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10108
10378
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
10109
10379
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10110
10380
  const rows2 = data?.rows ?? [];
10111
- if (!rows2.length) return void console.log(import_chalk50.default.dim("No LLM traffic in that window."));
10112
- console.log(import_chalk50.default.bold(`
10381
+ if (!rows2.length) return void console.log(import_chalk51.default.dim("No LLM traffic in that window."));
10382
+ console.log(import_chalk51.default.bold(`
10113
10383
  LLM timing \u2014 last ${data.window_hours}h
10114
10384
  `));
10115
- console.log(import_chalk50.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
10385
+ console.log(import_chalk51.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
10116
10386
  for (const r of rows2) {
10117
10387
  console.log(
10118
10388
  ` ${String(Math.round(r.requests)).padStart(8)} ${String(Math.round(r.gen_p95_ms)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95)).padStart(6)}/${String(Math.round(r.output_tokens_p95)).padEnd(6)} ${r.model || "-"}`
10119
10389
  );
10120
10390
  }
10121
- console.log(import_chalk50.default.dim(`
10391
+ console.log(import_chalk51.default.dim(`
10122
10392
  ${data.note}
10123
10393
  `));
10124
10394
  return;
@@ -10129,26 +10399,26 @@ LLM timing \u2014 last ${data.window_hours}h
10129
10399
  ]);
10130
10400
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
10131
10401
  const ov = summary?.apiblaze_overhead_ms ?? {};
10132
- console.log(import_chalk50.default.bold(`
10402
+ console.log(import_chalk51.default.bold(`
10133
10403
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
10134
10404
  `));
10135
- console.log(` ${import_chalk50.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_chalk50.default.dim("\u2190 ours")}`);
10136
- console.log(` ${import_chalk50.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk50.default.dim("\u2190 theirs")}`);
10137
- console.log(import_chalk50.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
10405
+ console.log(` ${import_chalk51.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_chalk51.default.dim("\u2190 ours")}`);
10406
+ console.log(` ${import_chalk51.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk51.default.dim("\u2190 theirs")}`);
10407
+ console.log(import_chalk51.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
10138
10408
  `));
10139
10409
  const rows = blame?.blame ?? [];
10140
- if (!rows.length) return void console.log(import_chalk50.default.dim("No latency rows in that window."));
10141
- console.log(import_chalk50.default.bold(" Which feature ate the time, and what inside it\n"));
10142
- console.log(import_chalk50.default.dim(" share p95 feature \u2192 dependency"));
10410
+ if (!rows.length) return void console.log(import_chalk51.default.dim("No latency rows in that window."));
10411
+ console.log(import_chalk51.default.bold(" Which feature ate the time, and what inside it\n"));
10412
+ console.log(import_chalk51.default.dim(" share p95 feature \u2192 dependency"));
10143
10413
  for (const r of rows.slice(0, 15)) {
10144
10414
  const share = `${(r.share * 100).toFixed(1)}%`;
10145
- console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk50.default.yellow(r.feature)} ${import_chalk50.default.dim("\u2192")} ${import_chalk50.default.cyan(r.dep)}`);
10415
+ console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk51.default.yellow(r.feature)} ${import_chalk51.default.dim("\u2192")} ${import_chalk51.default.cyan(r.dep)}`);
10146
10416
  }
10147
10417
  console.log("");
10148
10418
  return;
10149
10419
  }
10150
10420
  default:
10151
- console.log(import_chalk50.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
10421
+ console.log(import_chalk51.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
10152
10422
  }
10153
10423
  }
10154
10424
 
@@ -10211,7 +10481,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
10211
10481
  try {
10212
10482
  const resolved = parseInt(port ?? opts.port, 10);
10213
10483
  if (Number.isNaN(resolved)) {
10214
- console.error(import_chalk51.default.red(`Invalid port: ${port ?? opts.port}`));
10484
+ console.error(import_chalk52.default.red(`Invalid port: ${port ?? opts.port}`));
10215
10485
  process.exit(1);
10216
10486
  }
10217
10487
  await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
@@ -10310,7 +10580,7 @@ apikeys.command("list").description("List control-plane API keys in your team").
10310
10580
  apikeys.command("mint").description("Create a control-plane API key (secret shown once)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
10311
10581
  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 })));
10312
10582
  program.addCommand(apikeys, { hidden: true });
10313
- var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | mark | credits | residue | sweep (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare | grades \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)").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
10583
+ 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", () => `
10314
10584
  ${renderOpCommands()}
10315
10585
  `);
10316
10586
  program.addCommand(op, { hidden: true });
@@ -10343,7 +10613,7 @@ function groupedCommandHelp() {
10343
10613
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
10344
10614
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
10345
10615
  }).filter(Boolean).join("\n");
10346
- return `${import_chalk51.default.bold(g.title)}
10616
+ return `${import_chalk52.default.bold(g.title)}
10347
10617
  ${rows}`;
10348
10618
  }).join("\n\n");
10349
10619
  }
@@ -10381,14 +10651,14 @@ async function recoverStaleTeam() {
10381
10651
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
10382
10652
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
10383
10653
  if (!linked) {
10384
- console.error(import_chalk51.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
10654
+ console.error(import_chalk52.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
10385
10655
  return;
10386
10656
  }
10387
10657
  if (linked.teamId === creds.teamId) return;
10388
10658
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
10389
10659
  delete next.activeTenant;
10390
10660
  saveCredentials(next);
10391
- console.error(import_chalk51.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk51.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
10661
+ console.error(import_chalk52.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk52.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
10392
10662
  } catch {
10393
10663
  }
10394
10664
  }
@@ -10396,16 +10666,16 @@ async function printError(err) {
10396
10666
  if (err instanceof ApiError) {
10397
10667
  const data = err.body;
10398
10668
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
10399
- console.error(import_chalk51.default.red(`
10669
+ console.error(import_chalk52.default.red(`
10400
10670
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
10401
10671
  if (err.status === 403 || err.status === 404) {
10402
10672
  await recoverStaleTeam();
10403
10673
  }
10404
10674
  } else if (err instanceof Error) {
10405
- console.error(import_chalk51.default.red(`
10675
+ console.error(import_chalk52.default.red(`
10406
10676
  Error: ${err.message}`));
10407
10677
  } else {
10408
- console.error(import_chalk51.default.red("\nUnknown error"));
10678
+ console.error(import_chalk52.default.red("\nUnknown error"));
10409
10679
  }
10410
10680
  }
10411
10681
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.6",
3
+ "version": "0.20.9",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",