apiblaze 0.20.4 → 0.20.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +136 -28
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -932,7 +932,7 @@ var import_commander = require("commander");
932
932
  var import_chalk51 = __toESM(require("chalk"));
933
933
 
934
934
  // package.json
935
- var version = "0.20.4";
935
+ var version = "0.20.6";
936
936
 
937
937
  // src/index.ts
938
938
  init_types();
@@ -3601,11 +3601,11 @@ async function runRecipeShow(nameArg, opts) {
3601
3601
  console.log(import_chalk25.default.dim(" Sections: --settings --auth --transforms --openapi"));
3602
3602
  console.log();
3603
3603
  }
3604
- function describeCreates(keyTypes, routes, mcp) {
3605
- const parts = ["1 proxy", "1 tenant"];
3604
+ function describeCreates(keyTypes, routes, mcp, includeProxy = true) {
3605
+ const parts = includeProxy ? ["1 proxy", "1 tenant"] : [];
3606
3606
  if (keyTypes) parts.push(`${keyTypes} key type${keyTypes === 1 ? "" : "s"}`);
3607
3607
  if (routes) parts.push(mcp ? `${routes} MCP tools` : `${routes} route${routes === 1 ? "" : "s"}`);
3608
- return parts.join(", ");
3608
+ return parts.length ? parts.join(", ") : "nothing else";
3609
3609
  }
3610
3610
  function printSection(title, value) {
3611
3611
  console.log(import_chalk25.default.bold(` ${title}`));
@@ -3701,6 +3701,7 @@ async function runRecipeInstall(nameArg, opts) {
3701
3701
  return;
3702
3702
  }
3703
3703
  }
3704
+ if (recipe.settings) delete recipe.settings.publish_openapi;
3704
3705
  const ctx = { project, tenant: tenant2 };
3705
3706
  if (reuseTenant) {
3706
3707
  recipe = { ...recipe, auth: { app_client: null, provider: null, key_types: [] }, questions: [] };
@@ -4095,7 +4096,7 @@ async function chooseProxyName(slug, asOpt, teamId, interactive) {
4095
4096
  continue;
4096
4097
  }
4097
4098
  if (free === null) console.log(import_chalk26.default.dim(" (could not verify availability; continuing)"));
4098
- console.log(` ${import_chalk26.default.cyan("\u2192")} ${import_chalk26.default.bold(`https://${name}.abz.run`)} ${import_chalk26.default.dim(`users live in the tenant ${name}users`)}`);
4099
+ console.log(` ${import_chalk26.default.cyan("\u2192")} ${import_chalk26.default.bold(`https://${name}.abz.run`)}`);
4099
4100
  return name;
4100
4101
  }
4101
4102
  }
@@ -4111,19 +4112,23 @@ function printSummary(recipe, project, tenant2, reuseTenant = false) {
4111
4112
  const hosts = upstreamHosts(recipe);
4112
4113
  const routes = countRoutes(recipe.openapi);
4113
4114
  const keyTypes = recipe.auth?.key_types?.length ?? 0;
4114
- const questions = recipe.questions?.length ?? 0;
4115
+ const questions = reuseTenant ? 0 : recipe.questions?.length ?? 0;
4116
+ const row = (label3, value, note) => {
4117
+ console.log(` ${import_chalk26.default.dim(label3.padEnd(21))}${value}${note ? import_chalk26.default.dim(` ${note}`) : ""}`);
4118
+ };
4119
+ console.log();
4120
+ console.log(import_chalk26.default.bold(" This will:"));
4115
4121
  console.log();
4116
- if (hosts.length) console.log(` ${import_chalk26.default.dim("sends requests to")} ${hosts.join(", ")}`);
4117
- console.log(` ${import_chalk26.default.dim("creates ")} ${describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true)}`);
4118
- console.log(` ${import_chalk26.default.dim("asks you for ")} ${reuseTenant ? "nothing" : `${questions} value${questions === 1 ? "" : "s"}`}`);
4122
+ row("create a proxy", import_chalk26.default.bold(project), `at https://${project}.abz.run`);
4123
+ row("for its users", import_chalk26.default.bold(tenant2), reuseTenant ? "(one you already have)" : "(a new tenant)");
4124
+ if (hosts.length) row("sending requests to", hosts.join(", "));
4125
+ row("and set up", describeCreates(keyTypes, routes, recipe.settings.mcp_enabled === true, false));
4126
+ row("asking you for", questions ? `${questions} value${questions === 1 ? "" : "s"}` : "nothing");
4119
4127
  if (reuseTenant) {
4120
- console.log(` ${import_chalk26.default.dim("users live in ")} ${tenant2} ${import_chalk26.default.dim("(existing \u2014 its login is used as it is)")}`);
4121
- console.log(import_chalk26.default.dim(` ${" ".repeat(17)} the recipe's own login setup is not applied`));
4122
- }
4123
- if (recipe.settings.publish_openapi === true) {
4124
- console.log(` ${import_chalk26.default.dim("makes public ")} this API's spec, at ${project}.abz.run/${recipe.api_version}/openapi.json`);
4128
+ console.log();
4129
+ console.log(import_chalk26.default.dim(` ${tenant2} is used exactly as it is \u2014 its existing login stays, and this`));
4130
+ console.log(import_chalk26.default.dim(" recipe's own login setup is not applied."));
4125
4131
  }
4126
- console.log(import_chalk26.default.dim(` ${"users live in".padEnd(17)} ${tenant2}`));
4127
4132
  console.log();
4128
4133
  }
4129
4134
  function declineAll(items) {
@@ -9825,6 +9830,37 @@ async function opCall(call) {
9825
9830
  }
9826
9831
  return data;
9827
9832
  }
9833
+ var OP_COMMANDS = [
9834
+ { cmd: "op latency", blurb: "which FEATURE is slow, and what inside it", extra: "--hours N --team T --project P" },
9835
+ { cmd: "op latency slow", blurb: "slowest requests, each with a request id you can look up", extra: "--min-ms N" },
9836
+ { cmd: "op latency llm", blurb: "chat timing by provider/model, tokens, credit-reserve cost" },
9837
+ { cmd: "op latency grades", blurb: "daily %% excellent / okay / bad / terrible, and who to blame", extra: "--days N" },
9838
+ { cmd: "op mark", blurb: 'record "I changed something significant just now"', extra: 'apiblaze op mark "cached tenant count"' },
9839
+ { cmd: "op latency compare", blurb: "latency before vs after the last mark, equal windows", extra: "--hours N" },
9840
+ { cmd: "op credits", blurb: "list credit wallets across all teams" },
9841
+ { cmd: "op residue", blurb: "external-store residue report (Upstash + Neon/OpenFGA) \u2014 dry run, deletes nothing" },
9842
+ { cmd: "op sweep", blurb: "delete the orphans the residue report found", extra: "asks first; -y to skip" }
9843
+ ];
9844
+ function renderOpCommands() {
9845
+ const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
9846
+ const lines = OP_COMMANDS.map((c) => {
9847
+ const left = ` ${import_chalk50.default.cyan(`apiblaze ${c.cmd}`)}`;
9848
+ const pad = " ".repeat(Math.max(1, width - c.cmd.length));
9849
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk50.default.dim(`(${c.extra})`) : ""}`;
9850
+ });
9851
+ return [
9852
+ import_chalk50.default.bold("Operator commands"),
9853
+ ...lines,
9854
+ "",
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`."),
9858
+ "",
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.")
9862
+ ].join("\n");
9863
+ }
9828
9864
  function printResidue(report, applied) {
9829
9865
  const up = report?.upstash ?? {};
9830
9866
  const fga = report?.fga ?? {};
@@ -9900,15 +9936,9 @@ async function runOp(sub, opts = {}, view) {
9900
9936
  switch (sub) {
9901
9937
  case void 0:
9902
9938
  case "menu": {
9903
- console.log(import_chalk50.default.bold("\nOperator menu"));
9904
- console.log(` ${import_chalk50.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
9905
- console.log(` ${import_chalk50.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk50.default.dim("-y to skip")})`);
9906
- console.log(` ${import_chalk50.default.cyan("apiblaze op credits")} list credit wallets`);
9907
- console.log(` ${import_chalk50.default.cyan("apiblaze op latency")} which FEATURE is slow, and what inside it ${import_chalk50.default.dim("(--hours N --team T --project P)")}`);
9908
- console.log(` ${import_chalk50.default.cyan("apiblaze op latency slow")} the slowest requests, with a cf-ray to drill into`);
9909
- console.log(` ${import_chalk50.default.cyan("apiblaze op latency llm")} chat timing by provider/model, tokens, credit-reserve cost`);
9910
- console.log(import_chalk50.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
9911
- `));
9939
+ console.log("");
9940
+ console.log(renderOpCommands());
9941
+ console.log("");
9912
9942
  return;
9913
9943
  }
9914
9944
  case "residue": {
@@ -9943,6 +9973,20 @@ async function runOp(sub, opts = {}, view) {
9943
9973
  printResidue(result, true);
9944
9974
  return;
9945
9975
  }
9976
+ case "mark": {
9977
+ const label3 = (view ?? "").trim();
9978
+ if (!label3) {
9979
+ console.log(import_chalk50.default.red("Give the change a name:") + import_chalk50.default.cyan(' apiblaze op mark "cached tenant count"'));
9980
+ return;
9981
+ }
9982
+ const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
9983
+ 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");
9988
+ return;
9989
+ }
9946
9990
  case "credits": {
9947
9991
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
9948
9992
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
@@ -9971,7 +10015,11 @@ async function runOp(sub, opts = {}, view) {
9971
10015
  ["route", "route"],
9972
10016
  ["environment", "environment"],
9973
10017
  ["statusClass", "status_class"],
9974
- ["minMs", "min_ms"]
10018
+ ["minMs", "min_ms"],
10019
+ ["days", "days"],
10020
+ ["excellent", "excellent"],
10021
+ ["okay", "okay"],
10022
+ ["bad", "bad"]
9975
10023
  ];
9976
10024
  for (const [optKey, queryKey] of FILTERS) {
9977
10025
  const v = opts[optKey];
@@ -9979,6 +10027,63 @@ async function runOp(sub, opts = {}, view) {
9979
10027
  }
9980
10028
  const q = qs.toString() ? `?${qs}` : "";
9981
10029
  const which = view;
10030
+ if (which === "grades") {
10031
+ const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
10032
+ if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10033
+ 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+
10036
+ `));
10037
+ console.log(import_chalk50.default.dim(" date reqs excellent okay bad terrible"));
10038
+ for (const d of data.days ?? []) {
10039
+ const p = d.pct;
10040
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk50.default.dim(`${String(v).padStart(5)}%`);
10041
+ 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)}`
10043
+ );
10044
+ }
10045
+ const cul = data.culprits ?? [];
10046
+ 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"));
10049
+ for (const r of cul.slice(0, 12)) {
10050
+ if (!r.bad && !r.terrible) continue;
10051
+ 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)}`
10053
+ );
10054
+ }
10055
+ }
10056
+ if (data.caveat) console.log(import_chalk50.default.dim(`
10057
+ \u26A0 ${data.caveat}
10058
+ `));
10059
+ return;
10060
+ }
10061
+ if (which === "compare") {
10062
+ const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
10063
+ if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10064
+ 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
10068
+ `));
10069
+ const row = (name, before, after, delta) => {
10070
+ const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
10071
+ 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)}`);
10073
+ };
10074
+ console.log(import_chalk50.default.dim(" metric before after change"));
10075
+ row("total p50", b.total_p50, a.total_p50, d.total_p50);
10076
+ row("total p95", b.total_p95, a.total_p95, d.total_p95);
10077
+ row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
10078
+ row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
10079
+ console.log(import_chalk50.default.dim(`
10080
+ requests: ${b.requests} before \xB7 ${a.requests} after`));
10081
+ for (const w of data.warnings ?? []) {
10082
+ console.log((data.trustworthy ? import_chalk50.default.dim : import_chalk50.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
10083
+ }
10084
+ console.log("");
10085
+ return;
10086
+ }
9982
10087
  if (which === "slow") {
9983
10088
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
9984
10089
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
@@ -9987,14 +10092,15 @@ async function runOp(sub, opts = {}, view) {
9987
10092
  console.log(import_chalk50.default.bold(`
9988
10093
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
9989
10094
  `));
9990
- console.log(import_chalk50.default.dim(" total ours theirs blame request (cf-ray)"));
10095
+ console.log(import_chalk50.default.dim(" total ours theirs blame request id"));
9991
10096
  for (const r of rows2.slice(0, 30)) {
9992
10097
  console.log(
9993
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 || "")}`
9994
10099
  );
9995
10100
  }
9996
10101
  console.log(import_chalk50.default.dim(`
9997
- Take a cf-ray to \`apiblaze logs\` for the exact per-feature breakdown \u2014 that row is unsampled.
10102
+ 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.
9998
10104
  `));
9999
10105
  return;
10000
10106
  }
@@ -10204,7 +10310,9 @@ apikeys.command("list").description("List control-plane API keys in your team").
10204
10310
  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 })));
10205
10311
  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 })));
10206
10312
  program.addCommand(apikeys, { hidden: true });
10207
- var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "residue | sweep | credits | latency (omit for the menu)").argument("[view]", "for `latency`: slow | llm (omit for the blame table)").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)));
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", () => `
10314
+ ${renderOpCommands()}
10315
+ `);
10208
10316
  program.addCommand(op, { hidden: true });
10209
10317
  var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
10210
10318
  spec.command("get").description("Print the current OpenAPI document").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Compact JSON output").action(action((project, opts) => runSpecGet(project, opts)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.20.4",
3
+ "version": "0.20.6",
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",