apiblaze 0.21.11 → 0.21.14

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 +951 -615
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,9 +9,9 @@ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __esm = (fn, res) => function __init() {
10
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
11
  };
12
- var __export = (target, all) => {
12
+ var __export = (target2, all) => {
13
13
  for (var name in all)
14
- __defProp(target, name, { get: all[name], enumerable: true });
14
+ __defProp(target2, name, { get: all[name], enumerable: true });
15
15
  };
16
16
  var __copyProps = (to, from, except, desc) => {
17
17
  if (from && typeof from === "object" || typeof from === "function") {
@@ -21,12 +21,12 @@ var __copyProps = (to, from, except, desc) => {
21
21
  }
22
22
  return to;
23
23
  };
24
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
25
  // If the importer is in node compatibility mode or this is not an ESM
26
26
  // file that has been converted to a CommonJS file using a Babel-
27
27
  // compatible transform (i.e. "__esModule" has not been set), then set
28
28
  // "default" to the CommonJS "module.exports" for node compatibility.
29
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target2, "default", { value: mod, enumerable: true }) : target2,
30
30
  mod
31
31
  ));
32
32
 
@@ -524,7 +524,7 @@ function renderTrace() {
524
524
  const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
525
525
  const masked = maskBody(e.body);
526
526
  const hasBody = e.method !== "GET" && masked !== void 0;
527
- console.error(import_chalk6.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
527
+ console.error(import_chalk6.default.green(` curl -sS -X ${e.method} '${url}' \\`));
528
528
  console.error(import_chalk6.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
529
529
  if (hasBody) {
530
530
  console.error(import_chalk6.default.green(" -H 'Content-Type: application/json' \\"));
@@ -978,7 +978,7 @@ async function runAdminsAdd(email, opts) {
978
978
  else if (!opts.quiet) {
979
979
  console.log(import_chalk15.default.dim(" Reload the Users & Groups widget \u2014 access flips from \u201Cpending\u201D to ready."));
980
980
  console.log(import_chalk15.default.dim(` The widget's admin lane takes a personal key, one per admin \u2014 mint it with:`));
981
- console.log(import_chalk15.default.dim(` npx apiblaze apikeys mint --for ${email} --tenant ${tenant2}`));
981
+ console.log(import_chalk15.default.dim(` npx apiblaze apikeys mint --for ${email} --tenant ${tenant2} --admin`));
982
982
  console.log(import_chalk15.default.dim(" (a backend/tenant key with X-End-User-Id is refused there: widget_key_must_be_personal)."));
983
983
  }
984
984
  return out ?? { ok: true };
@@ -1123,7 +1123,7 @@ async function createTenantRow(teamId) {
1123
1123
  const { name } = await inquirer3.prompt([{
1124
1124
  type: "input",
1125
1125
  name: "name",
1126
- message: `Tenant name ${import_chalk45.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
1126
+ message: `Tenant name ${import_chalk46.default.dim("(lowercase letters/numbers; globally unique \u2014 becomes {name}.portal.apiblaze.com)")}:`,
1127
1127
  validate: (s) => /^[a-z0-9]+$/.test(s.trim()) ? true : "lowercase letters and numbers only"
1128
1128
  }]);
1129
1129
  const slug = name.trim();
@@ -1138,7 +1138,7 @@ async function createTenantRow(teamId) {
1138
1138
  } catch (err) {
1139
1139
  const msg = err instanceof Error ? err.message : String(err);
1140
1140
  if (/taken|unique|exists|reserved|conflict/i.test(msg)) {
1141
- console.log(import_chalk45.default.yellow(` "${slug}" is not available (tenant names are global): ${msg}`));
1141
+ console.log(import_chalk46.default.yellow(` "${slug}" is not available (tenant names are global): ${msg}`));
1142
1142
  continue;
1143
1143
  }
1144
1144
  throw err;
@@ -1155,11 +1155,11 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
1155
1155
  pageSize: 10,
1156
1156
  choices: [
1157
1157
  ...PROVIDERS2.map((p) => ({ name: p.label, value: p.id })),
1158
- ...opts.allowSkip ? [new inquirer3.Separator(), { name: import_chalk45.default.dim("Skip for now \u2014 set up a login method later"), value: null }] : []
1158
+ ...opts.allowSkip ? [new inquirer3.Separator(), { name: import_chalk46.default.dim("Skip for now \u2014 set up a login method later"), value: null }] : []
1159
1159
  ]
1160
1160
  }]);
1161
1161
  if (!provider) {
1162
- console.log(import_chalk45.default.dim(" No login method yet \u2014 add one later under Login methods."));
1162
+ console.log(import_chalk46.default.dim(" No login method yet \u2014 add one later under Login methods."));
1163
1163
  return false;
1164
1164
  }
1165
1165
  const opt = PROVIDERS2.find((p) => p.id === provider);
@@ -1190,7 +1190,7 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
1190
1190
  if (a.callbacks.trim()) clientBody.authorizedCallbackUrls = a.callbacks.split(/\s*,\s*/).map((s) => s.trim()).filter(Boolean);
1191
1191
  if (a.scopes.trim() && opt.own) providerBody.scopes = a.scopes.split(/\s+/).filter(Boolean);
1192
1192
  }
1193
- const spinner = (0, import_ora17.default)("Setting up login...").start();
1193
+ const spinner = (0, import_ora18.default)("Setting up login...").start();
1194
1194
  try {
1195
1195
  const client = await admin({ method: "POST", path: `${base2}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
1196
1196
  const clientId = client?.clientId ?? client?.client_id;
@@ -1205,27 +1205,27 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
1205
1205
  async function createTenantInteractive(teamId) {
1206
1206
  const { default: inquirer3 } = await import("inquirer");
1207
1207
  const tenant2 = await createTenantRow(teamId);
1208
- console.log(import_chalk45.default.green(` Tenant ${import_chalk45.default.bold(tenant2)} created.`));
1208
+ console.log(import_chalk46.default.green(` Tenant ${import_chalk46.default.bold(tenant2)} created.`));
1209
1209
  const { users } = await inquirer3.prompt([{
1210
1210
  type: "confirm",
1211
1211
  name: "users",
1212
1212
  default: false,
1213
- message: `Enable Users & groups? ${import_chalk45.default.dim(`(identity/key management at iam.apiblaze.com)`)}`
1213
+ message: `Enable Users & groups? ${import_chalk46.default.dim(`(identity/key management at iam.apiblaze.com)`)}`
1214
1214
  }]);
1215
1215
  if (users) {
1216
1216
  await admin({ method: "PATCH", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`, body: { enabled: true }, summary: "Enable Users & groups" }).catch(() => {
1217
1217
  });
1218
- console.log(import_chalk45.default.green(" Users & groups enabled."));
1218
+ console.log(import_chalk46.default.green(" Users & groups enabled."));
1219
1219
  }
1220
1220
  await addApiblazeHostedLogin(teamId, tenant2, { allowSkip: true });
1221
1221
  return tenant2;
1222
1222
  }
1223
- var import_chalk45, import_ora17, PROVIDERS2, DEFAULT_SCOPES;
1223
+ var import_chalk46, import_ora18, PROVIDERS2, DEFAULT_SCOPES;
1224
1224
  var init_tenant_create = __esm({
1225
1225
  "src/lib/tenant-create.ts"() {
1226
1226
  "use strict";
1227
- import_chalk45 = __toESM(require("chalk"));
1228
- import_ora17 = __toESM(require("ora"));
1227
+ import_chalk46 = __toESM(require("chalk"));
1228
+ import_ora18 = __toESM(require("ora"));
1229
1229
  init_admin();
1230
1230
  PROVIDERS2 = [
1231
1231
  { id: "apiblaze", label: "APIblaze (via GitHub) \u2014 zero setup, recommended", own: false },
@@ -1263,10 +1263,10 @@ async function fetchPage2(teamId, q) {
1263
1263
  }
1264
1264
  function label(t, defaultTenant, active) {
1265
1265
  const tags = [
1266
- t.tenant_name === active ? import_chalk46.default.cyan("active scope") : "",
1267
- t.tenant_name === defaultTenant ? import_chalk46.default.dim("team default") : ""
1266
+ t.tenant_name === active ? import_chalk47.default.cyan("active scope") : "",
1267
+ t.tenant_name === defaultTenant ? import_chalk47.default.dim("team default") : ""
1268
1268
  ].filter(Boolean).join(", ");
1269
- const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk46.default.dim(` ${t.display_name}`) : "";
1269
+ const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk47.default.dim(` ${t.display_name}`) : "";
1270
1270
  return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
1271
1271
  }
1272
1272
  async function pickTenant(teamId, opts = {}) {
@@ -1274,14 +1274,14 @@ async function pickTenant(teamId, opts = {}) {
1274
1274
  const active = loadCredentials()?.activeTenant;
1275
1275
  let q = opts.initialQuery ?? "";
1276
1276
  for (; ; ) {
1277
- const spinner = (0, import_ora18.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
1277
+ const spinner = (0, import_ora19.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
1278
1278
  const page = await fetchPage2(teamId, q).finally(() => spinner.stop());
1279
1279
  if (!page.total && !q) {
1280
1280
  if (opts.allowCreate) {
1281
1281
  const { make } = await inquirer3.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
1282
1282
  if (make) return await createTenantInline(teamId);
1283
1283
  }
1284
- console.error(import_chalk46.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
1284
+ console.error(import_chalk47.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
1285
1285
  return null;
1286
1286
  }
1287
1287
  const truncated = page.total > page.rows.length;
@@ -1290,7 +1290,7 @@ async function pickTenant(teamId, opts = {}) {
1290
1290
  value: t.tenant_name
1291
1291
  }));
1292
1292
  if (truncated || q) {
1293
- choices.push(new inquirer3.Separator(import_chalk46.default.dim(
1293
+ choices.push(new inquirer3.Separator(import_chalk47.default.dim(
1294
1294
  truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
1295
1295
  )));
1296
1296
  choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
@@ -1324,12 +1324,12 @@ async function createTenantInline(teamId) {
1324
1324
  const { createTenantInteractive: createTenantInteractive2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
1325
1325
  return createTenantInteractive2(teamId);
1326
1326
  }
1327
- var import_chalk46, import_ora18, PAGE;
1327
+ var import_chalk47, import_ora19, PAGE;
1328
1328
  var init_tenant_pick = __esm({
1329
1329
  "src/lib/tenant-pick.ts"() {
1330
1330
  "use strict";
1331
- import_chalk46 = __toESM(require("chalk"));
1332
- import_ora18 = __toESM(require("ora"));
1331
+ import_chalk47 = __toESM(require("chalk"));
1332
+ import_ora19 = __toESM(require("ora"));
1333
1333
  init_admin();
1334
1334
  init_auth();
1335
1335
  PAGE = 15;
@@ -1339,10 +1339,10 @@ var init_tenant_pick = __esm({
1339
1339
  // src/index.ts
1340
1340
  var import_fs5 = __toESM(require("fs"));
1341
1341
  var import_commander = require("commander");
1342
- var import_chalk63 = __toESM(require("chalk"));
1342
+ var import_chalk64 = __toESM(require("chalk"));
1343
1343
 
1344
1344
  // package.json
1345
- var version = "0.21.11";
1345
+ var version = "0.21.14";
1346
1346
 
1347
1347
  // src/index.ts
1348
1348
  init_types();
@@ -1972,8 +1972,10 @@ function randomProxyName() {
1972
1972
  // src/lib/tunnel-client.ts
1973
1973
  var import_ws = __toESM(require("ws"));
1974
1974
  var CHUNK_BYTES = 512 * 1024;
1975
- var PING_INTERVAL_MS = 6e4;
1975
+ var PING_INTERVAL_MS = 3e4;
1976
+ var SILENCE_LIMIT_MS = 75e3;
1976
1977
  var MAX_RECONNECT_DELAY_MS = 15e3;
1978
+ var TOKEN_REFRESH_AFTER_MS = 24e4;
1977
1979
  var OFFLINE_CODES = /* @__PURE__ */ new Set(["ECONNREFUSED", "ECONNRESET", "ENOTFOUND", "EHOSTUNREACH"]);
1978
1980
  var STRIP_HEADERS = /* @__PURE__ */ new Set([
1979
1981
  "host",
@@ -1997,28 +1999,52 @@ function encodeBody(buf) {
1997
1999
  return { value: buf.toString("utf8"), encoding: "utf8" };
1998
2000
  }
1999
2001
  function startTunnelClient(opts) {
2000
- const target = `http://127.0.0.1:${opts.localPort}`;
2002
+ const target2 = `http://127.0.0.1:${opts.localPort}`;
2001
2003
  const inflight = /* @__PURE__ */ new Map();
2002
2004
  let ws = null;
2003
2005
  let closed = false;
2004
2006
  let reconnects = 0;
2005
2007
  let pingTimer;
2008
+ let connectUrl = opts.connectUrl;
2009
+ let token = opts.token;
2010
+ let tokenAt = Date.now();
2011
+ let tokenRefused = false;
2012
+ let lastFrameAt = Date.now();
2006
2013
  let capturing = false;
2007
2014
  function connect() {
2008
- const url = `${opts.connectUrl}?project=${encodeURIComponent(opts.projectId)}&token=${encodeURIComponent(opts.token)}`;
2015
+ const url = `${connectUrl}?project=${encodeURIComponent(opts.projectId)}&token=${encodeURIComponent(token)}`;
2009
2016
  const socket = new import_ws.default(url);
2010
2017
  ws = socket;
2011
2018
  socket.on("open", () => {
2012
2019
  reconnects = 0;
2020
+ tokenRefused = false;
2021
+ lastFrameAt = Date.now();
2013
2022
  opts.onStatus?.("connected");
2014
2023
  pingTimer = setInterval(() => {
2024
+ if (Date.now() - lastFrameAt > SILENCE_LIMIT_MS) {
2025
+ try {
2026
+ socket.terminate();
2027
+ } catch {
2028
+ }
2029
+ return;
2030
+ }
2015
2031
  try {
2016
2032
  socket.send("ping");
2017
2033
  } catch {
2018
2034
  }
2019
2035
  }, PING_INTERVAL_MS);
2020
2036
  });
2021
- socket.on("message", (data) => handleFrame(socket, data.toString()));
2037
+ socket.on("unexpected-response", (_req, res) => {
2038
+ if (res.statusCode === 401 || res.statusCode === 403) tokenRefused = true;
2039
+ try {
2040
+ socket.terminate();
2041
+ } catch {
2042
+ }
2043
+ });
2044
+ socket.on("message", (data) => {
2045
+ lastFrameAt = Date.now();
2046
+ handleFrame(socket, data.toString());
2047
+ });
2022
2048
  socket.on("close", () => {
2023
2049
  if (pingTimer) clearInterval(pingTimer);
2024
2050
  if (!closed) scheduleReconnect();
@@ -2031,9 +2057,26 @@ function startTunnelClient(opts) {
2031
2057
  reconnects++;
2032
2058
  opts.onStatus?.(`disconnected \u2014 reconnecting in ${Math.round(delay / 1e3)}s`);
2033
2059
  setTimeout(() => {
2034
- if (!closed) connect();
2060
+ void reconnect();
2035
2061
  }, delay);
2036
2062
  }
2063
+ async function reconnect() {
2064
+ if (closed) return;
2065
+ if (opts.refresh && (tokenRefused || Date.now() - tokenAt > TOKEN_REFRESH_AFTER_MS)) {
2066
+ try {
2067
+ const fresh = await opts.refresh();
2068
+ connectUrl = fresh.url;
2069
+ token = fresh.token;
2070
+ tokenAt = Date.now();
2071
+ tokenRefused = false;
2072
+ } catch (err) {
2073
+ opts.onStatus?.(`could not refresh the tunnel token (${err?.message ?? err}) \u2014 retrying`);
2074
+ scheduleReconnect();
2075
+ return;
2076
+ }
2077
+ }
2078
+ if (!closed) connect();
2079
+ }
2037
2080
  function handleFrame(socket, raw) {
2038
2081
  let frame;
2039
2082
  try {
@@ -2099,7 +2142,7 @@ function startTunnelClient(opts) {
2099
2142
  const init = { method: f.method, headers: stripHeaders(f.headers) };
2100
2143
  if (body.length) init.body = body;
2101
2144
  try {
2102
- const resp = await fetch(target + f.path, init);
2145
+ const resp = await fetch(target2 + f.path, init);
2103
2146
  const status2 = resp.status;
2104
2147
  const buf = Buffer.from(await resp.arrayBuffer());
2105
2148
  const headers = {};
@@ -2713,7 +2756,7 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
2713
2756
  "asking for data the API can provide.",
2714
2757
  ...spec2.contextNote ? ["", spec2.contextNote] : [],
2715
2758
  "",
2716
- `_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze apichat ${spec2.name} --remove-mcp ${cli}\`._`,
2759
+ `_Added by \`apiblaze apichat\`. Remove this block, or run \`npx apiblaze@latest apichat ${spec2.name} --remove-mcp ${cli}\`._`,
2717
2760
  m.end,
2718
2761
  ""
2719
2762
  ].join("\n");
@@ -2835,7 +2878,7 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
2835
2878
  ${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
2836
2879
  }
2837
2880
  const instructionFile = writeAgentInstruction(cli.kind, spec2);
2838
- const off = `npx apiblaze apichat ${spec2.name} --remove-mcp ${cli.kind}`;
2881
+ const off = `npx apiblaze@latest apichat ${spec2.name} --remove-mcp ${cli.kind}`;
2839
2882
  const mdName = cli.kind === "claude" ? "CLAUDE.md" : "AGENTS.md";
2840
2883
  log4("");
2841
2884
  log4(import_chalk11.default.bgRed.white.bold(` ${cli.label} will launch with your MCP to ${spec2.projectLabel} enabled from now on. `));
@@ -3028,8 +3071,8 @@ async function fetchText(url) {
3028
3071
  return null;
3029
3072
  }
3030
3073
  }
3031
- async function discoverSpec(target) {
3032
- const base2 = target.replace(/\/+$/, "");
3074
+ async function discoverSpec(target2) {
3075
+ const base2 = target2.replace(/\/+$/, "");
3033
3076
  for (const suffix of ["/openapi.json", "/openapi.yaml", "/swagger.json"]) {
3034
3077
  const url = base2 + suffix;
3035
3078
  const text = await fetchText(url);
@@ -3261,14 +3304,14 @@ async function cpPost(anon, path11, body, summary) {
3261
3304
  }
3262
3305
  return admin({ method: "POST", path: path11, body, summary });
3263
3306
  }
3264
- async function provision(spec2, target, opts) {
3307
+ async function provision(spec2, target2, opts) {
3265
3308
  const loggedIn = !!loadCredentials();
3266
3309
  const anon = !loggedIn;
3267
3310
  const salt = () => Math.random().toString(36).slice(2, 6);
3268
3311
  let base2 = opts.name ? normalizeName(opts.name) : "";
3269
3312
  if (!base2) {
3270
3313
  try {
3271
- const host = new URL(target).hostname;
3314
+ const host = new URL(target2).hostname;
3272
3315
  base2 = normalizeName(host.split(".")[0]);
3273
3316
  if (base2.length < 3) base2 = normalizeName(host);
3274
3317
  } catch {
@@ -3295,7 +3338,7 @@ async function provision(spec2, target, opts) {
3295
3338
  const creds = loadCredentials();
3296
3339
  result = await createProxy({
3297
3340
  name,
3298
- target_url: target,
3341
+ target_url: target2,
3299
3342
  auth_type: "api_key",
3300
3343
  requests_auth: DUAL_AUTH,
3301
3344
  team_id: creds.teamId,
@@ -3307,8 +3350,8 @@ async function provision(spec2, target, opts) {
3307
3350
  const body = {
3308
3351
  name,
3309
3352
  subdomain: name,
3310
- target,
3311
- target_url: target,
3353
+ target: target2,
3354
+ target_url: target2,
3312
3355
  auth_type: "api_key",
3313
3356
  requests_auth: DUAL_AUTH,
3314
3357
  ...opts.apiversion ? { api_version: opts.apiversion } : {}
@@ -4493,9 +4536,9 @@ async function startChat(p, messages, opts) {
4493
4536
  }
4494
4537
  await runRepl(p, messages);
4495
4538
  }
4496
- function isApiblazeControlPlaneTarget(target) {
4539
+ function isApiblazeControlPlaneTarget(target2) {
4497
4540
  try {
4498
- const h = new URL(target).hostname.toLowerCase();
4541
+ const h = new URL(target2).hostname.toLowerCase();
4499
4542
  return h === "api.mcp.apiblaze.com" || /^externalauth(-[a-z0-9]+)?\.mcp\.apiblaze\.com$/.test(h);
4500
4543
  } catch {
4501
4544
  return false;
@@ -4631,8 +4674,8 @@ async function runApichat(opts) {
4631
4674
  }
4632
4675
  }
4633
4676
  const { spec: spec2, sourceUrl } = await loadSpec(opts);
4634
- const target = resolveTarget(spec2, opts, sourceUrl);
4635
- console.log(` ${import_chalk13.default.dim("Target:")} ${import_chalk13.default.bold(target)}`);
4677
+ const target2 = resolveTarget(spec2, opts, sourceUrl);
4678
+ console.log(` ${import_chalk13.default.dim("Target:")} ${import_chalk13.default.bold(target2)}`);
4636
4679
  const auth = await resolveTargetAuth(spec2, opts);
4637
4680
  if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
4638
4681
  fail3(
@@ -4640,11 +4683,11 @@ async function runApichat(opts) {
4640
4683
  `Re-run with --target-auth-env <ENV_VAR> holding the ${auth.basic ? "base64(user:pass)" : "secret"}.`
4641
4684
  );
4642
4685
  }
4643
- const p = await provision(spec2, target, opts);
4686
+ const p = await provision(spec2, target2, opts);
4644
4687
  console.log(` ${import_chalk13.default.dim("Proxy: ")} ${import_chalk13.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
4645
4688
  upsertApichat({
4646
4689
  name: p.projectId,
4647
- target,
4690
+ target: target2,
4648
4691
  projectId: p.projectId,
4649
4692
  version: p.version,
4650
4693
  environment: p.environment,
@@ -4804,17 +4847,18 @@ async function mintConsumerKey(o) {
4804
4847
  if (!tenant2) {
4805
4848
  throw new Error(who ? "--for needs a tenant: add --tenant <slug> (the tenant whose person this key is for)." : `a ${o.holder ?? "backend"} key is a consumer key: add --tenant <slug> (the tenant it calls).`);
4806
4849
  }
4807
- const environment = (o.env ?? "").trim() || "dev";
4850
+ if (o.admin && !who) throw new Error("--admin needs --for <admin email>: the admin lane only opens for a key issued to that admin.");
4851
+ const environment = (o.env ?? "").trim() || (o.admin ? "prod" : "dev");
4808
4852
  const holder = who ? "person" : o.holder ?? "backend";
4809
- const permissions = holder === "person" ? void 0 : holderPermissions(holder);
4853
+ const permissions = holder === "person" ? o.admin ? ["call", "manage-access"] : void 0 : holderPermissions(holder);
4810
4854
  const caller = requireCaller();
4811
4855
  const { teamId } = await resolveActingTeam(caller, o.team);
4812
4856
  const body = { tenant: tenant2, environment };
4813
4857
  if (o.desc) body.description = o.desc;
4814
4858
  if (o.expiresDays) body.expires_in_seconds = Number(o.expiresDays) * 24 * 60 * 60;
4815
4859
  if (permissions) {
4816
- body.holder = holder;
4817
4860
  body.permissions = permissions;
4861
+ if (holder !== "person") body.holder = holder;
4818
4862
  }
4819
4863
  const headers = { "Content-Type": "application/json" };
4820
4864
  if (who) {
@@ -4882,6 +4926,7 @@ async function runConsumerKeyMint(opts) {
4882
4926
  team: opts.team,
4883
4927
  desc: opts.desc,
4884
4928
  expiresDays: opts.expiresDays,
4929
+ admin: opts.admin,
4885
4930
  ...kind === "person" ? {} : { holder: kind }
4886
4931
  });
4887
4932
  spinner.succeed(who ? `Key minted for ${who}.` : `${kind === "client" ? "Client" : "Backend"} key minted.`);
@@ -4899,6 +4944,7 @@ async function runConsumerKeyMint(opts) {
4899
4944
  if (out.expiresAt) console.log(` ${import_chalk14.default.dim("expires:")} ${out.expiresAt}`);
4900
4945
  if (out.holder === "person") {
4901
4946
  console.log(` ${import_chalk14.default.dim("issued to:")} ${out.person} ${import_chalk14.default.dim("\u2014 calls made with this key count as this person; an X-End-User-Id naming anyone else is refused")}`);
4947
+ if (opts.admin) console.log(` ${import_chalk14.default.dim("admin key:")} ${import_chalk14.default.dim("manage-access \u2014 put it in APIBLAZE_CP_KEY for the Users & Groups widget, one key per admin")}`);
4902
4948
  } else {
4903
4949
  console.log(` ${import_chalk14.default.cyan(holderLine(out.holder))}`);
4904
4950
  console.log(import_chalk14.default.dim(out.holder === "backend" ? " (For a key you ship to users, re-mint with --client; to bind one to a person, --for <email>.)" : " (For your server, re-mint with --backend; to bind one to a person, --for <email>.)"));
@@ -5446,8 +5492,8 @@ function printEnvironments(o) {
5446
5492
  const rows = names.length ? names.map((e) => [`/${e}`, o.dev && e === "dev" ? `localhost:${o.dev.port} (tunnelled from this machine)` : envs[e]?.target ?? "\u2014"]) : o.dev ? [["/dev", `localhost:${o.dev.port} (tunnelled from this machine)`]] : [];
5447
5493
  if (!rows.length) return;
5448
5494
  const w = Math.max(...rows.map(([e]) => e.length));
5449
- for (const [env, target] of rows) {
5450
- console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target}`);
5495
+ for (const [env, target2] of rows) {
5496
+ console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target2}`);
5451
5497
  }
5452
5498
  }
5453
5499
  async function finishSetup(o) {
@@ -5895,9 +5941,9 @@ Create an ${B.thing}`));
5895
5941
  body.openapi = await loadOpenapiSource(opts.openapi);
5896
5942
  }
5897
5943
  if (opts.target && !isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
5898
- let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
5944
+ let target2 = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
5899
5945
  const hasOtherSource = !!(body.openapi || body.github);
5900
- if (!target && !hasOtherSource) {
5946
+ if (!target2 && !hasOtherSource) {
5901
5947
  if (interactive) {
5902
5948
  const { default: inquirer3 } = await import("inquirer");
5903
5949
  if (name === void 0) {
@@ -5922,7 +5968,7 @@ Create an ${B.thing}`));
5922
5968
  console.log(import_chalk17.default.yellow(" Enter a valid http(s) URL.\n"));
5923
5969
  continue;
5924
5970
  }
5925
- target = url.trim();
5971
+ target2 = url.trim();
5926
5972
  break;
5927
5973
  }
5928
5974
  } else {
@@ -5930,12 +5976,12 @@ Create an ${B.thing}`));
5930
5976
  }
5931
5977
  }
5932
5978
  if (name === void 0 && opts.auto) {
5933
- const nameSource = target || (typeof opts.openapi === "string" ? opts.openapi : void 0);
5979
+ const nameSource = target2 || (typeof opts.openapi === "string" ? opts.openapi : void 0);
5934
5980
  name = deriveNameFrom(nameSource) ?? randomProxyName();
5935
5981
  }
5936
- if (target) {
5937
- body.target = target;
5938
- body.target_url = target;
5982
+ if (target2) {
5983
+ body.target = target2;
5984
+ body.target_url = target2;
5939
5985
  }
5940
5986
  if (name) {
5941
5987
  body.name = name;
@@ -6146,13 +6192,13 @@ async function chooseName(opts, openapi, teamId, interactive) {
6146
6192
  }
6147
6193
  async function createDevProxy(args) {
6148
6194
  const { auth, name, port, openapi, authChoice, families, tenant: tenant2, teamId } = args;
6149
- const target = `http://localhost:${port}`;
6195
+ const target2 = `http://localhost:${port}`;
6150
6196
  const body = {
6151
6197
  name,
6152
6198
  subdomain: name,
6153
6199
  auth_type: authChoice.auth,
6154
6200
  ...authChoice.bodyPatch,
6155
- ...openapi ? { openapi, environments: { dev: { target, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target },
6201
+ ...openapi ? { openapi, environments: { dev: { target: target2, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target2 },
6156
6202
  ...tenant2 ? { tenant: tenant2 } : {},
6157
6203
  ...teamId ? { team_id: teamId } : {},
6158
6204
  ...families ? { secure: { families } } : {}
@@ -6162,7 +6208,7 @@ async function createDevProxy(args) {
6162
6208
  let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
6163
6209
  let resolvedTeam = teamId;
6164
6210
  if (caller) {
6165
- result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target}` });
6211
+ result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target2}` });
6166
6212
  } else {
6167
6213
  result = await createProxyAnonymous(body);
6168
6214
  if (result.cp_key && result.team_id) {
@@ -6218,7 +6264,7 @@ async function runDev(options) {
6218
6264
  }
6219
6265
  let targets = [];
6220
6266
  const wantsNew = options.openapi !== void 0 || options.name !== void 0;
6221
- if (auth.mode !== "none" && !wantsNew) {
6267
+ if (auth.mode !== "none" && (!wantsNew || options.name !== void 0 && !options.project)) {
6222
6268
  const spinner = (0, import_ora7.default)("Looking for projects that point at this machine...").start();
6223
6269
  try {
6224
6270
  targets = await getLocalhostTargets(teamId, keyAuth);
@@ -6232,7 +6278,15 @@ async function runDev(options) {
6232
6278
  let selected = [];
6233
6279
  let created = null;
6234
6280
  let setup = null;
6235
- if (options.project) {
6281
+ const rerun = !options.project && options.name !== void 0 ? targets.find((t) => t.projectId?.toLowerCase() === normalizeName2(options.name) || t.projectName?.toLowerCase() === normalizeName2(options.name)) : void 0;
6282
+ if (rerun) {
6283
+ console.log(import_chalk18.default.dim(` "${rerun.projectName}" already points at this machine \u2014 reattaching the tunnel (spec and keys unchanged).`));
6284
+ selected = [rerun];
6285
+ } else if (wantsNew) {
6286
+ targets = [];
6287
+ }
6288
+ if (selected.length) {
6289
+ } else if (options.project) {
6236
6290
  const want = options.project.toLowerCase();
6237
6291
  const match = targets.find((t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want);
6238
6292
  if (!match) {
@@ -6338,11 +6392,12 @@ async function runDev(options) {
6338
6392
  }
6339
6393
  let restore = [];
6340
6394
  let connect;
6395
+ const registerTargets = { targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) };
6341
6396
  {
6342
6397
  const spinner = (0, import_ora7.default)("Registering the tunnel...").start();
6343
6398
  try {
6344
- const result = await putDevTunnel({ targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) }, keyAuth);
6345
- restore = result.restore ?? [];
6399
+ const result = await putDevTunnel(registerTargets, keyAuth);
6400
+ restore = (result.restore ?? []).map((r) => unmarkRestore(r, options.port));
6346
6401
  connect = result.connect;
6347
6402
  spinner.stop();
6348
6403
  } catch (err) {
@@ -6360,6 +6415,13 @@ async function runDev(options) {
6360
6415
  (projectId) => startTunnelClient({
6361
6416
  connectUrl: connect.url,
6362
6417
  token: connect.token,
6418
+ // Re-register for a fresh token. PUT is idempotent on an already-tunnelled
6419
+ // project; its restore snapshot would be the marker, so it is IGNORED —
6420
+ // teardown keeps replaying the one taken at start.
6421
+ refresh: async () => {
6422
+ const again = await putDevTunnel(registerTargets, keyAuth);
6423
+ return { url: again.connect.url, token: again.connect.token };
6424
+ },
6363
6425
  projectId,
6364
6426
  localPort: options.port,
6365
6427
  onEntry: (entry) => say(formatLogLine(entry)),
@@ -6379,6 +6441,7 @@ async function runDev(options) {
6379
6441
  const localUp = await probeLocalServer(options.port);
6380
6442
  console.log();
6381
6443
  console.log(` ${import_chalk18.default.bold("Tunnel is up \u2014 leave this running. Ctrl-C stops it.")}`);
6444
+ console.log(import_chalk18.default.dim(` If it stops, run: npx ${B.cli} dev --port ${options.port} --project ${selected[0]?.projectName ?? "<name>"} --auto (reattaches; creates nothing)`));
6382
6445
  if (!localUp) console.log(import_chalk18.default.magenta(` \u26B2 Nothing listening on port ${options.port} yet \u2014 requests are captured until your server starts.`));
6383
6446
  if (options.captureFile) console.log(import_chalk18.default.gray(` Streaming full traffic to ${options.captureFile}`));
6384
6447
  console.log();
@@ -6398,9 +6461,30 @@ async function runDev(options) {
6398
6461
  }
6399
6462
  process.on("SIGINT", () => void cleanup());
6400
6463
  process.on("SIGTERM", () => void cleanup());
6464
+ process.on("SIGHUP", () => void cleanup());
6465
+ for (const stream of [process.stdout, process.stderr]) {
6466
+ stream.on("error", (err) => {
6467
+ if (err.code !== "EPIPE") throw err;
6468
+ });
6469
+ }
6401
6470
  await new Promise(() => {
6402
6471
  });
6403
6472
  }
6473
+ function unmarkRestore(r, port) {
6474
+ const envs = r.environments;
6475
+ if (!envs || typeof envs !== "object") return r;
6476
+ let changed = false;
6477
+ const next = {};
6478
+ for (const [stage, value] of Object.entries(envs)) {
6479
+ if (typeof value?.target === "string" && value.target.startsWith("devtunnel://")) {
6480
+ next[stage] = { ...value, target: `http://localhost:${port}` };
6481
+ changed = true;
6482
+ } else {
6483
+ next[stage] = value;
6484
+ }
6485
+ }
6486
+ return changed ? { ...r, environments: next } : r;
6487
+ }
6404
6488
 
6405
6489
  // src/commands/projects.ts
6406
6490
  var import_chalk19 = __toESM(require("chalk"));
@@ -6505,10 +6589,16 @@ business logic: the gateway sits in front of the code and enforces it.
6505
6589
  - They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
6506
6590
  could not find, then ship it through the Ship steps.
6507
6591
  - They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
6508
- No code yet? First write the smallest plain backend that serves the resources they named \u2014
6509
- no auth, no tenancy, no ownership checks (the gateway does those); stamp rows with the
6510
- \`x-abz-user-id\` header so ownership is visible \u2014 plus its \`openapi.yaml\`, start it on
6511
- their port, then continue at Ship \xA71. Do NOT build the Demo app for this. If they say "use
6592
+ No code yet? First write the smallest plain backend that serves the resources they named,
6593
+ plus its \`openapi.yaml\`, start it on their port, then continue at Ship \xA71. Do NOT build
6594
+ the Demo app for this. The gateway does auth, tenancy and item ownership; the backend does
6595
+ only these three things:
6596
+ - stamp each new row's \`owner\` with the \`x-abz-user-id\` header;
6597
+ - filter every collection GET to rows whose \`owner\` is that header (the gateway's rules
6598
+ lock single records, never lists \u2014 a list route has no one record to check);
6599
+ - answer 401 to any call whose \`x-target-api-key\` is not \`process.env.TARGET_SERVER_SECRET\`
6600
+ (generate it with \`openssl rand -hex 24\`, put it in \`.env.local\`, start the backend with
6601
+ it; Ship \xA73b makes the proxy send it). If they say "use
6512
6602
  ${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
6513
6603
 
6514
6604
  Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
@@ -6628,12 +6718,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6628
6718
  sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
6629
6719
  (\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
6630
6720
  route with \`createApiblazeGroups\`).
6631
- - Backend behind a secret: the proxy must send it upstream. That is a dashboard setting
6632
- (Settings \u2192 Transforms); the CLI has no flag for it yet \u2014 say so, do not invent one. For a
6633
- local run through the tunnel, the backend can instead accept calls carrying
6634
- \`x-abz-user-id\`, which the gateway stamps on every call it forwards. Say plainly that while
6635
- the tunnel runs, \`localhost:<port>\` still answers to the old secret and to anyone who sends
6636
- that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
6721
+ - Backend behind a secret: the proxy must send it upstream \u2014
6722
+ \`npx ${cli} transform set-header <name> <its header> --value-env <VAR> --secret\`. Until then
6723
+ anyone reaching \`localhost:<port>\` skips the gateway.
6724
+ - List routes: check each collection GET filters by owner; if it does not, say so \u2014 the
6725
+ gateway locks single records only.
6637
6726
  - Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
6638
6727
  - Change nothing in their business logic. Report what you changed, file by file.
6639
6728
 
@@ -6648,7 +6737,10 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6648
6737
  login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
6649
6738
  (both doors: keys for tenants, their login for people and agents). Shapes below.
6650
6739
  3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
6651
- in the BACKGROUND, left running \u2014 it is the tunnel. **Deployed code** \u2192
6740
+ left running \u2014 it is the tunnel. Start it as its own detached process, never \`cmd &\` inside
6741
+ a shell call that ends: \`nohup npx ${cli} dev \u2026 > ${cli}-dev.log 2>&1 < /dev/null &\`, then
6742
+ read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
6743
+ creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
6652
6744
  \`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
6653
6745
  URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
6654
6746
  goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
@@ -6657,22 +6749,32 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6657
6749
  the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
6658
6750
  say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
6659
6751
  \`ben\` \u2192 403.
6752
+ 3b. **Only the proxy reaches the backend.** If the backend checks \`x-target-api-key\`:
6753
+ \`npx ${cli} transform set-header <name> x-target-api-key --value-env TARGET_SERVER_SECRET --secret\`
6754
+ (export the variable from \`.env.local\` first). Prove it: the same list call through the
6755
+ proxy \u2192 200; \`curl localhost:<port>/<collection>\` with no header \u2192 401.
6660
6756
  4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
6661
- 5. **Limits.** \`npx ${cli} throttle <name> --end-user-rate 10 --quota 10000 --period daily\` \u2014
6662
- sane defaults; use the user's numbers if they gave any. The CLI echoes \`\u2714 Update throttling
6663
- {\u2026}\`. Prove it: 15 parallel GETs as one user \u2192 about 10\xD7200 and the rest 429; a different
6664
- user right after \u2192 200.
6757
+ 5. **Limits.** Every new proxy already allows 10 requests/s per person and 10,000 a day \u2014
6758
+ say so; that is "throttled". Only for other numbers:
6759
+ \`npx ${cli} throttle <name> --rate <n> --quota <n> --period daily\`. It works logged out;
6760
+ an anonymous workspace is capped at 10/s until claimed. Prove it: 15 parallel GETs as one
6761
+ user \u2192 about 10\xD7200 and the rest 429.
6665
6762
  5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
6666
6763
  \`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
6667
6764
  \`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
6668
- login token. Say plainly that list routes (GET on a collection) return everyone's rows;
6669
- only item routes are locked to their creator.
6670
- 6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` \u2014 ask for the email, never guess
6671
- it. If you cannot ask (a scripted run), skip it and end the report with
6672
- "Admin \u2014 none yet: what email should administer tenant <tenant>?"
6673
- 7. **Report** in the shape below, then ask: "Would you like me to add the self-serve API key
6674
- widget to your site?" and "Would you like to chat with your API now via your MCP?"
6675
- (yes \u2192 \`npx ${cli} apichat <name> --install-mcp claude\`).
6765
+ login token. Prove it: the key with no \`X-End-User-Id\` \u2192 403 \`identity_required\`.
6766
+ Lists are the backend's job (it filters by owner); say that the gateway locks records,
6767
+ the backend filters lists.
6768
+ 6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` with the email they named. None
6769
+ named \u2192 \`admin@ninopizzas.com\`, the demo admin (works before that person ever logs in);
6770
+ say so, and that they can \`admins add\` their own email and \`admins remove\` that one.
6771
+ 7. **Report** in the shape below, then hand it over \u2014 they try it, you do not:
6772
+ - the full curl with the real key and URL, ready to paste:
6773
+ \`curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"\`
6774
+ (say the key is the server key \u2014 for trying it here, never for a browser or an agent);
6775
+ - the command to chat with their API: \`npx ${cli} apichat <name>\` (add
6776
+ \`--xenduserid ana\` when \xA75b is on).
6777
+ Then ask once: "Would you like me to add the self-serve API key widget to your site?"
6676
6778
 
6677
6779
  ## Report like this
6678
6780
 
@@ -6685,9 +6787,15 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
6685
6787
  API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
6686
6788
  /dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
6687
6789
  /prod \u2192 <prod-url> your deploy
6688
- Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
6689
6790
  MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
6690
- Limits 10 requests/s per user \xB7 10,000 a day
6791
+ Limits 10 requests/s per person \xB7 10,000 a day
6792
+ Backend answers only to the proxy (x-target-api-key) \xB7 lists filtered by owner
6793
+ Admin <email>
6794
+
6795
+ Try it yourself:
6796
+ curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"
6797
+ Chat with it:
6798
+ npx ${cli} apichat <name> --xenduserid ana
6691
6799
 
6692
6800
  ## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
6693
6801
 
@@ -6709,9 +6817,10 @@ key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenan
6709
6817
  \`--tenant\` you get a consumer key, which is the wrong kind).
6710
6818
  Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
6711
6819
  of your customers mints their own, in their own tenant; the bootstrap key is only for your
6712
- frontend-server. A key bound to one person (an agent, a CI job):
6713
- \`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. That names a person by email,
6714
- which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
6820
+ frontend-server. Three kinds of key for a tenant, one flag each: \`apikeys mint --tenant <tenant>\` +
6821
+ \`--backend\` (a server that names the user with \`X-End-User-Id\`), \`--client\` (cannot name a
6822
+ user) or \`--for <email>\` (bound to one person \u2014 an agent, a CI job). \`--for\` names a person by
6823
+ email, which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
6715
6824
  the header are not owned by ana@example.com's key.
6716
6825
 
6717
6826
  ## Never
@@ -6722,13 +6831,16 @@ the header are not owned by ana@example.com's key.
6722
6831
  - Put ownership or tenancy checks into business logic. The gateway enforces them.
6723
6832
  - Ask for team ids, tenant ids or project ids the CLI already knows.
6724
6833
  - Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
6725
- \`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
6834
+ \`--yes\` skips the prompt; logged in only \u2014 an unclaimed workspace is removed after 30 days). Its impact banner lists tenant-scoped counts even when the tenant
6726
6835
  is shared \u2014 those are lost only if this is the tenant's LAST proxy.
6727
6836
 
6728
6837
  ## Later
6729
6838
 
6730
6839
  - \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
6731
6840
  - \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
6841
+ - \`npx ${cli} throttle <name> --rate <n>\` \xB7 \`identified <name> require|allow-anon\` \xB7
6842
+ \`transform <name> list|set-header|remove\` \xB7 \`target <name> --env prod --url <url>\` \u2014 all work
6843
+ logged out. \`npx ${cli} <command> --help\` for the flags; \`--verbose\` prints the API calls.
6732
6844
 
6733
6845
  ## No shell? The control-plane MCP
6734
6846
 
@@ -6744,14 +6856,14 @@ async function runSkill(opts = {}) {
6744
6856
  process.stdout.write(md);
6745
6857
  return;
6746
6858
  }
6747
- const target = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
6748
- if (import_fs3.default.existsSync(target) && !opts.force) {
6859
+ const target2 = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
6860
+ if (import_fs3.default.existsSync(target2) && !opts.force) {
6749
6861
  console.error(import_chalk20.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
6750
6862
  process.exit(1);
6751
6863
  }
6752
- import_fs3.default.mkdirSync(import_path2.default.dirname(target), { recursive: true });
6753
- import_fs3.default.writeFileSync(target, md, "utf8");
6754
- console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target)}`);
6864
+ import_fs3.default.mkdirSync(import_path2.default.dirname(target2), { recursive: true });
6865
+ import_fs3.default.writeFileSync(target2, md, "utf8");
6866
+ console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target2)}`);
6755
6867
  console.log(import_chalk20.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli} dev / create for you.`));
6756
6868
  }
6757
6869
 
@@ -6842,12 +6954,12 @@ async function runLogout(opts = {}) {
6842
6954
  console.log(import_chalk22.default.yellow("You were not logged in."));
6843
6955
  return;
6844
6956
  }
6845
- let target;
6846
- if (opts.all) target = "all";
6847
- else if (opts.producer) target = "producer";
6848
- else if (opts.consumer) target = "consumer";
6849
- else if (creds && !consumer2) target = "producer";
6850
- else if (consumer2 && !creds) target = "consumer";
6957
+ let target2;
6958
+ if (opts.all) target2 = "all";
6959
+ else if (opts.producer) target2 = "producer";
6960
+ else if (opts.consumer) target2 = "consumer";
6961
+ else if (creds && !consumer2) target2 = "producer";
6962
+ else if (consumer2 && !creds) target2 = "consumer";
6851
6963
  else {
6852
6964
  const { default: inquirer3 } = await import("inquirer");
6853
6965
  const { chosen } = await inquirer3.prompt([
@@ -6862,13 +6974,13 @@ async function runLogout(opts = {}) {
6862
6974
  ]
6863
6975
  }
6864
6976
  ]);
6865
- target = chosen;
6977
+ target2 = chosen;
6866
6978
  }
6867
- if (target === "producer" || target === "all") {
6979
+ if (target2 === "producer" || target2 === "all") {
6868
6980
  clearCredentials();
6869
6981
  console.log(import_chalk22.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
6870
6982
  }
6871
- if (target === "consumer" || target === "all") {
6983
+ if (target2 === "consumer" || target2 === "all") {
6872
6984
  clearConsumer();
6873
6985
  console.log(import_chalk22.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
6874
6986
  }
@@ -7881,17 +7993,17 @@ async function runExport(projectArg, versionArg, opts) {
7881
7993
  const match = await resolveProject(teamId, projectArg, versionArg);
7882
7994
  const projectId = match.projectId;
7883
7995
  const version2 = versionArg || match.apiVersion;
7884
- const target = opts.kong ? "kong" : "data";
7996
+ const target2 = opts.kong ? "kong" : "data";
7885
7997
  const q = new URLSearchParams();
7886
7998
  if (opts.secrets) q.set("include_secrets", "1");
7887
7999
  if (opts.noConsumers) q.set("include_consumers", "0");
7888
8000
  if (opts.keys) q.set("keys", opts.keys);
7889
8001
  const qs = q.toString() ? `?${q.toString()}` : "";
7890
- console.log(import_chalk36.default.dim(`Queuing ${target} export of ${projectId}@${version2}\u2026`));
8002
+ console.log(import_chalk36.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
7891
8003
  const job = await admin({
7892
- path: `/projects/${projectId}/${version2}/export/${target}${qs}`,
8004
+ path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
7893
8005
  method: "GET",
7894
- summary: `export ${projectId} (${target})`
8006
+ summary: `export ${projectId} (${target2})`
7895
8007
  });
7896
8008
  if (!job?.job_id) throw new Error("Failed to queue export.");
7897
8009
  const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
@@ -7910,7 +8022,7 @@ async function runExport(projectArg, versionArg, opts) {
7910
8022
  }
7911
8023
  }
7912
8024
  process.stdout.write("\n");
7913
- const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target}.zip`;
8025
+ const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target2}.zip`;
7914
8026
  const res = await fetch(`${DASHBOARD_BASE5}/api/cli/admin/download`, {
7915
8027
  method: "POST",
7916
8028
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
@@ -7922,7 +8034,7 @@ async function runExport(projectArg, versionArg, opts) {
7922
8034
  console.log(import_chalk36.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
7923
8035
  if (job.decryption_key) console.log(import_chalk36.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
7924
8036
  console.log(import_chalk36.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
7925
- if (target === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
8037
+ if (target2 === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
7926
8038
  }
7927
8039
 
7928
8040
  // src/commands/recipe-search.ts
@@ -8301,8 +8413,8 @@ async function runRecipeInstall(nameArg, opts) {
8301
8413
  let reinstall = false;
8302
8414
  if (opts.into) {
8303
8415
  reinstall = true;
8304
- const target = normalizeName3(opts.into);
8305
- const proj2 = await resolveProjectVia(caller, teamId, target, opts.apiversion);
8416
+ const target2 = normalizeName3(opts.into);
8417
+ const proj2 = await resolveProjectVia(caller, teamId, target2, opts.apiversion);
8306
8418
  project = proj2.projectId;
8307
8419
  apiVersion = proj2.apiVersion;
8308
8420
  tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
@@ -8385,14 +8497,14 @@ function buildSteps(inp) {
8385
8497
  label: `creating ${project}`,
8386
8498
  skip: reinstall,
8387
8499
  run: async () => {
8388
- const target = firstUpstream(recipe);
8389
- if (!target) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
8500
+ const target2 = firstUpstream(recipe);
8501
+ if (!target2) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
8390
8502
  const out = await producer(caller, {
8391
8503
  method: "POST",
8392
8504
  path: "/projects",
8393
8505
  body: {
8394
8506
  name: project,
8395
- target,
8507
+ target: target2,
8396
8508
  tenant: tenant2,
8397
8509
  product_slug: project,
8398
8510
  api_version: apiVersion,
@@ -10109,14 +10221,16 @@ async function runConsumerLogsList(opts) {
10109
10221
  // src/commands/config.ts
10110
10222
  var import_chalk43 = __toESM(require("chalk"));
10111
10223
  var import_ora15 = __toESM(require("ora"));
10112
- init_admin();
10113
- init_resolve();
10224
+ init_caller();
10225
+ init_paging();
10226
+ var resolveCallerMode = () => resolveCaller()?.mode ?? null;
10114
10227
  async function patchConfig(project, opts, body, summary) {
10115
- const { teamId } = await resolveTeam(opts.team);
10116
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10228
+ const caller = requireCaller();
10229
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10230
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10117
10231
  const spinner = (0, import_ora15.default)(summary + "...").start();
10118
10232
  try {
10119
- const out = await admin({
10233
+ const out = await producer(caller, {
10120
10234
  method: "PATCH",
10121
10235
  path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
10122
10236
  body,
@@ -10134,13 +10248,47 @@ async function runTargetSet(project, opts) {
10134
10248
  console.error(import_chalk43.default.red("--url is required."));
10135
10249
  process.exit(1);
10136
10250
  }
10137
- const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
10138
- await patchConfig(project, opts, body, `Set target${opts.env ? ` for env ${opts.env}` : ""} \u2192 ${opts.url}`);
10251
+ if (!opts.env) {
10252
+ await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
10253
+ return;
10254
+ }
10255
+ const caller = requireCaller();
10256
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10257
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10258
+ const listing = await pageAll(
10259
+ (cursor) => producer(caller, {
10260
+ method: "GET",
10261
+ path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
10262
+ summary: `Read environments for ${proj2.projectName}`
10263
+ }),
10264
+ (b) => b?.projects
10265
+ );
10266
+ const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
10267
+ const current = row?.config?.environments;
10268
+ const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
10269
+ const prev = existing[opts.env];
10270
+ const environments = {
10271
+ ...existing,
10272
+ [opts.env]: { ...prev && typeof prev === "object" ? prev : {}, target: opts.url }
10273
+ };
10274
+ await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
10139
10275
  }
10140
10276
  async function runThrottleSet(project, opts) {
10141
10277
  const throttling = {};
10142
- if (opts.rate !== void 0) throttling.userRateLimit = Number(opts.rate);
10143
- if (opts.endUserRate !== void 0) throttling.endUserRateLimit = Number(opts.endUserRate);
10278
+ const rate = opts.rate ?? opts.endUserRate;
10279
+ if (opts.rate !== void 0 && opts.endUserRate !== void 0 && opts.rate !== opts.endUserRate) {
10280
+ console.error(import_chalk43.default.red("--rate and --end-user-rate are the same limit now; pass one of them."));
10281
+ process.exit(1);
10282
+ }
10283
+ if (rate !== void 0) {
10284
+ const n = Number(rate);
10285
+ if (!Number.isFinite(n) || n <= 0) {
10286
+ console.error(import_chalk43.default.red("--rate must be a positive number of requests per second."));
10287
+ process.exit(1);
10288
+ }
10289
+ throttling.userRateLimit = n;
10290
+ throttling.endUserRateLimit = n;
10291
+ }
10144
10292
  if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
10145
10293
  if (opts.period !== void 0) {
10146
10294
  if (!["daily", "weekly", "monthly"].includes(opts.period)) {
@@ -10154,6 +10302,11 @@ async function runThrottleSet(project, opts) {
10154
10302
  process.exit(1);
10155
10303
  }
10156
10304
  await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
10305
+ if (resolveCallerMode() === "anon" && !opts.json) {
10306
+ console.log(import_chalk43.default.dim(
10307
+ " Anonymous workspace: the rate is capped at 10 requests/s per person and the quota at the default until you claim it (`apiblaze login`, then `apiblaze claim`)."
10308
+ ));
10309
+ }
10157
10310
  }
10158
10311
  async function runRename(project, opts) {
10159
10312
  if (!opts.displayName) {
@@ -10163,17 +10316,171 @@ async function runRename(project, opts) {
10163
10316
  await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
10164
10317
  }
10165
10318
 
10319
+ // src/commands/transform.ts
10320
+ var import_chalk44 = __toESM(require("chalk"));
10321
+ var import_ora16 = __toESM(require("ora"));
10322
+ init_caller();
10323
+ var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
10324
+ var ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
10325
+ async function target(project, opts) {
10326
+ const caller = requireCaller();
10327
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10328
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10329
+ const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
10330
+ return { caller, proj: proj2, base: base2 };
10331
+ }
10332
+ async function listRules(t) {
10333
+ const out = await producer(t.caller, {
10334
+ method: "GET",
10335
+ path: t.base,
10336
+ summary: `List transforms for ${t.proj.projectName}`
10337
+ });
10338
+ return out?.rules ?? [];
10339
+ }
10340
+ function describe(r) {
10341
+ const a = r.action ?? { type: "?" };
10342
+ if (a.type === "hardcode") {
10343
+ const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
10344
+ return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
10345
+ }
10346
+ if (a.type === "remove") return `remove ${a.field}`;
10347
+ if (a.type === "map") return `map ${a.source} \u2192 ${a.destination}${a.lookup?.mapping_id ? " (through a mapping table)" : ""}`;
10348
+ return a.type;
10349
+ }
10350
+ async function runTransformList(project, opts) {
10351
+ const t = await target(project, opts);
10352
+ const rules = await listRules(t);
10353
+ if (opts.json) {
10354
+ console.log(JSON.stringify(rules, null, 2));
10355
+ return;
10356
+ }
10357
+ if (!rules.length) {
10358
+ console.log(import_chalk44.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
10359
+ return;
10360
+ }
10361
+ for (const r of rules) {
10362
+ console.log(`${r.enabled ? import_chalk44.default.green("\u25CF") : import_chalk44.default.gray("\u25CB")} ${import_chalk44.default.bold(r.name)} ${import_chalk44.default.dim(`[${r.phase}] ${r.id}`)}`);
10363
+ console.log(` ${describe(r)}`);
10364
+ }
10365
+ }
10366
+ async function runTransformSetHeader(project, header, opts) {
10367
+ if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
10368
+ if (opts.value === void 0 === (opts.valueEnv === void 0)) {
10369
+ throw new Error("Pass exactly one of --value <value> or --value-env <VAR> (reads the value from that environment variable, so it stays out of your shell history).");
10370
+ }
10371
+ const value = opts.value ?? process.env[opts.valueEnv];
10372
+ if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
10373
+ const t = await target(project, opts);
10374
+ const name = ruleNameFor(header);
10375
+ const action2 = {
10376
+ type: "hardcode",
10377
+ destination: `header:${header}`,
10378
+ value,
10379
+ // Encrypted at rest and masked on every read; main-proxy decrypts it only
10380
+ // on the way to the upstream.
10381
+ ...opts.secret ? { value_secret: true } : {}
10382
+ };
10383
+ const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
10384
+ const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
10385
+ const spinner = opts.json ? null : (0, import_ora16.default)(summary + "...").start();
10386
+ try {
10387
+ const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action: action2, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action: action2, phase: "request", enabled: true }, summary });
10388
+ spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
10389
+ if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
10390
+ else console.log(import_chalk44.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
10391
+ } catch (err) {
10392
+ spinner?.fail(summary + " failed.");
10393
+ throw err;
10394
+ }
10395
+ }
10396
+ async function runTransformRemove(project, nameOrId, opts) {
10397
+ const t = await target(project, opts);
10398
+ const rules = await listRules(t);
10399
+ const want = nameOrId.toLowerCase();
10400
+ const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
10401
+ if (!rule) {
10402
+ throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
10403
+ }
10404
+ await producer(t.caller, {
10405
+ method: "DELETE",
10406
+ path: `${t.base}/${encodeURIComponent(rule.id)}`,
10407
+ summary: `Remove transform "${rule.name}"`
10408
+ });
10409
+ if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
10410
+ else console.log(`${import_chalk44.default.green("\u2713")} Removed transform ${import_chalk44.default.bold(rule.name)} from ${t.proj.projectName}.`);
10411
+ }
10412
+ function tablesBase(t) {
10413
+ return t.base.replace(/\/transforms$/, "/mappings");
10414
+ }
10415
+ async function listTables(t) {
10416
+ const out = await producer(t.caller, {
10417
+ method: "GET",
10418
+ path: tablesBase(t),
10419
+ summary: `List mapping tables for ${t.proj.projectName}`
10420
+ });
10421
+ return out?.tables ?? [];
10422
+ }
10423
+ function parseEntries(pairs) {
10424
+ return pairs.map((p) => {
10425
+ const i = p.indexOf("=");
10426
+ if (i <= 0) throw new Error(`"${p}" is not a from=to pair (e.g. FR=EUR).`);
10427
+ return { from: p.slice(0, i), to: p.slice(i + 1) };
10428
+ });
10429
+ }
10430
+ var FIELD_REF = /^(header|query|param|bodyvar|jwt):.+/;
10431
+ async function runTransformMapping(project, table, pairs, opts) {
10432
+ if (!pairs.length) throw new Error("Give at least one from=to pair, e.g. FR=EUR US=USD.");
10433
+ const entries2 = parseEntries(pairs);
10434
+ const t = await target(project, opts);
10435
+ const existing = (await listTables(t)).find((m) => m.name === table);
10436
+ const body = { name: table, entries: entries2, ...opts.secret ? { encrypt_values: true } : {} };
10437
+ const summary = `${existing ? "Replace" : "Create"} mapping table "${table}" (${entries2.length} entr${entries2.length === 1 ? "y" : "ies"})`;
10438
+ const out = await producer(t.caller, existing ? { method: "PUT", path: `${tablesBase(t)}/${encodeURIComponent(existing.id)}`, body, summary } : { method: "POST", path: tablesBase(t), body, summary });
10439
+ if (opts.json) {
10440
+ console.log(JSON.stringify(out ?? { ok: true }));
10441
+ return;
10442
+ }
10443
+ console.log(`${import_chalk44.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
10444
+ for (const e of entries2) console.log(import_chalk44.default.dim(` ${e.from} \u2192 ${opts.secret ? "(secret)" : e.to}`));
10445
+ }
10446
+ async function runTransformMap(project, source, destination, opts) {
10447
+ if (!FIELD_REF.test(source)) throw new Error(`Source "${source}" must look like header:X-Country, query:country or bodyvar:order.country.`);
10448
+ if (!/^(header|query|bodyvar):.+/.test(destination)) throw new Error(`Destination "${destination}" must look like header:X-Currency, query:currency or bodyvar:order.currency.`);
10449
+ if (!opts.table) throw new Error("--table <name> is required: the mapping table to look the value up in (create it with `transform mapping`).");
10450
+ const onNoMatch = opts.onNoMatch ?? "skip";
10451
+ if (!["skip", "fail", "passthrough"].includes(onNoMatch)) throw new Error("--on-no-match is skip, fail or passthrough.");
10452
+ const t = await target(project, opts);
10453
+ const table = (await listTables(t)).find((m) => m.name === opts.table);
10454
+ if (!table) throw new Error(`No mapping table "${opts.table}" on ${t.proj.projectName}. Create it: npx apiblaze transform mapping ${project} ${opts.table} FR=EUR \u2026`);
10455
+ const name = `map ${source} to ${destination}`;
10456
+ const action2 = {
10457
+ type: "map",
10458
+ source,
10459
+ destination,
10460
+ lookup: { mapping_id: table.id, on_no_match: onNoMatch }
10461
+ };
10462
+ const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
10463
+ const summary = `${existing ? "Update" : "Add"} transform: ${source} \u2192 ${destination} through "${table.name}"`;
10464
+ const out = await producer(t.caller, existing ? { method: "PUT", path: `${t.base}/${encodeURIComponent(existing.id)}`, body: { action: action2, enabled: true }, summary } : { method: "POST", path: t.base, body: { name, action: action2, phase: "request", enabled: true }, summary });
10465
+ if (opts.json) {
10466
+ console.log(JSON.stringify(out ?? { ok: true }));
10467
+ return;
10468
+ }
10469
+ console.log(`${import_chalk44.default.green("\u2714")} ${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}.`);
10470
+ console.log(import_chalk44.default.dim(` A value not in the table: ${onNoMatch === "skip" ? "nothing is written" : onNoMatch === "fail" ? "the call is refused" : "it is copied as-is"}.`));
10471
+ }
10472
+
10166
10473
  // src/commands/config-browse.ts
10167
- var import_chalk52 = __toESM(require("chalk"));
10168
- var import_ora23 = __toESM(require("ora"));
10474
+ var import_chalk53 = __toESM(require("chalk"));
10475
+ var import_ora24 = __toESM(require("ora"));
10169
10476
  init_admin();
10170
10477
  init_paging();
10171
10478
  init_auth();
10172
10479
  init_resolve();
10173
10480
 
10174
10481
  // src/commands/domain.ts
10175
- var import_chalk44 = __toESM(require("chalk"));
10176
- var import_ora16 = __toESM(require("ora"));
10482
+ var import_chalk45 = __toESM(require("chalk"));
10483
+ var import_ora17 = __toESM(require("ora"));
10177
10484
  init_admin();
10178
10485
  init_resolve();
10179
10486
  var KINDS = ["api", "mcp", "portal", "auth"];
@@ -10185,7 +10492,7 @@ async function scopeFor(project, opts) {
10185
10492
  return { kind: "project", base: `/projects/${proj2.projectId}/domains`, label: `${proj2.projectName} v${proj2.apiVersion}`, apiVersion: proj2.apiVersion };
10186
10493
  }
10187
10494
  if (!opts.tenant) {
10188
- console.error(import_chalk44.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
10495
+ console.error(import_chalk45.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
10189
10496
  process.exit(1);
10190
10497
  }
10191
10498
  const tenant2 = opts.tenant.toLowerCase();
@@ -10199,35 +10506,35 @@ function describeKind(d) {
10199
10506
  return d?.target ? `${d.kind ?? "api"} \u2192 stands in for ${d.target}` : d?.kind ?? "api";
10200
10507
  }
10201
10508
  function statusColor2(s) {
10202
- if (s === "active") return import_chalk44.default.green(s);
10203
- if (s === "failed" || s === "missing" || s === "suspended") return import_chalk44.default.red(s);
10204
- return import_chalk44.default.yellow(s ?? "unknown");
10509
+ if (s === "active") return import_chalk45.default.green(s);
10510
+ if (s === "failed" || s === "missing" || s === "suspended") return import_chalk45.default.red(s);
10511
+ return import_chalk45.default.yellow(s ?? "unknown");
10205
10512
  }
10206
10513
  async function runDomainAdd(project, opts) {
10207
10514
  if (!opts.domain) {
10208
- console.error(import_chalk44.default.red("--domain is required."));
10515
+ console.error(import_chalk45.default.red("--domain is required."));
10209
10516
  process.exit(1);
10210
10517
  }
10211
10518
  const kind = (opts.kind ?? (project ? "api" : "")).toLowerCase();
10212
10519
  if (!KINDS.includes(kind)) {
10213
- console.error(import_chalk44.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
10520
+ console.error(import_chalk45.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
10214
10521
  process.exit(1);
10215
10522
  }
10216
10523
  const tenantKind = TENANT_KINDS.includes(kind);
10217
10524
  if (tenantKind && project) {
10218
- console.error(import_chalk44.default.red(`A ${kind} domain belongs to a tenant, not a proxy: apiblaze domain add --tenant <slug> --kind ${kind} --domain ${opts.domain}`));
10525
+ console.error(import_chalk45.default.red(`A ${kind} domain belongs to a tenant, not a proxy: apiblaze domain add --tenant <slug> --kind ${kind} --domain ${opts.domain}`));
10219
10526
  process.exit(1);
10220
10527
  }
10221
10528
  if (tenantKind && !opts.tenant) {
10222
- console.error(import_chalk44.default.red(`--tenant <slug> is required for a ${kind} domain.`));
10529
+ console.error(import_chalk45.default.red(`--tenant <slug> is required for a ${kind} domain.`));
10223
10530
  process.exit(1);
10224
10531
  }
10225
10532
  if (!tenantKind && !project) {
10226
- console.error(import_chalk44.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
10533
+ console.error(import_chalk45.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
10227
10534
  process.exit(1);
10228
10535
  }
10229
10536
  const scope = await scopeFor(project, opts);
10230
- const spinner = (0, import_ora16.default)(`Registering ${opts.domain}...`).start();
10537
+ const spinner = (0, import_ora17.default)(`Registering ${opts.domain}...`).start();
10231
10538
  try {
10232
10539
  const body = scope.kind === "project" ? { hostname: opts.domain, api_version: scope.apiVersion, kind, ...opts.tenant ? { tenant_name: opts.tenant.toLowerCase() } : {} } : { hostname: opts.domain, kind };
10233
10540
  const out = await admin({
@@ -10241,10 +10548,10 @@ async function runDomainAdd(project, opts) {
10241
10548
  console.log(JSON.stringify(out));
10242
10549
  return;
10243
10550
  }
10244
- if (out?.cnameRecord) console.log(` ${import_chalk44.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value}`);
10245
- if (out?.dcv) console.log(` ${import_chalk44.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10246
- if (typeof out?.monthlyCents === "number") console.log(import_chalk44.default.dim(` billed $${(out.monthlyCents / 100).toFixed(2)}/month (first month pro rata)`));
10247
- if (out?.id) console.log(import_chalk44.default.dim(` domain id: ${out.id}`));
10551
+ if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value}`);
10552
+ if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10553
+ if (typeof out?.monthlyCents === "number") console.log(import_chalk45.default.dim(` billed $${(out.monthlyCents / 100).toFixed(2)}/month (first month pro rata)`));
10554
+ if (out?.id) console.log(import_chalk45.default.dim(` domain id: ${out.id}`));
10248
10555
  } catch (err) {
10249
10556
  spinner.fail("Domain registration failed.");
10250
10557
  throw err;
@@ -10263,17 +10570,17 @@ async function runDomainList(project, opts) {
10263
10570
  return;
10264
10571
  }
10265
10572
  if (!domains.length) {
10266
- console.log(import_chalk44.default.yellow("No custom domains."));
10573
+ console.log(import_chalk45.default.yellow("No custom domains."));
10267
10574
  return;
10268
10575
  }
10269
10576
  for (const d of domains) {
10270
- const pin = scope.kind === "project" && d.tenant ? import_chalk44.default.dim(` tenant ${d.tenant}`) : "";
10271
- console.log(` ${import_chalk44.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk44.default.dim(describeKind(d))}${pin} ${import_chalk44.default.dim(d.id ?? "")}`);
10577
+ const pin = scope.kind === "project" && d.tenant ? import_chalk45.default.dim(` tenant ${d.tenant}`) : "";
10578
+ console.log(` ${import_chalk45.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk45.default.dim(describeKind(d))}${pin} ${import_chalk45.default.dim(d.id ?? "")}`);
10272
10579
  }
10273
10580
  }
10274
10581
  async function runDomainStatus(project, opts) {
10275
10582
  if (!opts.id) {
10276
- console.error(import_chalk44.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10583
+ console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10277
10584
  process.exit(1);
10278
10585
  }
10279
10586
  const scope = await scopeFor(project, opts);
@@ -10286,23 +10593,23 @@ async function runDomainStatus(project, opts) {
10286
10593
  console.log(JSON.stringify(out));
10287
10594
  return;
10288
10595
  }
10289
- console.log(` ${import_chalk44.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk44.default.dim(describeKind(out))}`);
10596
+ console.log(` ${import_chalk45.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk45.default.dim(describeKind(out))}`);
10290
10597
  if (out?.status !== "active") {
10291
- if (out?.status === "suspended") console.log(import_chalk44.default.red(" Suspended for non-payment \u2014 add credit to restore it."));
10292
- else if (out?.status === "missing") console.log(import_chalk44.default.red(" No longer registered at Cloudflare \u2014 remove it and add it again."));
10598
+ if (out?.status === "suspended") console.log(import_chalk45.default.red(" Suspended for non-payment \u2014 add credit to restore it."));
10599
+ else if (out?.status === "missing") console.log(import_chalk45.default.red(" No longer registered at Cloudflare \u2014 remove it and add it again."));
10293
10600
  else {
10294
- if (out?.cnameRecord) console.log(` ${import_chalk44.default.cyan("CNAME")} ${out.cnameRecord.name} \u2192 ${out.cnameRecord.value}`);
10295
- if (out?.dcv) console.log(` ${import_chalk44.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10601
+ if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name} \u2192 ${out.cnameRecord.value}`);
10602
+ if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10296
10603
  }
10297
10604
  }
10298
10605
  }
10299
10606
  async function runDomainRemove(project, opts) {
10300
10607
  if (!opts.id) {
10301
- console.error(import_chalk44.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10608
+ console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10302
10609
  process.exit(1);
10303
10610
  }
10304
10611
  const scope = await scopeFor(project, opts);
10305
- const spinner = (0, import_ora16.default)("Removing domain...").start();
10612
+ const spinner = (0, import_ora17.default)("Removing domain...").start();
10306
10613
  try {
10307
10614
  await admin({
10308
10615
  method: "DELETE",
@@ -10319,7 +10626,7 @@ async function runDomainSetBase(project, opts) {
10319
10626
  const env = opts.env ?? "prod";
10320
10627
  const { teamId } = await resolveTeam(opts.team);
10321
10628
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
10322
- const spinner = (0, import_ora16.default)("Setting base domain...").start();
10629
+ const spinner = (0, import_ora17.default)("Setting base domain...").start();
10323
10630
  try {
10324
10631
  await admin({
10325
10632
  method: "PUT",
@@ -10335,8 +10642,8 @@ async function runDomainSetBase(project, opts) {
10335
10642
  }
10336
10643
 
10337
10644
  // src/commands/tenant.ts
10338
- var import_chalk47 = __toESM(require("chalk"));
10339
- var import_ora19 = __toESM(require("ora"));
10645
+ var import_chalk48 = __toESM(require("chalk"));
10646
+ var import_ora20 = __toESM(require("ora"));
10340
10647
  init_admin();
10341
10648
  init_resolve();
10342
10649
  init_auth();
@@ -10345,20 +10652,20 @@ init_paging();
10345
10652
  async function runTenantUse(query, opts) {
10346
10653
  const creds = loadCredentials();
10347
10654
  if (!creds) {
10348
- console.error(import_chalk47.default.red("Not logged in. Run `apiblaze login` first."));
10655
+ console.error(import_chalk48.default.red("Not logged in. Run `apiblaze login` first."));
10349
10656
  process.exit(1);
10350
10657
  }
10351
10658
  if (opts.clear) {
10352
10659
  delete creds.activeTenant;
10353
10660
  saveCredentials(creds);
10354
- console.log(import_chalk47.default.green("Tenant scope cleared."));
10661
+ console.log(import_chalk48.default.green("Tenant scope cleared."));
10355
10662
  return;
10356
10663
  }
10357
10664
  const { teamId } = await resolveTeam(opts.team);
10358
10665
  const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
10359
10666
  if (!slug) process.exit(1);
10360
10667
  saveCredentials({ ...creds, activeTenant: slug });
10361
- console.log(import_chalk47.default.green(`Tenant scope set to ${import_chalk47.default.bold(slug)}.`) + import_chalk47.default.dim(" (clear with `apiblaze tenant use --clear`)"));
10668
+ console.log(import_chalk48.default.green(`Tenant scope set to ${import_chalk48.default.bold(slug)}.`) + import_chalk48.default.dim(" (clear with `apiblaze tenant use --clear`)"));
10362
10669
  }
10363
10670
  async function runTenantList(opts) {
10364
10671
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -10382,26 +10689,26 @@ async function runTenantList(opts) {
10382
10689
  return;
10383
10690
  }
10384
10691
  if (!tenants.length) {
10385
- console.log(import_chalk47.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
10692
+ console.log(import_chalk48.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
10386
10693
  return;
10387
10694
  }
10388
10695
  for (const t of tenants) {
10389
10696
  const name = typeof t === "string" ? t : t.tenant_name;
10390
- const display = typeof t === "string" ? "" : import_chalk47.default.dim(` ${t.display_name ?? ""}`);
10391
- console.log(` ${import_chalk47.default.bold(name)}${display}`);
10697
+ const display = typeof t === "string" ? "" : import_chalk48.default.dim(` ${t.display_name ?? ""}`);
10698
+ console.log(` ${import_chalk48.default.bold(name)}${display}`);
10392
10699
  }
10393
10700
  const total = out?.total ?? tenants.length;
10394
10701
  if (total > tenants.length) {
10395
- console.log(import_chalk47.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
10702
+ console.log(import_chalk48.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
10396
10703
  }
10397
10704
  }
10398
10705
  async function runTenantCreate(opts) {
10399
10706
  if (!opts.name) {
10400
- console.error(import_chalk47.default.red("--name (display name) is required."));
10707
+ console.error(import_chalk48.default.red("--name (display name) is required."));
10401
10708
  process.exit(1);
10402
10709
  }
10403
10710
  const { teamId } = await resolveTeam(opts.team);
10404
- const spinner = (0, import_ora19.default)("Creating tenant...").start();
10711
+ const spinner = (0, import_ora20.default)("Creating tenant...").start();
10405
10712
  try {
10406
10713
  const out = await admin({
10407
10714
  method: "POST",
@@ -10409,7 +10716,7 @@ async function runTenantCreate(opts) {
10409
10716
  body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
10410
10717
  summary: `Create tenant "${opts.name}"`
10411
10718
  });
10412
- spinner.succeed(`Created tenant ${import_chalk47.default.bold(out?.tenant_name ?? opts.name)}.`);
10719
+ spinner.succeed(`Created tenant ${import_chalk48.default.bold(out?.tenant_name ?? opts.name)}.`);
10413
10720
  if (opts.json) console.log(JSON.stringify(out));
10414
10721
  } catch (err) {
10415
10722
  spinner.fail("Tenant create failed.");
@@ -10418,12 +10725,12 @@ async function runTenantCreate(opts) {
10418
10725
  }
10419
10726
  async function runTenantAttach(project, opts) {
10420
10727
  if (!opts.tenant) {
10421
- console.error(import_chalk47.default.red("--tenant <slug> is required."));
10728
+ console.error(import_chalk48.default.red("--tenant <slug> is required."));
10422
10729
  process.exit(1);
10423
10730
  }
10424
10731
  const { teamId } = await resolveTeam(opts.team);
10425
10732
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
10426
- const spinner = (0, import_ora19.default)("Attaching tenant...").start();
10733
+ const spinner = (0, import_ora20.default)("Attaching tenant...").start();
10427
10734
  try {
10428
10735
  const out = await admin({
10429
10736
  method: "POST",
@@ -10452,11 +10759,11 @@ async function runTenantDelete(slug, opts) {
10452
10759
  }
10453
10760
  ]);
10454
10761
  if (!confirm2) {
10455
- console.log(import_chalk47.default.dim("Aborted."));
10762
+ console.log(import_chalk48.default.dim("Aborted."));
10456
10763
  return;
10457
10764
  }
10458
10765
  }
10459
- const spinner = (0, import_ora19.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
10766
+ const spinner = (0, import_ora20.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
10460
10767
  try {
10461
10768
  await admin({
10462
10769
  method: "DELETE",
@@ -10480,7 +10787,7 @@ async function runTenantDelete(slug, opts) {
10480
10787
  summary: `Purge tenant ${slug} (full cascade)`
10481
10788
  });
10482
10789
  } catch (err) {
10483
- console.log(import_chalk47.default.red("\u2716 Tenant purge failed to start."));
10790
+ console.log(import_chalk48.default.red("\u2716 Tenant purge failed to start."));
10484
10791
  throw err;
10485
10792
  }
10486
10793
  const job = await settleDelete(accepted, {
@@ -10496,18 +10803,18 @@ async function runTenantDelete(slug, opts) {
10496
10803
  ...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
10497
10804
  }));
10498
10805
  } else if (opts.wait !== false) {
10499
- console.log(import_chalk47.default.green(`\u2714 Deleted tenant ${slug}.`));
10806
+ console.log(import_chalk48.default.green(`\u2714 Deleted tenant ${slug}.`));
10500
10807
  }
10501
10808
  }
10502
10809
  async function runTenantCors(opts) {
10503
10810
  if (!opts.tenant) {
10504
- console.error(import_chalk47.default.red("--tenant <slug> is required."));
10811
+ console.error(import_chalk48.default.red("--tenant <slug> is required."));
10505
10812
  process.exit(1);
10506
10813
  }
10507
10814
  const { teamId } = await resolveTeam(opts.team);
10508
10815
  const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
10509
10816
  const cors = origins.length ? { allowed_origins: origins } : null;
10510
- const spinner = (0, import_ora19.default)("Updating CORS...").start();
10817
+ const spinner = (0, import_ora20.default)("Updating CORS...").start();
10511
10818
  try {
10512
10819
  await admin({
10513
10820
  method: "PUT",
@@ -10523,8 +10830,8 @@ async function runTenantCors(opts) {
10523
10830
  }
10524
10831
 
10525
10832
  // src/commands/tenant-drill.ts
10526
- var import_chalk48 = __toESM(require("chalk"));
10527
- var import_ora20 = __toESM(require("ora"));
10833
+ var import_chalk49 = __toESM(require("chalk"));
10834
+ var import_ora21 = __toESM(require("ora"));
10528
10835
  var import_crypto = require("crypto");
10529
10836
  init_admin();
10530
10837
  init_auth();
@@ -10554,17 +10861,17 @@ async function validScopedTenant(teamId, query) {
10554
10861
  const next = { ...creds };
10555
10862
  delete next.activeTenant;
10556
10863
  saveCredentials2(next);
10557
- console.log(import_chalk48.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
10864
+ console.log(import_chalk49.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
10558
10865
  return void 0;
10559
10866
  }
10560
10867
  async function tenantHome(teamId, tenant2) {
10561
10868
  const { default: inquirer3 } = await import("inquirer");
10562
10869
  const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
10563
- console.log(import_chalk48.default.bold(`
10870
+ console.log(import_chalk49.default.bold(`
10564
10871
  Tenant ${tenant2}`));
10565
- console.log(import_chalk48.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
10872
+ console.log(import_chalk49.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
10566
10873
  for (; ; ) {
10567
- const spinner = (0, import_ora20.default)("Reading tenant state...").start();
10874
+ const spinner = (0, import_ora21.default)("Reading tenant state...").start();
10568
10875
  const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
10569
10876
  admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
10570
10877
  admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
@@ -10578,18 +10885,18 @@ Tenant ${tenant2}`));
10578
10885
  const nClients = Array.isArray(clients) ? clients.length : 0;
10579
10886
  const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
10580
10887
  const nLogin = nClients + nIssuers + nOpaque;
10581
- const onOff = (b) => b ? import_chalk48.default.green("on") : import_chalk48.default.dim("off");
10888
+ const onOff = (b) => b ? import_chalk49.default.green("on") : import_chalk49.default.dim("off");
10582
10889
  const { pick: pick2 } = await inquirer3.prompt([{
10583
10890
  type: "list",
10584
10891
  name: "pick",
10585
10892
  message: `Tenant ${tenant2}:`,
10586
10893
  pageSize: 12,
10587
10894
  choices: [
10588
- { name: `Login methods (${nLogin}) ${import_chalk48.default.dim("how your consumers sign in")}`, value: "login" },
10589
- { name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk48.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
10590
- { name: `Portal admins (${nEmails}) ${import_chalk48.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
10591
- new inquirer3.Separator(import_chalk48.default.dim(" Settings")),
10592
- { name: `CORS: ${cors?.cors ? import_chalk48.default.cyan(JSON.stringify(cors.cors)) : import_chalk48.default.dim("(unset)")} ${import_chalk48.default.dim("browser origins allowed to call this tenant")}`, value: "cors" },
10895
+ { name: `Login methods (${nLogin}) ${import_chalk49.default.dim("how your consumers sign in")}`, value: "login" },
10896
+ { name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk49.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
10897
+ { name: `Portal admins (${nEmails}) ${import_chalk49.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
10898
+ new inquirer3.Separator(import_chalk49.default.dim(" Settings")),
10899
+ { name: `CORS: ${cors?.cors ? import_chalk49.default.cyan(JSON.stringify(cors.cors)) : import_chalk49.default.dim("(unset)")} ${import_chalk49.default.dim("browser origins allowed to call this tenant")}`, value: "cors" },
10593
10900
  { name: "\u2190 Back", value: "back" }
10594
10901
  ]
10595
10902
  }]);
@@ -10602,7 +10909,7 @@ Tenant ${tenant2}`));
10602
10909
  case "iam": {
10603
10910
  const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
10604
10911
  await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
10605
- console.log(import_chalk48.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
10912
+ console.log(import_chalk49.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
10606
10913
  break;
10607
10914
  }
10608
10915
  case "cors": {
@@ -10615,11 +10922,11 @@ Tenant ${tenant2}`));
10615
10922
  if (v === "") break;
10616
10923
  const parsed = v === "null" ? null : safeJson(v);
10617
10924
  if (parsed === void 0) {
10618
- console.log(import_chalk48.default.yellow(" Not valid JSON \u2014 unchanged."));
10925
+ console.log(import_chalk49.default.yellow(" Not valid JSON \u2014 unchanged."));
10619
10926
  break;
10620
10927
  }
10621
10928
  await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
10622
- console.log(import_chalk48.default.green(" CORS updated."));
10929
+ console.log(import_chalk49.default.green(" CORS updated."));
10623
10930
  break;
10624
10931
  }
10625
10932
  case "emails":
@@ -10631,7 +10938,7 @@ Tenant ${tenant2}`));
10631
10938
  async function loginMethodsMenu(teamId, tenant2, base2) {
10632
10939
  const { default: inquirer3 } = await import("inquirer");
10633
10940
  for (; ; ) {
10634
- const spinner = (0, import_ora20.default)("Loading login methods...").start();
10941
+ const spinner = (0, import_ora21.default)("Loading login methods...").start();
10635
10942
  const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
10636
10943
  admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
10637
10944
  admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
@@ -10643,15 +10950,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
10643
10950
  const choices = [];
10644
10951
  for (const c of appClients) {
10645
10952
  const nP = (c.providers ?? []).length || c.providers_count || 0;
10646
- choices.push({ name: `${import_chalk48.default.cyan("APIblaze-hosted")} ${import_chalk48.default.bold(c.name ?? c.clientId)} ${import_chalk48.default.dim(`${c.clientId}${nP ? ` \xB7 ${nP} provider${nP === 1 ? "" : "s"}` : import_chalk48.default.yellow(" \xB7 needs a provider")}`)}`, value: { kind: "client", item: c } });
10953
+ choices.push({ name: `${import_chalk49.default.cyan("APIblaze-hosted")} ${import_chalk49.default.bold(c.name ?? c.clientId)} ${import_chalk49.default.dim(`${c.clientId}${nP ? ` \xB7 ${nP} provider${nP === 1 ? "" : "s"}` : import_chalk49.default.yellow(" \xB7 needs a provider")}`)}`, value: { kind: "client", item: c } });
10647
10954
  }
10648
10955
  for (const i of issuers) {
10649
- choices.push({ name: `${import_chalk48.default.cyan("Your own \u2014 JWT ")} ${import_chalk48.default.bold(i.iss)} ${import_chalk48.default.dim(`aud=${i.aud}`)}`, value: { kind: "issuer", item: i } });
10956
+ choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 JWT ")} ${import_chalk49.default.bold(i.iss)} ${import_chalk49.default.dim(`aud=${i.aud}`)}`, value: { kind: "issuer", item: i } });
10650
10957
  }
10651
10958
  if (opaque?.endpoint) {
10652
- choices.push({ name: `${import_chalk48.default.cyan("Your own \u2014 opaque")} ${import_chalk48.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
10959
+ choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 opaque")} ${import_chalk49.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
10653
10960
  }
10654
- if (!choices.length) console.log(import_chalk48.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
10961
+ if (!choices.length) console.log(import_chalk49.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
10655
10962
  const { pick: pick2 } = await inquirer3.prompt([{
10656
10963
  type: "list",
10657
10964
  name: "pick",
@@ -10691,9 +10998,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
10691
10998
  message: "How do people log in?",
10692
10999
  pageSize: 8,
10693
11000
  choices: [
10694
- { name: `APIblaze hosted login page ${import_chalk48.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
10695
- { name: `Your own hosted login \u2014 JWT ${import_chalk48.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
10696
- { name: `Your own login \u2014 opaque token ${import_chalk48.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
11001
+ { name: `APIblaze hosted login page ${import_chalk49.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
11002
+ { name: `Your own hosted login \u2014 JWT ${import_chalk49.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
11003
+ { name: `Your own login \u2014 opaque token ${import_chalk49.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
10697
11004
  { name: "\u2190 Back", value: null }
10698
11005
  ]
10699
11006
  }]);
@@ -10717,8 +11024,8 @@ function safeJson(s) {
10717
11024
  async function emailsMenu(base2, emails) {
10718
11025
  const { default: inquirer3 } = await import("inquirer");
10719
11026
  console.log();
10720
- if (!emails.length) console.log(import_chalk48.default.dim(" No consumer-admin emails."));
10721
- for (const e of emails) console.log(` ${import_chalk48.default.bold(e.email ?? e)} ${import_chalk48.default.dim(e.status ?? "")}`);
11027
+ if (!emails.length) console.log(import_chalk49.default.dim(" No consumer-admin emails."));
11028
+ for (const e of emails) console.log(` ${import_chalk49.default.bold(e.email ?? e)} ${import_chalk49.default.dim(e.status ?? "")}`);
10722
11029
  const { act } = await inquirer3.prompt([{
10723
11030
  type: "list",
10724
11031
  name: "act",
@@ -10733,7 +11040,7 @@ async function emailsMenu(base2, emails) {
10733
11040
  if (act === "add") {
10734
11041
  const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
10735
11042
  await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
10736
- console.log(import_chalk48.default.green(` ${email} added.`));
11043
+ console.log(import_chalk49.default.green(` ${email} added.`));
10737
11044
  } else {
10738
11045
  const { e } = await inquirer3.prompt([{
10739
11046
  type: "list",
@@ -10743,7 +11050,7 @@ async function emailsMenu(base2, emails) {
10743
11050
  }]);
10744
11051
  if (!e) return;
10745
11052
  await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
10746
- console.log(import_chalk48.default.green(` ${e} removed.`));
11053
+ console.log(import_chalk49.default.green(` ${e} removed.`));
10747
11054
  }
10748
11055
  }
10749
11056
  async function addIssuer(base2) {
@@ -10764,19 +11071,19 @@ async function addIssuer(base2) {
10764
11071
  body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
10765
11072
  summary: `Add external issuer ${a.iss.trim()}`
10766
11073
  });
10767
- console.log(import_chalk48.default.green(" JWT login method saved."));
11074
+ console.log(import_chalk49.default.green(" JWT login method saved."));
10768
11075
  }
10769
11076
  async function issuerHome(base2, issuer) {
10770
11077
  const { default: inquirer3 } = await import("inquirer");
10771
11078
  console.log(`
10772
- ${import_chalk48.default.bold(issuer.iss)} ${import_chalk48.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
11079
+ ${import_chalk49.default.bold(issuer.iss)} ${import_chalk49.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
10773
11080
  const { act } = await inquirer3.prompt([{
10774
11081
  type: "list",
10775
11082
  name: "act",
10776
11083
  message: "This JWT login method:",
10777
11084
  choices: [
10778
11085
  { name: "Replace it (re-enter details)", value: "edit" },
10779
- { name: import_chalk48.default.red("Delete it"), value: "rm" },
11086
+ { name: import_chalk49.default.red("Delete it"), value: "rm" },
10780
11087
  { name: "\u2190 Back", value: "back" }
10781
11088
  ]
10782
11089
  }]);
@@ -10792,7 +11099,7 @@ async function issuerHome(base2, issuer) {
10792
11099
  path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
10793
11100
  summary: `Delete issuer ${issuer.iss}`
10794
11101
  });
10795
- console.log(import_chalk48.default.green(" Deleted."));
11102
+ console.log(import_chalk49.default.green(" Deleted."));
10796
11103
  }
10797
11104
  async function setOpaque(base2, cur) {
10798
11105
  const { default: inquirer3 } = await import("inquirer");
@@ -10801,19 +11108,19 @@ async function setOpaque(base2, cur) {
10801
11108
  { type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
10802
11109
  ]);
10803
11110
  await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
10804
- console.log(import_chalk48.default.green(" Opaque login method set."));
11111
+ console.log(import_chalk49.default.green(" Opaque login method set."));
10805
11112
  }
10806
11113
  async function opaqueHome(base2, cur) {
10807
11114
  const { default: inquirer3 } = await import("inquirer");
10808
11115
  console.log(`
10809
- ${import_chalk48.default.bold(cur.endpoint)} ${import_chalk48.default.dim(cur.method ?? "GET")}`);
11116
+ ${import_chalk49.default.bold(cur.endpoint)} ${import_chalk49.default.dim(cur.method ?? "GET")}`);
10810
11117
  const { act } = await inquirer3.prompt([{
10811
11118
  type: "list",
10812
11119
  name: "act",
10813
11120
  message: "This opaque login method:",
10814
11121
  choices: [
10815
11122
  { name: "Replace it", value: "edit" },
10816
- { name: import_chalk48.default.red("Delete it"), value: "rm" },
11123
+ { name: import_chalk49.default.red("Delete it"), value: "rm" },
10817
11124
  { name: "\u2190 Back", value: "back" }
10818
11125
  ]
10819
11126
  }]);
@@ -10823,14 +11130,14 @@ async function opaqueHome(base2, cur) {
10823
11130
  return;
10824
11131
  }
10825
11132
  await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
10826
- console.log(import_chalk48.default.green(" Deleted."));
11133
+ console.log(import_chalk49.default.green(" Deleted."));
10827
11134
  }
10828
11135
  async function clientHome(base2, summary) {
10829
11136
  const { default: inquirer3 } = await import("inquirer");
10830
11137
  const id = summary.clientId ?? summary.client_id;
10831
11138
  const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
10832
11139
  for (; ; ) {
10833
- const spinner = (0, import_ora20.default)("Reading app client...").start();
11140
+ const spinner = (0, import_ora21.default)("Reading app client...").start();
10834
11141
  const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
10835
11142
  spinner.stop();
10836
11143
  const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
@@ -10842,13 +11149,13 @@ async function clientHome(base2, summary) {
10842
11149
  message: `${c.name ?? id}:`,
10843
11150
  pageSize: 12,
10844
11151
  choices: [
10845
- { name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk48.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
10846
- { name: `Callback URLs: ${cb.length ? import_chalk48.default.cyan(cb.join(", ")) : import_chalk48.default.dim("(none)")}`, value: "callbacks" },
10847
- { name: `Scopes: ${scopes.length ? import_chalk48.default.cyan(scopes.join(" ")) : import_chalk48.default.dim("(defaults)")}`, value: "scopes" },
11152
+ { name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk49.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
11153
+ { name: `Callback URLs: ${cb.length ? import_chalk49.default.cyan(cb.join(", ")) : import_chalk49.default.dim("(none)")}`, value: "callbacks" },
11154
+ { name: `Scopes: ${scopes.length ? import_chalk49.default.cyan(scopes.join(" ")) : import_chalk49.default.dim("(defaults)")}`, value: "scopes" },
10848
11155
  { name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
10849
11156
  { name: "Reveal client secret", value: "secret" },
10850
11157
  { name: "Rotate client secret", value: "rotate" },
10851
- { name: import_chalk48.default.red("Delete this app client"), value: "delete" },
11158
+ { name: import_chalk49.default.red("Delete this app client"), value: "delete" },
10852
11159
  { name: "\u2190 Back", value: "back" }
10853
11160
  ]
10854
11161
  }]);
@@ -10861,13 +11168,13 @@ async function clientHome(base2, summary) {
10861
11168
  case "callbacks": {
10862
11169
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
10863
11170
  await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
10864
- console.log(import_chalk48.default.green(" Callbacks updated."));
11171
+ console.log(import_chalk49.default.green(" Callbacks updated."));
10865
11172
  break;
10866
11173
  }
10867
11174
  case "scopes": {
10868
11175
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
10869
11176
  await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
10870
- console.log(import_chalk48.default.green(" Scopes updated."));
11177
+ console.log(import_chalk49.default.green(" Scopes updated."));
10871
11178
  break;
10872
11179
  }
10873
11180
  case "expiries": {
@@ -10882,14 +11189,14 @@ async function clientHome(base2, summary) {
10882
11189
  body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
10883
11190
  summary: "Update token expiries"
10884
11191
  });
10885
- console.log(import_chalk48.default.green(" Expiries updated."));
11192
+ console.log(import_chalk49.default.green(" Expiries updated."));
10886
11193
  break;
10887
11194
  }
10888
11195
  case "secret": {
10889
11196
  const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
10890
11197
  if (!sure) break;
10891
11198
  const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
10892
- console.log(` ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
11199
+ console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
10893
11200
  break;
10894
11201
  }
10895
11202
  case "rotate": {
@@ -10897,14 +11204,14 @@ async function clientHome(base2, summary) {
10897
11204
  if (!sure) break;
10898
11205
  const fresh = randomSecret();
10899
11206
  await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
10900
- console.log(` New ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(fresh)} ${import_chalk48.default.dim("(store it now)")}`);
11207
+ console.log(` New ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(fresh)} ${import_chalk49.default.dim("(store it now)")}`);
10901
11208
  break;
10902
11209
  }
10903
11210
  case "delete": {
10904
11211
  const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Delete app client "${c.name ?? id}"? Consumers logged in through it will lose access.`, default: false }]);
10905
11212
  if (!sure) break;
10906
11213
  await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
10907
- console.log(import_chalk48.default.green(" App client deleted."));
11214
+ console.log(import_chalk49.default.green(" App client deleted."));
10908
11215
  return;
10909
11216
  }
10910
11217
  }
@@ -10925,15 +11232,15 @@ var DEFAULT_SCOPES2 = {
10925
11232
  async function providersMenu(cBase, clientLabel) {
10926
11233
  const { default: inquirer3 } = await import("inquirer");
10927
11234
  for (; ; ) {
10928
- const spinner = (0, import_ora20.default)("Loading providers...").start();
11235
+ const spinner = (0, import_ora21.default)("Loading providers...").start();
10929
11236
  const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
10930
11237
  spinner.stop();
10931
11238
  const providers = Array.isArray(raw) ? raw : [];
10932
11239
  console.log();
10933
11240
  for (const p of providers) {
10934
- console.log(` ${import_chalk48.default.bold(p.type)} ${import_chalk48.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
11241
+ console.log(` ${import_chalk49.default.bold(p.type)} ${import_chalk49.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
10935
11242
  }
10936
- if (!providers.length) console.log(import_chalk48.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
11243
+ if (!providers.length) console.log(import_chalk49.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
10937
11244
  const { act } = await inquirer3.prompt([{
10938
11245
  type: "list",
10939
11246
  name: "act",
@@ -10991,23 +11298,23 @@ async function providersMenu(cBase, clientLabel) {
10991
11298
  body.targetServerToken = routing;
10992
11299
  }
10993
11300
  await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
10994
- console.log(import_chalk48.default.green(` ${type} provider added.`));
11301
+ console.log(import_chalk49.default.green(` ${type} provider added.`));
10995
11302
  } else {
10996
11303
  const { p } = await inquirer3.prompt([{
10997
11304
  type: "list",
10998
11305
  name: "p",
10999
11306
  message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
11000
- choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk48.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
11307
+ choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk49.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
11001
11308
  }]);
11002
11309
  if (!p) continue;
11003
11310
  if (act === "rm") {
11004
11311
  const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Remove the ${p.type} provider? Consumers using it can no longer sign in.`, default: false }]);
11005
11312
  if (!sure) continue;
11006
11313
  await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
11007
- console.log(import_chalk48.default.green(` ${p.type} removed.`));
11314
+ console.log(import_chalk49.default.green(` ${p.type} removed.`));
11008
11315
  } else {
11009
11316
  const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
11010
- console.log(` ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
11317
+ console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
11011
11318
  }
11012
11319
  }
11013
11320
  }
@@ -11015,8 +11322,8 @@ async function providersMenu(cBase, clientLabel) {
11015
11322
 
11016
11323
  // src/commands/spec.ts
11017
11324
  var fs12 = __toESM(require("fs"));
11018
- var import_chalk49 = __toESM(require("chalk"));
11019
- var import_ora21 = __toESM(require("ora"));
11325
+ var import_chalk50 = __toESM(require("chalk"));
11326
+ var import_ora22 = __toESM(require("ora"));
11020
11327
  init_admin();
11021
11328
  init_api();
11022
11329
  init_resolve();
@@ -11032,19 +11339,19 @@ async function runSpecGet(project, opts) {
11032
11339
  }
11033
11340
  async function runSpecSet(project, opts) {
11034
11341
  if (!opts.file) {
11035
- console.error(import_chalk49.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
11342
+ console.error(import_chalk50.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
11036
11343
  process.exit(1);
11037
11344
  }
11038
11345
  let specContent;
11039
11346
  try {
11040
11347
  specContent = fs12.readFileSync(opts.file, "utf-8");
11041
11348
  } catch {
11042
- console.error(import_chalk49.default.red(`Cannot read file: ${opts.file}`));
11349
+ console.error(import_chalk50.default.red(`Cannot read file: ${opts.file}`));
11043
11350
  process.exit(1);
11044
11351
  }
11045
11352
  const { teamId } = await resolveTeam(opts.team);
11046
11353
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
11047
- const spinner = (0, import_ora21.default)("Uploading spec...").start();
11354
+ const spinner = (0, import_ora22.default)("Uploading spec...").start();
11048
11355
  try {
11049
11356
  const out = await admin({
11050
11357
  method: "POST",
@@ -11068,28 +11375,28 @@ async function runSpecSet(project, opts) {
11068
11375
  function printSpecChangeReport(report, projectName) {
11069
11376
  const r = report.routes;
11070
11377
  if (r) {
11071
- for (const a of r.added) console.log(import_chalk49.default.green(` + ${a}`));
11072
- for (const d of r.removed) console.log(import_chalk49.default.red(` \u2212 ${d}`));
11073
- for (const rn of r.renamed ?? []) console.log(import_chalk49.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
11074
- for (const m of r.modified ?? []) console.log(import_chalk49.default.dim(` \xB1 ${m} (params/schema changed)`));
11378
+ for (const a of r.added) console.log(import_chalk50.default.green(` + ${a}`));
11379
+ for (const d of r.removed) console.log(import_chalk50.default.red(` \u2212 ${d}`));
11380
+ for (const rn of r.renamed ?? []) console.log(import_chalk50.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
11381
+ for (const m of r.modified ?? []) console.log(import_chalk50.default.dim(` \xB1 ${m} (params/schema changed)`));
11075
11382
  }
11076
- if (report.applied?.mcp_regenerated) console.log(import_chalk49.default.dim(" MCP catalogue regenerated."));
11077
- if (report.applied?.candidates_cleared) console.log(import_chalk49.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
11383
+ if (report.applied?.mcp_regenerated) console.log(import_chalk50.default.dim(" MCP catalogue regenerated."));
11384
+ if (report.applied?.candidates_cleared) console.log(import_chalk50.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
11078
11385
  for (const item of report.attention ?? []) {
11079
- console.log(import_chalk49.default.yellow(` \u26A0 ${item.detail}`));
11386
+ console.log(import_chalk50.default.yellow(` \u26A0 ${item.detail}`));
11080
11387
  if (item.suggestion?.carry_from && item.suggestion.carry_to) {
11081
- console.log(import_chalk49.default.dim(` Fix: apiblaze spec carry ${projectName} --method ${item.suggestion.method} --from "${item.suggestion.carry_from}" --to "${item.suggestion.carry_to}"`));
11388
+ console.log(import_chalk50.default.dim(` Fix: apiblaze spec carry ${projectName} --method ${item.suggestion.method} --from "${item.suggestion.carry_from}" --to "${item.suggestion.carry_to}"`));
11082
11389
  }
11083
11390
  }
11084
11391
  }
11085
11392
  async function runSpecCarry(project, opts) {
11086
11393
  if (!opts.method || !opts.from || !opts.to) {
11087
- console.error(import_chalk49.default.red("--method, --from and --to are required."));
11394
+ console.error(import_chalk50.default.red("--method, --from and --to are required."));
11088
11395
  process.exit(1);
11089
11396
  }
11090
11397
  const { teamId } = await resolveTeam(opts.team);
11091
11398
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
11092
- const spinner = (0, import_ora21.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
11399
+ const spinner = (0, import_ora22.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
11093
11400
  const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
11094
11401
  method: opts.method.toUpperCase(),
11095
11402
  from_resource: opts.from,
@@ -11106,13 +11413,13 @@ async function runSpecCarry(project, opts) {
11106
11413
  }
11107
11414
  async function runSpecDeleteRule(project, opts) {
11108
11415
  if (!opts.method || !opts.route) {
11109
- console.error(import_chalk49.default.red("--method and --route are required."));
11416
+ console.error(import_chalk50.default.red("--method and --route are required."));
11110
11417
  process.exit(1);
11111
11418
  }
11112
11419
  const { teamId } = await resolveTeam(opts.team);
11113
11420
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
11114
11421
  const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
11115
- const spinner = (0, import_ora21.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
11422
+ const spinner = (0, import_ora22.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
11116
11423
  const res = await agentCall(
11117
11424
  `/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
11118
11425
  "DELETE"
@@ -11126,14 +11433,14 @@ async function runSpecDeleteRule(project, opts) {
11126
11433
  }
11127
11434
 
11128
11435
  // src/commands/agent.ts
11129
- var import_chalk51 = __toESM(require("chalk"));
11130
- var import_ora22 = __toESM(require("ora"));
11436
+ var import_chalk52 = __toESM(require("chalk"));
11437
+ var import_ora23 = __toESM(require("ora"));
11131
11438
  init_auth();
11132
11439
  init_admin();
11133
11440
  init_resolve();
11134
11441
 
11135
11442
  // src/lib/tools.ts
11136
- var import_chalk50 = __toESM(require("chalk"));
11443
+ var import_chalk51 = __toESM(require("chalk"));
11137
11444
  init_admin();
11138
11445
  init_api();
11139
11446
  init_resolve();
@@ -11164,15 +11471,15 @@ var TOOLS = [
11164
11471
  const key = keys.dev ?? Object.values(keys)[0];
11165
11472
  const url = `https://${a.name}.abz.run/${version2}/dev`;
11166
11473
  const tryIt = buildTryItCurl(url, auth, key, res.tenant);
11167
- const lines = [` ${import_chalk50.default.dim("Proxy URL:")} ${import_chalk50.default.bold(url)}`];
11168
- if (res.devPortal) lines.push(` ${import_chalk50.default.dim("Dev portal:")} ${res.devPortal}`);
11474
+ const lines = [` ${import_chalk51.default.dim("Proxy URL:")} ${import_chalk51.default.bold(url)}`];
11475
+ if (res.devPortal) lines.push(` ${import_chalk51.default.dim("Dev portal:")} ${res.devPortal}`);
11169
11476
  const envs = Object.keys(keys);
11170
11477
  if (envs.length) {
11171
- lines.push("", ` ${import_chalk50.default.bold("API keys")} ${import_chalk50.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
11478
+ lines.push("", ` ${import_chalk51.default.bold("API keys")} ${import_chalk51.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
11172
11479
  const w = Math.max(...envs.map((e) => e.length));
11173
- for (const env of envs) lines.push(` ${import_chalk50.default.cyan(env.padEnd(w))} ${import_chalk50.default.green(keys[env])}`);
11480
+ for (const env of envs) lines.push(` ${import_chalk51.default.cyan(env.padEnd(w))} ${import_chalk51.default.green(keys[env])}`);
11174
11481
  }
11175
- if (tryIt) lines.push("", ` ${import_chalk50.default.dim("Try it:")}`, ` ${import_chalk50.default.cyan(tryIt)}`);
11482
+ if (tryIt) lines.push("", ` ${import_chalk51.default.dim("Try it:")}`, ` ${import_chalk51.default.cyan(tryIt)}`);
11176
11483
  return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
11177
11484
  }
11178
11485
  },
@@ -11341,23 +11648,23 @@ function truncate(value, max = 1500) {
11341
11648
  }
11342
11649
  function printCost(resp) {
11343
11650
  const line = formatBilling(resp.billing);
11344
- if (line) console.log(import_chalk51.default.magenta(` ${line}`) + import_chalk51.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
11651
+ if (line) console.log(import_chalk52.default.magenta(` ${line}`) + import_chalk52.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
11345
11652
  }
11346
11653
  async function runAgent(opts) {
11347
11654
  requireAuth();
11348
11655
  const { teamId, teamName } = await resolveTeam(opts.team);
11349
11656
  const { default: inquirer3 } = await import("inquirer");
11350
- console.log(import_chalk51.default.bold("APIblaze agent") + import_chalk51.default.dim(` \xB7 team ${teamName ?? teamId}`));
11351
- console.log(import_chalk51.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
11657
+ console.log(import_chalk52.default.bold("APIblaze agent") + import_chalk52.default.dim(` \xB7 team ${teamName ?? teamId}`));
11658
+ console.log(import_chalk52.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
11352
11659
  const history = [];
11353
11660
  while (true) {
11354
- const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk51.default.cyan("you") + " \u203A" }]);
11661
+ const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk52.default.cyan("you") + " \u203A" }]);
11355
11662
  const text = (input ?? "").trim();
11356
11663
  if (!text) continue;
11357
11664
  if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
11358
11665
  history.push({ role: "user", content: text });
11359
11666
  for (let step = 0; step < MAX_TOOL_STEPS; step++) {
11360
- const spinner = (0, import_ora22.default)({ text: "thinking...", color: "magenta" }).start();
11667
+ const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
11361
11668
  let resp;
11362
11669
  try {
11363
11670
  resp = await callAgent(history, teamId);
@@ -11365,21 +11672,21 @@ async function runAgent(opts) {
11365
11672
  } catch (err) {
11366
11673
  spinner.stop();
11367
11674
  if (err instanceof ApiError && err.status === 402) {
11368
- console.log(import_chalk51.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
11675
+ console.log(import_chalk52.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
11369
11676
  break;
11370
11677
  }
11371
11678
  throw err;
11372
11679
  }
11373
11680
  history.push({ role: "assistant", content: resp.raw });
11374
11681
  printCost(resp);
11375
- if (resp.reply) console.log(import_chalk51.default.green("agent") + " \u203A " + resp.reply);
11682
+ if (resp.reply) console.log(import_chalk52.default.green("agent") + " \u203A " + resp.reply);
11376
11683
  if (!resp.action) break;
11377
11684
  const tool = findTool(resp.action.tool);
11378
11685
  if (!tool) {
11379
11686
  history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
11380
11687
  continue;
11381
11688
  }
11382
- const runSpinner = (0, import_ora22.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
11689
+ const runSpinner = (0, import_ora23.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
11383
11690
  try {
11384
11691
  const result = await tool.run(resp.action.args, { teamId });
11385
11692
  runSpinner.succeed(`${tool.name} \u2713`);
@@ -11397,11 +11704,11 @@ async function runAgent(opts) {
11397
11704
  }
11398
11705
  renderTrace();
11399
11706
  if (step === MAX_TOOL_STEPS - 1) {
11400
- console.log(import_chalk51.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
11707
+ console.log(import_chalk52.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
11401
11708
  }
11402
11709
  }
11403
11710
  }
11404
- console.log(import_chalk51.default.dim("\nBye."));
11711
+ console.log(import_chalk52.default.dim("\nBye."));
11405
11712
  }
11406
11713
 
11407
11714
  // src/commands/config-browse.ts
@@ -11709,11 +12016,11 @@ function dig(blob, dotted) {
11709
12016
  }
11710
12017
  var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
11711
12018
  function show(v) {
11712
- if (v === void 0) return import_chalk52.default.dim("(unset)");
11713
- if (v === null) return import_chalk52.default.dim("null");
11714
- if (typeof v === "object") return import_chalk52.default.cyan(JSON.stringify(v));
11715
- if (typeof v === "boolean") return v ? import_chalk52.default.green("on") : import_chalk52.default.red("off");
11716
- return import_chalk52.default.cyan(String(v));
12019
+ if (v === void 0) return import_chalk53.default.dim("(unset)");
12020
+ if (v === null) return import_chalk53.default.dim("null");
12021
+ if (typeof v === "object") return import_chalk53.default.cyan(JSON.stringify(v));
12022
+ if (typeof v === "boolean") return v ? import_chalk53.default.green("on") : import_chalk53.default.red("off");
12023
+ return import_chalk53.default.cyan(String(v));
11717
12024
  }
11718
12025
  function parseValue(raw) {
11719
12026
  if (raw === "true") return true;
@@ -11748,7 +12055,7 @@ async function patchSetting(proj2, s, value, cfg) {
11748
12055
  if (!tenant2) throw new Error(`Could not tell which tenant ${proj2.projectName} serves \u2014 pass it via \`apiblaze config ${proj2.projectName} --tenant <slug>\` is not supported yet; run \`apiblaze projects\` and check the project has a tenant.`);
11749
12056
  body.tenant = tenant2;
11750
12057
  }
11751
- const spinner = (0, import_ora23.default)(`Set ${s.key}...`).start();
12058
+ const spinner = (0, import_ora24.default)(`Set ${s.key}...`).start();
11752
12059
  try {
11753
12060
  await admin({
11754
12061
  method: "PATCH",
@@ -11763,10 +12070,10 @@ async function patchSetting(proj2, s, value, cfg) {
11763
12070
  }
11764
12071
  }
11765
12072
  var loginFirst = (what) => {
11766
- console.log(import_chalk52.default.yellow(`
12073
+ console.log(import_chalk53.default.yellow(`
11767
12074
  Log in first to ${what}.`));
11768
- console.log(import_chalk52.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
11769
- console.log(import_chalk52.default.dim(" anonymously and want to bring it into your account.\n"));
12075
+ console.log(import_chalk53.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
12076
+ console.log(import_chalk53.default.dim(" anonymously and want to bring it into your account.\n"));
11770
12077
  };
11771
12078
  async function runConfig(project, key, value, opts) {
11772
12079
  const creds = loadCredentials();
@@ -11783,9 +12090,9 @@ async function runConfig(project, key, value, opts) {
11783
12090
  }
11784
12091
  const setting = SETTINGS.find((s) => s.key === key);
11785
12092
  if (!setting) {
11786
- console.error(import_chalk52.default.red(`Unknown setting "${key}".`));
11787
- console.error(import_chalk52.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
11788
- console.error(import_chalk52.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
12093
+ console.error(import_chalk53.default.red(`Unknown setting "${key}".`));
12094
+ console.error(import_chalk53.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
12095
+ console.error(import_chalk53.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
11789
12096
  process.exit(1);
11790
12097
  }
11791
12098
  if (value === void 0) {
@@ -11800,7 +12107,7 @@ async function pickProject(teamId) {
11800
12107
  const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
11801
12108
  const projects = await getProjects2(teamId).catch(() => []);
11802
12109
  if (!projects.length) {
11803
- console.error(import_chalk52.default.red("No projects in this team. Create one: `npx apiblaze create`."));
12110
+ console.error(import_chalk53.default.red("No projects in this team. Create one: `npx apiblaze create`."));
11804
12111
  process.exit(1);
11805
12112
  }
11806
12113
  const { default: inquirer3 } = await import("inquirer");
@@ -11808,7 +12115,7 @@ async function pickProject(teamId) {
11808
12115
  type: "list",
11809
12116
  name: "picked",
11810
12117
  message: "Which project?",
11811
- choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk52.default.dim("v" + p.apiVersion)}`, value: p }))
12118
+ choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk53.default.dim("v" + p.apiVersion)}`, value: p }))
11812
12119
  }]);
11813
12120
  return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
11814
12121
  }
@@ -11819,25 +12126,25 @@ function printAll(proj2, cfg, json) {
11819
12126
  console.log(JSON.stringify(out, null, 2));
11820
12127
  return;
11821
12128
  }
11822
- console.log(import_chalk52.default.bold(`
12129
+ console.log(import_chalk53.default.bold(`
11823
12130
  ${proj2.projectName} v${proj2.apiVersion} \u2014 settings
11824
12131
  `));
11825
12132
  for (const group2 of SETTING_GROUPS) {
11826
- console.log(import_chalk52.default.bold(group2));
12133
+ console.log(import_chalk53.default.bold(group2));
11827
12134
  for (const s of SETTINGS.filter((x) => x.group === group2)) {
11828
- console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk52.default.dim(s.desc)}`);
12135
+ console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk53.default.dim(s.desc)}`);
11829
12136
  }
11830
12137
  console.log();
11831
12138
  }
11832
- console.log(import_chalk52.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
12139
+ console.log(import_chalk53.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
11833
12140
  }
11834
12141
  async function discoveryMenu(project) {
11835
12142
  const { default: inquirer3 } = await import("inquirer");
11836
- console.log(import_chalk52.default.bold(`
12143
+ console.log(import_chalk53.default.bold(`
11837
12144
  APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11838
12145
  `));
11839
- console.log(import_chalk52.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
11840
- console.log(import_chalk52.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
12146
+ console.log(import_chalk53.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
12147
+ console.log(import_chalk53.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
11841
12148
  for (; ; ) {
11842
12149
  const { pick: pick2 } = await inquirer3.prompt([{
11843
12150
  type: "list",
@@ -11845,13 +12152,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11845
12152
  message: "Explore:",
11846
12153
  pageSize: 20,
11847
12154
  choices: [
11848
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Settings \u2014")),
12155
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
11849
12156
  ...SETTING_GROUPS.map((g) => ({
11850
- name: `${g} ${import_chalk52.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
12157
+ name: `${g} ${import_chalk53.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
11851
12158
  value: { kind: "settings", g }
11852
12159
  })),
11853
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Features \u2014")),
11854
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk52.default.dim(f.desc)}`, value: { kind: "feature", f } })),
12160
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
12161
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: "feature", f } })),
11855
12162
  new inquirer3.Separator(),
11856
12163
  { name: "Exit", value: { kind: "exit" } }
11857
12164
  ]
@@ -11860,24 +12167,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11860
12167
  if (pick2.kind === "settings") {
11861
12168
  console.log();
11862
12169
  for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
11863
- console.log(` ${import_chalk52.default.bold(s.label.padEnd(28))} ${import_chalk52.default.dim(s.desc)}`);
11864
- console.log(` ${import_chalk52.default.dim(" key: " + s.key)}`);
12170
+ console.log(` ${import_chalk53.default.bold(s.label.padEnd(28))} ${import_chalk53.default.dim(s.desc)}`);
12171
+ console.log(` ${import_chalk53.default.dim(" key: " + s.key)}`);
11865
12172
  }
11866
12173
  loginFirst("view or change these settings");
11867
12174
  } else {
11868
12175
  const f = pick2.f;
11869
12176
  console.log(`
11870
- ${import_chalk52.default.bold(f.label)} \u2014 ${f.desc}`);
12177
+ ${import_chalk53.default.bold(f.label)} \u2014 ${f.desc}`);
11871
12178
  loginFirst(`use ${f.label.toLowerCase()}`);
11872
12179
  }
11873
12180
  }
11874
12181
  }
11875
12182
  async function navigator(proj2, cfg, opts) {
11876
12183
  const { default: inquirer3 } = await import("inquirer");
11877
- console.log(import_chalk52.default.bold(`
12184
+ console.log(import_chalk53.default.bold(`
11878
12185
  ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11879
12186
  `));
11880
- console.log(import_chalk52.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
12187
+ console.log(import_chalk53.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
11881
12188
  let blob = cfg;
11882
12189
  for (; ; ) {
11883
12190
  const { pick: pick2 } = await inquirer3.prompt([{
@@ -11886,10 +12193,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11886
12193
  message: "Where to?",
11887
12194
  pageSize: 20,
11888
12195
  choices: [
11889
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Settings \u2014")),
12196
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
11890
12197
  ...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
11891
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Features \u2014")),
11892
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk52.default.dim(f.desc)}`, value: { kind: f.go } })),
12198
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
12199
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: f.go } })),
11893
12200
  new inquirer3.Separator(),
11894
12201
  { name: "Show all settings", value: { kind: "list" } },
11895
12202
  { name: "Exit", value: { kind: "exit" } }
@@ -11929,7 +12236,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11929
12236
  }
11930
12237
  }
11931
12238
  } catch (err) {
11932
- console.error(import_chalk52.default.red(` ${err instanceof Error ? err.message : String(err)}`));
12239
+ console.error(import_chalk53.default.red(` ${err instanceof Error ? err.message : String(err)}`));
11933
12240
  }
11934
12241
  }
11935
12242
  }
@@ -11943,7 +12250,7 @@ async function settingsGroup(proj2, cfg, group2) {
11943
12250
  message: group2 + ":",
11944
12251
  pageSize: 16,
11945
12252
  choices: [
11946
- ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk52.default.dim(s2.desc)}`, value: s2 })),
12253
+ ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk53.default.dim(s2.desc)}`, value: s2 })),
11947
12254
  new inquirer3.Separator(),
11948
12255
  { name: "\u2190 Back", value: null }
11949
12256
  ]
@@ -11961,7 +12268,7 @@ async function settingsGroup(proj2, cfg, group2) {
11961
12268
  } else if (s.type === "number") {
11962
12269
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
11963
12270
  if (v === "" || Number.isNaN(Number(v))) {
11964
- console.log(import_chalk52.default.yellow(" Not a number \u2014 unchanged."));
12271
+ console.log(import_chalk53.default.yellow(" Not a number \u2014 unchanged."));
11965
12272
  continue;
11966
12273
  }
11967
12274
  value = Number(v);
@@ -12042,7 +12349,7 @@ async function buildCondition(phase) {
12042
12349
  const items = [];
12043
12350
  for (; ; ) {
12044
12351
  const a = await inquirer3.prompt([
12045
- { type: "input", name: "source", message: `Condition field ${import_chalk52.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
12352
+ { type: "input", name: "source", message: `Condition field ${import_chalk53.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
12046
12353
  { type: "list", name: "operator", message: "Operator:", choices: [
12047
12354
  "eq",
12048
12355
  "neq",
@@ -12073,7 +12380,7 @@ async function buildCondition(phase) {
12073
12380
  function showCondition(cond) {
12074
12381
  if (!Array.isArray(cond) || !cond.length) return "";
12075
12382
  const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
12076
- return import_chalk52.default.dim(` when ${s}`);
12383
+ return import_chalk53.default.dim(` when ${s}`);
12077
12384
  }
12078
12385
  async function transformsMenu(proj2) {
12079
12386
  const { default: inquirer3 } = await import("inquirer");
@@ -12082,12 +12389,12 @@ async function transformsMenu(proj2) {
12082
12389
  const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
12083
12390
  const rules = out?.rules ?? [];
12084
12391
  console.log();
12085
- if (!rules.length) console.log(import_chalk52.default.dim(" No transform rules yet."));
12392
+ if (!rules.length) console.log(import_chalk53.default.dim(" No transform rules yet."));
12086
12393
  for (const r of rules) {
12087
12394
  const a = r.action ?? {};
12088
12395
  const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
12089
- const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ? import_chalk52.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
12090
- console.log(` ${r.enabled ? import_chalk52.default.green("\u25CF") : import_chalk52.default.dim("\u25CB")} ${import_chalk52.default.bold(r.name)} ${import_chalk52.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
12396
+ const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}${fns.length ? import_chalk53.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
12397
+ console.log(` ${r.enabled ? import_chalk53.default.green("\u25CF") : import_chalk53.default.dim("\u25CB")} ${import_chalk53.default.bold(r.name)} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
12091
12398
  }
12092
12399
  const { act } = await inquirer3.prompt([{
12093
12400
  type: "list",
@@ -12099,7 +12406,7 @@ async function transformsMenu(proj2) {
12099
12406
  { name: "Enable/disable a rule", value: "toggle" },
12100
12407
  { name: "Delete a rule", value: "delete" }
12101
12408
  ] : [],
12102
- { name: import_chalk52.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
12409
+ { name: import_chalk53.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
12103
12410
  { name: "\u2190 Back", value: "back" }
12104
12411
  ]
12105
12412
  }]);
@@ -12112,11 +12419,11 @@ async function transformsMenu(proj2) {
12112
12419
  }]);
12113
12420
  const body = parseValue(raw);
12114
12421
  if (!body || typeof body !== "object" || Array.isArray(body)) {
12115
- console.log(import_chalk52.default.yellow(" Not a JSON object \u2014 skipped."));
12422
+ console.log(import_chalk53.default.yellow(" Not a JSON object \u2014 skipped."));
12116
12423
  continue;
12117
12424
  }
12118
12425
  await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
12119
- console.log(import_chalk52.default.green(" Rule created."));
12426
+ console.log(import_chalk53.default.green(" Rule created."));
12120
12427
  continue;
12121
12428
  }
12122
12429
  if (act === "add") {
@@ -12132,7 +12439,7 @@ async function transformsMenu(proj2) {
12132
12439
  { name: "Remove a field", value: "remove" }
12133
12440
  ] }
12134
12441
  ]);
12135
- const fieldHint = import_chalk52.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
12442
+ const fieldHint = import_chalk53.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
12136
12443
  let action2;
12137
12444
  if (ans.kind === "hardcode") {
12138
12445
  const a = await inquirer3.prompt([
@@ -12161,7 +12468,7 @@ async function transformsMenu(proj2) {
12161
12468
  };
12162
12469
  }
12163
12470
  const condition = await buildCondition(ans.phase);
12164
- const spinner = (0, import_ora23.default)("Creating rule...").start();
12471
+ const spinner = (0, import_ora24.default)("Creating rule...").start();
12165
12472
  try {
12166
12473
  await admin({
12167
12474
  method: "POST",
@@ -12179,16 +12486,16 @@ async function transformsMenu(proj2) {
12179
12486
  type: "list",
12180
12487
  name: "rule",
12181
12488
  message: act === "toggle" ? "Which rule?" : "Delete which rule?",
12182
- choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk52.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
12489
+ choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
12183
12490
  }]);
12184
12491
  if (!rule) continue;
12185
12492
  if (act === "toggle") {
12186
12493
  const flipped = { ...rule, enabled: rule.enabled === false };
12187
12494
  await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
12188
- console.log(import_chalk52.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
12495
+ console.log(import_chalk53.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
12189
12496
  } else {
12190
12497
  await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
12191
- console.log(import_chalk52.default.green(` ${rule.name} deleted.`));
12498
+ console.log(import_chalk53.default.green(` ${rule.name} deleted.`));
12192
12499
  }
12193
12500
  }
12194
12501
  }
@@ -12200,9 +12507,9 @@ async function mappingsMenu(proj2) {
12200
12507
  const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
12201
12508
  const tables = out?.mappings ?? out?.tables ?? [];
12202
12509
  console.log();
12203
- if (!tables.length) console.log(import_chalk52.default.dim(" No mapping tables yet."));
12510
+ if (!tables.length) console.log(import_chalk53.default.dim(" No mapping tables yet."));
12204
12511
  for (const t of tables) {
12205
- console.log(` ${import_chalk52.default.bold(t.name)} ${import_chalk52.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
12512
+ console.log(` ${import_chalk53.default.bold(t.name)} ${import_chalk53.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
12206
12513
  }
12207
12514
  const { act } = await inquirer3.prompt([{
12208
12515
  type: "list",
@@ -12222,11 +12529,11 @@ async function mappingsMenu(proj2) {
12222
12529
  ]);
12223
12530
  const entries2 = parseValue(a.entries);
12224
12531
  if (!Array.isArray(entries2)) {
12225
- console.log(import_chalk52.default.yellow(" Entries must be a JSON array \u2014 not created."));
12532
+ console.log(import_chalk53.default.yellow(" Entries must be a JSON array \u2014 not created."));
12226
12533
  continue;
12227
12534
  }
12228
12535
  await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
12229
- console.log(import_chalk52.default.green(` Table "${a.name}" created.`));
12536
+ console.log(import_chalk53.default.green(` Table "${a.name}" created.`));
12230
12537
  } else {
12231
12538
  const { table } = await inquirer3.prompt([{
12232
12539
  type: "list",
@@ -12236,7 +12543,7 @@ async function mappingsMenu(proj2) {
12236
12543
  }]);
12237
12544
  if (!table) continue;
12238
12545
  await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
12239
- console.log(import_chalk52.default.green(` ${table.name} deleted.`));
12546
+ console.log(import_chalk53.default.green(` ${table.name} deleted.`));
12240
12547
  }
12241
12548
  }
12242
12549
  }
@@ -12247,14 +12554,14 @@ async function tenantsMenu(proj2, opts) {
12247
12554
  const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
12248
12555
  const tenants = out?.tenants ?? [];
12249
12556
  console.log();
12250
- if (!tenants.length) console.log(import_chalk52.default.dim(" No tenants attached (consumers use the default tenant)."));
12251
- for (const t of tenants) console.log(` ${import_chalk52.default.bold(t.tenant_name ?? t.name)} ${import_chalk52.default.dim(t.display_name ?? "")}`);
12557
+ if (!tenants.length) console.log(import_chalk53.default.dim(" No tenants attached (consumers use the default tenant)."));
12558
+ for (const t of tenants) console.log(` ${import_chalk53.default.bold(t.tenant_name ?? t.name)} ${import_chalk53.default.dim(t.display_name ?? "")}`);
12252
12559
  const { act } = await inquirer3.prompt([{
12253
12560
  type: "list",
12254
12561
  name: "act",
12255
12562
  message: "Tenants:",
12256
12563
  choices: [
12257
- { name: `Manage a tenant\u2026 ${import_chalk52.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
12564
+ { name: `Manage a tenant\u2026 ${import_chalk53.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
12258
12565
  { name: "Attach a tenant to this project", value: "attach" },
12259
12566
  ...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
12260
12567
  { name: "\u2190 Back", value: "back" }
@@ -12277,7 +12584,7 @@ async function tenantsMenu(proj2, opts) {
12277
12584
  }]);
12278
12585
  if (!t) continue;
12279
12586
  await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
12280
- console.log(import_chalk52.default.green(` Detached ${t.tenant_name ?? t.name}.`));
12587
+ console.log(import_chalk53.default.green(` Detached ${t.tenant_name ?? t.name}.`));
12281
12588
  }
12282
12589
  }
12283
12590
  }
@@ -12320,7 +12627,7 @@ async function specMenu(proj2, opts) {
12320
12627
  choices: [
12321
12628
  { name: "Print the stored spec", value: "get" },
12322
12629
  { name: "Refresh the spec from its source", value: "refresh" },
12323
- { name: import_chalk52.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
12630
+ { name: import_chalk53.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
12324
12631
  { name: "\u2190 Back", value: "back" }
12325
12632
  ]
12326
12633
  }]);
@@ -12328,7 +12635,7 @@ async function specMenu(proj2, opts) {
12328
12635
  if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
12329
12636
  else if (act === "refresh") {
12330
12637
  await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
12331
- console.log(import_chalk52.default.green(" Spec refresh triggered."));
12638
+ console.log(import_chalk53.default.green(" Spec refresh triggered."));
12332
12639
  } else await runOpenapi(proj2.projectName, proj2.apiVersion);
12333
12640
  }
12334
12641
  async function agentsMenu(proj2, opts) {
@@ -12353,8 +12660,8 @@ async function agentsMenu(proj2, opts) {
12353
12660
  }
12354
12661
 
12355
12662
  // src/commands/group.ts
12356
- var import_chalk53 = __toESM(require("chalk"));
12357
- var import_ora24 = __toESM(require("ora"));
12663
+ var import_chalk54 = __toESM(require("chalk"));
12664
+ var import_ora25 = __toESM(require("ora"));
12358
12665
  init_auth();
12359
12666
  init_admin();
12360
12667
  init_resolve();
@@ -12454,7 +12761,7 @@ async function userByRefOrCreate(t, ref) {
12454
12761
  if (!ref.includes("@")) throw e;
12455
12762
  const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
12456
12763
  if (!created?.abz_sub) throw e;
12457
- console.log(import_chalk53.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
12764
+ console.log(import_chalk54.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
12458
12765
  return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
12459
12766
  }
12460
12767
  }
@@ -12462,11 +12769,11 @@ async function runGroupList(opts) {
12462
12769
  const t = await resolveTransport(opts);
12463
12770
  const groups = await listGroups(t);
12464
12771
  if (opts.json) return void console.log(JSON.stringify(groups));
12465
- if (!groups.length) return void console.log(import_chalk53.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
12466
- console.log(import_chalk53.default.dim(`Tenant ${t.tenant}:`));
12772
+ if (!groups.length) return void console.log(import_chalk54.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
12773
+ console.log(import_chalk54.default.dim(`Tenant ${t.tenant}:`));
12467
12774
  for (const g of groups) {
12468
- const sealed = g.name === "apiblaze_admins" ? import_chalk53.default.dim(" (admins \u2014 managed by allowlist)") : "";
12469
- console.log(` ${import_chalk53.default.bold(g.name)} ${import_chalk53.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
12775
+ const sealed = g.name === "apiblaze_admins" ? import_chalk54.default.dim(" (admins \u2014 managed by allowlist)") : "";
12776
+ console.log(` ${import_chalk54.default.bold(g.name)} ${import_chalk54.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
12470
12777
  }
12471
12778
  }
12472
12779
  async function runGroupCreate(name, opts) {
@@ -12491,10 +12798,10 @@ async function runGroupCreate(name, opts) {
12491
12798
  }
12492
12799
  }
12493
12800
  }
12494
- const spinner = (0, import_ora24.default)(`Creating group ${name}...`).start();
12801
+ const spinner = (0, import_ora25.default)(`Creating group ${name}...`).start();
12495
12802
  try {
12496
12803
  await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
12497
- spinner.succeed(`Group ${import_chalk53.default.bold(name)} created in ${t.tenant}.`);
12804
+ spinner.succeed(`Group ${import_chalk54.default.bold(name)} created in ${t.tenant}.`);
12498
12805
  } catch (e) {
12499
12806
  spinner.fail("Create failed.");
12500
12807
  throw e;
@@ -12503,10 +12810,10 @@ async function runGroupCreate(name, opts) {
12503
12810
  async function runGroupDelete(name, opts) {
12504
12811
  const t = await resolveTransport(opts);
12505
12812
  const g = await groupByName(t, name);
12506
- const spinner = (0, import_ora24.default)(`Deleting group ${name}...`).start();
12813
+ const spinner = (0, import_ora25.default)(`Deleting group ${name}...`).start();
12507
12814
  try {
12508
12815
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
12509
- spinner.succeed(`Group ${import_chalk53.default.bold(name)} deleted.`);
12816
+ spinner.succeed(`Group ${import_chalk54.default.bold(name)} deleted.`);
12510
12817
  } catch (e) {
12511
12818
  spinner.fail("Delete failed.");
12512
12819
  throw e;
@@ -12525,21 +12832,21 @@ async function runGroupMembers(name, opts) {
12525
12832
  return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
12526
12833
  }
12527
12834
  const members = walkedMembers.items;
12528
- console.log(import_chalk53.default.bold(`${g.name}`) + import_chalk53.default.dim(` (${t.tenant})`));
12529
- for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk53.default.cyan("group-admin") : import_chalk53.default.dim("member")}`);
12530
- if (!members.length) console.log(import_chalk53.default.dim(" no members"));
12835
+ console.log(import_chalk54.default.bold(`${g.name}`) + import_chalk54.default.dim(` (${t.tenant})`));
12836
+ for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk54.default.cyan("group-admin") : import_chalk54.default.dim("member")}`);
12837
+ if (!members.length) console.log(import_chalk54.default.dim(" no members"));
12531
12838
  const sg = subs?.subgroups ?? [];
12532
- if (sg.length) console.log(import_chalk53.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
12839
+ if (sg.length) console.log(import_chalk54.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
12533
12840
  }
12534
12841
  async function runGroupAddUser(userRef, groupName, opts) {
12535
12842
  const t = await resolveTransport(opts);
12536
12843
  const g = await groupByName(t, groupName);
12537
12844
  const u = await userByRefOrCreate(t, userRef);
12538
12845
  const role = opts.role === "admin" ? "admin" : "member";
12539
- const spinner = (0, import_ora24.default)(`Adding ${label2(u)} to ${g.name}...`).start();
12846
+ const spinner = (0, import_ora25.default)(`Adding ${label2(u)} to ${g.name}...`).start();
12540
12847
  try {
12541
12848
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
12542
- spinner.succeed(`${import_chalk53.default.bold(label2(u))} added to ${import_chalk53.default.bold(g.name)} as ${role}.`);
12849
+ spinner.succeed(`${import_chalk54.default.bold(label2(u))} added to ${import_chalk54.default.bold(g.name)} as ${role}.`);
12543
12850
  } catch (e) {
12544
12851
  spinner.fail("Add failed.");
12545
12852
  throw e;
@@ -12548,10 +12855,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
12548
12855
  async function runGroupRemoveUser(userRef, groupName, opts) {
12549
12856
  const t = await resolveTransport(opts);
12550
12857
  const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
12551
- const spinner = (0, import_ora24.default)(`Removing ${label2(u)} from ${g.name}...`).start();
12858
+ const spinner = (0, import_ora25.default)(`Removing ${label2(u)} from ${g.name}...`).start();
12552
12859
  try {
12553
12860
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
12554
- spinner.succeed(`${import_chalk53.default.bold(label2(u))} removed from ${import_chalk53.default.bold(g.name)}.`);
12861
+ spinner.succeed(`${import_chalk54.default.bold(label2(u))} removed from ${import_chalk54.default.bold(g.name)}.`);
12555
12862
  } catch (e) {
12556
12863
  spinner.fail("Remove failed.");
12557
12864
  throw e;
@@ -12560,10 +12867,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
12560
12867
  async function runGroupAddGroup(childName, parentName, opts) {
12561
12868
  const t = await resolveTransport(opts);
12562
12869
  const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
12563
- const spinner = (0, import_ora24.default)(`Nesting ${child.name} under ${parent.name}...`).start();
12870
+ const spinner = (0, import_ora25.default)(`Nesting ${child.name} under ${parent.name}...`).start();
12564
12871
  try {
12565
12872
  await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
12566
- spinner.succeed(`${import_chalk53.default.bold(child.name)} is now a subgroup of ${import_chalk53.default.bold(parent.name)}.`);
12873
+ spinner.succeed(`${import_chalk54.default.bold(child.name)} is now a subgroup of ${import_chalk54.default.bold(parent.name)}.`);
12567
12874
  } catch (e) {
12568
12875
  spinner.fail("Nest failed.");
12569
12876
  throw e;
@@ -12572,10 +12879,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
12572
12879
  async function runGroupRemoveGroup(childName, parentName, opts) {
12573
12880
  const t = await resolveTransport(opts);
12574
12881
  const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
12575
- const spinner = (0, import_ora24.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
12882
+ const spinner = (0, import_ora25.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
12576
12883
  try {
12577
12884
  await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
12578
- spinner.succeed(`${import_chalk53.default.bold(child.name)} un-nested from ${import_chalk53.default.bold(parent.name)}.`);
12885
+ spinner.succeed(`${import_chalk54.default.bold(child.name)} un-nested from ${import_chalk54.default.bold(parent.name)}.`);
12579
12886
  } catch (e) {
12580
12887
  spinner.fail("Un-nest failed.");
12581
12888
  throw e;
@@ -12586,13 +12893,13 @@ async function runGroupUsers(opts) {
12586
12893
  const users = await listUsers(t);
12587
12894
  const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
12588
12895
  if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
12589
- console.log(import_chalk53.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
12590
- for (const u of users) console.log(` ${label2(u)} ${import_chalk53.default.dim(u.abz_sub)}`);
12591
- if (!users.length) console.log(import_chalk53.default.dim(" no users yet"));
12896
+ console.log(import_chalk54.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
12897
+ for (const u of users) console.log(` ${label2(u)} ${import_chalk54.default.dim(u.abz_sub)}`);
12898
+ if (!users.length) console.log(import_chalk54.default.dim(" no users yet"));
12592
12899
  const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
12593
12900
  if (fresh.length) {
12594
- console.log(import_chalk53.default.dim("Seen in traffic (not yet users):"));
12595
- for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk53.default.dim(o.source_label ?? "")}`);
12901
+ console.log(import_chalk54.default.dim("Seen in traffic (not yet users):"));
12902
+ for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk54.default.dim(o.source_label ?? "")}`);
12596
12903
  }
12597
12904
  }
12598
12905
 
@@ -12601,22 +12908,22 @@ init_iam();
12601
12908
  init_admins();
12602
12909
 
12603
12910
  // src/commands/preapprove.ts
12604
- var import_chalk54 = __toESM(require("chalk"));
12605
- var import_ora25 = __toESM(require("ora"));
12911
+ var import_chalk55 = __toESM(require("chalk"));
12912
+ var import_ora26 = __toESM(require("ora"));
12606
12913
  async function runPreapprove(who, opts) {
12607
12914
  const tenant2 = await resolveProducerTenant(opts.tenant);
12608
12915
  if (opts.list) {
12609
12916
  const rules = await listPreapprovalRules(tenant2);
12610
12917
  if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
12611
12918
  if (!rules.length) {
12612
- console.log(import_chalk54.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
12613
- console.log(import_chalk54.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
12919
+ console.log(import_chalk55.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
12920
+ console.log(import_chalk55.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
12614
12921
  return;
12615
12922
  }
12616
- console.log(import_chalk54.default.dim(`Pre-approved for ${import_chalk54.default.bold(tenant2)}:`));
12923
+ console.log(import_chalk55.default.dim(`Pre-approved for ${import_chalk55.default.bold(tenant2)}:`));
12617
12924
  for (const r of rules) {
12618
- const tag = r.kind === "domain" ? import_chalk54.default.cyan("@" + r.value + " (whole domain)") : r.value;
12619
- const grp = r.groups?.length ? import_chalk54.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
12925
+ const tag = r.kind === "domain" ? import_chalk55.default.cyan("@" + r.value + " (whole domain)") : r.value;
12926
+ const grp = r.groups?.length ? import_chalk55.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
12620
12927
  console.log(` ${tag}${grp}`);
12621
12928
  }
12622
12929
  return;
@@ -12625,19 +12932,19 @@ async function runPreapprove(who, opts) {
12625
12932
  throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
12626
12933
  }
12627
12934
  if (opts.remove) {
12628
- const spinner2 = (0, import_ora25.default)(`Removing ${who} from ${tenant2}\u2026`).start();
12935
+ const spinner2 = (0, import_ora26.default)(`Removing ${who} from ${tenant2}\u2026`).start();
12629
12936
  const { removed, value } = await removePreapprovalRule(tenant2, who);
12630
- if (removed) spinner2.succeed(`${import_chalk54.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
12631
- else spinner2.warn(`No pre-approval rule for ${import_chalk54.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
12937
+ if (removed) spinner2.succeed(`${import_chalk55.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
12938
+ else spinner2.warn(`No pre-approval rule for ${import_chalk55.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
12632
12939
  return;
12633
12940
  }
12634
- const spinner = (0, import_ora25.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
12941
+ const spinner = (0, import_ora26.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
12635
12942
  try {
12636
12943
  const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
12637
12944
  const what = kind === "domain" ? `Anyone @${value}` : value;
12638
- spinner.succeed(`${import_chalk54.default.bold(what)} can now sign in to ${import_chalk54.default.bold(tenant2)}.`);
12639
- if (opts.group?.length) console.log(import_chalk54.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
12640
- console.log(import_chalk54.default.dim(` See the full list: apiblaze preapprove --list`));
12945
+ spinner.succeed(`${import_chalk55.default.bold(what)} can now sign in to ${import_chalk55.default.bold(tenant2)}.`);
12946
+ if (opts.group?.length) console.log(import_chalk55.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
12947
+ console.log(import_chalk55.default.dim(` See the full list: apiblaze preapprove --list`));
12641
12948
  } catch (err) {
12642
12949
  spinner.fail("Could not add the rule.");
12643
12950
  throw err;
@@ -12645,8 +12952,8 @@ async function runPreapprove(who, opts) {
12645
12952
  }
12646
12953
 
12647
12954
  // src/commands/consumer.ts
12648
- var import_chalk55 = __toESM(require("chalk"));
12649
- var import_ora26 = __toESM(require("ora"));
12955
+ var import_chalk56 = __toESM(require("chalk"));
12956
+ var import_ora27 = __toESM(require("ora"));
12650
12957
  init_admin();
12651
12958
  init_resolve();
12652
12959
  init_caller();
@@ -12668,7 +12975,7 @@ async function consumerFetch(creds, suffix, init) {
12668
12975
  function requireConsumer2() {
12669
12976
  const c = loadConsumer();
12670
12977
  if (!c) {
12671
- console.error(import_chalk55.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
12978
+ console.error(import_chalk56.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
12672
12979
  process.exit(1);
12673
12980
  }
12674
12981
  return c;
@@ -12679,7 +12986,7 @@ async function runConsumerLogin(opts) {
12679
12986
  let clientId = opts.client;
12680
12987
  if (clientId) {
12681
12988
  if (!tenant2) {
12682
- console.error(import_chalk55.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
12989
+ console.error(import_chalk56.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
12683
12990
  process.exit(1);
12684
12991
  }
12685
12992
  } else {
@@ -12691,24 +12998,24 @@ async function runConsumerLogin(opts) {
12691
12998
  if (!picked) process.exit(1);
12692
12999
  tenant2 = picked;
12693
13000
  }
12694
- const s2 = (0, import_ora26.default)("Finding the login app...").start();
13001
+ const s2 = (0, import_ora27.default)("Finding the login app...").start();
12695
13002
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
12696
13003
  s2.stop();
12697
13004
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
12698
13005
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
12699
13006
  if (!pick2) {
12700
- console.error(import_chalk55.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
13007
+ console.error(import_chalk56.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
12701
13008
  process.exit(1);
12702
13009
  }
12703
13010
  clientId = pick2.client_id ?? pick2.clientId;
12704
13011
  }
12705
- console.log(`${import_chalk55.default.cyan("\u2192")} Logging in to ${import_chalk55.default.bold(tenant2)} as a consumer...`);
13012
+ console.log(`${import_chalk56.default.cyan("\u2192")} Logging in to ${import_chalk56.default.bold(tenant2)} as a consumer...`);
12706
13013
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
12707
13014
  console.log(`
12708
- Open: ${import_chalk55.default.underline(verificationUri)}`);
12709
- console.log(` Code: ${import_chalk55.default.bold(userCode)}
13015
+ Open: ${import_chalk56.default.underline(verificationUri)}`);
13016
+ console.log(` Code: ${import_chalk56.default.bold(userCode)}
12710
13017
  `);
12711
- console.log(import_chalk55.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
13018
+ console.log(import_chalk56.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
12712
13019
  });
12713
13020
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
12714
13021
  const creds = {
@@ -12723,7 +13030,7 @@ async function runConsumerLogin(opts) {
12723
13030
  obtainedAt: Date.now()
12724
13031
  };
12725
13032
  saveConsumer(creds);
12726
- console.log(import_chalk55.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
13033
+ console.log(import_chalk56.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
12727
13034
  }
12728
13035
  async function runConsumerTokens(opts) {
12729
13036
  const creds = requireConsumer2();
@@ -12736,28 +13043,28 @@ async function runConsumerTokens(opts) {
12736
13043
  console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
12737
13044
  return;
12738
13045
  }
12739
- console.log(`${import_chalk55.default.cyan("Consumer")} ${import_chalk55.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk55.default.bold(fresh.tenant)}
13046
+ console.log(`${import_chalk56.default.cyan("Consumer")} ${import_chalk56.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk56.default.bold(fresh.tenant)}
12740
13047
  `);
12741
- console.log(`${import_chalk55.default.bold("access_token")} ${import_chalk55.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
13048
+ console.log(`${import_chalk56.default.bold("access_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
12742
13049
  ${fresh.accessToken}
12743
13050
  `);
12744
- if (fresh.idToken) console.log(`${import_chalk55.default.bold("id_token")} ${import_chalk55.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
13051
+ if (fresh.idToken) console.log(`${import_chalk56.default.bold("id_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
12745
13052
  ${fresh.idToken}
12746
13053
  `);
12747
- if (fresh.refreshToken) console.log(`${import_chalk55.default.bold("refresh_token")}
13054
+ if (fresh.refreshToken) console.log(`${import_chalk56.default.bold("refresh_token")}
12748
13055
  ${fresh.refreshToken}
12749
13056
  `);
12750
- console.log(import_chalk55.default.dim("These are your own tokens \u2014 keep them secret."));
13057
+ console.log(import_chalk56.default.dim("These are your own tokens \u2014 keep them secret."));
12751
13058
  }
12752
13059
  async function runConsumerApikeys(opts) {
12753
13060
  const creds = requireConsumer2();
12754
13061
  const { default: inquirer3 } = await import("inquirer");
12755
- const spinner = (0, import_ora26.default)("Loading your API keys...").start();
13062
+ const spinner = (0, import_ora27.default)("Loading your API keys...").start();
12756
13063
  const list = await consumerFetch(creds, withPage("/apikeys", null));
12757
13064
  if (list.status >= 400) {
12758
13065
  spinner.stop();
12759
- console.error(import_chalk55.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
12760
- if (list.status === 401) console.error(import_chalk55.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
13066
+ console.error(import_chalk56.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
13067
+ if (list.status === 401) console.error(import_chalk56.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
12761
13068
  process.exit(1);
12762
13069
  }
12763
13070
  let walkCreds = list.creds;
@@ -12786,16 +13093,16 @@ async function runConsumerApikeys(opts) {
12786
13093
  if (opts.json) {
12787
13094
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
12788
13095
  } else if (!keys.length) {
12789
- console.log(import_chalk55.default.yellow("No API keys yet."));
13096
+ console.log(import_chalk56.default.yellow("No API keys yet."));
12790
13097
  } else {
12791
13098
  for (const k of keys) {
12792
13099
  const clear = revealMap[k.environment]?.key;
12793
- const shown = clear ? import_chalk55.default.green(clear) : import_chalk55.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
12794
- const exp = k.expires_at ? import_chalk55.default.dim(`exp ${k.expires_at}`) : import_chalk55.default.dim("no expiry");
12795
- console.log(` ${import_chalk55.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk55.default.dim(k.description ?? "")}`);
13100
+ const shown = clear ? import_chalk56.default.green(clear) : import_chalk56.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
13101
+ const exp = k.expires_at ? import_chalk56.default.dim(`exp ${k.expires_at}`) : import_chalk56.default.dim("no expiry");
13102
+ console.log(` ${import_chalk56.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk56.default.dim(k.description ?? "")}`);
12796
13103
  }
12797
13104
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
12798
- console.log(import_chalk55.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
13105
+ console.log(import_chalk56.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
12799
13106
  }
12800
13107
  }
12801
13108
  if (opts.json) return;
@@ -12809,7 +13116,7 @@ async function runConsumerApikeys(opts) {
12809
13116
  const body = { environment: answers.environment };
12810
13117
  if (answers.description) body.description = answers.description;
12811
13118
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
12812
- const s2 = (0, import_ora26.default)("Creating key...").start();
13119
+ const s2 = (0, import_ora27.default)("Creating key...").start();
12813
13120
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
12814
13121
  if (created.status >= 400) {
12815
13122
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -12817,13 +13124,13 @@ async function runConsumerApikeys(opts) {
12817
13124
  }
12818
13125
  s2.succeed("Key created.");
12819
13126
  const key = created.data?.key ?? created.data?.fullKey;
12820
- if (key) console.log(` ${import_chalk55.default.green(key)} ${import_chalk55.default.dim("(shown once \u2014 store it now)")}`);
12821
- else console.log(import_chalk55.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
13127
+ if (key) console.log(` ${import_chalk56.default.green(key)} ${import_chalk56.default.dim("(shown once \u2014 store it now)")}`);
13128
+ else console.log(import_chalk56.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
12822
13129
  }
12823
13130
 
12824
13131
  // src/commands/sidecar.ts
12825
- var import_chalk56 = __toESM(require("chalk"));
12826
- var import_ora27 = __toESM(require("ora"));
13132
+ var import_chalk57 = __toESM(require("chalk"));
13133
+ var import_ora28 = __toESM(require("ora"));
12827
13134
  var fs13 = __toESM(require("fs"));
12828
13135
  var path10 = __toESM(require("path"));
12829
13136
  init_admin();
@@ -12865,18 +13172,18 @@ function upsertEnvLocal(root, token) {
12865
13172
  }
12866
13173
  function installSidecarPackage(root) {
12867
13174
  if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
12868
- console.log(` ${import_chalk56.default.green("\u2713")} apiblaze package already installed`);
13175
+ console.log(` ${import_chalk57.default.green("\u2713")} apiblaze package already installed`);
12869
13176
  return;
12870
13177
  }
12871
13178
  const has = (f) => fs13.existsSync(path10.join(root, f));
12872
13179
  const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
12873
- const spinner = (0, import_ora27.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
13180
+ const spinner = (0, import_ora28.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
12874
13181
  try {
12875
13182
  const { execSync } = require("child_process");
12876
13183
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
12877
13184
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
12878
13185
  } catch {
12879
- spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk56.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk56.default.cyan("npm run dev")}.`);
13186
+ spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk57.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk57.default.cyan("npm run dev")}.`);
12880
13187
  }
12881
13188
  }
12882
13189
  function readEnvKey(root) {
@@ -13015,7 +13322,7 @@ async function runAnonymousInit(root, router, opts) {
13015
13322
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
13016
13323
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
13017
13324
  if (opts.newSession) clearAnonCred2();
13018
- const spinner = (0, import_ora27.default)("Setting up a sidecar (no login needed)...").start();
13325
+ const spinner = (0, import_ora28.default)("Setting up a sidecar (no login needed)...").start();
13019
13326
  let out;
13020
13327
  try {
13021
13328
  out = await sidecarInitAnonymous2();
@@ -13027,29 +13334,29 @@ async function runAnonymousInit(root, router, opts) {
13027
13334
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
13028
13335
  const envState = upsertEnvLocal(root, out.token);
13029
13336
  ensureGitignored(root);
13030
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13031
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13337
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13338
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13032
13339
  installSidecarPackage(root);
13033
13340
  let inspectorPath = null;
13034
13341
  if (!opts.noInspector) {
13035
13342
  inspectorPath = generateInspector(root, router);
13036
- if (inspectorPath) console.log(` ${import_chalk56.default.green("\u2713")} inspector at ${inspectorPath}`);
13343
+ if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
13037
13344
  }
13038
13345
  console.log("");
13039
- console.log(import_chalk56.default.bold("Done (no account needed). What happens next:"));
13040
- console.log(` 1. ${import_chalk56.default.cyan("npm run dev")} and use your app.`);
13346
+ console.log(import_chalk57.default.bold("Done (no account needed). What happens next:"));
13347
+ console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app.`);
13041
13348
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
13042
- console.log(` ${import_chalk56.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
13349
+ console.log(` ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
13043
13350
  console.log("");
13044
- console.log(import_chalk56.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
13045
- console.log(` ${import_chalk56.default.cyan("apiblaze login")} then ${import_chalk56.default.cyan("apiblaze claim")} ${import_chalk56.default.dim("(no code needed here)")}`);
13046
- console.log(import_chalk56.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
13351
+ console.log(import_chalk57.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
13352
+ console.log(` ${import_chalk57.default.cyan("apiblaze login")} then ${import_chalk57.default.cyan("apiblaze claim")} ${import_chalk57.default.dim("(no code needed here)")}`);
13353
+ console.log(import_chalk57.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
13047
13354
  }
13048
13355
  async function runSidecar(opts) {
13049
13356
  const root = path10.resolve(opts.dir ?? process.cwd());
13050
13357
  const detected2 = detectNextProject(root);
13051
13358
  if (!detected2.found) {
13052
- console.log(import_chalk56.default.yellow(`No Next.js project detected in ${root}.`));
13359
+ console.log(import_chalk57.default.yellow(`No Next.js project detected in ${root}.`));
13053
13360
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
13054
13361
  return;
13055
13362
  }
@@ -13060,10 +13367,10 @@ async function runSidecar(opts) {
13060
13367
  if (!loadCredentials()) {
13061
13368
  upsertEnvLocal(root, readEnvKey(root));
13062
13369
  ensureGitignored(root);
13063
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
13064
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13370
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
13371
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13065
13372
  installSidecarPackage(root);
13066
- console.log(import_chalk56.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
13373
+ console.log(import_chalk57.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
13067
13374
  return;
13068
13375
  }
13069
13376
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -13072,7 +13379,7 @@ async function runSidecar(opts) {
13072
13379
  const mustMint = !existingKey || opts.rotate || switchingTeam;
13073
13380
  let token = existingKey ?? "";
13074
13381
  if (mustMint) {
13075
- const spinner = (0, import_ora27.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
13382
+ const spinner = (0, import_ora28.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
13076
13383
  try {
13077
13384
  const out = await admin({
13078
13385
  method: "POST",
@@ -13086,39 +13393,39 @@ async function runSidecar(opts) {
13086
13393
  throw err;
13087
13394
  }
13088
13395
  } else {
13089
- console.log(import_chalk56.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
13396
+ console.log(import_chalk57.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
13090
13397
  }
13091
13398
  const envState = upsertEnvLocal(root, token);
13092
13399
  ensureGitignored(root);
13093
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13400
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13094
13401
  const wireState = wireInstrumentation(root);
13095
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireState}`);
13402
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireState}`);
13096
13403
  installSidecarPackage(root);
13097
13404
  let inspectorPath = null;
13098
13405
  if (!opts.noInspector) {
13099
13406
  inspectorPath = generateInspector(root, detected2.router);
13100
- if (inspectorPath) console.log(` ${import_chalk56.default.green("\u2713")} inspector at ${inspectorPath}`);
13407
+ if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
13101
13408
  }
13102
13409
  console.log("");
13103
- console.log(import_chalk56.default.bold("Done. What happens next:"));
13104
- console.log(` 1. ${import_chalk56.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
13105
- console.log(` 2. The origins your app calls appear as ${import_chalk56.default.bold("candidates")} \u2014 list them: ${import_chalk56.default.cyan("apiblaze sidecar")}`);
13106
- console.log(` 3. Approve the ones to route: ${import_chalk56.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
13410
+ console.log(import_chalk57.default.bold("Done. What happens next:"));
13411
+ console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
13412
+ console.log(` 2. The origins your app calls appear as ${import_chalk57.default.bold("candidates")} \u2014 list them: ${import_chalk57.default.cyan("apiblaze sidecar")}`);
13413
+ console.log(` 3. Approve the ones to route: ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
13107
13414
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
13108
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk56.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path10.dirname(inspectorPath)} before shipping)`);
13109
- if (switchingTeam) console.log(import_chalk56.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
13415
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk57.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path10.dirname(inspectorPath)} before shipping)`);
13416
+ if (switchingTeam) console.log(import_chalk57.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
13110
13417
  console.log("");
13111
- console.log(import_chalk56.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
13112
- console.log(import_chalk56.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
13113
- console.log(import_chalk56.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
13418
+ console.log(import_chalk57.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
13419
+ console.log(import_chalk57.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
13420
+ console.log(import_chalk57.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
13114
13421
  console.log("");
13115
- console.log(import_chalk56.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
13116
- console.log(import_chalk56.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
13422
+ console.log(import_chalk57.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
13423
+ console.log(import_chalk57.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
13117
13424
  }
13118
13425
 
13119
13426
  // src/commands/origins.ts
13120
- var import_chalk57 = __toESM(require("chalk"));
13121
- var import_ora28 = __toESM(require("ora"));
13427
+ var import_chalk58 = __toESM(require("chalk"));
13428
+ var import_ora29 = __toESM(require("ora"));
13122
13429
  init_admin();
13123
13430
  init_resolve();
13124
13431
  init_auth();
@@ -13128,7 +13435,7 @@ async function runOriginsList(opts) {
13128
13435
  if (!loadCredentials()) {
13129
13436
  const cred = loadAnonCred();
13130
13437
  if (!cred) {
13131
- console.log(import_chalk57.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
13438
+ console.log(import_chalk58.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
13132
13439
  return;
13133
13440
  }
13134
13441
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -13146,30 +13453,30 @@ async function runOriginsList(opts) {
13146
13453
  }
13147
13454
  const routed = out.routed ?? [];
13148
13455
  const candidates = out.candidates ?? [];
13149
- console.log(import_chalk57.default.bold(`
13456
+ console.log(import_chalk58.default.bold(`
13150
13457
  Routed through APIblaze (${routed.length})`));
13151
- if (!routed.length) console.log(import_chalk57.default.dim(" none yet"));
13152
- for (const r of routed) console.log(` ${import_chalk57.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk57.default.dim(`\u2192 ${r.project_id}`)}`);
13153
- console.log(import_chalk57.default.bold(`
13458
+ if (!routed.length) console.log(import_chalk58.default.dim(" none yet"));
13459
+ for (const r of routed) console.log(` ${import_chalk58.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk58.default.dim(`\u2192 ${r.project_id}`)}`);
13460
+ console.log(import_chalk58.default.bold(`
13154
13461
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
13155
- if (!candidates.length) console.log(import_chalk57.default.dim(" none \u2014 run your app to discover the origins it calls"));
13462
+ if (!candidates.length) console.log(import_chalk58.default.dim(" none \u2014 run your app to discover the origins it calls"));
13156
13463
  for (const c of candidates) {
13157
- console.log(` ${import_chalk57.default.yellow("\u25CB")} ${c.origin} ${import_chalk57.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
13464
+ console.log(` ${import_chalk58.default.yellow("\u25CB")} ${c.origin} ${import_chalk58.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
13158
13465
  }
13159
13466
  if (candidates.length) {
13160
- console.log(import_chalk57.default.dim(`
13467
+ console.log(import_chalk58.default.dim(`
13161
13468
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
13162
- console.log(import_chalk57.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
13469
+ console.log(import_chalk58.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
13163
13470
  }
13164
13471
  }
13165
13472
  async function runOriginsApprove(origin, opts) {
13166
13473
  if (!loadCredentials()) {
13167
13474
  const cred = loadAnonCred();
13168
13475
  if (!cred) {
13169
- console.error(import_chalk57.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
13476
+ console.error(import_chalk58.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
13170
13477
  process.exit(1);
13171
13478
  }
13172
- const spinner2 = (0, import_ora28.default)(`Approving ${origin} (anonymous)...`).start();
13479
+ const spinner2 = (0, import_ora29.default)(`Approving ${origin} (anonymous)...`).start();
13173
13480
  try {
13174
13481
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
13175
13482
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -13180,7 +13487,7 @@ async function runOriginsApprove(origin, opts) {
13180
13487
  return;
13181
13488
  }
13182
13489
  const { teamId } = await resolveTeam(opts.team);
13183
- const spinner = (0, import_ora28.default)(`Approving ${origin}...`).start();
13490
+ const spinner = (0, import_ora29.default)(`Approving ${origin}...`).start();
13184
13491
  try {
13185
13492
  const out = await admin({
13186
13493
  method: "POST",
@@ -13197,7 +13504,7 @@ async function runOriginsApprove(origin, opts) {
13197
13504
  }
13198
13505
  async function runOriginsDeny(origin, opts) {
13199
13506
  const { teamId } = await resolveTeam(opts.team);
13200
- const spinner = (0, import_ora28.default)(`Dismissing ${origin}...`).start();
13507
+ const spinner = (0, import_ora29.default)(`Dismissing ${origin}...`).start();
13201
13508
  try {
13202
13509
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
13203
13510
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -13208,7 +13515,7 @@ async function runOriginsDeny(origin, opts) {
13208
13515
  }
13209
13516
  async function runOriginsRemove(origin, opts) {
13210
13517
  const { teamId } = await resolveTeam(opts.team);
13211
- const spinner = (0, import_ora28.default)(`Removing the proxy for ${origin}...`).start();
13518
+ const spinner = (0, import_ora29.default)(`Removing the proxy for ${origin}...`).start();
13212
13519
  try {
13213
13520
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
13214
13521
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -13219,7 +13526,7 @@ async function runOriginsRemove(origin, opts) {
13219
13526
  }
13220
13527
 
13221
13528
  // src/commands/op.ts
13222
- var import_chalk62 = __toESM(require("chalk"));
13529
+ var import_chalk63 = __toESM(require("chalk"));
13223
13530
  init_auth();
13224
13531
 
13225
13532
  // src/lib/op-call.ts
@@ -13248,7 +13555,7 @@ async function opCall(call2) {
13248
13555
  }
13249
13556
 
13250
13557
  // src/commands/op-billing.ts
13251
- var import_chalk58 = __toESM(require("chalk"));
13558
+ var import_chalk59 = __toESM(require("chalk"));
13252
13559
  init_admin();
13253
13560
  var SANDBOX = {
13254
13561
  teamId: "team_1782844865835_zujrf",
@@ -13307,7 +13614,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
13307
13614
  }
13308
13615
  function printDoors(data) {
13309
13616
  const checks = [];
13310
- console.log(import_chalk58.default.bold("\n Doors \u2014 is every way in metered?\n"));
13617
+ console.log(import_chalk59.default.bold("\n Doors \u2014 is every way in metered?\n"));
13311
13618
  const doors = data?.doors ?? [];
13312
13619
  const metered = doors.filter((d) => d.verdict === "metered");
13313
13620
  const allowed = doors.filter((d) => d.verdict === "allowed-free");
@@ -13316,29 +13623,29 @@ function printDoors(data) {
13316
13623
  const errs = data?.errors ?? [];
13317
13624
  const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
13318
13625
  const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
13319
- console.log(import_chalk58.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
13320
- for (const d of metered) console.log(import_chalk58.default.green(` \u2713 ${d.pattern}`));
13321
- console.log(import_chalk58.default.dim(`
13626
+ console.log(import_chalk59.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
13627
+ for (const d of metered) console.log(import_chalk59.default.green(` \u2713 ${d.pattern}`));
13628
+ console.log(import_chalk59.default.dim(`
13322
13629
  ${allowed.length} route(s) free ON PURPOSE`));
13323
13630
  for (const d of allowed) {
13324
- console.log(import_chalk58.default.cyan(` \u2022 ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13325
- console.log(import_chalk58.default.dim(` ${d.why}`));
13631
+ console.log(import_chalk59.default.cyan(` \u2022 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13632
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13326
13633
  }
13327
13634
  if (known.length) {
13328
- console.log(import_chalk58.default.yellow(`
13635
+ console.log(import_chalk59.default.yellow(`
13329
13636
  ${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
13330
13637
  for (const d of known) {
13331
- console.log(import_chalk58.default.yellow(` ! ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13332
- console.log(import_chalk58.default.dim(` ${d.why}`));
13638
+ console.log(import_chalk59.default.yellow(` ! ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13639
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13333
13640
  }
13334
13641
  checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
13335
13642
  }
13336
13643
  if (stray.length) {
13337
- console.log(import_chalk58.default.red(`
13644
+ console.log(import_chalk59.default.red(`
13338
13645
  ${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
13339
13646
  for (const d of stray) {
13340
- console.log(import_chalk58.default.red(` \u2717 ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13341
- console.log(import_chalk58.default.dim(` ${d.why}`));
13647
+ console.log(import_chalk59.default.red(` \u2717 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13648
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13342
13649
  }
13343
13650
  checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
13344
13651
  } else if (routeAuditBroke) {
@@ -13349,22 +13656,22 @@ function printDoors(data) {
13349
13656
  const wd = data?.workers_dev ?? {};
13350
13657
  const open = wd.enabled ?? [];
13351
13658
  if (open.length) {
13352
- console.log(import_chalk58.default.red(`
13659
+ console.log(import_chalk59.default.red(`
13353
13660
  ${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
13354
- for (const s of open) console.log(import_chalk58.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk58.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
13355
- console.log(import_chalk58.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
13661
+ for (const s of open) console.log(import_chalk59.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk59.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
13662
+ console.log(import_chalk59.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
13356
13663
  checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
13357
13664
  } else if (devAuditBroke || !wd.total) {
13358
13665
  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." });
13359
13666
  } else {
13360
- console.log(import_chalk58.default.green(`
13667
+ console.log(import_chalk59.default.green(`
13361
13668
  \u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
13362
13669
  checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
13363
13670
  }
13364
- if (data?.how_to_fix) console.log(import_chalk58.default.yellow(`
13365
- ${data.reason}`) + import_chalk58.default.dim(`
13671
+ if (data?.how_to_fix) console.log(import_chalk59.default.yellow(`
13672
+ ${data.reason}`) + import_chalk59.default.dim(`
13366
13673
  ${data.how_to_fix}`));
13367
- else for (const e of errs) console.log(import_chalk58.default.red(`
13674
+ else for (const e of errs) console.log(import_chalk59.default.red(`
13368
13675
  audit error: ${e}`));
13369
13676
  if (errs.length) {
13370
13677
  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` });
@@ -13374,9 +13681,9 @@ function printDoors(data) {
13374
13681
  async function runMeter(readLedger, opts) {
13375
13682
  const checks = [];
13376
13683
  const N = Math.max(1, Math.min(10, opts.count ?? 3));
13377
- console.log(import_chalk58.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
13684
+ console.log(import_chalk59.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
13378
13685
  const snap = await readLedger();
13379
- console.log(import_chalk58.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
13686
+ console.log(import_chalk59.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
13380
13687
  `));
13381
13688
  const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
13382
13689
  const headers = opts.key ? { "X-API-Key": opts.key } : {};
@@ -13458,27 +13765,27 @@ async function runMeter(readLedger, opts) {
13458
13765
  return checks;
13459
13766
  }
13460
13767
  function printChecks(checks) {
13461
- console.log(import_chalk58.default.bold("\n Results\n"));
13462
- const mark = { PASS: import_chalk58.default.green(" PASS"), FAIL: import_chalk58.default.red(" FAIL"), SKIP: import_chalk58.default.dim(" SKIP"), KNOWN: import_chalk58.default.yellow(" KNOWN") };
13768
+ console.log(import_chalk59.default.bold("\n Results\n"));
13769
+ const mark = { PASS: import_chalk59.default.green(" PASS"), FAIL: import_chalk59.default.red(" FAIL"), SKIP: import_chalk59.default.dim(" SKIP"), KNOWN: import_chalk59.default.yellow(" KNOWN") };
13463
13770
  for (const ch of checks) {
13464
- console.log(` ${mark[ch.status]} ${import_chalk58.default.bold(ch.name)}`);
13465
- console.log(import_chalk58.default.dim(` ${ch.detail}`));
13771
+ console.log(` ${mark[ch.status]} ${import_chalk59.default.bold(ch.name)}`);
13772
+ console.log(import_chalk59.default.dim(` ${ch.detail}`));
13466
13773
  }
13467
13774
  const fails = checks.filter((c) => c.status === "FAIL").length;
13468
13775
  const skips = checks.filter((c) => c.status === "SKIP").length;
13469
13776
  const known = checks.filter((c) => c.status === "KNOWN").length;
13470
13777
  console.log("");
13471
13778
  const passes = checks.filter((c) => c.status === "PASS").length;
13472
- if (fails) console.log(import_chalk58.default.red(` ${fails} check(s) FAILED.`));
13473
- else if (passes) console.log(import_chalk58.default.green(` ${passes} check(s) passed, 0 failed.`));
13474
- else console.log(import_chalk58.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
13475
- if (known) console.log(import_chalk58.default.yellow(` ${known} known-open issue(s) still outstanding.`));
13476
- if (skips) console.log(import_chalk58.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
13779
+ if (fails) console.log(import_chalk59.default.red(` ${fails} check(s) FAILED.`));
13780
+ else if (passes) console.log(import_chalk59.default.green(` ${passes} check(s) passed, 0 failed.`));
13781
+ else console.log(import_chalk59.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
13782
+ if (known) console.log(import_chalk59.default.yellow(` ${known} known-open issue(s) still outstanding.`));
13783
+ if (skips) console.log(import_chalk59.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
13477
13784
  console.log("");
13478
13785
  }
13479
13786
 
13480
13787
  // src/commands/op-test.ts
13481
- var import_chalk59 = __toESM(require("chalk"));
13788
+ var import_chalk60 = __toESM(require("chalk"));
13482
13789
  init_auth();
13483
13790
  init_types();
13484
13791
  var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
@@ -13503,10 +13810,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
13503
13810
  try {
13504
13811
  const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
13505
13812
  const first = r.results?.[0]?.detail ?? r.error ?? "";
13506
- log(import_chalk59.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
13813
+ log(import_chalk60.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
13507
13814
  return r.ok;
13508
13815
  } catch (e) {
13509
- log(import_chalk59.default.dim(`teardown: sandbox key scrub skipped (${e instanceof Error ? e.message : String(e)}) \u2014 leftovers expire within 14 days; scripts/purge-test-keys.mjs reaps them`));
13816
+ log(import_chalk60.default.dim(`teardown: sandbox key scrub skipped (${e instanceof Error ? e.message : String(e)}) \u2014 leftovers expire within 14 days; scripts/purge-test-keys.mjs reaps them`));
13510
13817
  return false;
13511
13818
  }
13512
13819
  }
@@ -13528,23 +13835,23 @@ async function runOpTest(o = {}) {
13528
13835
  const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
13529
13836
  const total = specs.length + guts.length;
13530
13837
  if (total === 0) {
13531
- log(import_chalk59.default.yellow("nothing matched"));
13838
+ log(import_chalk60.default.yellow("nothing matched"));
13532
13839
  process.exitCode = 2;
13533
13840
  return;
13534
13841
  }
13535
- log(import_chalk59.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
13842
+ log(import_chalk60.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
13536
13843
  const results = [];
13537
13844
  let failed = 0;
13538
13845
  const started = Date.now();
13539
13846
  const record = (r) => {
13540
13847
  results.push(r);
13541
13848
  if (!r.ok) failed += 1;
13542
- const mark = r.ok ? import_chalk59.default.green("PASS") : import_chalk59.default.red("FAIL");
13849
+ const mark = r.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
13543
13850
  log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
13544
13851
  if (!r.ok) {
13545
- if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk59.default.red(` \u2717 ${c.name}: ${c.detail}`));
13546
- else if (r.error) log(import_chalk59.default.red(` \u2717 ${r.error}`));
13547
- else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(import_chalk59.default.red(` \u2717 ${x.name ?? ""}: ${x.detail ?? JSON.stringify(x)}`));
13852
+ if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk60.default.red(` \u2717 ${c.name}: ${c.detail}`));
13853
+ else if (r.error) log(import_chalk60.default.red(` \u2717 ${r.error}`));
13854
+ else for (const x of r.results.filter((x2) => x2 && x2.ok === false)) log(import_chalk60.default.red(` \u2717 ${x.name ?? ""}: ${x.detail ?? JSON.stringify(x)}`));
13548
13855
  }
13549
13856
  };
13550
13857
  try {
@@ -13575,13 +13882,13 @@ async function runOpTest(o = {}) {
13575
13882
  }
13576
13883
  const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
13577
13884
  log("");
13578
- log(failed ? import_chalk59.default.red(`${summary.passed}/${summary.ran} passed, ${failed} failed (${ms(summary.duration_ms)})`) : import_chalk59.default.green(`${summary.passed}/${summary.ran} passed (${ms(summary.duration_ms)})`));
13885
+ log(failed ? import_chalk60.default.red(`${summary.passed}/${summary.ran} passed, ${failed} failed (${ms(summary.duration_ms)})`) : import_chalk60.default.green(`${summary.passed}/${summary.ran} passed (${ms(summary.duration_ms)})`));
13579
13886
  if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
13580
13887
  if (failed) process.exitCode = 1;
13581
13888
  }
13582
13889
 
13583
13890
  // src/commands/op-consumer.ts
13584
- var import_chalk60 = __toESM(require("chalk"));
13891
+ var import_chalk61 = __toESM(require("chalk"));
13585
13892
  var SANDBOX_TENANT = "portalteststenant";
13586
13893
  var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
13587
13894
  function proxyUrlFor(spec2, path11 = "/get") {
@@ -13623,9 +13930,9 @@ async function runOpConsumer(o = {}) {
13623
13930
  const creds = loadConsumer();
13624
13931
  const done = (r) => {
13625
13932
  const out = { kind: "consumer", id: "consumer-door", label: "Consumer door: the stored consumer token opens its tenant's OAuth-door proxy", duration_ms: Date.now() - t0, ...r };
13626
- const mark = out.skipped ? import_chalk60.default.yellow("SKIP") : out.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
13933
+ const mark = out.skipped ? import_chalk61.default.yellow("SKIP") : out.ok ? import_chalk61.default.green("PASS") : import_chalk61.default.red("FAIL");
13627
13934
  log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
13628
- for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ? import_chalk60.default.dim : import_chalk60.default.red)(` ${c.ok ? "ok " : "\u2717"} ${c.name}: ${c.detail}`));
13935
+ for (const c of out.cases) if (!c.ok || out.skipped) log2((c.ok ? import_chalk61.default.dim : import_chalk61.default.red)(` ${c.ok ? "ok " : "\u2717"} ${c.name}: ${c.detail}`));
13629
13936
  if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
13630
13937
  if (!out.ok && !out.skipped) process.exitCode = 1;
13631
13938
  if (out.skipped) process.exitCode = 2;
@@ -13665,7 +13972,7 @@ async function runOpConsumer(o = {}) {
13665
13972
  }
13666
13973
 
13667
13974
  // src/commands/op-perf.ts
13668
- var import_chalk61 = __toESM(require("chalk"));
13975
+ var import_chalk62 = __toESM(require("chalk"));
13669
13976
  var RPS_DEFAULT = 100;
13670
13977
  var RPS_CAP = 300;
13671
13978
  var SECONDS_DEFAULT = 60;
@@ -13849,10 +14156,10 @@ async function readGateway(proxy, sent, signal) {
13849
14156
  }
13850
14157
  const left = deadline - Date.now();
13851
14158
  if (left <= 0) {
13852
- log3(import_chalk61.default.yellow(` gateway: not available \u2014 Analytics Engine had ${lastSeen}/${sent} rows for ${proxy} after ${GATEWAY_WAIT_MS / 1e3}s${lastErr ? ` (last error: ${lastErr})` : ""}`));
14159
+ log3(import_chalk62.default.yellow(` gateway: not available \u2014 Analytics Engine had ${lastSeen}/${sent} rows for ${proxy} after ${GATEWAY_WAIT_MS / 1e3}s${lastErr ? ` (last error: ${lastErr})` : ""}`));
13853
14160
  return null;
13854
14161
  }
13855
- log3(import_chalk61.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
14162
+ log3(import_chalk62.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
13856
14163
  await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
13857
14164
  }
13858
14165
  }
@@ -13861,7 +14168,7 @@ async function runOpPerf(o = {}) {
13861
14168
  const thresholds = thresholdsFrom(o);
13862
14169
  const ctrl = new AbortController();
13863
14170
  const onSigint = () => {
13864
- log3(import_chalk61.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
14171
+ log3(import_chalk62.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
13865
14172
  ctrl.abort();
13866
14173
  };
13867
14174
  process.once("SIGINT", onSigint);
@@ -13870,7 +14177,7 @@ async function runOpPerf(o = {}) {
13870
14177
  if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
13871
14178
  fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
13872
14179
  } else {
13873
- log3(import_chalk61.default.dim("provisioning the perf fixture through the runner\u2026"));
14180
+ log3(import_chalk62.default.dim("provisioning the perf fixture through the runner\u2026"));
13874
14181
  const f = await runnerCall({ action: "perf-fixture" }, 120);
13875
14182
  if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
13876
14183
  fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
@@ -13878,7 +14185,7 @@ async function runOpPerf(o = {}) {
13878
14185
  try {
13879
14186
  const total = Math.round(rps * seconds);
13880
14187
  const intervalMs = 1e3 / rps;
13881
- log3(import_chalk61.default.bold(`apiblaze op test --perf \u2014 ${rps} rps \xD7 ${seconds}s (${total} requests) against ${fixture.url}${fixture.team ? ` (sandbox ${fixture.team})` : ""}`));
14188
+ log3(import_chalk62.default.bold(`apiblaze op test --perf \u2014 ${rps} rps \xD7 ${seconds}s (${total} requests) against ${fixture.url}${fixture.team ? ` (sandbox ${fixture.team})` : ""}`));
13882
14189
  const samples = [];
13883
14190
  const inflight = /* @__PURE__ */ new Set();
13884
14191
  let fired = 0;
@@ -13886,7 +14193,7 @@ async function runOpPerf(o = {}) {
13886
14193
  const progress = setInterval(() => {
13887
14194
  const m = summarize(samples, Date.now() - t0);
13888
14195
  const secs = (Date.now() - t0) / 1e3;
13889
- log3(import_chalk61.default.dim(` ${Math.round(secs)}s sent ${fired} ok ${m.ok} err ${m.errors} p50 ${fmtMs(m.p50)} p95 ${fmtMs(m.p95)} (${(fired / secs).toFixed(1)} rps)`));
14196
+ log3(import_chalk62.default.dim(` ${Math.round(secs)}s sent ${fired} ok ${m.ok} err ${m.errors} p50 ${fmtMs(m.p50)} p95 ${fmtMs(m.p95)} (${(fired / secs).toFixed(1)} rps)`));
13890
14197
  }, PROGRESS_EVERY_MS);
13891
14198
  const { sent, elapsedMs } = await schedule({
13892
14199
  total,
@@ -13900,21 +14207,21 @@ async function runOpPerf(o = {}) {
13900
14207
  }
13901
14208
  });
13902
14209
  clearInterval(progress);
13903
- if (inflight.size) log3(import_chalk61.default.dim(` load sent; waiting for ${inflight.size} in-flight response(s) (\u2264 ${REQUEST_TIMEOUT_MS / 1e3}s)\u2026`));
14210
+ if (inflight.size) log3(import_chalk62.default.dim(` load sent; waiting for ${inflight.size} in-flight response(s) (\u2264 ${REQUEST_TIMEOUT_MS / 1e3}s)\u2026`));
13904
14211
  await Promise.all([...inflight]);
13905
14212
  const client = summarize(samples, elapsedMs);
13906
14213
  log3("");
13907
- log3(` ${import_chalk61.default.bold("client ")} p50 ${fmtMs(client.p50).padStart(7)} p95 ${fmtMs(client.p95).padStart(7)} p99 ${fmtMs(client.p99).padStart(7)} errors ${client.error_rate_pct.toFixed(2)}% (${client.errors}/${client.sent}: ${client.non2xx} non-2xx, ${client.timeouts} timeouts, ${client.network} network) achieved ${client.rps_achieved} rps of ${rps}`);
14214
+ log3(` ${import_chalk62.default.bold("client ")} p50 ${fmtMs(client.p50).padStart(7)} p95 ${fmtMs(client.p95).padStart(7)} p99 ${fmtMs(client.p99).padStart(7)} errors ${client.error_rate_pct.toFixed(2)}% (${client.errors}/${client.sent}: ${client.non2xx} non-2xx, ${client.timeouts} timeouts, ${client.network} network) achieved ${client.rps_achieved} rps of ${rps}`);
13908
14215
  const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
13909
14216
  if (gateway) {
13910
14217
  const extras = [
13911
14218
  gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
13912
14219
  gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
13913
14220
  ].filter(Boolean).join(" ");
13914
- log3(` ${import_chalk61.default.bold("gateway")} p50 ${fmtMs(gateway.p50).padStart(7)} p95 ${fmtMs(gateway.p95).padStart(7)}${gateway.p99 !== void 0 ? ` p99 ${fmtMs(gateway.p99).padStart(7)}` : ""} ${import_chalk61.default.dim(`(${gateway.requests} ledger rows${extras ? `, ${extras}` : ""})`)}`);
13915
- for (const b of gateway.blame ?? []) log3(import_chalk61.default.dim(` ${(b.share * 100).toFixed(1).padStart(5)}% p95 ${fmtMs(b.p95_ms).padStart(6)} ${b.feature} \u2192 ${b.dep}`));
14221
+ log3(` ${import_chalk62.default.bold("gateway")} p50 ${fmtMs(gateway.p50).padStart(7)} p95 ${fmtMs(gateway.p95).padStart(7)}${gateway.p99 !== void 0 ? ` p99 ${fmtMs(gateway.p99).padStart(7)}` : ""} ${import_chalk62.default.dim(`(${gateway.requests} ledger rows${extras ? `, ${extras}` : ""})`)}`);
14222
+ for (const b of gateway.blame ?? []) log3(import_chalk62.default.dim(` ${(b.share * 100).toFixed(1).padStart(5)}% p95 ${fmtMs(b.p95_ms).padStart(6)} ${b.feature} \u2192 ${b.dep}`));
13916
14223
  } else {
13917
- log3(` ${import_chalk61.default.bold("gateway")} ${import_chalk61.default.yellow("not available")} ${import_chalk61.default.dim("(gateway thresholds not evaluated)")}`);
14224
+ log3(` ${import_chalk62.default.bold("gateway")} ${import_chalk62.default.yellow("not available")} ${import_chalk62.default.dim("(gateway thresholds not evaluated)")}`);
13918
14225
  }
13919
14226
  const breaches = evaluate(client, gateway, thresholds);
13920
14227
  const aborted = ctrl.signal.aborted;
@@ -13922,7 +14229,7 @@ async function runOpPerf(o = {}) {
13922
14229
  log3("");
13923
14230
  const row = (metric, value, limit, unit) => {
13924
14231
  const hit = breaches.find((b) => b.metric === metric);
13925
- log3(`[${hit ? import_chalk61.default.red("FAIL") : import_chalk61.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
14232
+ log3(`[${hit ? import_chalk62.default.red("FAIL") : import_chalk62.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
13926
14233
  };
13927
14234
  row("client_p50", client.p50, thresholds.p50_ms, "ms");
13928
14235
  row("client_p95", client.p95, thresholds.p95_ms, "ms");
@@ -13931,12 +14238,12 @@ async function runOpPerf(o = {}) {
13931
14238
  row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
13932
14239
  row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
13933
14240
  } else {
13934
- log3(`[${import_chalk61.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
14241
+ log3(`[${import_chalk62.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
13935
14242
  }
13936
14243
  log3("");
13937
- if (aborted) log3(import_chalk61.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
13938
- else if (ok) log3(import_chalk61.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
13939
- else log3(import_chalk61.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
14244
+ if (aborted) log3(import_chalk62.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
14245
+ else if (ok) log3(import_chalk62.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
14246
+ else log3(import_chalk62.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
13940
14247
  if (o.json) {
13941
14248
  const doc = {
13942
14249
  kind: "perf",
@@ -13974,9 +14281,9 @@ async function runOpPerf(o = {}) {
13974
14281
  if (fixture.provisioned) {
13975
14282
  try {
13976
14283
  await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
13977
- log3(import_chalk61.default.dim(`fixture ${fixture.proxy} torn down`));
14284
+ log3(import_chalk62.default.dim(`fixture ${fixture.proxy} torn down`));
13978
14285
  } catch (e) {
13979
- log3(import_chalk61.default.red(`teardown of ${fixture.proxy} failed: ${e instanceof Error ? e.message : String(e)} \u2014 run \`apiblaze op residue\` / clean the sandbox`));
14286
+ log3(import_chalk62.default.red(`teardown of ${fixture.proxy} failed: ${e instanceof Error ? e.message : String(e)} \u2014 run \`apiblaze op residue\` / clean the sandbox`));
13980
14287
  process.exitCode = 1;
13981
14288
  }
13982
14289
  await scrubSandboxKeys(120);
@@ -14010,93 +14317,93 @@ var OP_COMMANDS = [
14010
14317
  function renderOpCommands() {
14011
14318
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
14012
14319
  const lines = OP_COMMANDS.map((c) => {
14013
- const left = ` ${import_chalk62.default.cyan(`apiblaze ${c.cmd}`)}`;
14320
+ const left = ` ${import_chalk63.default.cyan(`apiblaze ${c.cmd}`)}`;
14014
14321
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
14015
- return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk62.default.dim(`(${c.extra})`) : ""}`;
14322
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk63.default.dim(`(${c.extra})`) : ""}`;
14016
14323
  });
14017
14324
  return [
14018
- import_chalk62.default.bold("Operator commands"),
14325
+ import_chalk63.default.bold("Operator commands"),
14019
14326
  ...lines,
14020
14327
  "",
14021
- import_chalk62.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
14022
- import_chalk62.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
14023
- import_chalk62.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
14328
+ import_chalk63.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
14329
+ import_chalk63.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
14330
+ import_chalk63.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
14024
14331
  "",
14025
- import_chalk62.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
14026
- import_chalk62.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
14027
- import_chalk62.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
14332
+ import_chalk63.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
14333
+ import_chalk63.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
14334
+ import_chalk63.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
14028
14335
  ].join("\n");
14029
14336
  }
14030
14337
  function printResidue(report, applied) {
14031
14338
  const up = report?.upstash ?? {};
14032
14339
  const fga = report?.fga ?? {};
14033
14340
  const ghosts = report?.ghosts ?? {};
14034
- console.log(import_chalk62.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
14035
- console.log(import_chalk62.default.bold("\n Upstash"));
14341
+ console.log(import_chalk63.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
14342
+ console.log(import_chalk63.default.bold("\n Upstash"));
14036
14343
  const orphans = up.orphans ?? [];
14037
- if (orphans.length === 0) console.log(import_chalk62.default.green(" no orphaned keys"));
14038
- for (const o of orphans) console.log(` ${import_chalk62.default.yellow(o.key)} ${import_chalk62.default.dim(`\u2014 ${o.reason}`)}`);
14039
- console.log(import_chalk62.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
14344
+ if (orphans.length === 0) console.log(import_chalk63.default.green(" no orphaned keys"));
14345
+ for (const o of orphans) console.log(` ${import_chalk63.default.yellow(o.key)} ${import_chalk63.default.dim(`\u2014 ${o.reason}`)}`);
14346
+ console.log(import_chalk63.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
14040
14347
  if (up.anon_wallet_detail) {
14041
14348
  const d = up.anon_wallet_detail;
14042
- console.log(import_chalk62.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)"}`));
14349
+ console.log(import_chalk63.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)"}`));
14043
14350
  }
14044
14351
  if (up.keyspace_census) {
14045
14352
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
14046
- console.log(import_chalk62.default.dim(` keyspace: ${census}`));
14353
+ console.log(import_chalk63.default.dim(` keyspace: ${census}`));
14047
14354
  }
14048
- if (up.unknown?.length) console.log(import_chalk62.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
14049
- if (applied) console.log(` ${import_chalk62.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
14050
- for (const e of up.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
14051
- console.log(import_chalk62.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
14355
+ if (up.unknown?.length) console.log(import_chalk63.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
14356
+ if (applied) console.log(` ${import_chalk63.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
14357
+ for (const e of up.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14358
+ console.log(import_chalk63.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
14052
14359
  if (applied) {
14053
14360
  const swept = fga?.swept ?? [];
14054
- if (swept.length === 0) console.log(import_chalk62.default.green(" no orphaned stores"));
14361
+ if (swept.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
14055
14362
  for (const s of swept) {
14056
14363
  console.log(
14057
- ` ${import_chalk62.default.yellow(s.store_id)} ${import_chalk62.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
14364
+ ` ${import_chalk63.default.yellow(s.store_id)} ${import_chalk63.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
14058
14365
  );
14059
14366
  }
14060
- if (fga?.remaining) console.log(import_chalk62.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
14367
+ if (fga?.remaining) console.log(import_chalk63.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
14061
14368
  const st = fga?.side_tables;
14062
- if (st) console.log(import_chalk62.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})` : ""}`));
14369
+ if (st) console.log(import_chalk63.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})` : ""}`));
14063
14370
  } else {
14064
14371
  const fgaOrphans = fga?.orphans ?? [];
14065
- if (fgaOrphans.length === 0) console.log(import_chalk62.default.green(" no orphaned stores"));
14372
+ if (fgaOrphans.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
14066
14373
  for (const s of fgaOrphans) {
14067
14374
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
14068
- console.log(` ${import_chalk62.default.yellow(s.store_id)} ${import_chalk62.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
14375
+ console.log(` ${import_chalk63.default.yellow(s.store_id)} ${import_chalk63.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
14069
14376
  }
14070
- console.log(import_chalk62.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
14377
+ console.log(import_chalk63.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
14071
14378
  const st = fga?.side_tables;
14072
- if (st) console.log(import_chalk62.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`));
14379
+ if (st) console.log(import_chalk63.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`));
14073
14380
  }
14074
- for (const e of fga?.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
14075
- console.log(import_chalk62.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
14381
+ for (const e of fga?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14382
+ console.log(import_chalk63.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
14076
14383
  if (applied) {
14077
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk62.default.green(" no ghost tuples"));
14078
- else console.log(` ${import_chalk62.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk62.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
14384
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk63.default.green(" no ghost tuples"));
14385
+ else console.log(` ${import_chalk63.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk63.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
14079
14386
  } else {
14080
14387
  const n = ghosts?.ghost_count ?? 0;
14081
- if (n === 0) console.log(import_chalk62.default.green(` no ghost tuples ${import_chalk62.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
14388
+ if (n === 0) console.log(import_chalk63.default.green(` no ghost tuples ${import_chalk63.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
14082
14389
  else {
14083
- console.log(import_chalk62.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
14390
+ console.log(import_chalk63.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
14084
14391
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
14085
- console.log(import_chalk62.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
14392
+ console.log(import_chalk63.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
14086
14393
  }
14087
- if (n > 20) console.log(import_chalk62.default.dim(` \u2026 and ${n - 20} more`));
14394
+ if (n > 20) console.log(import_chalk63.default.dim(` \u2026 and ${n - 20} more`));
14088
14395
  }
14089
14396
  }
14090
- for (const e of ghosts?.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
14397
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14091
14398
  console.log();
14092
14399
  }
14093
14400
  async function runOp(sub, opts = {}, view) {
14094
14401
  if (!loadCredentials()) {
14095
- console.log(import_chalk62.default.dim("Not logged in. Run `apiblaze login`."));
14402
+ console.log(import_chalk63.default.dim("Not logged in. Run `apiblaze login`."));
14096
14403
  return;
14097
14404
  }
14098
14405
  if (!isOperatorLogin()) {
14099
- console.log(import_chalk62.default.dim("`apiblaze op` is only available to platform operators."));
14406
+ console.log(import_chalk63.default.dim("`apiblaze op` is only available to platform operators."));
14100
14407
  return;
14101
14408
  }
14102
14409
  switch (sub) {
@@ -14152,17 +14459,17 @@ async function runOp(sub, opts = {}, view) {
14152
14459
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
14153
14460
  printResidue(report, false);
14154
14461
  if (nUp + nFga + nGhost + nSide === 0) {
14155
- console.log(import_chalk62.default.green("Nothing to sweep."));
14462
+ console.log(import_chalk63.default.green("Nothing to sweep."));
14156
14463
  return;
14157
14464
  }
14158
14465
  if (!opts.yes) {
14159
14466
  const readline3 = await import("readline/promises");
14160
14467
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
14161
14468
  const answer = await rl.question(
14162
- import_chalk62.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: `)
14469
+ import_chalk63.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: `)
14163
14470
  );
14164
14471
  rl.close();
14165
- if (answer.trim() !== "sweep") return void console.log(import_chalk62.default.dim("Aborted."));
14472
+ if (answer.trim() !== "sweep") return void console.log(import_chalk63.default.dim("Aborted."));
14166
14473
  }
14167
14474
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
14168
14475
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -14172,25 +14479,25 @@ async function runOp(sub, opts = {}, view) {
14172
14479
  case "mark": {
14173
14480
  const label3 = (view ?? "").trim();
14174
14481
  if (!label3) {
14175
- console.log(import_chalk62.default.red("Give the change a name:") + import_chalk62.default.cyan(' apiblaze op mark "cached tenant count"'));
14482
+ console.log(import_chalk63.default.red("Give the change a name:") + import_chalk63.default.cyan(' apiblaze op mark "cached tenant count"'));
14176
14483
  return;
14177
14484
  }
14178
14485
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
14179
14486
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
14180
- console.log(import_chalk62.default.green(`
14181
- Marked: `) + import_chalk62.default.bold(label3));
14182
- console.log(import_chalk62.default.dim(` ${ts}`));
14183
- console.log(import_chalk62.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk62.default.cyan("apiblaze op latency compare") + "\n");
14487
+ console.log(import_chalk63.default.green(`
14488
+ Marked: `) + import_chalk63.default.bold(label3));
14489
+ console.log(import_chalk63.default.dim(` ${ts}`));
14490
+ console.log(import_chalk63.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk63.default.cyan("apiblaze op latency compare") + "\n");
14184
14491
  return;
14185
14492
  }
14186
14493
  case "credits": {
14187
14494
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
14188
14495
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14189
14496
  const accounts = data?.accounts ?? [];
14190
- if (accounts.length === 0) return void console.log(import_chalk62.default.dim("No credit wallets."));
14497
+ if (accounts.length === 0) return void console.log(import_chalk63.default.dim("No credit wallets."));
14191
14498
  for (const a of accounts) {
14192
14499
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
14193
- console.log(` ${import_chalk62.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk62.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk62.default.dim(" \u2014 anon") : ""}`);
14500
+ console.log(` ${import_chalk63.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk63.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk63.default.dim(" \u2014 anon") : ""}`);
14194
14501
  }
14195
14502
  return;
14196
14503
  }
@@ -14201,7 +14508,7 @@ async function runOp(sub, opts = {}, view) {
14201
14508
  case "billing": {
14202
14509
  const which = (view ?? "").trim().toLowerCase();
14203
14510
  if (which && which !== "doors" && which !== "meter") {
14204
- return void console.log(import_chalk62.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
14511
+ return void console.log(import_chalk63.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
14205
14512
  }
14206
14513
  const checks = [];
14207
14514
  let doorsData = null;
@@ -14270,29 +14577,29 @@ async function runOp(sub, opts = {}, view) {
14270
14577
  const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
14271
14578
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14272
14579
  const t = data.thresholds_ms;
14273
- console.log(import_chalk62.default.bold("\nHow good was apiblaze itself?") + import_chalk62.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
14274
- console.log(import_chalk62.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
14580
+ console.log(import_chalk63.default.bold("\nHow good was apiblaze itself?") + import_chalk63.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
14581
+ console.log(import_chalk63.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
14275
14582
  `));
14276
- console.log(import_chalk62.default.dim(" date reqs excellent okay bad terrible"));
14583
+ console.log(import_chalk63.default.dim(" date reqs excellent okay bad terrible"));
14277
14584
  for (const d of data.days ?? []) {
14278
14585
  const p = d.pct;
14279
- const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk62.default.dim(`${String(v).padStart(5)}%`);
14586
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk63.default.dim(`${String(v).padStart(5)}%`);
14280
14587
  console.log(
14281
- ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk62.default.green)} ${cell(p.okay, import_chalk62.default.cyan)} ${cell(p.bad, import_chalk62.default.yellow)} ${cell(p.terrible, import_chalk62.default.red)}`
14588
+ ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk63.default.green)} ${cell(p.okay, import_chalk63.default.cyan)} ${cell(p.bad, import_chalk63.default.yellow)} ${cell(p.terrible, import_chalk63.default.red)}`
14282
14589
  );
14283
14590
  }
14284
14591
  const cul = data.culprits ?? [];
14285
14592
  if (cul.length) {
14286
- console.log(import_chalk62.default.bold("\n Who caused the bad and terrible ones\n"));
14287
- console.log(import_chalk62.default.dim(" bad terrible feature \u2192 dependency"));
14593
+ console.log(import_chalk63.default.bold("\n Who caused the bad and terrible ones\n"));
14594
+ console.log(import_chalk63.default.dim(" bad terrible feature \u2192 dependency"));
14288
14595
  for (const r of cul.slice(0, 12)) {
14289
14596
  if (!r.bad && !r.terrible) continue;
14290
14597
  console.log(
14291
- ` ${String(r.bad).padStart(6)} ${import_chalk62.default.red(String(r.terrible).padStart(8))} ${import_chalk62.default.yellow(r.feature)} ${import_chalk62.default.dim("\u2192")} ${import_chalk62.default.cyan(r.dep)}`
14598
+ ` ${String(r.bad).padStart(6)} ${import_chalk63.default.red(String(r.terrible).padStart(8))} ${import_chalk63.default.yellow(r.feature)} ${import_chalk63.default.dim("\u2192")} ${import_chalk63.default.cyan(r.dep)}`
14292
14599
  );
14293
14600
  }
14294
14601
  }
14295
- if (data.caveat) console.log(import_chalk62.default.dim(`
14602
+ if (data.caveat) console.log(import_chalk63.default.dim(`
14296
14603
  \u26A0 ${data.caveat}
14297
14604
  `));
14298
14605
  return;
@@ -14301,24 +14608,24 @@ async function runOp(sub, opts = {}, view) {
14301
14608
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
14302
14609
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14303
14610
  const b = data.before, a = data.after, d = data.delta;
14304
- console.log(import_chalk62.default.bold(`
14305
- Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
14306
- console.log(import_chalk62.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
14611
+ console.log(import_chalk63.default.bold(`
14612
+ Before vs after: `) + import_chalk63.default.cyan(data.marker.label));
14613
+ console.log(import_chalk63.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
14307
14614
  `));
14308
14615
  const row = (name, before, after, delta) => {
14309
14616
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
14310
14617
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
14311
- console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk62.default.green(txt) : import_chalk62.default.red(txt) : import_chalk62.default.dim(txt)}`);
14618
+ console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk63.default.green(txt) : import_chalk63.default.red(txt) : import_chalk63.default.dim(txt)}`);
14312
14619
  };
14313
- console.log(import_chalk62.default.dim(" metric before after change"));
14620
+ console.log(import_chalk63.default.dim(" metric before after change"));
14314
14621
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
14315
14622
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
14316
14623
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
14317
14624
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
14318
- console.log(import_chalk62.default.dim(`
14625
+ console.log(import_chalk63.default.dim(`
14319
14626
  requests: ${b.requests} before \xB7 ${a.requests} after`));
14320
14627
  for (const w of data.warnings ?? []) {
14321
- console.log((data.trustworthy ? import_chalk62.default.dim : import_chalk62.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
14628
+ console.log((data.trustworthy ? import_chalk63.default.dim : import_chalk63.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
14322
14629
  }
14323
14630
  console.log("");
14324
14631
  return;
@@ -14327,19 +14634,19 @@ Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
14327
14634
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
14328
14635
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14329
14636
  const rows2 = data?.rows ?? [];
14330
- if (!rows2.length) return void console.log(import_chalk62.default.dim("No requests over the threshold in that window."));
14331
- console.log(import_chalk62.default.bold(`
14637
+ if (!rows2.length) return void console.log(import_chalk63.default.dim("No requests over the threshold in that window."));
14638
+ console.log(import_chalk63.default.bold(`
14332
14639
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
14333
14640
  `));
14334
- console.log(import_chalk62.default.dim(" total ours theirs blame request id"));
14641
+ console.log(import_chalk63.default.dim(" total ours theirs blame request id"));
14335
14642
  for (const r of rows2.slice(0, 30)) {
14336
14643
  console.log(
14337
- ` ${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_chalk62.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk62.default.dim(r.request_id || "")}`
14644
+ ` ${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_chalk63.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk63.default.dim(r.request_id || "")}`
14338
14645
  );
14339
14646
  }
14340
- console.log(import_chalk62.default.dim(`
14647
+ console.log(import_chalk63.default.dim(`
14341
14648
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
14342
- console.log(import_chalk62.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
14649
+ console.log(import_chalk63.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
14343
14650
  `));
14344
14651
  return;
14345
14652
  }
@@ -14347,18 +14654,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
14347
14654
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
14348
14655
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14349
14656
  const rows2 = data?.rows ?? [];
14350
- if (!rows2.length) return void console.log(import_chalk62.default.dim("No LLM traffic in that window."));
14351
- console.log(import_chalk62.default.bold(`
14657
+ if (!rows2.length) return void console.log(import_chalk63.default.dim("No LLM traffic in that window."));
14658
+ console.log(import_chalk63.default.bold(`
14352
14659
  LLM timing \u2014 last ${data.window_hours}h
14353
14660
  `));
14354
- console.log(import_chalk62.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
14661
+ console.log(import_chalk63.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
14355
14662
  for (const r of rows2) {
14356
14663
  const n = r.turns ?? r.requests ?? 0;
14357
14664
  console.log(
14358
14665
  ` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
14359
14666
  );
14360
14667
  }
14361
- console.log(import_chalk62.default.dim(`
14668
+ console.log(import_chalk63.default.dim(`
14362
14669
  ${data.note}
14363
14670
  `));
14364
14671
  return;
@@ -14369,26 +14676,26 @@ LLM timing \u2014 last ${data.window_hours}h
14369
14676
  ]);
14370
14677
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
14371
14678
  const ov = summary?.apiblaze_overhead_ms ?? {};
14372
- console.log(import_chalk62.default.bold(`
14679
+ console.log(import_chalk63.default.bold(`
14373
14680
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
14374
14681
  `));
14375
- console.log(` ${import_chalk62.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_chalk62.default.dim("\u2190 ours")}`);
14376
- console.log(` ${import_chalk62.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk62.default.dim("\u2190 theirs")}`);
14377
- console.log(import_chalk62.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
14682
+ console.log(` ${import_chalk63.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_chalk63.default.dim("\u2190 ours")}`);
14683
+ console.log(` ${import_chalk63.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk63.default.dim("\u2190 theirs")}`);
14684
+ console.log(import_chalk63.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
14378
14685
  `));
14379
14686
  const rows = blame?.blame ?? [];
14380
- if (!rows.length) return void console.log(import_chalk62.default.dim("No latency rows in that window."));
14381
- console.log(import_chalk62.default.bold(" Which feature ate the time, and what inside it\n"));
14382
- console.log(import_chalk62.default.dim(" share p95 feature \u2192 dependency"));
14687
+ if (!rows.length) return void console.log(import_chalk63.default.dim("No latency rows in that window."));
14688
+ console.log(import_chalk63.default.bold(" Which feature ate the time, and what inside it\n"));
14689
+ console.log(import_chalk63.default.dim(" share p95 feature \u2192 dependency"));
14383
14690
  for (const r of rows.slice(0, 15)) {
14384
14691
  const share = `${(r.share * 100).toFixed(1)}%`;
14385
- console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk62.default.yellow(r.feature)} ${import_chalk62.default.dim("\u2192")} ${import_chalk62.default.cyan(r.dep)}`);
14692
+ console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk63.default.yellow(r.feature)} ${import_chalk63.default.dim("\u2192")} ${import_chalk63.default.cyan(r.dep)}`);
14386
14693
  }
14387
14694
  console.log("");
14388
14695
  return;
14389
14696
  }
14390
14697
  default:
14391
- console.log(import_chalk62.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
14698
+ console.log(import_chalk63.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
14392
14699
  }
14393
14700
  }
14394
14701
 
@@ -14439,8 +14746,8 @@ agent.command("openapi").description("Chat to build your API spec from real traf
14439
14746
  agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
14440
14747
  program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => {
14441
14748
  const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
14442
- const target = opts.target ?? (looksLikeSource ? project : void 0);
14443
- return runApichat({ ...opts, target, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
14749
+ const target2 = opts.target ?? (looksLikeSource ? project : void 0);
14750
+ return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
14444
14751
  }));
14445
14752
  var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
14446
14753
  llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
@@ -14458,7 +14765,7 @@ program.command("dev").description(`Put the code running on this machine behind
14458
14765
  try {
14459
14766
  const resolved = parseInt(port ?? opts.port, 10);
14460
14767
  if (Number.isNaN(resolved)) {
14461
- console.error(import_chalk63.default.red(`Invalid port: ${port ?? opts.port}`));
14768
+ console.error(import_chalk64.default.red(`Invalid port: ${port ?? opts.port}`));
14462
14769
  process.exit(1);
14463
14770
  }
14464
14771
  await runDev({ ...opts, port: resolved });
@@ -14521,7 +14828,19 @@ var logs = program.command("logs").description("API Producer view \u2014 stream
14521
14828
  logs.command("list").description("API Producer view \u2014 browse EVERY caller's past requests by day, then open one to see its 4 views").argument("<project>", "Project name or id").argument("[version]", "API version (defaults to the first match)").option("--tenant <slug>", "Tenant to read (auto when one is attached; prompted otherwise)").option("--errors-only", "Only show 4xx/5xx responses").option("--json", "Emit ALL matching rows as NDJSON, newest first (non-interactive)").option("--plain", "Print day-grouped rows instead of prompting (automatic when piped)").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, _o, cmd) => runLogsList(project, version2, cmd.optsWithGlobals())));
14522
14829
  program.command("config").description("Browse and change every proxy setting & feature (interactive; git-config-style get/set)").argument("[project]", "Project name or id (omit to pick interactively)").argument("[key]", "Setting key, e.g. throttling.proxyQuota (omit for the menu)").argument("[value]", "New value (omit to read the current value)").option("--list", "Print every setting and its current value").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Machine-readable output (with --list or a key read)").action(action((project, key, value, opts) => runConfig(project, key, value, opts)));
14523
14830
  program.command("target").description("Change where a proxy forwards requests").argument("<project>", "Project name or id").requiredOption("--url <url>", "Target URL to forward to").option("--env <env>", "Environment to scope the target to (e.g. prod, dev)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTargetSet(project, opts)));
14524
- program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "User rate limit (requests/sec)").option("--end-user-rate <n>", "Per-end-user rate limit (requests/sec)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
14831
+ program.command("throttle").description("Set rate limits and quotas for a proxy (works in an anonymous workspace too; new proxies start at 10/s per person and 10,000/day)").argument("<project>", "Project name or id").option("--rate <n>", "Requests/sec per person (or per key when a call names no person). Anonymous workspaces: at most 10").option("--end-user-rate <n>", "Same as --rate (kept for older scripts)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
14832
+ var transform = program.command("transform").description("Change requests on their way to your backend \u2014 e.g. send it a secret header so it only answers to the proxy");
14833
+ transform.command("list").description("List a proxy's transforms").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTransformList(project, opts)));
14834
+ transform.command("set-header").description("Send a header with a fixed value to the upstream on every call (replaces the value if already set)").argument("<project>", "Project name or id").argument("<header>", "Header name, e.g. x-target-api-key").option("--value <value>", "The header value").option("--value-env <VAR>", "Read the value from this environment variable (keeps secrets out of shell history)").option("--secret", "Store the value encrypted and never show it again").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
14835
+ Example \u2014 lock your backend to the proxy:
14836
+ export TARGET_SERVER_SECRET=$(openssl rand -hex 24) # also give it to your backend
14837
+ apiblaze transform set-header shop x-target-api-key --value-env TARGET_SERVER_SECRET --secret`).action(action((project, header, opts) => runTransformSetHeader(project, header, opts)));
14838
+ transform.command("mapping").description("Create or replace a lookup table for map transforms, e.g. country code \u2192 currency").argument("<project>", "Project name or id").argument("<table>", "Table name, e.g. currencies").argument("<pairs...>", "from=to pairs, e.g. FR=EUR US=USD GB=GBP").option("--secret", "Store the values encrypted and never show them again").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, table, pairs, opts) => runTransformMapping(project, table, pairs, opts)));
14839
+ transform.command("map").description("Rewrite a value through a mapping table on its way to the backend, e.g. header:X-Country \u2192 header:X-Currency").argument("<project>", "Project name or id").argument("<source>", "Where to read: header:X-Country \xB7 query:country \xB7 bodyvar:order.country").argument("<destination>", "Where to write: header:X-Currency \xB7 query:currency \xB7 bodyvar:order.currency").requiredOption("--table <name>", "The mapping table to look the value up in").option("--on-no-match <mode>", "skip (default) \xB7 fail \xB7 passthrough").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
14840
+ Example \u2014 the backend wants a currency, callers send a country:
14841
+ apiblaze transform mapping shop currencies FR=EUR US=USD GB=GBP
14842
+ apiblaze transform map shop header:X-Country header:X-Currency --table currencies`).action(action((project, source, destination, opts) => runTransformMap(project, source, destination, opts)));
14843
+ transform.command("remove").description("Remove a transform by name, id, or header name").argument("<project>", "Project name or id").argument("<rule>", "Rule name, id, or the header a set-header rule sets").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, rule, opts) => runTransformRemove(project, rule, opts)));
14525
14844
  program.command("rename").description("Change a proxy's display name").argument("<project>", "Project name or id").requiredOption("--display-name <name>", "New human-friendly display name").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRename(project, opts)));
14526
14845
  var domain = program.command("domain").description("Use your own domain for your API, MCP server, developer portal or login pages (billed monthly)");
14527
14846
  domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("[project]", "Project name or id (for api / mcp domains; omit with --tenant for portal / auth)").requiredOption("--domain <host>", "Custom hostname to add, e.g. api.example.com").option("--kind <kind>", "api (default) | mcp \u2014 on a project; portal | auth \u2014 on a --tenant").option("--tenant <slug>", "portal/auth: the tenant the domain belongs to. api/mcp: pin the domain to one of the project's tenants").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").addHelpText("after", `
@@ -14562,11 +14881,11 @@ admins.command("remove").description("Remove a tenant admin").argument("<email>"
14562
14881
  program.addCommand(admins);
14563
14882
  program.command("iam").description("Turn users & groups on/off for a proxy's tenant (identified calls get their user's groups applied)").argument("<project>", "Project name or id").argument("<state>", "on | off").option("--tenant <name>", "Tenant to toggle (defaults to the project's tenant)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, state, opts) => runIamToggle(project, state, opts)));
14564
14883
  program.command("identified").description("Require calls to identify their end user (X-End-User-Id from a backend key, or a login token \u2014 a client key alone cannot) \u2014 or allow unattributed calls again").argument("<project>", "Project name or id").argument("<mode>", "require | allow-anon").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, mode, opts) => runIdentifiedToggle(project, mode, opts)));
14565
- var apikeys = new import_commander.Command("apikeys").description("Producer control-plane API keys (list, then offer to create one)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
14884
+ var apikeys = new import_commander.Command("apikeys").description("API keys \u2014 for your team (control plane), or with `mint --tenant` for your API's callers: a backend key, a client key (--client) or a key bound to one person (--for)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
14566
14885
  apikeys.command("list").description("List control-plane API keys in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyList({ ...cmd.parent?.opts(), ...opts })));
14567
- apikeys.command("mint").description("Create a control-plane API key (secret shown once) \u2014 or, with --tenant, a consumer key in that tenant: a backend key (default), a client key (--client) or a person-bound key (--for)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--tenant <slug>", "Mint a CONSUMER key in this tenant instead of a control-plane key").option("--backend", `Backend key (default). ${HOLDER_HELP.backend}`).option("--client", `Client key. ${HOLDER_HELP.client} Needs --tenant.`).option("--for <email|id>", "Issue this key to a named person \u2014 calls made with it count as that person, and ownership rules trust it. The Users & Groups widget's admin lane accepts only such a key, one per admin. Needs --tenant.").option("--env <name>", "Environment the consumer key is valid for (with --tenant; default dev)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
14886
+ apikeys.command("mint").description("Create a control-plane API key (secret shown once) \u2014 or, with --tenant, a consumer key in that tenant: a backend key (default), a client key (--client) or a person-bound key (--for)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--tenant <slug>", "Mint a CONSUMER key in this tenant instead of a control-plane key").option("--backend", `Backend key (default). ${HOLDER_HELP.backend}`).option("--client", `Client key. ${HOLDER_HELP.client} Needs --tenant.`).option("--for <email|id>", "Issue this key to a named person \u2014 calls made with it count as that person, and ownership rules trust it. The Users & Groups widget's admin lane accepts only such a key, one per admin. Needs --tenant.").option("--admin", "With --for: the key a tenant admin uses in the Users & Groups widget (adds manage-access; env defaults to prod)").option("--env <name>", "Environment the consumer key is valid for (with --tenant; default dev, prod with --admin)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts, cmd) => runKeyMint({ ...cmd.parent?.opts(), ...opts })));
14568
14887
  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 })));
14569
- program.addCommand(apikeys, { hidden: true });
14888
+ program.addCommand(apikeys);
14570
14889
  var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "latency | billing | mark | credits | residue | sweep | test | perf (omit for the menu)").argument("[view]", "for `latency`: slow | llm | compare | grades \xB7 for `billing`: doors | meter \xB7 for `mark`: the change name, quoted").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").option("--hours <n>", "Look-back window in hours (default 1)").option("--team <id>", "Filter to one team").option("--only <ids>", "for `test`: comma-separated spec/gut ids or prefixes").option("--group <name>", "for `test`: only specs whose group matches").option("--skip-guts", "for `test`: contract specs only").option("--guts-only", "for `test`: server-side gut checks only").option("--fail-fast", "for `test`: stop at the first failure").option("--timeout <s>", "for `test`: seconds to wait per check (default 150)").option("--perf", "for `test`: run the performance gate instead of the suite (fixed-rate load on a sandbox proxy)").option("--consumer", "for `test`: probe the data plane with the stored `consumer login` token instead of the suite").option("--path <p>", "for `test --consumer`: request path on the proxy (default /get)").option("--proxy <name@version>", "for `test --consumer`: the OAuth-door proxy to knock on (default: the sandbox proxy for a sandbox-tenant session)").option("--rps <n>", "for `test --perf`: requests per second (default 100, max 300)").option("--seconds <n>", "for `test --perf`: how long to send for (default 60, max 300)").option("--url <url>", "for `test --perf`: load this fixture URL instead of provisioning one (with --key; nothing is torn down)").option("--max-p50 <ms>", "for `test --perf`: client p50 threshold (default 250)").option("--max-p95 <ms>", "for `test --perf`: client p95 threshold (default 800)").option("--max-error-rate <pct>", "for `test --perf`: error-rate threshold in percent (default 0.5)").option("--max-gateway-p50 <ms>", "for `test --perf`: apiblaze-overhead p50 threshold (default 60)").option("--max-gateway-p95 <ms>", "for `test --perf`: apiblaze-overhead p95 threshold (default 250)").option("--project <name>", "Filter to one proxy").option("--api-version <v>", "Filter to one API version").option("--tenant <name>", "Filter to one tenant").option("--route <path>", "Filter to one route").option("--environment <env>", "Filter to one environment").option("--status-class <c>", "Filter to a status class (2xx/4xx/5xx)").option("--min-ms <n>", "For `latency slow`: minimum duration (default 1000)").option("--days <n>", "For `latency grades`: how many days back (default 7)").option("--excellent <ms>", "Grade threshold: under this is excellent (default 250)").option("--okay <ms>", "Grade threshold: under this is okay (default 500)").option("--bad <ms>", "Grade threshold: under this is bad, at/over is terrible (default 1000)").option("--key <key>", "For `billing meter`: a data-plane API key for the sandbox proxy (without it the served-request checks are SKIPPED) \xB7 for `test --perf --url`: the fixture's API key").option("--count <n>", "For `billing meter`: requests per check (default 3, max 10)").option("--mcp-host <host>", "For `billing meter`: an MCP host to check tools/call is charged once").action(action((sub, view, opts) => runOp(sub, opts, view))).addHelpText("after", () => `
14571
14890
  ${renderOpCommands()}
14572
14891
  `);
@@ -14577,10 +14896,10 @@ spec.command("set").description("Replace the stored OpenAPI spec from a local fi
14577
14896
  spec.command("carry").description("Copy a route's saved rules to the path a spec rename moved traffic to").argument("<project>", "Project name or id").requiredOption("--method <method>", "HTTP method of the route (GET, POST, ...)").requiredOption("--from <path>", "Old route path, e.g. /owners/{ownerId}").requiredOption("--to <path>", "New route path, e.g. /customers/{customerId}").option("--delete-source", "Also delete the old rules after carrying").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runSpecCarry(project, opts)));
14578
14897
  spec.command("delete-rule").description("Delete the saved rules for a route (e.g. an orphan left by a spec change)").argument("<project>", "Project name or id").requiredOption("--method <method>", "HTTP method of the route").requiredOption("--route <path>", "Route path, e.g. /owners/{ownerId}").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runSpecDeleteRule(project, opts)));
14579
14898
  var HELP_GROUPS = [
14580
- { title: "Chat", commands: ["docs", "apichat", "agent"] },
14581
- { title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout"] },
14899
+ { title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
14900
+ { title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
14582
14901
  { title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
14583
- { title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
14902
+ { title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "apikeys", "iam", "identified", "preapprove", "allowoauthregistration", "rule", "domain", "delete", "job", "target", "throttle", "transform", "rename", "spec", "export"] },
14584
14903
  { title: "Data plane commands", commands: [
14585
14904
  { parent: "consumer", sub: "login" },
14586
14905
  { parent: "consumer", sub: "apikeys" },
@@ -14606,7 +14925,7 @@ function groupedCommandHelp() {
14606
14925
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
14607
14926
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
14608
14927
  }).filter(Boolean).join("\n");
14609
- return `${import_chalk63.default.bold(g.title)}
14928
+ return `${import_chalk64.default.bold(g.title)}
14610
14929
  ${rows}`;
14611
14930
  }).join("\n\n");
14612
14931
  }
@@ -14617,7 +14936,7 @@ program.configureHelp({
14617
14936
  }
14618
14937
  });
14619
14938
  program.addHelpText("after", () => `
14620
- ${import_chalk63.default.bold("Don't like reading docs?")} Run ${import_chalk63.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk63.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
14939
+ ${import_chalk64.default.bold("Don't like reading docs?")} Run ${import_chalk64.default.bgCyan.black.bold(` npx ${B.cli} docs `)} ${import_chalk64.default.cyan(`\u2014 ask an AI chatbot about ${B.product}, and let it act on your account.`)}
14621
14940
 
14622
14941
  ${groupedCommandHelp()}
14623
14942
 
@@ -14653,14 +14972,14 @@ async function recoverStaleTeam() {
14653
14972
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
14654
14973
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
14655
14974
  if (!linked) {
14656
- console.error(import_chalk63.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
14975
+ console.error(import_chalk64.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
14657
14976
  return;
14658
14977
  }
14659
14978
  if (linked.teamId === creds.teamId) return;
14660
14979
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
14661
14980
  delete next.activeTenant;
14662
14981
  saveCredentials(next);
14663
- console.error(import_chalk63.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk63.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
14982
+ console.error(import_chalk64.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk64.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
14664
14983
  } catch {
14665
14984
  }
14666
14985
  }
@@ -14668,16 +14987,33 @@ async function printError(err) {
14668
14987
  if (err instanceof ApiError) {
14669
14988
  const data = err.body;
14670
14989
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
14671
- console.error(import_chalk63.default.red(`
14990
+ console.error(import_chalk64.default.red(`
14672
14991
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
14673
14992
  if (err.status === 403 || err.status === 404) {
14674
14993
  await recoverStaleTeam();
14675
14994
  }
14676
14995
  } else if (err instanceof Error) {
14677
- console.error(import_chalk63.default.red(`
14996
+ console.error(import_chalk64.default.red(`
14678
14997
  Error: ${err.message}`));
14679
14998
  } else {
14680
- console.error(import_chalk63.default.red("\nUnknown error"));
14999
+ console.error(import_chalk64.default.red("\nUnknown error"));
14681
15000
  }
14682
15001
  }
15002
+ function hintStaleCacheOnUnknownOption(cmd) {
15003
+ cmd.exitOverride((err) => {
15004
+ if (err.code === "commander.unknownOption") {
15005
+ console.error(import_chalk64.default.yellow(
15006
+ `
15007
+ This is ${B.cli} v${version}. If that option is documented but this build
15008
+ does not have it, your npx cache is serving an older copy \u2014 re-run as:
15009
+ `
15010
+ ));
15011
+ console.error(import_chalk64.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
15012
+ `));
15013
+ }
15014
+ process.exit(err.exitCode);
15015
+ });
15016
+ for (const sub of cmd.commands) hintStaleCacheOnUnknownOption(sub);
15017
+ }
15018
+ hintStaleCacheOnUnknownOption(program);
14683
15019
  program.parse(process.argv);