apiblaze 0.20.5 → 0.20.8

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 +393 -87
  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.5";
935
+ var version = "0.20.8";
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() {
@@ -9834,102 +10066,106 @@ var OP_COMMANDS = [
9834
10066
  { cmd: "op latency", blurb: "which FEATURE is slow, and what inside it", extra: "--hours N --team T --project P" },
9835
10067
  { cmd: "op latency slow", blurb: "slowest requests, each with a request id you can look up", extra: "--min-ms N" },
9836
10068
  { cmd: "op latency llm", blurb: "chat timing by provider/model, tokens, credit-reserve cost" },
10069
+ { cmd: "op latency grades", blurb: "daily %% excellent / okay / bad / terrible, and who to blame", extra: "--days N" },
9837
10070
  { cmd: "op mark", blurb: 'record "I changed something significant just now"', extra: 'apiblaze op mark "cached tenant count"' },
9838
10071
  { cmd: "op latency compare", blurb: "latency before vs after the last mark, equal windows", extra: "--hours N" },
9839
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" },
9840
10076
  { cmd: "op residue", blurb: "external-store residue report (Upstash + Neon/OpenFGA) \u2014 dry run, deletes nothing" },
9841
10077
  { cmd: "op sweep", blurb: "delete the orphans the residue report found", extra: "asks first; -y to skip" }
9842
10078
  ];
9843
10079
  function renderOpCommands() {
9844
10080
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
9845
10081
  const lines = OP_COMMANDS.map((c) => {
9846
- const left = ` ${import_chalk50.default.cyan(`apiblaze ${c.cmd}`)}`;
10082
+ const left = ` ${import_chalk51.default.cyan(`apiblaze ${c.cmd}`)}`;
9847
10083
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
9848
- 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})`) : ""}`;
9849
10085
  });
9850
10086
  return [
9851
- import_chalk50.default.bold("Operator commands"),
10087
+ import_chalk51.default.bold("Operator commands"),
9852
10088
  ...lines,
9853
10089
  "",
9854
- import_chalk50.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
9855
- import_chalk50.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
9856
- 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`."),
9857
10093
  "",
9858
- import_chalk50.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
9859
- import_chalk50.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
9860
- 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.")
9861
10097
  ].join("\n");
9862
10098
  }
9863
10099
  function printResidue(report, applied) {
9864
10100
  const up = report?.upstash ?? {};
9865
10101
  const fga = report?.fga ?? {};
9866
10102
  const ghosts = report?.ghosts ?? {};
9867
- console.log(import_chalk50.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
9868
- 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"));
9869
10105
  const orphans = up.orphans ?? [];
9870
- if (orphans.length === 0) console.log(import_chalk50.default.green(" no orphaned keys"));
9871
- for (const o of orphans) console.log(` ${import_chalk50.default.yellow(o.key)} ${import_chalk50.default.dim(`\u2014 ${o.reason}`)}`);
9872
- 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}`));
9873
10109
  if (up.anon_wallet_detail) {
9874
10110
  const d = up.anon_wallet_detail;
9875
- 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)"}`));
9876
10112
  }
9877
10113
  if (up.keyspace_census) {
9878
10114
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
9879
- console.log(import_chalk50.default.dim(` keyspace: ${census}`));
10115
+ console.log(import_chalk51.default.dim(` keyspace: ${census}`));
9880
10116
  }
9881
- if (up.unknown?.length) console.log(import_chalk50.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
9882
- if (applied) console.log(` ${import_chalk50.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
9883
- for (const e of up.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
9884
- 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"));
9885
10121
  if (applied) {
9886
10122
  const swept = fga?.swept ?? [];
9887
- 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"));
9888
10124
  for (const s of swept) {
9889
10125
  console.log(
9890
- ` ${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`)}`
9891
10127
  );
9892
10128
  }
9893
- 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`));
9894
10130
  const st = fga?.side_tables;
9895
- 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})` : ""}`));
9896
10132
  } else {
9897
10133
  const fgaOrphans = fga?.orphans ?? [];
9898
- 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"));
9899
10135
  for (const s of fgaOrphans) {
9900
10136
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
9901
- 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)`)}`);
9902
10138
  }
9903
- 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}`));
9904
10140
  const st = fga?.side_tables;
9905
- 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`));
9906
10142
  }
9907
- for (const e of fga?.errors ?? []) console.log(import_chalk50.default.red(` error: ${e}`));
9908
- 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"));
9909
10145
  if (applied) {
9910
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk50.default.green(" no ghost tuples"));
9911
- 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)`)}`);
9912
10148
  } else {
9913
10149
  const n = ghosts?.ghost_count ?? 0;
9914
- 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)`)}`));
9915
10151
  else {
9916
- 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:`));
9917
10153
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
9918
- 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}`));
9919
10155
  }
9920
- 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`));
9921
10157
  }
9922
10158
  }
9923
- 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}`));
9924
10160
  console.log();
9925
10161
  }
9926
10162
  async function runOp(sub, opts = {}, view) {
9927
10163
  if (!loadCredentials()) {
9928
- 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`."));
9929
10165
  return;
9930
10166
  }
9931
10167
  if (!isOperatorLogin()) {
9932
- 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."));
9933
10169
  return;
9934
10170
  }
9935
10171
  switch (sub) {
@@ -9955,17 +10191,17 @@ async function runOp(sub, opts = {}, view) {
9955
10191
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
9956
10192
  printResidue(report, false);
9957
10193
  if (nUp + nFga + nGhost + nSide === 0) {
9958
- console.log(import_chalk50.default.green("Nothing to sweep."));
10194
+ console.log(import_chalk51.default.green("Nothing to sweep."));
9959
10195
  return;
9960
10196
  }
9961
10197
  if (!opts.yes) {
9962
10198
  const readline3 = await import("readline/promises");
9963
10199
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
9964
10200
  const answer = await rl.question(
9965
- 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: `)
9966
10202
  );
9967
10203
  rl.close();
9968
- 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."));
9969
10205
  }
9970
10206
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
9971
10207
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -9975,26 +10211,61 @@ async function runOp(sub, opts = {}, view) {
9975
10211
  case "mark": {
9976
10212
  const label3 = (view ?? "").trim();
9977
10213
  if (!label3) {
9978
- 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"'));
9979
10215
  return;
9980
10216
  }
9981
10217
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
9982
10218
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
9983
- console.log(import_chalk50.default.green(`
9984
- Marked: `) + import_chalk50.default.bold(label3));
9985
- console.log(import_chalk50.default.dim(` ${ts}`));
9986
- 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");
9987
10223
  return;
9988
10224
  }
9989
10225
  case "credits": {
9990
10226
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
9991
10227
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
9992
10228
  const accounts = data?.accounts ?? [];
9993
- 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."));
9994
10230
  for (const a of accounts) {
9995
10231
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
9996
- 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
+ }));
9997
10266
  }
10267
+ if (opts.json) return void console.log(JSON.stringify({ doors: doorsData, checks }, null, 2));
10268
+ printChecks(checks);
9998
10269
  return;
9999
10270
  }
10000
10271
  // ── LATENCY (specs/latency/ §7.4) ─────────────────────────────────────
@@ -10014,7 +10285,11 @@ async function runOp(sub, opts = {}, view) {
10014
10285
  ["route", "route"],
10015
10286
  ["environment", "environment"],
10016
10287
  ["statusClass", "status_class"],
10017
- ["minMs", "min_ms"]
10288
+ ["minMs", "min_ms"],
10289
+ ["days", "days"],
10290
+ ["excellent", "excellent"],
10291
+ ["okay", "okay"],
10292
+ ["bad", "bad"]
10018
10293
  ];
10019
10294
  for (const [optKey, queryKey] of FILTERS) {
10020
10295
  const v = opts[optKey];
@@ -10022,28 +10297,59 @@ async function runOp(sub, opts = {}, view) {
10022
10297
  }
10023
10298
  const q = qs.toString() ? `?${qs}` : "";
10024
10299
  const which = view;
10300
+ if (which === "grades") {
10301
+ const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
10302
+ if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10303
+ const t = data.thresholds_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+
10306
+ `));
10307
+ console.log(import_chalk51.default.dim(" date reqs excellent okay bad terrible"));
10308
+ for (const d of data.days ?? []) {
10309
+ const p = d.pct;
10310
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk51.default.dim(`${String(v).padStart(5)}%`);
10311
+ console.log(
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)}`
10313
+ );
10314
+ }
10315
+ const cul = data.culprits ?? [];
10316
+ if (cul.length) {
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"));
10319
+ for (const r of cul.slice(0, 12)) {
10320
+ if (!r.bad && !r.terrible) continue;
10321
+ console.log(
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)}`
10323
+ );
10324
+ }
10325
+ }
10326
+ if (data.caveat) console.log(import_chalk51.default.dim(`
10327
+ \u26A0 ${data.caveat}
10328
+ `));
10329
+ return;
10330
+ }
10025
10331
  if (which === "compare") {
10026
10332
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
10027
10333
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10028
10334
  const b = data.before, a = data.after, d = data.delta;
10029
- console.log(import_chalk50.default.bold(`
10030
- Before vs after: `) + import_chalk50.default.cyan(data.marker.label));
10031
- 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
10032
10338
  `));
10033
10339
  const row = (name, before, after, delta) => {
10034
10340
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
10035
10341
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
10036
- 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)}`);
10037
10343
  };
10038
- console.log(import_chalk50.default.dim(" metric before after change"));
10344
+ console.log(import_chalk51.default.dim(" metric before after change"));
10039
10345
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
10040
10346
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
10041
10347
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
10042
10348
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
10043
- console.log(import_chalk50.default.dim(`
10349
+ console.log(import_chalk51.default.dim(`
10044
10350
  requests: ${b.requests} before \xB7 ${a.requests} after`));
10045
10351
  for (const w of data.warnings ?? []) {
10046
- 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}`));
10047
10353
  }
10048
10354
  console.log("");
10049
10355
  return;
@@ -10052,19 +10358,19 @@ Before vs after: `) + import_chalk50.default.cyan(data.marker.label));
10052
10358
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
10053
10359
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10054
10360
  const rows2 = data?.rows ?? [];
10055
- if (!rows2.length) return void console.log(import_chalk50.default.dim("No requests over the threshold in that window."));
10056
- 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(`
10057
10363
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10058
10364
  `));
10059
- 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"));
10060
10366
  for (const r of rows2.slice(0, 30)) {
10061
10367
  console.log(
10062
- ` ${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 || "")}`
10063
10369
  );
10064
10370
  }
10065
- console.log(import_chalk50.default.dim(`
10371
+ console.log(import_chalk51.default.dim(`
10066
10372
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
10067
- 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.
10068
10374
  `));
10069
10375
  return;
10070
10376
  }
@@ -10072,17 +10378,17 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10072
10378
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
10073
10379
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10074
10380
  const rows2 = data?.rows ?? [];
10075
- if (!rows2.length) return void console.log(import_chalk50.default.dim("No LLM traffic in that window."));
10076
- 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(`
10077
10383
  LLM timing \u2014 last ${data.window_hours}h
10078
10384
  `));
10079
- 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"));
10080
10386
  for (const r of rows2) {
10081
10387
  console.log(
10082
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 || "-"}`
10083
10389
  );
10084
10390
  }
10085
- console.log(import_chalk50.default.dim(`
10391
+ console.log(import_chalk51.default.dim(`
10086
10392
  ${data.note}
10087
10393
  `));
10088
10394
  return;
@@ -10093,26 +10399,26 @@ LLM timing \u2014 last ${data.window_hours}h
10093
10399
  ]);
10094
10400
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
10095
10401
  const ov = summary?.apiblaze_overhead_ms ?? {};
10096
- console.log(import_chalk50.default.bold(`
10402
+ console.log(import_chalk51.default.bold(`
10097
10403
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
10098
10404
  `));
10099
- 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")}`);
10100
- 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")}`);
10101
- 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)
10102
10408
  `));
10103
10409
  const rows = blame?.blame ?? [];
10104
- if (!rows.length) return void console.log(import_chalk50.default.dim("No latency rows in that window."));
10105
- console.log(import_chalk50.default.bold(" Which feature ate the time, and what inside it\n"));
10106
- 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"));
10107
10413
  for (const r of rows.slice(0, 15)) {
10108
10414
  const share = `${(r.share * 100).toFixed(1)}%`;
10109
- 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)}`);
10110
10416
  }
10111
10417
  console.log("");
10112
10418
  return;
10113
10419
  }
10114
10420
  default:
10115
- 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.`));
10116
10422
  }
10117
10423
  }
10118
10424
 
@@ -10175,7 +10481,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
10175
10481
  try {
10176
10482
  const resolved = parseInt(port ?? opts.port, 10);
10177
10483
  if (Number.isNaN(resolved)) {
10178
- console.error(import_chalk51.default.red(`Invalid port: ${port ?? opts.port}`));
10484
+ console.error(import_chalk52.default.red(`Invalid port: ${port ?? opts.port}`));
10179
10485
  process.exit(1);
10180
10486
  }
10181
10487
  await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
@@ -10274,7 +10580,7 @@ apikeys.command("list").description("List control-plane API keys in your team").
10274
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 })));
10275
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 })));
10276
10582
  program.addCommand(apikeys, { hidden: true });
10277
- 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 \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)").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", () => `
10278
10584
  ${renderOpCommands()}
10279
10585
  `);
10280
10586
  program.addCommand(op, { hidden: true });
@@ -10307,7 +10613,7 @@ function groupedCommandHelp() {
10307
10613
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
10308
10614
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
10309
10615
  }).filter(Boolean).join("\n");
10310
- return `${import_chalk51.default.bold(g.title)}
10616
+ return `${import_chalk52.default.bold(g.title)}
10311
10617
  ${rows}`;
10312
10618
  }).join("\n\n");
10313
10619
  }
@@ -10345,14 +10651,14 @@ async function recoverStaleTeam() {
10345
10651
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
10346
10652
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
10347
10653
  if (!linked) {
10348
- 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."));
10349
10655
  return;
10350
10656
  }
10351
10657
  if (linked.teamId === creds.teamId) return;
10352
10658
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
10353
10659
  delete next.activeTenant;
10354
10660
  saveCredentials(next);
10355
- 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.`));
10356
10662
  } catch {
10357
10663
  }
10358
10664
  }
@@ -10360,16 +10666,16 @@ async function printError(err) {
10360
10666
  if (err instanceof ApiError) {
10361
10667
  const data = err.body;
10362
10668
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
10363
- console.error(import_chalk51.default.red(`
10669
+ console.error(import_chalk52.default.red(`
10364
10670
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
10365
10671
  if (err.status === 403 || err.status === 404) {
10366
10672
  await recoverStaleTeam();
10367
10673
  }
10368
10674
  } else if (err instanceof Error) {
10369
- console.error(import_chalk51.default.red(`
10675
+ console.error(import_chalk52.default.red(`
10370
10676
  Error: ${err.message}`));
10371
10677
  } else {
10372
- console.error(import_chalk51.default.red("\nUnknown error"));
10678
+ console.error(import_chalk52.default.red("\nUnknown error"));
10373
10679
  }
10374
10680
  }
10375
10681
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.5",
3
+ "version": "0.20.8",
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",