apiblaze 0.21.9 → 0.21.13

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 +968 -619
  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' \\"));
@@ -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.9";
1345
+ var version = "0.21.13";
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,
@@ -4848,10 +4891,30 @@ async function mintConsumerKey(o) {
4848
4891
  raw: out
4849
4892
  };
4850
4893
  }
4894
+ async function tenantKeyEndUserMode(caller, teamId, tenant2) {
4895
+ try {
4896
+ const p = await producer(caller, {
4897
+ method: "GET",
4898
+ path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/key-policy`,
4899
+ summary: "Read the tenant key policy (default holder)"
4900
+ });
4901
+ return p?.key_end_user_mode === "one" ? "one" : "many";
4902
+ } catch {
4903
+ return "many";
4904
+ }
4905
+ }
4906
+ function defaultHolderRefusal(tenant2) {
4907
+ return `Tenant ${tenant2} issues one key per end user (1 API key = 1 end user): add --for <email|id>. To create a different key anyway, pass --backend (a server key that names each caller) or --client.`;
4908
+ }
4851
4909
  async function runConsumerKeyMint(opts) {
4852
4910
  const kind = resolveHolder(opts);
4853
4911
  const who = (opts.for ?? "").trim();
4854
4912
  const tenant2 = (opts.tenant ?? "").trim();
4913
+ if (tenant2 && !who && !opts.backend && !opts.client) {
4914
+ const caller = requireCaller();
4915
+ const { teamId } = await resolveActingTeam(caller, opts.team);
4916
+ if (await tenantKeyEndUserMode(caller, teamId, tenant2) === "one") throw new Error(defaultHolderRefusal(tenant2));
4917
+ }
4855
4918
  const spinner = (0, import_ora3.default)(who ? `Minting a key for ${who} in ${tenant2}...` : `Minting a ${kind} key in ${tenant2}...`).start();
4856
4919
  let out;
4857
4920
  try {
@@ -5426,8 +5489,8 @@ function printEnvironments(o) {
5426
5489
  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)`]] : [];
5427
5490
  if (!rows.length) return;
5428
5491
  const w = Math.max(...rows.map(([e]) => e.length));
5429
- for (const [env, target] of rows) {
5430
- console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target}`);
5492
+ for (const [env, target2] of rows) {
5493
+ console.log(` ${import_chalk17.default.cyan(env.padEnd(w))} ${import_chalk17.default.green("\u2192")} ${target2}`);
5431
5494
  }
5432
5495
  }
5433
5496
  async function finishSetup(o) {
@@ -5875,9 +5938,9 @@ Create an ${B.thing}`));
5875
5938
  body.openapi = await loadOpenapiSource(opts.openapi);
5876
5939
  }
5877
5940
  if (opts.target && !isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
5878
- let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
5941
+ let target2 = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
5879
5942
  const hasOtherSource = !!(body.openapi || body.github);
5880
- if (!target && !hasOtherSource) {
5943
+ if (!target2 && !hasOtherSource) {
5881
5944
  if (interactive) {
5882
5945
  const { default: inquirer3 } = await import("inquirer");
5883
5946
  if (name === void 0) {
@@ -5902,7 +5965,7 @@ Create an ${B.thing}`));
5902
5965
  console.log(import_chalk17.default.yellow(" Enter a valid http(s) URL.\n"));
5903
5966
  continue;
5904
5967
  }
5905
- target = url.trim();
5968
+ target2 = url.trim();
5906
5969
  break;
5907
5970
  }
5908
5971
  } else {
@@ -5910,12 +5973,12 @@ Create an ${B.thing}`));
5910
5973
  }
5911
5974
  }
5912
5975
  if (name === void 0 && opts.auto) {
5913
- const nameSource = target || (typeof opts.openapi === "string" ? opts.openapi : void 0);
5976
+ const nameSource = target2 || (typeof opts.openapi === "string" ? opts.openapi : void 0);
5914
5977
  name = deriveNameFrom(nameSource) ?? randomProxyName();
5915
5978
  }
5916
- if (target) {
5917
- body.target = target;
5918
- body.target_url = target;
5979
+ if (target2) {
5980
+ body.target = target2;
5981
+ body.target_url = target2;
5919
5982
  }
5920
5983
  if (name) {
5921
5984
  body.name = name;
@@ -6126,13 +6189,13 @@ async function chooseName(opts, openapi, teamId, interactive) {
6126
6189
  }
6127
6190
  async function createDevProxy(args) {
6128
6191
  const { auth, name, port, openapi, authChoice, families, tenant: tenant2, teamId } = args;
6129
- const target = `http://localhost:${port}`;
6192
+ const target2 = `http://localhost:${port}`;
6130
6193
  const body = {
6131
6194
  name,
6132
6195
  subdomain: name,
6133
6196
  auth_type: authChoice.auth,
6134
6197
  ...authChoice.bodyPatch,
6135
- ...openapi ? { openapi, environments: { dev: { target, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target },
6198
+ ...openapi ? { openapi, environments: { dev: { target: target2, description: `localhost:${port} through the ${B.cli} dev tunnel` } } } : { target_url: target2 },
6136
6199
  ...tenant2 ? { tenant: tenant2 } : {},
6137
6200
  ...teamId ? { team_id: teamId } : {},
6138
6201
  ...families ? { secure: { families } } : {}
@@ -6142,7 +6205,7 @@ async function createDevProxy(args) {
6142
6205
  let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
6143
6206
  let resolvedTeam = teamId;
6144
6207
  if (caller) {
6145
- result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target}` });
6208
+ result = await producer(caller, { method: "POST", path: "/projects", body, summary: `Create ${B.thingShort} ${name} \u2192 ${target2}` });
6146
6209
  } else {
6147
6210
  result = await createProxyAnonymous(body);
6148
6211
  if (result.cp_key && result.team_id) {
@@ -6198,7 +6261,7 @@ async function runDev(options) {
6198
6261
  }
6199
6262
  let targets = [];
6200
6263
  const wantsNew = options.openapi !== void 0 || options.name !== void 0;
6201
- if (auth.mode !== "none" && !wantsNew) {
6264
+ if (auth.mode !== "none" && (!wantsNew || options.name !== void 0 && !options.project)) {
6202
6265
  const spinner = (0, import_ora7.default)("Looking for projects that point at this machine...").start();
6203
6266
  try {
6204
6267
  targets = await getLocalhostTargets(teamId, keyAuth);
@@ -6212,7 +6275,15 @@ async function runDev(options) {
6212
6275
  let selected = [];
6213
6276
  let created = null;
6214
6277
  let setup = null;
6215
- if (options.project) {
6278
+ 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;
6279
+ if (rerun) {
6280
+ console.log(import_chalk18.default.dim(` "${rerun.projectName}" already points at this machine \u2014 reattaching the tunnel (spec and keys unchanged).`));
6281
+ selected = [rerun];
6282
+ } else if (wantsNew) {
6283
+ targets = [];
6284
+ }
6285
+ if (selected.length) {
6286
+ } else if (options.project) {
6216
6287
  const want = options.project.toLowerCase();
6217
6288
  const match = targets.find((t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want);
6218
6289
  if (!match) {
@@ -6318,11 +6389,12 @@ async function runDev(options) {
6318
6389
  }
6319
6390
  let restore = [];
6320
6391
  let connect;
6392
+ const registerTargets = { targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) };
6321
6393
  {
6322
6394
  const spinner = (0, import_ora7.default)("Registering the tunnel...").start();
6323
6395
  try {
6324
- const result = await putDevTunnel({ targets: selected.map((t) => ({ projectId: t.projectId, tenantId: t.tenantId })) }, keyAuth);
6325
- restore = result.restore ?? [];
6396
+ const result = await putDevTunnel(registerTargets, keyAuth);
6397
+ restore = (result.restore ?? []).map((r) => unmarkRestore(r, options.port));
6326
6398
  connect = result.connect;
6327
6399
  spinner.stop();
6328
6400
  } catch (err) {
@@ -6340,6 +6412,13 @@ async function runDev(options) {
6340
6412
  (projectId) => startTunnelClient({
6341
6413
  connectUrl: connect.url,
6342
6414
  token: connect.token,
6415
+ // Re-register for a fresh token. PUT is idempotent on an already-tunnelled
6416
+ // project; its restore snapshot would be the marker, so it is IGNORED —
6417
+ // teardown keeps replaying the one taken at start.
6418
+ refresh: async () => {
6419
+ const again = await putDevTunnel(registerTargets, keyAuth);
6420
+ return { url: again.connect.url, token: again.connect.token };
6421
+ },
6343
6422
  projectId,
6344
6423
  localPort: options.port,
6345
6424
  onEntry: (entry) => say(formatLogLine(entry)),
@@ -6359,6 +6438,7 @@ async function runDev(options) {
6359
6438
  const localUp = await probeLocalServer(options.port);
6360
6439
  console.log();
6361
6440
  console.log(` ${import_chalk18.default.bold("Tunnel is up \u2014 leave this running. Ctrl-C stops it.")}`);
6441
+ 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)`));
6362
6442
  if (!localUp) console.log(import_chalk18.default.magenta(` \u26B2 Nothing listening on port ${options.port} yet \u2014 requests are captured until your server starts.`));
6363
6443
  if (options.captureFile) console.log(import_chalk18.default.gray(` Streaming full traffic to ${options.captureFile}`));
6364
6444
  console.log();
@@ -6378,9 +6458,30 @@ async function runDev(options) {
6378
6458
  }
6379
6459
  process.on("SIGINT", () => void cleanup());
6380
6460
  process.on("SIGTERM", () => void cleanup());
6461
+ process.on("SIGHUP", () => void cleanup());
6462
+ for (const stream of [process.stdout, process.stderr]) {
6463
+ stream.on("error", (err) => {
6464
+ if (err.code !== "EPIPE") throw err;
6465
+ });
6466
+ }
6381
6467
  await new Promise(() => {
6382
6468
  });
6383
6469
  }
6470
+ function unmarkRestore(r, port) {
6471
+ const envs = r.environments;
6472
+ if (!envs || typeof envs !== "object") return r;
6473
+ let changed = false;
6474
+ const next = {};
6475
+ for (const [stage, value] of Object.entries(envs)) {
6476
+ if (typeof value?.target === "string" && value.target.startsWith("devtunnel://")) {
6477
+ next[stage] = { ...value, target: `http://localhost:${port}` };
6478
+ changed = true;
6479
+ } else {
6480
+ next[stage] = value;
6481
+ }
6482
+ }
6483
+ return changed ? { ...r, environments: next } : r;
6484
+ }
6384
6485
 
6385
6486
  // src/commands/projects.ts
6386
6487
  var import_chalk19 = __toESM(require("chalk"));
@@ -6485,10 +6586,16 @@ business logic: the gateway sits in front of the code and enforces it.
6485
6586
  - They say yes to "can I read the code" \u2192 **Adopt**: scan their app, ask only what the scan
6486
6587
  could not find, then ship it through the Ship steps.
6487
6588
  - They have a backend, just wrote one, or ask you to BUILD one and then ship it \u2192 **Ship**.
6488
- No code yet? First write the smallest plain backend that serves the resources they named \u2014
6489
- no auth, no tenancy, no ownership checks (the gateway does those); stamp rows with the
6490
- \`x-abz-user-id\` header so ownership is visible \u2014 plus its \`openapi.yaml\`, start it on
6491
- their port, then continue at Ship \xA71. Do NOT build the Demo app for this. If they say "use
6589
+ No code yet? First write the smallest plain backend that serves the resources they named,
6590
+ plus its \`openapi.yaml\`, start it on their port, then continue at Ship \xA71. Do NOT build
6591
+ the Demo app for this. The gateway does auth, tenancy and item ownership; the backend does
6592
+ only these three things:
6593
+ - stamp each new row's \`owner\` with the \`x-abz-user-id\` header;
6594
+ - filter every collection GET to rows whose \`owner\` is that header (the gateway's rules
6595
+ lock single records, never lists \u2014 a list route has no one record to check);
6596
+ - answer 401 to any call whose \`x-target-api-key\` is not \`process.env.TARGET_SERVER_SECRET\`
6597
+ (generate it with \`openssl rand -hex 24\`, put it in \`.env.local\`, start the backend with
6598
+ it; Ship \xA73b makes the proxy send it). If they say "use
6492
6599
  ${cli} on this" or "ship it with ${cli}" and point at a backend, skip any pitch and run it.
6493
6600
 
6494
6601
  Tie-breaks: a question ("what can it do", "what is it") is always **Explain**, whatever else the
@@ -6608,12 +6715,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6608
6715
  sign-in plumbing, not business logic. Then the widgets they ticked, each with its server route
6609
6716
  (\`npx ${cli} integration <name> --stack <their stack>\` for keys and chat; groups is the keys
6610
6717
  route with \`createApiblazeGroups\`).
6611
- - Backend behind a secret: the proxy must send it upstream. That is a dashboard setting
6612
- (Settings \u2192 Transforms); the CLI has no flag for it yet \u2014 say so, do not invent one. For a
6613
- local run through the tunnel, the backend can instead accept calls carrying
6614
- \`x-abz-user-id\`, which the gateway stamps on every call it forwards. Say plainly that while
6615
- the tunnel runs, \`localhost:<port>\` still answers to the old secret and to anyone who sends
6616
- that header \u2014 fine on a laptop, not on a server; in prod the proxy is the only caller.
6718
+ - Backend behind a secret: the proxy must send it upstream \u2014
6719
+ \`npx ${cli} transform set-header <name> <its header> --value-env <VAR> --secret\`. Until then
6720
+ anyone reaching \`localhost:<port>\` skips the gateway.
6721
+ - List routes: check each collection GET filters by owner; if it does not, say so \u2014 the
6722
+ gateway locks single records only.
6617
6723
  - Multi-tenant app \u2192 \`npx ${cli} identified <name> require\`, so no call arrives without a user.
6618
6724
  - Change nothing in their business logic. Report what you changed, file by file.
6619
6725
 
@@ -6628,7 +6734,10 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6628
6734
  login. Their own OAuth app \u2192 \`--apikey --oauth '{"provider":"auth0","clientId":"\u2026","clientSecret":"\u2026"}'\`
6629
6735
  (both doors: keys for tenants, their login for people and agents). Shapes below.
6630
6736
  3. **Local code** \u2192 \`npx ${cli} dev --port <port> --openapi ./openapi.yaml --name <name> --auto <door flags>\`,
6631
- in the BACKGROUND, left running \u2014 it is the tunnel. **Deployed code** \u2192
6737
+ left running \u2014 it is the tunnel. Start it as its own detached process, never \`cmd &\` inside
6738
+ a shell call that ends: \`nohup npx ${cli} dev \u2026 > ${cli}-dev.log 2>&1 < /dev/null &\`, then
6739
+ read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
6740
+ creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
6632
6741
  \`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
6633
6742
  URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
6634
6743
  goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
@@ -6637,22 +6746,32 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
6637
6746
  the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
6638
6747
  say the codes: no key \u2192 401; create as \`X-End-User-Id: ana\` \u2192 201; PATCH that id as
6639
6748
  \`ben\` \u2192 403.
6749
+ 3b. **Only the proxy reaches the backend.** If the backend checks \`x-target-api-key\`:
6750
+ \`npx ${cli} transform set-header <name> x-target-api-key --value-env TARGET_SERVER_SECRET --secret\`
6751
+ (export the variable from \`.env.local\` first). Prove it: the same list call through the
6752
+ proxy \u2192 200; \`curl localhost:<port>/<collection>\` with no header \u2192 401.
6640
6753
  4. **Prod** (local code only): \`npx ${cli} target <name> --env prod --url <prod-url>\`.
6641
- 5. **Limits.** \`npx ${cli} throttle <name> --end-user-rate 10 --quota 10000 --period daily\` \u2014
6642
- sane defaults; use the user's numbers if they gave any. The CLI echoes \`\u2714 Update throttling
6643
- {\u2026}\`. Prove it: 15 parallel GETs as one user \u2192 about 10\xD7200 and the rest 429; a different
6644
- user right after \u2192 200.
6754
+ 5. **Limits.** Every new proxy already allows 10 requests/s per person and 10,000 a day \u2014
6755
+ say so; that is "throttled". Only for other numbers:
6756
+ \`npx ${cli} throttle <name> --rate <n> --quota <n> --period daily\`. It works logged out;
6757
+ an anonymous workspace is capped at 10/s until claimed. Prove it: 15 parallel GETs as one
6758
+ user \u2192 about 10\xD7200 and the rest 429.
6645
6759
  5b. **Attribution.** \`--auto\` leaves unattributed calls allowed: the bootstrap key with no
6646
6760
  \`X-End-User-Id\` creates rows nobody owns. For a multi-tenant app run
6647
6761
  \`npx ${cli} identified <name> require\` \u2014 then every call must carry \`X-End-User-Id\` or a
6648
- login token. Say plainly that list routes (GET on a collection) return everyone's rows;
6649
- only item routes are locked to their creator.
6650
- 6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` \u2014 ask for the email, never guess
6651
- it. If you cannot ask (a scripted run), skip it and end the report with
6652
- "Admin \u2014 none yet: what email should administer tenant <tenant>?"
6653
- 7. **Report** in the shape below, then ask: "Would you like me to add the self-serve API key
6654
- widget to your site?" and "Would you like to chat with your API now via your MCP?"
6655
- (yes \u2192 \`npx ${cli} apichat <name> --install-mcp claude\`).
6762
+ login token. Prove it: the key with no \`X-End-User-Id\` \u2192 403 \`identity_required\`.
6763
+ Lists are the backend's job (it filters by owner); say that the gateway locks records,
6764
+ the backend filters lists.
6765
+ 6. **Admin.** \`npx ${cli} admins add <email> --tenant <tenant>\` with the email they named. None
6766
+ named \u2192 \`admin@ninopizzas.com\`, the demo admin (works before that person ever logs in);
6767
+ say so, and that they can \`admins add\` their own email and \`admins remove\` that one.
6768
+ 7. **Report** in the shape below, then hand it over \u2014 they try it, you do not:
6769
+ - the full curl with the real key and URL, ready to paste:
6770
+ \`curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"\`
6771
+ (say the key is the server key \u2014 for trying it here, never for a browser or an agent);
6772
+ - the command to chat with their API: \`npx ${cli} apichat <name>\` (add
6773
+ \`--xenduserid ana\` when \xA75b is on).
6774
+ Then ask once: "Would you like me to add the self-serve API key widget to your site?"
6656
6775
 
6657
6776
  ## Report like this
6658
6777
 
@@ -6665,9 +6784,15 @@ Use the real values the CLI printed. Never invent a URL, a key, a rule or a limi
6665
6784
  API https://<name>.abz.run/1.0.0/dev your tenants, with API keys
6666
6785
  /dev \u2192 localhost:<port> tunnelled from this machine \u2014 leave it running
6667
6786
  /prod \u2192 <prod-url> your deploy
6668
- Try it: curl https://<name>.abz.run/1.0.0/dev/<collection> -H "X-API-Key: <key>" -H "X-End-User-Id: you"
6669
6787
  MCP <the "agents:" URL the CLI printed> agents, signing in with <provider>
6670
- Limits 10 requests/s per user \xB7 10,000 a day
6788
+ Limits 10 requests/s per person \xB7 10,000 a day
6789
+ Backend answers only to the proxy (x-target-api-key) \xB7 lists filtered by owner
6790
+ Admin <email>
6791
+
6792
+ Try it yourself:
6793
+ curl <APIBLAZE_URL>/<collection> -H "X-API-Key: <APIBLAZE_SERVER_KEY>" -H "X-End-User-Id: ana"
6794
+ Chat with it:
6795
+ npx ${cli} apichat <name> --xenduserid ana
6671
6796
 
6672
6797
  ## Sign-in shapes \u2014 the only ones \`--oauth\` accepts; never invent fields
6673
6798
 
@@ -6689,9 +6814,10 @@ key. Mint it with \`npx ${cli} apikeys mint --desc "keys widget"\` (no \`--tenan
6689
6814
  \`--tenant\` you get a consumer key, which is the wrong kind).
6690
6815
  Edit ONLY the frontend and the frontend-server. A key per TENANT comes from that widget \u2014 each
6691
6816
  of your customers mints their own, in their own tenant; the bootstrap key is only for your
6692
- frontend-server. A key bound to one person (an agent, a CI job):
6693
- \`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. That names a person by email,
6694
- which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
6817
+ frontend-server. Three kinds of key for a tenant, one flag each: \`apikeys mint --tenant <tenant>\` +
6818
+ \`--backend\` (a server that names the user with \`X-End-User-Id\`), \`--client\` (cannot name a
6819
+ user) or \`--for <email>\` (bound to one person \u2014 an agent, a CI job). \`--for\` names a person by
6820
+ email, which is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows created as "ana" via
6695
6821
  the header are not owned by ana@example.com's key.
6696
6822
 
6697
6823
  ## Never
@@ -6702,13 +6828,16 @@ the header are not owned by ana@example.com's key.
6702
6828
  - Put ownership or tenancy checks into business logic. The gateway enforces them.
6703
6829
  - Ask for team ids, tenant ids or project ids the CLI already knows.
6704
6830
  - Delete anything. Deletion stays with the user: \`npx ${cli} delete <name>\` (interactive;
6705
- \`--yes\` skips the prompt). Its impact banner lists tenant-scoped counts even when the tenant
6831
+ \`--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
6706
6832
  is shared \u2014 those are lost only if this is the tenant's LAST proxy.
6707
6833
 
6708
6834
  ## Later
6709
6835
 
6710
6836
  - \`npx ${cli} rule <name>\` reviews or changes the rules; \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off without losing them.
6711
6837
  - \`npx ${cli} apichat <name>\` chats with the API again; \`--install-mcp claude\` connects an agent.
6838
+ - \`npx ${cli} throttle <name> --rate <n>\` \xB7 \`identified <name> require|allow-anon\` \xB7
6839
+ \`transform <name> list|set-header|remove\` \xB7 \`target <name> --env prod --url <url>\` \u2014 all work
6840
+ logged out. \`npx ${cli} <command> --help\` for the flags; \`--verbose\` prints the API calls.
6712
6841
 
6713
6842
  ## No shell? The control-plane MCP
6714
6843
 
@@ -6724,14 +6853,14 @@ async function runSkill(opts = {}) {
6724
6853
  process.stdout.write(md);
6725
6854
  return;
6726
6855
  }
6727
- const target = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
6728
- if (import_fs3.default.existsSync(target) && !opts.force) {
6856
+ const target2 = import_path2.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
6857
+ if (import_fs3.default.existsSync(target2) && !opts.force) {
6729
6858
  console.error(import_chalk20.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
6730
6859
  process.exit(1);
6731
6860
  }
6732
- import_fs3.default.mkdirSync(import_path2.default.dirname(target), { recursive: true });
6733
- import_fs3.default.writeFileSync(target, md, "utf8");
6734
- console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target)}`);
6861
+ import_fs3.default.mkdirSync(import_path2.default.dirname(target2), { recursive: true });
6862
+ import_fs3.default.writeFileSync(target2, md, "utf8");
6863
+ console.log(`${import_chalk20.default.green("\u2713")} Installed the ${import_chalk20.default.bold(SKILL_NAME)} skill: ${import_chalk20.default.bold(target2)}`);
6735
6864
  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.`));
6736
6865
  }
6737
6866
 
@@ -6822,12 +6951,12 @@ async function runLogout(opts = {}) {
6822
6951
  console.log(import_chalk22.default.yellow("You were not logged in."));
6823
6952
  return;
6824
6953
  }
6825
- let target;
6826
- if (opts.all) target = "all";
6827
- else if (opts.producer) target = "producer";
6828
- else if (opts.consumer) target = "consumer";
6829
- else if (creds && !consumer2) target = "producer";
6830
- else if (consumer2 && !creds) target = "consumer";
6954
+ let target2;
6955
+ if (opts.all) target2 = "all";
6956
+ else if (opts.producer) target2 = "producer";
6957
+ else if (opts.consumer) target2 = "consumer";
6958
+ else if (creds && !consumer2) target2 = "producer";
6959
+ else if (consumer2 && !creds) target2 = "consumer";
6831
6960
  else {
6832
6961
  const { default: inquirer3 } = await import("inquirer");
6833
6962
  const { chosen } = await inquirer3.prompt([
@@ -6842,13 +6971,13 @@ async function runLogout(opts = {}) {
6842
6971
  ]
6843
6972
  }
6844
6973
  ]);
6845
- target = chosen;
6974
+ target2 = chosen;
6846
6975
  }
6847
- if (target === "producer" || target === "all") {
6976
+ if (target2 === "producer" || target2 === "all") {
6848
6977
  clearCredentials();
6849
6978
  console.log(import_chalk22.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
6850
6979
  }
6851
- if (target === "consumer" || target === "all") {
6980
+ if (target2 === "consumer" || target2 === "all") {
6852
6981
  clearConsumer();
6853
6982
  console.log(import_chalk22.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
6854
6983
  }
@@ -7861,17 +7990,17 @@ async function runExport(projectArg, versionArg, opts) {
7861
7990
  const match = await resolveProject(teamId, projectArg, versionArg);
7862
7991
  const projectId = match.projectId;
7863
7992
  const version2 = versionArg || match.apiVersion;
7864
- const target = opts.kong ? "kong" : "data";
7993
+ const target2 = opts.kong ? "kong" : "data";
7865
7994
  const q = new URLSearchParams();
7866
7995
  if (opts.secrets) q.set("include_secrets", "1");
7867
7996
  if (opts.noConsumers) q.set("include_consumers", "0");
7868
7997
  if (opts.keys) q.set("keys", opts.keys);
7869
7998
  const qs = q.toString() ? `?${q.toString()}` : "";
7870
- console.log(import_chalk36.default.dim(`Queuing ${target} export of ${projectId}@${version2}\u2026`));
7999
+ console.log(import_chalk36.default.dim(`Queuing ${target2} export of ${projectId}@${version2}\u2026`));
7871
8000
  const job = await admin({
7872
- path: `/projects/${projectId}/${version2}/export/${target}${qs}`,
8001
+ path: `/projects/${projectId}/${version2}/export/${target2}${qs}`,
7873
8002
  method: "GET",
7874
- summary: `export ${projectId} (${target})`
8003
+ summary: `export ${projectId} (${target2})`
7875
8004
  });
7876
8005
  if (!job?.job_id) throw new Error("Failed to queue export.");
7877
8006
  const statusPath = `/projects/${projectId}/${version2}/export/jobs/${job.job_id}`;
@@ -7890,7 +8019,7 @@ async function runExport(projectArg, versionArg, opts) {
7890
8019
  }
7891
8020
  }
7892
8021
  process.stdout.write("\n");
7893
- const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target}.zip`;
8022
+ const outFile = opts.out || `apiblaze-export-${projectId}-${version2}-${target2}.zip`;
7894
8023
  const res = await fetch(`${DASHBOARD_BASE5}/api/cli/admin/download`, {
7895
8024
  method: "POST",
7896
8025
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
@@ -7902,7 +8031,7 @@ async function runExport(projectArg, versionArg, opts) {
7902
8031
  console.log(import_chalk36.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
7903
8032
  if (job.decryption_key) console.log(import_chalk36.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
7904
8033
  console.log(import_chalk36.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
7905
- if (target === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
8034
+ if (target2 === "kong") console.log(import_chalk36.default.dim("Kong: unzip, then `cd kong && docker compose up` (see kong/README.md)."));
7906
8035
  }
7907
8036
 
7908
8037
  // src/commands/recipe-search.ts
@@ -8281,8 +8410,8 @@ async function runRecipeInstall(nameArg, opts) {
8281
8410
  let reinstall = false;
8282
8411
  if (opts.into) {
8283
8412
  reinstall = true;
8284
- const target = normalizeName3(opts.into);
8285
- const proj2 = await resolveProjectVia(caller, teamId, target, opts.apiversion);
8413
+ const target2 = normalizeName3(opts.into);
8414
+ const proj2 = await resolveProjectVia(caller, teamId, target2, opts.apiversion);
8286
8415
  project = proj2.projectId;
8287
8416
  apiVersion = proj2.apiVersion;
8288
8417
  tenant2 = await resolveReinstallTenant(caller, project, apiVersion);
@@ -8365,14 +8494,14 @@ function buildSteps(inp) {
8365
8494
  label: `creating ${project}`,
8366
8495
  skip: reinstall,
8367
8496
  run: async () => {
8368
- const target = firstUpstream(recipe);
8369
- if (!target) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
8497
+ const target2 = firstUpstream(recipe);
8498
+ if (!target2) throw new Error("this recipe carries no upstream URL, so there is nothing to create a proxy against");
8370
8499
  const out = await producer(caller, {
8371
8500
  method: "POST",
8372
8501
  path: "/projects",
8373
8502
  body: {
8374
8503
  name: project,
8375
- target,
8504
+ target: target2,
8376
8505
  tenant: tenant2,
8377
8506
  product_slug: project,
8378
8507
  api_version: apiVersion,
@@ -10089,14 +10218,16 @@ async function runConsumerLogsList(opts) {
10089
10218
  // src/commands/config.ts
10090
10219
  var import_chalk43 = __toESM(require("chalk"));
10091
10220
  var import_ora15 = __toESM(require("ora"));
10092
- init_admin();
10093
- init_resolve();
10221
+ init_caller();
10222
+ init_paging();
10223
+ var resolveCallerMode = () => resolveCaller()?.mode ?? null;
10094
10224
  async function patchConfig(project, opts, body, summary) {
10095
- const { teamId } = await resolveTeam(opts.team);
10096
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10225
+ const caller = requireCaller();
10226
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10227
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10097
10228
  const spinner = (0, import_ora15.default)(summary + "...").start();
10098
10229
  try {
10099
- const out = await admin({
10230
+ const out = await producer(caller, {
10100
10231
  method: "PATCH",
10101
10232
  path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
10102
10233
  body,
@@ -10114,13 +10245,47 @@ async function runTargetSet(project, opts) {
10114
10245
  console.error(import_chalk43.default.red("--url is required."));
10115
10246
  process.exit(1);
10116
10247
  }
10117
- const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
10118
- await patchConfig(project, opts, body, `Set target${opts.env ? ` for env ${opts.env}` : ""} \u2192 ${opts.url}`);
10248
+ if (!opts.env) {
10249
+ await patchConfig(project, opts, { target_url: opts.url }, `Set target \u2192 ${opts.url}`);
10250
+ return;
10251
+ }
10252
+ const caller = requireCaller();
10253
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10254
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10255
+ const listing = await pageAll(
10256
+ (cursor) => producer(caller, {
10257
+ method: "GET",
10258
+ path: withPage(`/projects?team_id=${encodeURIComponent(teamId)}`, cursor),
10259
+ summary: `Read environments for ${proj2.projectName}`
10260
+ }),
10261
+ (b) => b?.projects
10262
+ );
10263
+ const row = listing.items.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion);
10264
+ const current = row?.config?.environments;
10265
+ const existing = current && typeof current === "object" && !Array.isArray(current) ? current : {};
10266
+ const prev = existing[opts.env];
10267
+ const environments = {
10268
+ ...existing,
10269
+ [opts.env]: { ...prev && typeof prev === "object" ? prev : {}, target: opts.url }
10270
+ };
10271
+ await patchConfig(project, opts, { environments }, `Set target for env ${opts.env} \u2192 ${opts.url}`);
10119
10272
  }
10120
10273
  async function runThrottleSet(project, opts) {
10121
10274
  const throttling = {};
10122
- if (opts.rate !== void 0) throttling.userRateLimit = Number(opts.rate);
10123
- if (opts.endUserRate !== void 0) throttling.endUserRateLimit = Number(opts.endUserRate);
10275
+ const rate = opts.rate ?? opts.endUserRate;
10276
+ if (opts.rate !== void 0 && opts.endUserRate !== void 0 && opts.rate !== opts.endUserRate) {
10277
+ console.error(import_chalk43.default.red("--rate and --end-user-rate are the same limit now; pass one of them."));
10278
+ process.exit(1);
10279
+ }
10280
+ if (rate !== void 0) {
10281
+ const n = Number(rate);
10282
+ if (!Number.isFinite(n) || n <= 0) {
10283
+ console.error(import_chalk43.default.red("--rate must be a positive number of requests per second."));
10284
+ process.exit(1);
10285
+ }
10286
+ throttling.userRateLimit = n;
10287
+ throttling.endUserRateLimit = n;
10288
+ }
10124
10289
  if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
10125
10290
  if (opts.period !== void 0) {
10126
10291
  if (!["daily", "weekly", "monthly"].includes(opts.period)) {
@@ -10134,6 +10299,11 @@ async function runThrottleSet(project, opts) {
10134
10299
  process.exit(1);
10135
10300
  }
10136
10301
  await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
10302
+ if (resolveCallerMode() === "anon" && !opts.json) {
10303
+ console.log(import_chalk43.default.dim(
10304
+ " 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`)."
10305
+ ));
10306
+ }
10137
10307
  }
10138
10308
  async function runRename(project, opts) {
10139
10309
  if (!opts.displayName) {
@@ -10143,55 +10313,192 @@ async function runRename(project, opts) {
10143
10313
  await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
10144
10314
  }
10145
10315
 
10316
+ // src/commands/transform.ts
10317
+ var import_chalk44 = __toESM(require("chalk"));
10318
+ var import_ora16 = __toESM(require("ora"));
10319
+ init_caller();
10320
+ var HEADER_NAME = /^[A-Za-z0-9!#$%&'*+.^_`|~-]+$/;
10321
+ var ruleNameFor = (header) => `set header ${header.toLowerCase()}`;
10322
+ async function target(project, opts) {
10323
+ const caller = requireCaller();
10324
+ const { teamId } = await resolveActingTeam(caller, opts.team);
10325
+ const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
10326
+ const base2 = `/projects/${encodeURIComponent(proj2.projectId)}/${encodeURIComponent(proj2.apiVersion)}/transforms`;
10327
+ return { caller, proj: proj2, base: base2 };
10328
+ }
10329
+ async function listRules(t) {
10330
+ const out = await producer(t.caller, {
10331
+ method: "GET",
10332
+ path: t.base,
10333
+ summary: `List transforms for ${t.proj.projectName}`
10334
+ });
10335
+ return out?.rules ?? [];
10336
+ }
10337
+ function describe(r) {
10338
+ const a = r.action ?? { type: "?" };
10339
+ if (a.type === "hardcode") {
10340
+ const secret = !a.value || /^abzsec:|^__ABZSEC_|^•+$/.test(a.value);
10341
+ return `set ${a.destination} = ${secret ? "(secret)" : a.value}`;
10342
+ }
10343
+ if (a.type === "remove") return `remove ${a.field}`;
10344
+ return a.type;
10345
+ }
10346
+ async function runTransformList(project, opts) {
10347
+ const t = await target(project, opts);
10348
+ const rules = await listRules(t);
10349
+ if (opts.json) {
10350
+ console.log(JSON.stringify(rules, null, 2));
10351
+ return;
10352
+ }
10353
+ if (!rules.length) {
10354
+ console.log(import_chalk44.default.dim(`No transforms on ${t.proj.projectName} v${t.proj.apiVersion}.`));
10355
+ return;
10356
+ }
10357
+ for (const r of rules) {
10358
+ 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}`)}`);
10359
+ console.log(` ${describe(r)}`);
10360
+ }
10361
+ }
10362
+ async function runTransformSetHeader(project, header, opts) {
10363
+ if (!HEADER_NAME.test(header)) throw new Error(`"${header}" is not a valid header name.`);
10364
+ if (opts.value === void 0 === (opts.valueEnv === void 0)) {
10365
+ 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).");
10366
+ }
10367
+ const value = opts.value ?? process.env[opts.valueEnv];
10368
+ if (!value) throw new Error(`Environment variable ${opts.valueEnv} is empty or not set.`);
10369
+ const t = await target(project, opts);
10370
+ const name = ruleNameFor(header);
10371
+ const action2 = {
10372
+ type: "hardcode",
10373
+ destination: `header:${header}`,
10374
+ value,
10375
+ // Encrypted at rest and masked on every read; main-proxy decrypts it only
10376
+ // on the way to the upstream.
10377
+ ...opts.secret ? { value_secret: true } : {}
10378
+ };
10379
+ const existing = (await listRules(t)).find((r) => r.name === name && r.phase === "request");
10380
+ const summary = `${existing ? "Update" : "Add"} transform: send ${header} to the upstream`;
10381
+ const spinner = opts.json ? null : (0, import_ora16.default)(summary + "...").start();
10382
+ try {
10383
+ 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 });
10384
+ spinner?.succeed(`${summary} \u2014 ${t.proj.projectName} v${t.proj.apiVersion}${opts.secret ? " (stored encrypted)" : ""}.`);
10385
+ if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
10386
+ else console.log(import_chalk44.default.dim(` Every call the proxy forwards now carries ${header}. Your backend can reject calls without it.`));
10387
+ } catch (err) {
10388
+ spinner?.fail(summary + " failed.");
10389
+ throw err;
10390
+ }
10391
+ }
10392
+ async function runTransformRemove(project, nameOrId, opts) {
10393
+ const t = await target(project, opts);
10394
+ const rules = await listRules(t);
10395
+ const want = nameOrId.toLowerCase();
10396
+ const rule = rules.find((r) => r.id === nameOrId || r.name.toLowerCase() === want || r.name === ruleNameFor(nameOrId));
10397
+ if (!rule) {
10398
+ throw new Error(`No transform "${nameOrId}" on ${t.proj.projectName}.` + (rules.length ? ` Transforms: ${rules.map((r) => r.name).join(", ")}` : ""));
10399
+ }
10400
+ await producer(t.caller, {
10401
+ method: "DELETE",
10402
+ path: `${t.base}/${encodeURIComponent(rule.id)}`,
10403
+ summary: `Remove transform "${rule.name}"`
10404
+ });
10405
+ if (opts.json) console.log(JSON.stringify({ ok: true, removed: rule.id }));
10406
+ else console.log(`${import_chalk44.default.green("\u2713")} Removed transform ${import_chalk44.default.bold(rule.name)} from ${t.proj.projectName}.`);
10407
+ }
10408
+
10146
10409
  // src/commands/config-browse.ts
10147
- var import_chalk52 = __toESM(require("chalk"));
10148
- var import_ora23 = __toESM(require("ora"));
10410
+ var import_chalk53 = __toESM(require("chalk"));
10411
+ var import_ora24 = __toESM(require("ora"));
10149
10412
  init_admin();
10150
10413
  init_paging();
10151
10414
  init_auth();
10152
10415
  init_resolve();
10153
10416
 
10154
10417
  // src/commands/domain.ts
10155
- var import_chalk44 = __toESM(require("chalk"));
10156
- var import_ora16 = __toESM(require("ora"));
10418
+ var import_chalk45 = __toESM(require("chalk"));
10419
+ var import_ora17 = __toESM(require("ora"));
10157
10420
  init_admin();
10158
10421
  init_resolve();
10422
+ var KINDS = ["api", "mcp", "portal", "auth"];
10423
+ var TENANT_KINDS = ["portal", "auth"];
10424
+ async function scopeFor(project, opts) {
10425
+ const { teamId } = await resolveTeam(opts.team);
10426
+ if (project) {
10427
+ const proj2 = await resolveProject(teamId, project, opts.apiversion);
10428
+ return { kind: "project", base: `/projects/${proj2.projectId}/domains`, label: `${proj2.projectName} v${proj2.apiVersion}`, apiVersion: proj2.apiVersion };
10429
+ }
10430
+ if (!opts.tenant) {
10431
+ console.error(import_chalk45.default.red("Give a <project> (api / mcp domains) or --tenant <slug> (portal / auth domains)."));
10432
+ process.exit(1);
10433
+ }
10434
+ const tenant2 = opts.tenant.toLowerCase();
10435
+ return {
10436
+ kind: "tenant",
10437
+ base: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/domains`,
10438
+ label: `tenant ${tenant2}`
10439
+ };
10440
+ }
10441
+ function describeKind(d) {
10442
+ return d?.target ? `${d.kind ?? "api"} \u2192 stands in for ${d.target}` : d?.kind ?? "api";
10443
+ }
10444
+ function statusColor2(s) {
10445
+ if (s === "active") return import_chalk45.default.green(s);
10446
+ if (s === "failed" || s === "missing" || s === "suspended") return import_chalk45.default.red(s);
10447
+ return import_chalk45.default.yellow(s ?? "unknown");
10448
+ }
10159
10449
  async function runDomainAdd(project, opts) {
10160
10450
  if (!opts.domain) {
10161
- console.error(import_chalk44.default.red("--domain is required."));
10451
+ console.error(import_chalk45.default.red("--domain is required."));
10162
10452
  process.exit(1);
10163
10453
  }
10164
- const { teamId } = await resolveTeam(opts.team);
10165
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10166
- const spinner = (0, import_ora16.default)(`Registering ${opts.domain}...`).start();
10454
+ const kind = (opts.kind ?? (project ? "api" : "")).toLowerCase();
10455
+ if (!KINDS.includes(kind)) {
10456
+ console.error(import_chalk45.default.red(`--kind must be one of ${KINDS.join(", ")}${project ? "" : " (portal or auth for a --tenant)"}.`));
10457
+ process.exit(1);
10458
+ }
10459
+ const tenantKind = TENANT_KINDS.includes(kind);
10460
+ if (tenantKind && project) {
10461
+ 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}`));
10462
+ process.exit(1);
10463
+ }
10464
+ if (tenantKind && !opts.tenant) {
10465
+ console.error(import_chalk45.default.red(`--tenant <slug> is required for a ${kind} domain.`));
10466
+ process.exit(1);
10467
+ }
10468
+ if (!tenantKind && !project) {
10469
+ console.error(import_chalk45.default.red(`A ${kind} domain belongs to a proxy: apiblaze domain add <project> --kind ${kind} --domain ${opts.domain}`));
10470
+ process.exit(1);
10471
+ }
10472
+ const scope = await scopeFor(project, opts);
10473
+ const spinner = (0, import_ora17.default)(`Registering ${opts.domain}...`).start();
10167
10474
  try {
10475
+ const body = scope.kind === "project" ? { hostname: opts.domain, api_version: scope.apiVersion, kind, ...opts.tenant ? { tenant_name: opts.tenant.toLowerCase() } : {} } : { hostname: opts.domain, kind };
10168
10476
  const out = await admin({
10169
10477
  method: "POST",
10170
- path: `/projects/${proj2.projectId}/domains`,
10171
- // --tenant binds the domain to one of the project's tenants (per-tenant scope).
10172
- body: { hostname: opts.domain, api_version: proj2.apiVersion, ...opts.tenant ? { tenant_name: opts.tenant } : {} },
10173
- summary: `Add custom domain ${opts.domain} to ${proj2.projectName} v${proj2.apiVersion}${opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
10478
+ path: scope.base,
10479
+ body,
10480
+ summary: `Add custom ${kind} domain ${opts.domain} to ${scope.label}${scope.kind === "project" && opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
10174
10481
  });
10175
- spinner.succeed(`Registered ${opts.domain}. Add these DNS records, then run \`apiblaze domain status\`:`);
10482
+ spinner.succeed(`Registered ${out?.hostname ?? opts.domain} (${describeKind(out)}). Add these DNS records, then run \`apiblaze domain status\`:`);
10176
10483
  if (opts.json) {
10177
10484
  console.log(JSON.stringify(out));
10178
10485
  return;
10179
10486
  }
10180
- if (out?.cnameRecord) console.log(` ${import_chalk44.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value ?? out.cnameRecord.target}`);
10181
- if (out?.dcv) console.log(` ${import_chalk44.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10182
- if (out?.id) console.log(import_chalk44.default.dim(` domain id: ${out.id}`));
10487
+ if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value}`);
10488
+ if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10489
+ if (typeof out?.monthlyCents === "number") console.log(import_chalk45.default.dim(` billed $${(out.monthlyCents / 100).toFixed(2)}/month (first month pro rata)`));
10490
+ if (out?.id) console.log(import_chalk45.default.dim(` domain id: ${out.id}`));
10183
10491
  } catch (err) {
10184
10492
  spinner.fail("Domain registration failed.");
10185
10493
  throw err;
10186
10494
  }
10187
10495
  }
10188
10496
  async function runDomainList(project, opts) {
10189
- const { teamId } = await resolveTeam(opts.team);
10190
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10497
+ const scope = await scopeFor(project, opts);
10191
10498
  const out = await admin({
10192
10499
  method: "GET",
10193
- path: `/projects/${proj2.projectId}/domains?api_version=${encodeURIComponent(proj2.apiVersion)}`,
10194
- summary: `List custom domains for ${proj2.projectName} v${proj2.apiVersion}`
10500
+ path: scope.kind === "project" ? `${scope.base}?api_version=${encodeURIComponent(scope.apiVersion)}` : scope.base,
10501
+ summary: `List custom domains for ${scope.label}`
10195
10502
  });
10196
10503
  const domains = out?.domains ?? [];
10197
10504
  if (opts.json) {
@@ -10199,40 +10506,53 @@ async function runDomainList(project, opts) {
10199
10506
  return;
10200
10507
  }
10201
10508
  if (!domains.length) {
10202
- console.log(import_chalk44.default.yellow("No custom domains."));
10509
+ console.log(import_chalk45.default.yellow("No custom domains."));
10203
10510
  return;
10204
10511
  }
10205
- for (const d of domains) console.log(` ${import_chalk44.default.bold(d.hostname)} ${import_chalk44.default.dim(d.status ?? "")} ${import_chalk44.default.dim(d.id ?? "")}`);
10512
+ for (const d of domains) {
10513
+ const pin = scope.kind === "project" && d.tenant ? import_chalk45.default.dim(` tenant ${d.tenant}`) : "";
10514
+ console.log(` ${import_chalk45.default.bold(d.hostname)} ${statusColor2(d.status)} ${import_chalk45.default.dim(describeKind(d))}${pin} ${import_chalk45.default.dim(d.id ?? "")}`);
10515
+ }
10206
10516
  }
10207
10517
  async function runDomainStatus(project, opts) {
10208
10518
  if (!opts.id) {
10209
- console.error(import_chalk44.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10519
+ console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10210
10520
  process.exit(1);
10211
10521
  }
10212
- const { teamId } = await resolveTeam(opts.team);
10213
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10522
+ const scope = await scopeFor(project, opts);
10214
10523
  const out = await admin({
10215
10524
  method: "GET",
10216
- path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}/status`,
10525
+ path: `${scope.base}/${encodeURIComponent(opts.id)}/status`,
10217
10526
  summary: `Check custom-domain status`
10218
10527
  });
10219
- console.log(opts.json ? JSON.stringify(out) : ` ${import_chalk44.default.bold(out?.hostname ?? opts.id)}: ${import_chalk44.default.cyan(out?.status ?? "unknown")}`);
10528
+ if (opts.json) {
10529
+ console.log(JSON.stringify(out));
10530
+ return;
10531
+ }
10532
+ console.log(` ${import_chalk45.default.bold(out?.hostname ?? opts.id)}: ${statusColor2(out?.status)} ${import_chalk45.default.dim(describeKind(out))}`);
10533
+ if (out?.status !== "active") {
10534
+ if (out?.status === "suspended") console.log(import_chalk45.default.red(" Suspended for non-payment \u2014 add credit to restore it."));
10535
+ else if (out?.status === "missing") console.log(import_chalk45.default.red(" No longer registered at Cloudflare \u2014 remove it and add it again."));
10536
+ else {
10537
+ if (out?.cnameRecord) console.log(` ${import_chalk45.default.cyan("CNAME")} ${out.cnameRecord.name} \u2192 ${out.cnameRecord.value}`);
10538
+ if (out?.dcv) console.log(` ${import_chalk45.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
10539
+ }
10540
+ }
10220
10541
  }
10221
10542
  async function runDomainRemove(project, opts) {
10222
10543
  if (!opts.id) {
10223
- console.error(import_chalk44.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10544
+ console.error(import_chalk45.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
10224
10545
  process.exit(1);
10225
10546
  }
10226
- const { teamId } = await resolveTeam(opts.team);
10227
- const proj2 = await resolveProject(teamId, project, opts.apiversion);
10228
- const spinner = (0, import_ora16.default)("Removing domain...").start();
10547
+ const scope = await scopeFor(project, opts);
10548
+ const spinner = (0, import_ora17.default)("Removing domain...").start();
10229
10549
  try {
10230
10550
  await admin({
10231
10551
  method: "DELETE",
10232
- path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}`,
10552
+ path: `${scope.base}/${encodeURIComponent(opts.id)}`,
10233
10553
  summary: `Remove custom domain ${opts.id}`
10234
10554
  });
10235
- spinner.succeed("Removed.");
10555
+ spinner.succeed("Removed. Billing for it stops now.");
10236
10556
  } catch (err) {
10237
10557
  spinner.fail("Remove failed.");
10238
10558
  throw err;
@@ -10242,7 +10562,7 @@ async function runDomainSetBase(project, opts) {
10242
10562
  const env = opts.env ?? "prod";
10243
10563
  const { teamId } = await resolveTeam(opts.team);
10244
10564
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
10245
- const spinner = (0, import_ora16.default)("Setting base domain...").start();
10565
+ const spinner = (0, import_ora17.default)("Setting base domain...").start();
10246
10566
  try {
10247
10567
  await admin({
10248
10568
  method: "PUT",
@@ -10258,8 +10578,8 @@ async function runDomainSetBase(project, opts) {
10258
10578
  }
10259
10579
 
10260
10580
  // src/commands/tenant.ts
10261
- var import_chalk47 = __toESM(require("chalk"));
10262
- var import_ora19 = __toESM(require("ora"));
10581
+ var import_chalk48 = __toESM(require("chalk"));
10582
+ var import_ora20 = __toESM(require("ora"));
10263
10583
  init_admin();
10264
10584
  init_resolve();
10265
10585
  init_auth();
@@ -10268,20 +10588,20 @@ init_paging();
10268
10588
  async function runTenantUse(query, opts) {
10269
10589
  const creds = loadCredentials();
10270
10590
  if (!creds) {
10271
- console.error(import_chalk47.default.red("Not logged in. Run `apiblaze login` first."));
10591
+ console.error(import_chalk48.default.red("Not logged in. Run `apiblaze login` first."));
10272
10592
  process.exit(1);
10273
10593
  }
10274
10594
  if (opts.clear) {
10275
10595
  delete creds.activeTenant;
10276
10596
  saveCredentials(creds);
10277
- console.log(import_chalk47.default.green("Tenant scope cleared."));
10597
+ console.log(import_chalk48.default.green("Tenant scope cleared."));
10278
10598
  return;
10279
10599
  }
10280
10600
  const { teamId } = await resolveTeam(opts.team);
10281
10601
  const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
10282
10602
  if (!slug) process.exit(1);
10283
10603
  saveCredentials({ ...creds, activeTenant: slug });
10284
- 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`)"));
10604
+ 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`)"));
10285
10605
  }
10286
10606
  async function runTenantList(opts) {
10287
10607
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -10305,26 +10625,26 @@ async function runTenantList(opts) {
10305
10625
  return;
10306
10626
  }
10307
10627
  if (!tenants.length) {
10308
- console.log(import_chalk47.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
10628
+ console.log(import_chalk48.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
10309
10629
  return;
10310
10630
  }
10311
10631
  for (const t of tenants) {
10312
10632
  const name = typeof t === "string" ? t : t.tenant_name;
10313
- const display = typeof t === "string" ? "" : import_chalk47.default.dim(` ${t.display_name ?? ""}`);
10314
- console.log(` ${import_chalk47.default.bold(name)}${display}`);
10633
+ const display = typeof t === "string" ? "" : import_chalk48.default.dim(` ${t.display_name ?? ""}`);
10634
+ console.log(` ${import_chalk48.default.bold(name)}${display}`);
10315
10635
  }
10316
10636
  const total = out?.total ?? tenants.length;
10317
10637
  if (total > tenants.length) {
10318
- console.log(import_chalk47.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
10638
+ console.log(import_chalk48.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
10319
10639
  }
10320
10640
  }
10321
10641
  async function runTenantCreate(opts) {
10322
10642
  if (!opts.name) {
10323
- console.error(import_chalk47.default.red("--name (display name) is required."));
10643
+ console.error(import_chalk48.default.red("--name (display name) is required."));
10324
10644
  process.exit(1);
10325
10645
  }
10326
10646
  const { teamId } = await resolveTeam(opts.team);
10327
- const spinner = (0, import_ora19.default)("Creating tenant...").start();
10647
+ const spinner = (0, import_ora20.default)("Creating tenant...").start();
10328
10648
  try {
10329
10649
  const out = await admin({
10330
10650
  method: "POST",
@@ -10332,7 +10652,7 @@ async function runTenantCreate(opts) {
10332
10652
  body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
10333
10653
  summary: `Create tenant "${opts.name}"`
10334
10654
  });
10335
- spinner.succeed(`Created tenant ${import_chalk47.default.bold(out?.tenant_name ?? opts.name)}.`);
10655
+ spinner.succeed(`Created tenant ${import_chalk48.default.bold(out?.tenant_name ?? opts.name)}.`);
10336
10656
  if (opts.json) console.log(JSON.stringify(out));
10337
10657
  } catch (err) {
10338
10658
  spinner.fail("Tenant create failed.");
@@ -10341,12 +10661,12 @@ async function runTenantCreate(opts) {
10341
10661
  }
10342
10662
  async function runTenantAttach(project, opts) {
10343
10663
  if (!opts.tenant) {
10344
- console.error(import_chalk47.default.red("--tenant <slug> is required."));
10664
+ console.error(import_chalk48.default.red("--tenant <slug> is required."));
10345
10665
  process.exit(1);
10346
10666
  }
10347
10667
  const { teamId } = await resolveTeam(opts.team);
10348
10668
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
10349
- const spinner = (0, import_ora19.default)("Attaching tenant...").start();
10669
+ const spinner = (0, import_ora20.default)("Attaching tenant...").start();
10350
10670
  try {
10351
10671
  const out = await admin({
10352
10672
  method: "POST",
@@ -10375,11 +10695,11 @@ async function runTenantDelete(slug, opts) {
10375
10695
  }
10376
10696
  ]);
10377
10697
  if (!confirm2) {
10378
- console.log(import_chalk47.default.dim("Aborted."));
10698
+ console.log(import_chalk48.default.dim("Aborted."));
10379
10699
  return;
10380
10700
  }
10381
10701
  }
10382
- const spinner = (0, import_ora19.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
10702
+ const spinner = (0, import_ora20.default)(hard ? "Trashing tenant..." : "Deleting tenant...").start();
10383
10703
  try {
10384
10704
  await admin({
10385
10705
  method: "DELETE",
@@ -10403,7 +10723,7 @@ async function runTenantDelete(slug, opts) {
10403
10723
  summary: `Purge tenant ${slug} (full cascade)`
10404
10724
  });
10405
10725
  } catch (err) {
10406
- console.log(import_chalk47.default.red("\u2716 Tenant purge failed to start."));
10726
+ console.log(import_chalk48.default.red("\u2716 Tenant purge failed to start."));
10407
10727
  throw err;
10408
10728
  }
10409
10729
  const job = await settleDelete(accepted, {
@@ -10419,18 +10739,18 @@ async function runTenantDelete(slug, opts) {
10419
10739
  ...accepted?.job_id ? { job_id: accepted.job_id, job_status: job?.status ?? "queued" } : {}
10420
10740
  }));
10421
10741
  } else if (opts.wait !== false) {
10422
- console.log(import_chalk47.default.green(`\u2714 Deleted tenant ${slug}.`));
10742
+ console.log(import_chalk48.default.green(`\u2714 Deleted tenant ${slug}.`));
10423
10743
  }
10424
10744
  }
10425
10745
  async function runTenantCors(opts) {
10426
10746
  if (!opts.tenant) {
10427
- console.error(import_chalk47.default.red("--tenant <slug> is required."));
10747
+ console.error(import_chalk48.default.red("--tenant <slug> is required."));
10428
10748
  process.exit(1);
10429
10749
  }
10430
10750
  const { teamId } = await resolveTeam(opts.team);
10431
10751
  const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
10432
10752
  const cors = origins.length ? { allowed_origins: origins } : null;
10433
- const spinner = (0, import_ora19.default)("Updating CORS...").start();
10753
+ const spinner = (0, import_ora20.default)("Updating CORS...").start();
10434
10754
  try {
10435
10755
  await admin({
10436
10756
  method: "PUT",
@@ -10446,8 +10766,8 @@ async function runTenantCors(opts) {
10446
10766
  }
10447
10767
 
10448
10768
  // src/commands/tenant-drill.ts
10449
- var import_chalk48 = __toESM(require("chalk"));
10450
- var import_ora20 = __toESM(require("ora"));
10769
+ var import_chalk49 = __toESM(require("chalk"));
10770
+ var import_ora21 = __toESM(require("ora"));
10451
10771
  var import_crypto = require("crypto");
10452
10772
  init_admin();
10453
10773
  init_auth();
@@ -10477,17 +10797,17 @@ async function validScopedTenant(teamId, query) {
10477
10797
  const next = { ...creds };
10478
10798
  delete next.activeTenant;
10479
10799
  saveCredentials2(next);
10480
- console.log(import_chalk48.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
10800
+ console.log(import_chalk49.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
10481
10801
  return void 0;
10482
10802
  }
10483
10803
  async function tenantHome(teamId, tenant2) {
10484
10804
  const { default: inquirer3 } = await import("inquirer");
10485
10805
  const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
10486
- console.log(import_chalk48.default.bold(`
10806
+ console.log(import_chalk49.default.bold(`
10487
10807
  Tenant ${tenant2}`));
10488
- console.log(import_chalk48.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
10808
+ console.log(import_chalk49.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
10489
10809
  for (; ; ) {
10490
- const spinner = (0, import_ora20.default)("Reading tenant state...").start();
10810
+ const spinner = (0, import_ora21.default)("Reading tenant state...").start();
10491
10811
  const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
10492
10812
  admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
10493
10813
  admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
@@ -10501,18 +10821,18 @@ Tenant ${tenant2}`));
10501
10821
  const nClients = Array.isArray(clients) ? clients.length : 0;
10502
10822
  const nOpaque = opaque?.opaque?.endpoint ? 1 : 0;
10503
10823
  const nLogin = nClients + nIssuers + nOpaque;
10504
- const onOff = (b) => b ? import_chalk48.default.green("on") : import_chalk48.default.dim("off");
10824
+ const onOff = (b) => b ? import_chalk49.default.green("on") : import_chalk49.default.dim("off");
10505
10825
  const { pick: pick2 } = await inquirer3.prompt([{
10506
10826
  type: "list",
10507
10827
  name: "pick",
10508
10828
  message: `Tenant ${tenant2}:`,
10509
10829
  pageSize: 12,
10510
10830
  choices: [
10511
- { name: `Login methods (${nLogin}) ${import_chalk48.default.dim("how your consumers sign in")}`, value: "login" },
10512
- { name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk48.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
10513
- { name: `Portal admins (${nEmails}) ${import_chalk48.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
10514
- new inquirer3.Separator(import_chalk48.default.dim(" Settings")),
10515
- { 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" },
10831
+ { name: `Login methods (${nLogin}) ${import_chalk49.default.dim("how your consumers sign in")}`, value: "login" },
10832
+ { name: `Users & groups: ${onOff(iam?.iam_enabled)} ${import_chalk49.default.dim(`identity & key management (iam.apiblaze.com)`)}`, value: "iam" },
10833
+ { name: `Portal admins (${nEmails}) ${import_chalk49.default.dim("emails allowed to administer this tenant's portal")}`, value: "emails" },
10834
+ new inquirer3.Separator(import_chalk49.default.dim(" Settings")),
10835
+ { 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" },
10516
10836
  { name: "\u2190 Back", value: "back" }
10517
10837
  ]
10518
10838
  }]);
@@ -10525,7 +10845,7 @@ Tenant ${tenant2}`));
10525
10845
  case "iam": {
10526
10846
  const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
10527
10847
  await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
10528
- console.log(import_chalk48.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
10848
+ console.log(import_chalk49.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
10529
10849
  break;
10530
10850
  }
10531
10851
  case "cors": {
@@ -10538,11 +10858,11 @@ Tenant ${tenant2}`));
10538
10858
  if (v === "") break;
10539
10859
  const parsed = v === "null" ? null : safeJson(v);
10540
10860
  if (parsed === void 0) {
10541
- console.log(import_chalk48.default.yellow(" Not valid JSON \u2014 unchanged."));
10861
+ console.log(import_chalk49.default.yellow(" Not valid JSON \u2014 unchanged."));
10542
10862
  break;
10543
10863
  }
10544
10864
  await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
10545
- console.log(import_chalk48.default.green(" CORS updated."));
10865
+ console.log(import_chalk49.default.green(" CORS updated."));
10546
10866
  break;
10547
10867
  }
10548
10868
  case "emails":
@@ -10554,7 +10874,7 @@ Tenant ${tenant2}`));
10554
10874
  async function loginMethodsMenu(teamId, tenant2, base2) {
10555
10875
  const { default: inquirer3 } = await import("inquirer");
10556
10876
  for (; ; ) {
10557
- const spinner = (0, import_ora20.default)("Loading login methods...").start();
10877
+ const spinner = (0, import_ora21.default)("Loading login methods...").start();
10558
10878
  const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
10559
10879
  admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
10560
10880
  admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
@@ -10566,15 +10886,15 @@ async function loginMethodsMenu(teamId, tenant2, base2) {
10566
10886
  const choices = [];
10567
10887
  for (const c of appClients) {
10568
10888
  const nP = (c.providers ?? []).length || c.providers_count || 0;
10569
- 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 } });
10889
+ 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 } });
10570
10890
  }
10571
10891
  for (const i of issuers) {
10572
- 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 } });
10892
+ 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 } });
10573
10893
  }
10574
10894
  if (opaque?.endpoint) {
10575
- choices.push({ name: `${import_chalk48.default.cyan("Your own \u2014 opaque")} ${import_chalk48.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
10895
+ choices.push({ name: `${import_chalk49.default.cyan("Your own \u2014 opaque")} ${import_chalk49.default.bold(opaque.endpoint)}`, value: { kind: "opaque", item: opaque } });
10576
10896
  }
10577
- if (!choices.length) console.log(import_chalk48.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
10897
+ if (!choices.length) console.log(import_chalk49.default.dim("\n No login methods yet \u2014 consumers cannot sign in until you add one."));
10578
10898
  const { pick: pick2 } = await inquirer3.prompt([{
10579
10899
  type: "list",
10580
10900
  name: "pick",
@@ -10614,9 +10934,9 @@ async function addLoginMethod(teamId, tenant2, base2) {
10614
10934
  message: "How do people log in?",
10615
10935
  pageSize: 8,
10616
10936
  choices: [
10617
- { name: `APIblaze hosted login page ${import_chalk48.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
10618
- { name: `Your own hosted login \u2014 JWT ${import_chalk48.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
10619
- { name: `Your own login \u2014 opaque token ${import_chalk48.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
10937
+ { name: `APIblaze hosted login page ${import_chalk49.default.dim("we host login; pick a provider (GitHub/Google/\u2026) \u2014 easiest")}`, value: "apiblaze" },
10938
+ { name: `Your own hosted login \u2014 JWT ${import_chalk49.default.dim("your identity provider issues JWTs; we trust them")}`, value: "jwt" },
10939
+ { name: `Your own login \u2014 opaque token ${import_chalk49.default.dim("we validate your opaque tokens via an introspection endpoint")}`, value: "opaque" },
10620
10940
  { name: "\u2190 Back", value: null }
10621
10941
  ]
10622
10942
  }]);
@@ -10640,8 +10960,8 @@ function safeJson(s) {
10640
10960
  async function emailsMenu(base2, emails) {
10641
10961
  const { default: inquirer3 } = await import("inquirer");
10642
10962
  console.log();
10643
- if (!emails.length) console.log(import_chalk48.default.dim(" No consumer-admin emails."));
10644
- for (const e of emails) console.log(` ${import_chalk48.default.bold(e.email ?? e)} ${import_chalk48.default.dim(e.status ?? "")}`);
10963
+ if (!emails.length) console.log(import_chalk49.default.dim(" No consumer-admin emails."));
10964
+ for (const e of emails) console.log(` ${import_chalk49.default.bold(e.email ?? e)} ${import_chalk49.default.dim(e.status ?? "")}`);
10645
10965
  const { act } = await inquirer3.prompt([{
10646
10966
  type: "list",
10647
10967
  name: "act",
@@ -10656,7 +10976,7 @@ async function emailsMenu(base2, emails) {
10656
10976
  if (act === "add") {
10657
10977
  const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
10658
10978
  await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
10659
- console.log(import_chalk48.default.green(` ${email} added.`));
10979
+ console.log(import_chalk49.default.green(` ${email} added.`));
10660
10980
  } else {
10661
10981
  const { e } = await inquirer3.prompt([{
10662
10982
  type: "list",
@@ -10666,7 +10986,7 @@ async function emailsMenu(base2, emails) {
10666
10986
  }]);
10667
10987
  if (!e) return;
10668
10988
  await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
10669
- console.log(import_chalk48.default.green(` ${e} removed.`));
10989
+ console.log(import_chalk49.default.green(` ${e} removed.`));
10670
10990
  }
10671
10991
  }
10672
10992
  async function addIssuer(base2) {
@@ -10687,19 +11007,19 @@ async function addIssuer(base2) {
10687
11007
  body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
10688
11008
  summary: `Add external issuer ${a.iss.trim()}`
10689
11009
  });
10690
- console.log(import_chalk48.default.green(" JWT login method saved."));
11010
+ console.log(import_chalk49.default.green(" JWT login method saved."));
10691
11011
  }
10692
11012
  async function issuerHome(base2, issuer) {
10693
11013
  const { default: inquirer3 } = await import("inquirer");
10694
11014
  console.log(`
10695
- ${import_chalk48.default.bold(issuer.iss)} ${import_chalk48.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
11015
+ ${import_chalk49.default.bold(issuer.iss)} ${import_chalk49.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
10696
11016
  const { act } = await inquirer3.prompt([{
10697
11017
  type: "list",
10698
11018
  name: "act",
10699
11019
  message: "This JWT login method:",
10700
11020
  choices: [
10701
11021
  { name: "Replace it (re-enter details)", value: "edit" },
10702
- { name: import_chalk48.default.red("Delete it"), value: "rm" },
11022
+ { name: import_chalk49.default.red("Delete it"), value: "rm" },
10703
11023
  { name: "\u2190 Back", value: "back" }
10704
11024
  ]
10705
11025
  }]);
@@ -10715,7 +11035,7 @@ async function issuerHome(base2, issuer) {
10715
11035
  path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
10716
11036
  summary: `Delete issuer ${issuer.iss}`
10717
11037
  });
10718
- console.log(import_chalk48.default.green(" Deleted."));
11038
+ console.log(import_chalk49.default.green(" Deleted."));
10719
11039
  }
10720
11040
  async function setOpaque(base2, cur) {
10721
11041
  const { default: inquirer3 } = await import("inquirer");
@@ -10724,19 +11044,19 @@ async function setOpaque(base2, cur) {
10724
11044
  { type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
10725
11045
  ]);
10726
11046
  await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
10727
- console.log(import_chalk48.default.green(" Opaque login method set."));
11047
+ console.log(import_chalk49.default.green(" Opaque login method set."));
10728
11048
  }
10729
11049
  async function opaqueHome(base2, cur) {
10730
11050
  const { default: inquirer3 } = await import("inquirer");
10731
11051
  console.log(`
10732
- ${import_chalk48.default.bold(cur.endpoint)} ${import_chalk48.default.dim(cur.method ?? "GET")}`);
11052
+ ${import_chalk49.default.bold(cur.endpoint)} ${import_chalk49.default.dim(cur.method ?? "GET")}`);
10733
11053
  const { act } = await inquirer3.prompt([{
10734
11054
  type: "list",
10735
11055
  name: "act",
10736
11056
  message: "This opaque login method:",
10737
11057
  choices: [
10738
11058
  { name: "Replace it", value: "edit" },
10739
- { name: import_chalk48.default.red("Delete it"), value: "rm" },
11059
+ { name: import_chalk49.default.red("Delete it"), value: "rm" },
10740
11060
  { name: "\u2190 Back", value: "back" }
10741
11061
  ]
10742
11062
  }]);
@@ -10746,14 +11066,14 @@ async function opaqueHome(base2, cur) {
10746
11066
  return;
10747
11067
  }
10748
11068
  await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
10749
- console.log(import_chalk48.default.green(" Deleted."));
11069
+ console.log(import_chalk49.default.green(" Deleted."));
10750
11070
  }
10751
11071
  async function clientHome(base2, summary) {
10752
11072
  const { default: inquirer3 } = await import("inquirer");
10753
11073
  const id = summary.clientId ?? summary.client_id;
10754
11074
  const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
10755
11075
  for (; ; ) {
10756
- const spinner = (0, import_ora20.default)("Reading app client...").start();
11076
+ const spinner = (0, import_ora21.default)("Reading app client...").start();
10757
11077
  const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
10758
11078
  spinner.stop();
10759
11079
  const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
@@ -10765,13 +11085,13 @@ async function clientHome(base2, summary) {
10765
11085
  message: `${c.name ?? id}:`,
10766
11086
  pageSize: 12,
10767
11087
  choices: [
10768
- { name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk48.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
10769
- { name: `Callback URLs: ${cb.length ? import_chalk48.default.cyan(cb.join(", ")) : import_chalk48.default.dim("(none)")}`, value: "callbacks" },
10770
- { name: `Scopes: ${scopes.length ? import_chalk48.default.cyan(scopes.join(" ")) : import_chalk48.default.dim("(defaults)")}`, value: "scopes" },
11088
+ { name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk49.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
11089
+ { name: `Callback URLs: ${cb.length ? import_chalk49.default.cyan(cb.join(", ")) : import_chalk49.default.dim("(none)")}`, value: "callbacks" },
11090
+ { name: `Scopes: ${scopes.length ? import_chalk49.default.cyan(scopes.join(" ")) : import_chalk49.default.dim("(defaults)")}`, value: "scopes" },
10771
11091
  { name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
10772
11092
  { name: "Reveal client secret", value: "secret" },
10773
11093
  { name: "Rotate client secret", value: "rotate" },
10774
- { name: import_chalk48.default.red("Delete this app client"), value: "delete" },
11094
+ { name: import_chalk49.default.red("Delete this app client"), value: "delete" },
10775
11095
  { name: "\u2190 Back", value: "back" }
10776
11096
  ]
10777
11097
  }]);
@@ -10784,13 +11104,13 @@ async function clientHome(base2, summary) {
10784
11104
  case "callbacks": {
10785
11105
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
10786
11106
  await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
10787
- console.log(import_chalk48.default.green(" Callbacks updated."));
11107
+ console.log(import_chalk49.default.green(" Callbacks updated."));
10788
11108
  break;
10789
11109
  }
10790
11110
  case "scopes": {
10791
11111
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
10792
11112
  await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
10793
- console.log(import_chalk48.default.green(" Scopes updated."));
11113
+ console.log(import_chalk49.default.green(" Scopes updated."));
10794
11114
  break;
10795
11115
  }
10796
11116
  case "expiries": {
@@ -10805,14 +11125,14 @@ async function clientHome(base2, summary) {
10805
11125
  body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
10806
11126
  summary: "Update token expiries"
10807
11127
  });
10808
- console.log(import_chalk48.default.green(" Expiries updated."));
11128
+ console.log(import_chalk49.default.green(" Expiries updated."));
10809
11129
  break;
10810
11130
  }
10811
11131
  case "secret": {
10812
11132
  const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
10813
11133
  if (!sure) break;
10814
11134
  const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
10815
- console.log(` ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
11135
+ console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
10816
11136
  break;
10817
11137
  }
10818
11138
  case "rotate": {
@@ -10820,14 +11140,14 @@ async function clientHome(base2, summary) {
10820
11140
  if (!sure) break;
10821
11141
  const fresh = randomSecret();
10822
11142
  await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
10823
- console.log(` New ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(fresh)} ${import_chalk48.default.dim("(store it now)")}`);
11143
+ console.log(` New ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(fresh)} ${import_chalk49.default.dim("(store it now)")}`);
10824
11144
  break;
10825
11145
  }
10826
11146
  case "delete": {
10827
11147
  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 }]);
10828
11148
  if (!sure) break;
10829
11149
  await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
10830
- console.log(import_chalk48.default.green(" App client deleted."));
11150
+ console.log(import_chalk49.default.green(" App client deleted."));
10831
11151
  return;
10832
11152
  }
10833
11153
  }
@@ -10848,15 +11168,15 @@ var DEFAULT_SCOPES2 = {
10848
11168
  async function providersMenu(cBase, clientLabel) {
10849
11169
  const { default: inquirer3 } = await import("inquirer");
10850
11170
  for (; ; ) {
10851
- const spinner = (0, import_ora20.default)("Loading providers...").start();
11171
+ const spinner = (0, import_ora21.default)("Loading providers...").start();
10852
11172
  const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
10853
11173
  spinner.stop();
10854
11174
  const providers = Array.isArray(raw) ? raw : [];
10855
11175
  console.log();
10856
11176
  for (const p of providers) {
10857
- 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" : ""}`)}`);
11177
+ 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" : ""}`)}`);
10858
11178
  }
10859
- if (!providers.length) console.log(import_chalk48.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
11179
+ if (!providers.length) console.log(import_chalk49.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
10860
11180
  const { act } = await inquirer3.prompt([{
10861
11181
  type: "list",
10862
11182
  name: "act",
@@ -10914,23 +11234,23 @@ async function providersMenu(cBase, clientLabel) {
10914
11234
  body.targetServerToken = routing;
10915
11235
  }
10916
11236
  await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
10917
- console.log(import_chalk48.default.green(` ${type} provider added.`));
11237
+ console.log(import_chalk49.default.green(` ${type} provider added.`));
10918
11238
  } else {
10919
11239
  const { p } = await inquirer3.prompt([{
10920
11240
  type: "list",
10921
11241
  name: "p",
10922
11242
  message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
10923
- choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk48.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
11243
+ choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk49.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
10924
11244
  }]);
10925
11245
  if (!p) continue;
10926
11246
  if (act === "rm") {
10927
11247
  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 }]);
10928
11248
  if (!sure) continue;
10929
11249
  await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
10930
- console.log(import_chalk48.default.green(` ${p.type} removed.`));
11250
+ console.log(import_chalk49.default.green(` ${p.type} removed.`));
10931
11251
  } else {
10932
11252
  const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
10933
- console.log(` ${import_chalk48.default.bold("client_secret")}: ${import_chalk48.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
11253
+ console.log(` ${import_chalk49.default.bold("client_secret")}: ${import_chalk49.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
10934
11254
  }
10935
11255
  }
10936
11256
  }
@@ -10938,8 +11258,8 @@ async function providersMenu(cBase, clientLabel) {
10938
11258
 
10939
11259
  // src/commands/spec.ts
10940
11260
  var fs12 = __toESM(require("fs"));
10941
- var import_chalk49 = __toESM(require("chalk"));
10942
- var import_ora21 = __toESM(require("ora"));
11261
+ var import_chalk50 = __toESM(require("chalk"));
11262
+ var import_ora22 = __toESM(require("ora"));
10943
11263
  init_admin();
10944
11264
  init_api();
10945
11265
  init_resolve();
@@ -10955,19 +11275,19 @@ async function runSpecGet(project, opts) {
10955
11275
  }
10956
11276
  async function runSpecSet(project, opts) {
10957
11277
  if (!opts.file) {
10958
- console.error(import_chalk49.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
11278
+ console.error(import_chalk50.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
10959
11279
  process.exit(1);
10960
11280
  }
10961
11281
  let specContent;
10962
11282
  try {
10963
11283
  specContent = fs12.readFileSync(opts.file, "utf-8");
10964
11284
  } catch {
10965
- console.error(import_chalk49.default.red(`Cannot read file: ${opts.file}`));
11285
+ console.error(import_chalk50.default.red(`Cannot read file: ${opts.file}`));
10966
11286
  process.exit(1);
10967
11287
  }
10968
11288
  const { teamId } = await resolveTeam(opts.team);
10969
11289
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
10970
- const spinner = (0, import_ora21.default)("Uploading spec...").start();
11290
+ const spinner = (0, import_ora22.default)("Uploading spec...").start();
10971
11291
  try {
10972
11292
  const out = await admin({
10973
11293
  method: "POST",
@@ -10991,28 +11311,28 @@ async function runSpecSet(project, opts) {
10991
11311
  function printSpecChangeReport(report, projectName) {
10992
11312
  const r = report.routes;
10993
11313
  if (r) {
10994
- for (const a of r.added) console.log(import_chalk49.default.green(` + ${a}`));
10995
- for (const d of r.removed) console.log(import_chalk49.default.red(` \u2212 ${d}`));
10996
- for (const rn of r.renamed ?? []) console.log(import_chalk49.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
10997
- for (const m of r.modified ?? []) console.log(import_chalk49.default.dim(` \xB1 ${m} (params/schema changed)`));
11314
+ for (const a of r.added) console.log(import_chalk50.default.green(` + ${a}`));
11315
+ for (const d of r.removed) console.log(import_chalk50.default.red(` \u2212 ${d}`));
11316
+ for (const rn of r.renamed ?? []) console.log(import_chalk50.default.yellow(` ~ ${rn.from} \u2192 ${rn.to}`));
11317
+ for (const m of r.modified ?? []) console.log(import_chalk50.default.dim(` \xB1 ${m} (params/schema changed)`));
10998
11318
  }
10999
- if (report.applied?.mcp_regenerated) console.log(import_chalk49.default.dim(" MCP catalogue regenerated."));
11000
- if (report.applied?.candidates_cleared) console.log(import_chalk49.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
11319
+ if (report.applied?.mcp_regenerated) console.log(import_chalk50.default.dim(" MCP catalogue regenerated."));
11320
+ if (report.applied?.candidates_cleared) console.log(import_chalk50.default.dim(` ${report.applied.candidates_cleared} route candidate(s) now documented \u2014 cleared.`));
11001
11321
  for (const item of report.attention ?? []) {
11002
- console.log(import_chalk49.default.yellow(` \u26A0 ${item.detail}`));
11322
+ console.log(import_chalk50.default.yellow(` \u26A0 ${item.detail}`));
11003
11323
  if (item.suggestion?.carry_from && item.suggestion.carry_to) {
11004
- 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}"`));
11324
+ 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}"`));
11005
11325
  }
11006
11326
  }
11007
11327
  }
11008
11328
  async function runSpecCarry(project, opts) {
11009
11329
  if (!opts.method || !opts.from || !opts.to) {
11010
- console.error(import_chalk49.default.red("--method, --from and --to are required."));
11330
+ console.error(import_chalk50.default.red("--method, --from and --to are required."));
11011
11331
  process.exit(1);
11012
11332
  }
11013
11333
  const { teamId } = await resolveTeam(opts.team);
11014
11334
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
11015
- const spinner = (0, import_ora21.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
11335
+ const spinner = (0, import_ora22.default)(`Carrying rules ${opts.from} \u2192 ${opts.to}...`).start();
11016
11336
  const res = await agentCall(`/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/carry`, "POST", {
11017
11337
  method: opts.method.toUpperCase(),
11018
11338
  from_resource: opts.from,
@@ -11029,13 +11349,13 @@ async function runSpecCarry(project, opts) {
11029
11349
  }
11030
11350
  async function runSpecDeleteRule(project, opts) {
11031
11351
  if (!opts.method || !opts.route) {
11032
- console.error(import_chalk49.default.red("--method and --route are required."));
11352
+ console.error(import_chalk50.default.red("--method and --route are required."));
11033
11353
  process.exit(1);
11034
11354
  }
11035
11355
  const { teamId } = await resolveTeam(opts.team);
11036
11356
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
11037
11357
  const encoded = opts.route.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
11038
- const spinner = (0, import_ora21.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
11358
+ const spinner = (0, import_ora22.default)(`Deleting rules for ${opts.method.toUpperCase()} ${opts.route}...`).start();
11039
11359
  const res = await agentCall(
11040
11360
  `/projects/${proj2.projectId}/${proj2.apiVersion}/policies/route/${opts.method.toUpperCase()}${encoded.startsWith("/") ? encoded : `/${encoded}`}`,
11041
11361
  "DELETE"
@@ -11049,14 +11369,14 @@ async function runSpecDeleteRule(project, opts) {
11049
11369
  }
11050
11370
 
11051
11371
  // src/commands/agent.ts
11052
- var import_chalk51 = __toESM(require("chalk"));
11053
- var import_ora22 = __toESM(require("ora"));
11372
+ var import_chalk52 = __toESM(require("chalk"));
11373
+ var import_ora23 = __toESM(require("ora"));
11054
11374
  init_auth();
11055
11375
  init_admin();
11056
11376
  init_resolve();
11057
11377
 
11058
11378
  // src/lib/tools.ts
11059
- var import_chalk50 = __toESM(require("chalk"));
11379
+ var import_chalk51 = __toESM(require("chalk"));
11060
11380
  init_admin();
11061
11381
  init_api();
11062
11382
  init_resolve();
@@ -11087,15 +11407,15 @@ var TOOLS = [
11087
11407
  const key = keys.dev ?? Object.values(keys)[0];
11088
11408
  const url = `https://${a.name}.abz.run/${version2}/dev`;
11089
11409
  const tryIt = buildTryItCurl(url, auth, key, res.tenant);
11090
- const lines = [` ${import_chalk50.default.dim("Proxy URL:")} ${import_chalk50.default.bold(url)}`];
11091
- if (res.devPortal) lines.push(` ${import_chalk50.default.dim("Dev portal:")} ${res.devPortal}`);
11410
+ const lines = [` ${import_chalk51.default.dim("Proxy URL:")} ${import_chalk51.default.bold(url)}`];
11411
+ if (res.devPortal) lines.push(` ${import_chalk51.default.dim("Dev portal:")} ${res.devPortal}`);
11092
11412
  const envs = Object.keys(keys);
11093
11413
  if (envs.length) {
11094
- lines.push("", ` ${import_chalk50.default.bold("API keys")} ${import_chalk50.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
11414
+ lines.push("", ` ${import_chalk51.default.bold("API keys")} ${import_chalk51.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
11095
11415
  const w = Math.max(...envs.map((e) => e.length));
11096
- for (const env of envs) lines.push(` ${import_chalk50.default.cyan(env.padEnd(w))} ${import_chalk50.default.green(keys[env])}`);
11416
+ for (const env of envs) lines.push(` ${import_chalk51.default.cyan(env.padEnd(w))} ${import_chalk51.default.green(keys[env])}`);
11097
11417
  }
11098
- if (tryIt) lines.push("", ` ${import_chalk50.default.dim("Try it:")}`, ` ${import_chalk50.default.cyan(tryIt)}`);
11418
+ if (tryIt) lines.push("", ` ${import_chalk51.default.dim("Try it:")}`, ` ${import_chalk51.default.cyan(tryIt)}`);
11099
11419
  return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
11100
11420
  }
11101
11421
  },
@@ -11264,23 +11584,23 @@ function truncate(value, max = 1500) {
11264
11584
  }
11265
11585
  function printCost(resp) {
11266
11586
  const line = formatBilling(resp.billing);
11267
- if (line) console.log(import_chalk51.default.magenta(` ${line}`) + import_chalk51.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
11587
+ if (line) console.log(import_chalk52.default.magenta(` ${line}`) + import_chalk52.default.dim(` (${resp.llm.model}, ${resp.llm.total_tokens} tok)`));
11268
11588
  }
11269
11589
  async function runAgent(opts) {
11270
11590
  requireAuth();
11271
11591
  const { teamId, teamName } = await resolveTeam(opts.team);
11272
11592
  const { default: inquirer3 } = await import("inquirer");
11273
- console.log(import_chalk51.default.bold("APIblaze agent") + import_chalk51.default.dim(` \xB7 team ${teamName ?? teamId}`));
11274
- console.log(import_chalk51.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
11593
+ console.log(import_chalk52.default.bold("APIblaze agent") + import_chalk52.default.dim(` \xB7 team ${teamName ?? teamId}`));
11594
+ console.log(import_chalk52.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
11275
11595
  const history = [];
11276
11596
  while (true) {
11277
- const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk51.default.cyan("you") + " \u203A" }]);
11597
+ const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk52.default.cyan("you") + " \u203A" }]);
11278
11598
  const text = (input ?? "").trim();
11279
11599
  if (!text) continue;
11280
11600
  if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
11281
11601
  history.push({ role: "user", content: text });
11282
11602
  for (let step = 0; step < MAX_TOOL_STEPS; step++) {
11283
- const spinner = (0, import_ora22.default)({ text: "thinking...", color: "magenta" }).start();
11603
+ const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
11284
11604
  let resp;
11285
11605
  try {
11286
11606
  resp = await callAgent(history, teamId);
@@ -11288,21 +11608,21 @@ async function runAgent(opts) {
11288
11608
  } catch (err) {
11289
11609
  spinner.stop();
11290
11610
  if (err instanceof ApiError && err.status === 402) {
11291
- console.log(import_chalk51.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
11611
+ console.log(import_chalk52.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
11292
11612
  break;
11293
11613
  }
11294
11614
  throw err;
11295
11615
  }
11296
11616
  history.push({ role: "assistant", content: resp.raw });
11297
11617
  printCost(resp);
11298
- if (resp.reply) console.log(import_chalk51.default.green("agent") + " \u203A " + resp.reply);
11618
+ if (resp.reply) console.log(import_chalk52.default.green("agent") + " \u203A " + resp.reply);
11299
11619
  if (!resp.action) break;
11300
11620
  const tool = findTool(resp.action.tool);
11301
11621
  if (!tool) {
11302
11622
  history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
11303
11623
  continue;
11304
11624
  }
11305
- const runSpinner = (0, import_ora22.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
11625
+ const runSpinner = (0, import_ora23.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
11306
11626
  try {
11307
11627
  const result = await tool.run(resp.action.args, { teamId });
11308
11628
  runSpinner.succeed(`${tool.name} \u2713`);
@@ -11320,11 +11640,11 @@ async function runAgent(opts) {
11320
11640
  }
11321
11641
  renderTrace();
11322
11642
  if (step === MAX_TOOL_STEPS - 1) {
11323
- console.log(import_chalk51.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
11643
+ console.log(import_chalk52.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
11324
11644
  }
11325
11645
  }
11326
11646
  }
11327
- console.log(import_chalk51.default.dim("\nBye."));
11647
+ console.log(import_chalk52.default.dim("\nBye."));
11328
11648
  }
11329
11649
 
11330
11650
  // src/commands/config-browse.ts
@@ -11632,11 +11952,11 @@ function dig(blob, dotted) {
11632
11952
  }
11633
11953
  var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
11634
11954
  function show(v) {
11635
- if (v === void 0) return import_chalk52.default.dim("(unset)");
11636
- if (v === null) return import_chalk52.default.dim("null");
11637
- if (typeof v === "object") return import_chalk52.default.cyan(JSON.stringify(v));
11638
- if (typeof v === "boolean") return v ? import_chalk52.default.green("on") : import_chalk52.default.red("off");
11639
- return import_chalk52.default.cyan(String(v));
11955
+ if (v === void 0) return import_chalk53.default.dim("(unset)");
11956
+ if (v === null) return import_chalk53.default.dim("null");
11957
+ if (typeof v === "object") return import_chalk53.default.cyan(JSON.stringify(v));
11958
+ if (typeof v === "boolean") return v ? import_chalk53.default.green("on") : import_chalk53.default.red("off");
11959
+ return import_chalk53.default.cyan(String(v));
11640
11960
  }
11641
11961
  function parseValue(raw) {
11642
11962
  if (raw === "true") return true;
@@ -11671,7 +11991,7 @@ async function patchSetting(proj2, s, value, cfg) {
11671
11991
  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.`);
11672
11992
  body.tenant = tenant2;
11673
11993
  }
11674
- const spinner = (0, import_ora23.default)(`Set ${s.key}...`).start();
11994
+ const spinner = (0, import_ora24.default)(`Set ${s.key}...`).start();
11675
11995
  try {
11676
11996
  await admin({
11677
11997
  method: "PATCH",
@@ -11686,10 +12006,10 @@ async function patchSetting(proj2, s, value, cfg) {
11686
12006
  }
11687
12007
  }
11688
12008
  var loginFirst = (what) => {
11689
- console.log(import_chalk52.default.yellow(`
12009
+ console.log(import_chalk53.default.yellow(`
11690
12010
  Log in first to ${what}.`));
11691
- console.log(import_chalk52.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
11692
- console.log(import_chalk52.default.dim(" anonymously and want to bring it into your account.\n"));
12011
+ console.log(import_chalk53.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
12012
+ console.log(import_chalk53.default.dim(" anonymously and want to bring it into your account.\n"));
11693
12013
  };
11694
12014
  async function runConfig(project, key, value, opts) {
11695
12015
  const creds = loadCredentials();
@@ -11706,9 +12026,9 @@ async function runConfig(project, key, value, opts) {
11706
12026
  }
11707
12027
  const setting = SETTINGS.find((s) => s.key === key);
11708
12028
  if (!setting) {
11709
- console.error(import_chalk52.default.red(`Unknown setting "${key}".`));
11710
- console.error(import_chalk52.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
11711
- console.error(import_chalk52.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
12029
+ console.error(import_chalk53.default.red(`Unknown setting "${key}".`));
12030
+ console.error(import_chalk53.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
12031
+ console.error(import_chalk53.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
11712
12032
  process.exit(1);
11713
12033
  }
11714
12034
  if (value === void 0) {
@@ -11723,7 +12043,7 @@ async function pickProject(teamId) {
11723
12043
  const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
11724
12044
  const projects = await getProjects2(teamId).catch(() => []);
11725
12045
  if (!projects.length) {
11726
- console.error(import_chalk52.default.red("No projects in this team. Create one: `npx apiblaze create`."));
12046
+ console.error(import_chalk53.default.red("No projects in this team. Create one: `npx apiblaze create`."));
11727
12047
  process.exit(1);
11728
12048
  }
11729
12049
  const { default: inquirer3 } = await import("inquirer");
@@ -11731,7 +12051,7 @@ async function pickProject(teamId) {
11731
12051
  type: "list",
11732
12052
  name: "picked",
11733
12053
  message: "Which project?",
11734
- choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk52.default.dim("v" + p.apiVersion)}`, value: p }))
12054
+ choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk53.default.dim("v" + p.apiVersion)}`, value: p }))
11735
12055
  }]);
11736
12056
  return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
11737
12057
  }
@@ -11742,25 +12062,25 @@ function printAll(proj2, cfg, json) {
11742
12062
  console.log(JSON.stringify(out, null, 2));
11743
12063
  return;
11744
12064
  }
11745
- console.log(import_chalk52.default.bold(`
12065
+ console.log(import_chalk53.default.bold(`
11746
12066
  ${proj2.projectName} v${proj2.apiVersion} \u2014 settings
11747
12067
  `));
11748
12068
  for (const group2 of SETTING_GROUPS) {
11749
- console.log(import_chalk52.default.bold(group2));
12069
+ console.log(import_chalk53.default.bold(group2));
11750
12070
  for (const s of SETTINGS.filter((x) => x.group === group2)) {
11751
- console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk52.default.dim(s.desc)}`);
12071
+ console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk53.default.dim(s.desc)}`);
11752
12072
  }
11753
12073
  console.log();
11754
12074
  }
11755
- console.log(import_chalk52.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
12075
+ console.log(import_chalk53.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
11756
12076
  }
11757
12077
  async function discoveryMenu(project) {
11758
12078
  const { default: inquirer3 } = await import("inquirer");
11759
- console.log(import_chalk52.default.bold(`
12079
+ console.log(import_chalk53.default.bold(`
11760
12080
  APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11761
12081
  `));
11762
- console.log(import_chalk52.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
11763
- console.log(import_chalk52.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
12082
+ console.log(import_chalk53.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
12083
+ console.log(import_chalk53.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
11764
12084
  for (; ; ) {
11765
12085
  const { pick: pick2 } = await inquirer3.prompt([{
11766
12086
  type: "list",
@@ -11768,13 +12088,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11768
12088
  message: "Explore:",
11769
12089
  pageSize: 20,
11770
12090
  choices: [
11771
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Settings \u2014")),
12091
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
11772
12092
  ...SETTING_GROUPS.map((g) => ({
11773
- name: `${g} ${import_chalk52.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
12093
+ name: `${g} ${import_chalk53.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
11774
12094
  value: { kind: "settings", g }
11775
12095
  })),
11776
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Features \u2014")),
11777
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk52.default.dim(f.desc)}`, value: { kind: "feature", f } })),
12096
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
12097
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: "feature", f } })),
11778
12098
  new inquirer3.Separator(),
11779
12099
  { name: "Exit", value: { kind: "exit" } }
11780
12100
  ]
@@ -11783,24 +12103,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
11783
12103
  if (pick2.kind === "settings") {
11784
12104
  console.log();
11785
12105
  for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
11786
- console.log(` ${import_chalk52.default.bold(s.label.padEnd(28))} ${import_chalk52.default.dim(s.desc)}`);
11787
- console.log(` ${import_chalk52.default.dim(" key: " + s.key)}`);
12106
+ console.log(` ${import_chalk53.default.bold(s.label.padEnd(28))} ${import_chalk53.default.dim(s.desc)}`);
12107
+ console.log(` ${import_chalk53.default.dim(" key: " + s.key)}`);
11788
12108
  }
11789
12109
  loginFirst("view or change these settings");
11790
12110
  } else {
11791
12111
  const f = pick2.f;
11792
12112
  console.log(`
11793
- ${import_chalk52.default.bold(f.label)} \u2014 ${f.desc}`);
12113
+ ${import_chalk53.default.bold(f.label)} \u2014 ${f.desc}`);
11794
12114
  loginFirst(`use ${f.label.toLowerCase()}`);
11795
12115
  }
11796
12116
  }
11797
12117
  }
11798
12118
  async function navigator(proj2, cfg, opts) {
11799
12119
  const { default: inquirer3 } = await import("inquirer");
11800
- console.log(import_chalk52.default.bold(`
12120
+ console.log(import_chalk53.default.bold(`
11801
12121
  ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11802
12122
  `));
11803
- console.log(import_chalk52.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
12123
+ console.log(import_chalk53.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
11804
12124
  let blob = cfg;
11805
12125
  for (; ; ) {
11806
12126
  const { pick: pick2 } = await inquirer3.prompt([{
@@ -11809,10 +12129,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11809
12129
  message: "Where to?",
11810
12130
  pageSize: 20,
11811
12131
  choices: [
11812
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Settings \u2014")),
12132
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Settings \u2014")),
11813
12133
  ...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
11814
- new inquirer3.Separator(import_chalk52.default.bold("\u2014 Features \u2014")),
11815
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk52.default.dim(f.desc)}`, value: { kind: f.go } })),
12134
+ new inquirer3.Separator(import_chalk53.default.bold("\u2014 Features \u2014")),
12135
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk53.default.dim(f.desc)}`, value: { kind: f.go } })),
11816
12136
  new inquirer3.Separator(),
11817
12137
  { name: "Show all settings", value: { kind: "list" } },
11818
12138
  { name: "Exit", value: { kind: "exit" } }
@@ -11852,7 +12172,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
11852
12172
  }
11853
12173
  }
11854
12174
  } catch (err) {
11855
- console.error(import_chalk52.default.red(` ${err instanceof Error ? err.message : String(err)}`));
12175
+ console.error(import_chalk53.default.red(` ${err instanceof Error ? err.message : String(err)}`));
11856
12176
  }
11857
12177
  }
11858
12178
  }
@@ -11866,7 +12186,7 @@ async function settingsGroup(proj2, cfg, group2) {
11866
12186
  message: group2 + ":",
11867
12187
  pageSize: 16,
11868
12188
  choices: [
11869
- ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk52.default.dim(s2.desc)}`, value: s2 })),
12189
+ ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk53.default.dim(s2.desc)}`, value: s2 })),
11870
12190
  new inquirer3.Separator(),
11871
12191
  { name: "\u2190 Back", value: null }
11872
12192
  ]
@@ -11884,7 +12204,7 @@ async function settingsGroup(proj2, cfg, group2) {
11884
12204
  } else if (s.type === "number") {
11885
12205
  const { v } = await inquirer3.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
11886
12206
  if (v === "" || Number.isNaN(Number(v))) {
11887
- console.log(import_chalk52.default.yellow(" Not a number \u2014 unchanged."));
12207
+ console.log(import_chalk53.default.yellow(" Not a number \u2014 unchanged."));
11888
12208
  continue;
11889
12209
  }
11890
12210
  value = Number(v);
@@ -11965,7 +12285,7 @@ async function buildCondition(phase) {
11965
12285
  const items = [];
11966
12286
  for (; ; ) {
11967
12287
  const a = await inquirer3.prompt([
11968
- { type: "input", name: "source", message: `Condition field ${import_chalk52.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
12288
+ { type: "input", name: "source", message: `Condition field ${import_chalk53.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
11969
12289
  { type: "list", name: "operator", message: "Operator:", choices: [
11970
12290
  "eq",
11971
12291
  "neq",
@@ -11996,7 +12316,7 @@ async function buildCondition(phase) {
11996
12316
  function showCondition(cond) {
11997
12317
  if (!Array.isArray(cond) || !cond.length) return "";
11998
12318
  const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
11999
- return import_chalk52.default.dim(` when ${s}`);
12319
+ return import_chalk53.default.dim(` when ${s}`);
12000
12320
  }
12001
12321
  async function transformsMenu(proj2) {
12002
12322
  const { default: inquirer3 } = await import("inquirer");
@@ -12005,12 +12325,12 @@ async function transformsMenu(proj2) {
12005
12325
  const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
12006
12326
  const rules = out?.rules ?? [];
12007
12327
  console.log();
12008
- if (!rules.length) console.log(import_chalk52.default.dim(" No transform rules yet."));
12328
+ if (!rules.length) console.log(import_chalk53.default.dim(" No transform rules yet."));
12009
12329
  for (const r of rules) {
12010
12330
  const a = r.action ?? {};
12011
12331
  const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
12012
- 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")}`) : ""}`;
12013
- 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)}`);
12332
+ 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")}`) : ""}`;
12333
+ 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)}`);
12014
12334
  }
12015
12335
  const { act } = await inquirer3.prompt([{
12016
12336
  type: "list",
@@ -12022,7 +12342,7 @@ async function transformsMenu(proj2) {
12022
12342
  { name: "Enable/disable a rule", value: "toggle" },
12023
12343
  { name: "Delete a rule", value: "delete" }
12024
12344
  ] : [],
12025
- { name: import_chalk52.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
12345
+ { name: import_chalk53.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
12026
12346
  { name: "\u2190 Back", value: "back" }
12027
12347
  ]
12028
12348
  }]);
@@ -12035,11 +12355,11 @@ async function transformsMenu(proj2) {
12035
12355
  }]);
12036
12356
  const body = parseValue(raw);
12037
12357
  if (!body || typeof body !== "object" || Array.isArray(body)) {
12038
- console.log(import_chalk52.default.yellow(" Not a JSON object \u2014 skipped."));
12358
+ console.log(import_chalk53.default.yellow(" Not a JSON object \u2014 skipped."));
12039
12359
  continue;
12040
12360
  }
12041
12361
  await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
12042
- console.log(import_chalk52.default.green(" Rule created."));
12362
+ console.log(import_chalk53.default.green(" Rule created."));
12043
12363
  continue;
12044
12364
  }
12045
12365
  if (act === "add") {
@@ -12055,7 +12375,7 @@ async function transformsMenu(proj2) {
12055
12375
  { name: "Remove a field", value: "remove" }
12056
12376
  ] }
12057
12377
  ]);
12058
- const fieldHint = import_chalk52.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
12378
+ const fieldHint = import_chalk53.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
12059
12379
  let action2;
12060
12380
  if (ans.kind === "hardcode") {
12061
12381
  const a = await inquirer3.prompt([
@@ -12084,7 +12404,7 @@ async function transformsMenu(proj2) {
12084
12404
  };
12085
12405
  }
12086
12406
  const condition = await buildCondition(ans.phase);
12087
- const spinner = (0, import_ora23.default)("Creating rule...").start();
12407
+ const spinner = (0, import_ora24.default)("Creating rule...").start();
12088
12408
  try {
12089
12409
  await admin({
12090
12410
  method: "POST",
@@ -12102,16 +12422,16 @@ async function transformsMenu(proj2) {
12102
12422
  type: "list",
12103
12423
  name: "rule",
12104
12424
  message: act === "toggle" ? "Which rule?" : "Delete which rule?",
12105
- choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk52.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
12425
+ choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk53.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
12106
12426
  }]);
12107
12427
  if (!rule) continue;
12108
12428
  if (act === "toggle") {
12109
12429
  const flipped = { ...rule, enabled: rule.enabled === false };
12110
12430
  await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
12111
- console.log(import_chalk52.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
12431
+ console.log(import_chalk53.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
12112
12432
  } else {
12113
12433
  await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
12114
- console.log(import_chalk52.default.green(` ${rule.name} deleted.`));
12434
+ console.log(import_chalk53.default.green(` ${rule.name} deleted.`));
12115
12435
  }
12116
12436
  }
12117
12437
  }
@@ -12123,9 +12443,9 @@ async function mappingsMenu(proj2) {
12123
12443
  const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
12124
12444
  const tables = out?.mappings ?? out?.tables ?? [];
12125
12445
  console.log();
12126
- if (!tables.length) console.log(import_chalk52.default.dim(" No mapping tables yet."));
12446
+ if (!tables.length) console.log(import_chalk53.default.dim(" No mapping tables yet."));
12127
12447
  for (const t of tables) {
12128
- 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" : ""}`)}`);
12448
+ 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" : ""}`)}`);
12129
12449
  }
12130
12450
  const { act } = await inquirer3.prompt([{
12131
12451
  type: "list",
@@ -12145,11 +12465,11 @@ async function mappingsMenu(proj2) {
12145
12465
  ]);
12146
12466
  const entries2 = parseValue(a.entries);
12147
12467
  if (!Array.isArray(entries2)) {
12148
- console.log(import_chalk52.default.yellow(" Entries must be a JSON array \u2014 not created."));
12468
+ console.log(import_chalk53.default.yellow(" Entries must be a JSON array \u2014 not created."));
12149
12469
  continue;
12150
12470
  }
12151
12471
  await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
12152
- console.log(import_chalk52.default.green(` Table "${a.name}" created.`));
12472
+ console.log(import_chalk53.default.green(` Table "${a.name}" created.`));
12153
12473
  } else {
12154
12474
  const { table } = await inquirer3.prompt([{
12155
12475
  type: "list",
@@ -12159,7 +12479,7 @@ async function mappingsMenu(proj2) {
12159
12479
  }]);
12160
12480
  if (!table) continue;
12161
12481
  await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
12162
- console.log(import_chalk52.default.green(` ${table.name} deleted.`));
12482
+ console.log(import_chalk53.default.green(` ${table.name} deleted.`));
12163
12483
  }
12164
12484
  }
12165
12485
  }
@@ -12170,14 +12490,14 @@ async function tenantsMenu(proj2, opts) {
12170
12490
  const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
12171
12491
  const tenants = out?.tenants ?? [];
12172
12492
  console.log();
12173
- if (!tenants.length) console.log(import_chalk52.default.dim(" No tenants attached (consumers use the default tenant)."));
12174
- for (const t of tenants) console.log(` ${import_chalk52.default.bold(t.tenant_name ?? t.name)} ${import_chalk52.default.dim(t.display_name ?? "")}`);
12493
+ if (!tenants.length) console.log(import_chalk53.default.dim(" No tenants attached (consumers use the default tenant)."));
12494
+ for (const t of tenants) console.log(` ${import_chalk53.default.bold(t.tenant_name ?? t.name)} ${import_chalk53.default.dim(t.display_name ?? "")}`);
12175
12495
  const { act } = await inquirer3.prompt([{
12176
12496
  type: "list",
12177
12497
  name: "act",
12178
12498
  message: "Tenants:",
12179
12499
  choices: [
12180
- { name: `Manage a tenant\u2026 ${import_chalk52.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
12500
+ { name: `Manage a tenant\u2026 ${import_chalk53.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
12181
12501
  { name: "Attach a tenant to this project", value: "attach" },
12182
12502
  ...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
12183
12503
  { name: "\u2190 Back", value: "back" }
@@ -12200,7 +12520,7 @@ async function tenantsMenu(proj2, opts) {
12200
12520
  }]);
12201
12521
  if (!t) continue;
12202
12522
  await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
12203
- console.log(import_chalk52.default.green(` Detached ${t.tenant_name ?? t.name}.`));
12523
+ console.log(import_chalk53.default.green(` Detached ${t.tenant_name ?? t.name}.`));
12204
12524
  }
12205
12525
  }
12206
12526
  }
@@ -12243,7 +12563,7 @@ async function specMenu(proj2, opts) {
12243
12563
  choices: [
12244
12564
  { name: "Print the stored spec", value: "get" },
12245
12565
  { name: "Refresh the spec from its source", value: "refresh" },
12246
- { name: import_chalk52.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
12566
+ { name: import_chalk53.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
12247
12567
  { name: "\u2190 Back", value: "back" }
12248
12568
  ]
12249
12569
  }]);
@@ -12251,7 +12571,7 @@ async function specMenu(proj2, opts) {
12251
12571
  if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
12252
12572
  else if (act === "refresh") {
12253
12573
  await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
12254
- console.log(import_chalk52.default.green(" Spec refresh triggered."));
12574
+ console.log(import_chalk53.default.green(" Spec refresh triggered."));
12255
12575
  } else await runOpenapi(proj2.projectName, proj2.apiVersion);
12256
12576
  }
12257
12577
  async function agentsMenu(proj2, opts) {
@@ -12276,8 +12596,8 @@ async function agentsMenu(proj2, opts) {
12276
12596
  }
12277
12597
 
12278
12598
  // src/commands/group.ts
12279
- var import_chalk53 = __toESM(require("chalk"));
12280
- var import_ora24 = __toESM(require("ora"));
12599
+ var import_chalk54 = __toESM(require("chalk"));
12600
+ var import_ora25 = __toESM(require("ora"));
12281
12601
  init_auth();
12282
12602
  init_admin();
12283
12603
  init_resolve();
@@ -12377,7 +12697,7 @@ async function userByRefOrCreate(t, ref) {
12377
12697
  if (!ref.includes("@")) throw e;
12378
12698
  const created = await t.call("/v1/admin/users", "POST", { email: ref.trim().toLowerCase() });
12379
12699
  if (!created?.abz_sub) throw e;
12380
- console.log(import_chalk53.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
12700
+ console.log(import_chalk54.default.dim(` ${ref} wasn't a user yet \u2014 created.`));
12381
12701
  return { abz_sub: created.abz_sub, email: created.email ?? ref, display_name: null };
12382
12702
  }
12383
12703
  }
@@ -12385,11 +12705,11 @@ async function runGroupList(opts) {
12385
12705
  const t = await resolveTransport(opts);
12386
12706
  const groups = await listGroups(t);
12387
12707
  if (opts.json) return void console.log(JSON.stringify(groups));
12388
- if (!groups.length) return void console.log(import_chalk53.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
12389
- console.log(import_chalk53.default.dim(`Tenant ${t.tenant}:`));
12708
+ if (!groups.length) return void console.log(import_chalk54.default.yellow(`No groups in ${t.tenant}. Create one: apiblaze group create <name>`));
12709
+ console.log(import_chalk54.default.dim(`Tenant ${t.tenant}:`));
12390
12710
  for (const g of groups) {
12391
- const sealed = g.name === "apiblaze_admins" ? import_chalk53.default.dim(" (admins \u2014 managed by allowlist)") : "";
12392
- console.log(` ${import_chalk53.default.bold(g.name)} ${import_chalk53.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
12711
+ const sealed = g.name === "apiblaze_admins" ? import_chalk54.default.dim(" (admins \u2014 managed by allowlist)") : "";
12712
+ console.log(` ${import_chalk54.default.bold(g.name)} ${import_chalk54.default.dim(`${g.member_count ?? 0} member(s)`)}${sealed}`);
12393
12713
  }
12394
12714
  }
12395
12715
  async function runGroupCreate(name, opts) {
@@ -12414,10 +12734,10 @@ async function runGroupCreate(name, opts) {
12414
12734
  }
12415
12735
  }
12416
12736
  }
12417
- const spinner = (0, import_ora24.default)(`Creating group ${name}...`).start();
12737
+ const spinner = (0, import_ora25.default)(`Creating group ${name}...`).start();
12418
12738
  try {
12419
12739
  await t.call("/v1/admin/groups", "POST", { name, admins: [adminSub] });
12420
- spinner.succeed(`Group ${import_chalk53.default.bold(name)} created in ${t.tenant}.`);
12740
+ spinner.succeed(`Group ${import_chalk54.default.bold(name)} created in ${t.tenant}.`);
12421
12741
  } catch (e) {
12422
12742
  spinner.fail("Create failed.");
12423
12743
  throw e;
@@ -12426,10 +12746,10 @@ async function runGroupCreate(name, opts) {
12426
12746
  async function runGroupDelete(name, opts) {
12427
12747
  const t = await resolveTransport(opts);
12428
12748
  const g = await groupByName(t, name);
12429
- const spinner = (0, import_ora24.default)(`Deleting group ${name}...`).start();
12749
+ const spinner = (0, import_ora25.default)(`Deleting group ${name}...`).start();
12430
12750
  try {
12431
12751
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}`, "DELETE");
12432
- spinner.succeed(`Group ${import_chalk53.default.bold(name)} deleted.`);
12752
+ spinner.succeed(`Group ${import_chalk54.default.bold(name)} deleted.`);
12433
12753
  } catch (e) {
12434
12754
  spinner.fail("Delete failed.");
12435
12755
  throw e;
@@ -12448,21 +12768,21 @@ async function runGroupMembers(name, opts) {
12448
12768
  return void console.log(JSON.stringify({ ...detail, members: walkedMembers.items, subgroups: subs?.subgroups ?? [] }));
12449
12769
  }
12450
12770
  const members = walkedMembers.items;
12451
- console.log(import_chalk53.default.bold(`${g.name}`) + import_chalk53.default.dim(` (${t.tenant})`));
12452
- for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk53.default.cyan("group-admin") : import_chalk53.default.dim("member")}`);
12453
- if (!members.length) console.log(import_chalk53.default.dim(" no members"));
12771
+ console.log(import_chalk54.default.bold(`${g.name}`) + import_chalk54.default.dim(` (${t.tenant})`));
12772
+ for (const m of members) console.log(` ${label2(m)} ${m.role === "admin" ? import_chalk54.default.cyan("group-admin") : import_chalk54.default.dim("member")}`);
12773
+ if (!members.length) console.log(import_chalk54.default.dim(" no members"));
12454
12774
  const sg = subs?.subgroups ?? [];
12455
- if (sg.length) console.log(import_chalk53.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
12775
+ if (sg.length) console.log(import_chalk54.default.dim(` subgroups: ${sg.map((s) => s.name).join(", ")}`));
12456
12776
  }
12457
12777
  async function runGroupAddUser(userRef, groupName, opts) {
12458
12778
  const t = await resolveTransport(opts);
12459
12779
  const g = await groupByName(t, groupName);
12460
12780
  const u = await userByRefOrCreate(t, userRef);
12461
12781
  const role = opts.role === "admin" ? "admin" : "member";
12462
- const spinner = (0, import_ora24.default)(`Adding ${label2(u)} to ${g.name}...`).start();
12782
+ const spinner = (0, import_ora25.default)(`Adding ${label2(u)} to ${g.name}...`).start();
12463
12783
  try {
12464
12784
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members`, "POST", { abz_sub: u.abz_sub, role });
12465
- spinner.succeed(`${import_chalk53.default.bold(label2(u))} added to ${import_chalk53.default.bold(g.name)} as ${role}.`);
12785
+ spinner.succeed(`${import_chalk54.default.bold(label2(u))} added to ${import_chalk54.default.bold(g.name)} as ${role}.`);
12466
12786
  } catch (e) {
12467
12787
  spinner.fail("Add failed.");
12468
12788
  throw e;
@@ -12471,10 +12791,10 @@ async function runGroupAddUser(userRef, groupName, opts) {
12471
12791
  async function runGroupRemoveUser(userRef, groupName, opts) {
12472
12792
  const t = await resolveTransport(opts);
12473
12793
  const [g, u] = [await groupByName(t, groupName), await userByRef(t, userRef)];
12474
- const spinner = (0, import_ora24.default)(`Removing ${label2(u)} from ${g.name}...`).start();
12794
+ const spinner = (0, import_ora25.default)(`Removing ${label2(u)} from ${g.name}...`).start();
12475
12795
  try {
12476
12796
  await t.call(`/v1/admin/groups/${encodeURIComponent(g.id)}/members/_?abz_sub=${encodeURIComponent(u.abz_sub)}`, "DELETE");
12477
- spinner.succeed(`${import_chalk53.default.bold(label2(u))} removed from ${import_chalk53.default.bold(g.name)}.`);
12797
+ spinner.succeed(`${import_chalk54.default.bold(label2(u))} removed from ${import_chalk54.default.bold(g.name)}.`);
12478
12798
  } catch (e) {
12479
12799
  spinner.fail("Remove failed.");
12480
12800
  throw e;
@@ -12483,10 +12803,10 @@ async function runGroupRemoveUser(userRef, groupName, opts) {
12483
12803
  async function runGroupAddGroup(childName, parentName, opts) {
12484
12804
  const t = await resolveTransport(opts);
12485
12805
  const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
12486
- const spinner = (0, import_ora24.default)(`Nesting ${child.name} under ${parent.name}...`).start();
12806
+ const spinner = (0, import_ora25.default)(`Nesting ${child.name} under ${parent.name}...`).start();
12487
12807
  try {
12488
12808
  await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups`, "POST", { child_group_id: child.id });
12489
- spinner.succeed(`${import_chalk53.default.bold(child.name)} is now a subgroup of ${import_chalk53.default.bold(parent.name)}.`);
12809
+ spinner.succeed(`${import_chalk54.default.bold(child.name)} is now a subgroup of ${import_chalk54.default.bold(parent.name)}.`);
12490
12810
  } catch (e) {
12491
12811
  spinner.fail("Nest failed.");
12492
12812
  throw e;
@@ -12495,10 +12815,10 @@ async function runGroupAddGroup(childName, parentName, opts) {
12495
12815
  async function runGroupRemoveGroup(childName, parentName, opts) {
12496
12816
  const t = await resolveTransport(opts);
12497
12817
  const [child, parent] = [await groupByName(t, childName), await groupByName(t, parentName)];
12498
- const spinner = (0, import_ora24.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
12818
+ const spinner = (0, import_ora25.default)(`Un-nesting ${child.name} from ${parent.name}...`).start();
12499
12819
  try {
12500
12820
  await t.call(`/v1/admin/groups/${encodeURIComponent(parent.id)}/subgroups/${encodeURIComponent(child.id)}`, "DELETE");
12501
- spinner.succeed(`${import_chalk53.default.bold(child.name)} un-nested from ${import_chalk53.default.bold(parent.name)}.`);
12821
+ spinner.succeed(`${import_chalk54.default.bold(child.name)} un-nested from ${import_chalk54.default.bold(parent.name)}.`);
12502
12822
  } catch (e) {
12503
12823
  spinner.fail("Un-nest failed.");
12504
12824
  throw e;
@@ -12509,13 +12829,13 @@ async function runGroupUsers(opts) {
12509
12829
  const users = await listUsers(t);
12510
12830
  const observed = await t.call("/v1/admin/observed-identities").catch(() => ({ identities: [] }));
12511
12831
  if (opts.json) return void console.log(JSON.stringify({ users, observed: observed?.identities ?? [] }));
12512
- console.log(import_chalk53.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
12513
- for (const u of users) console.log(` ${label2(u)} ${import_chalk53.default.dim(u.abz_sub)}`);
12514
- if (!users.length) console.log(import_chalk53.default.dim(" no users yet"));
12832
+ console.log(import_chalk54.default.dim(`Tenant ${t.tenant} \u2014 user directory:`));
12833
+ for (const u of users) console.log(` ${label2(u)} ${import_chalk54.default.dim(u.abz_sub)}`);
12834
+ if (!users.length) console.log(import_chalk54.default.dim(" no users yet"));
12515
12835
  const fresh = (observed?.identities ?? []).filter((o) => !o.provisioned);
12516
12836
  if (fresh.length) {
12517
- console.log(import_chalk53.default.dim("Seen in traffic (not yet users):"));
12518
- for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk53.default.dim(o.source_label ?? "")}`);
12837
+ console.log(import_chalk54.default.dim("Seen in traffic (not yet users):"));
12838
+ for (const o of fresh) console.log(` ${o.email || o.abz_sub} ${import_chalk54.default.dim(o.source_label ?? "")}`);
12519
12839
  }
12520
12840
  }
12521
12841
 
@@ -12524,22 +12844,22 @@ init_iam();
12524
12844
  init_admins();
12525
12845
 
12526
12846
  // src/commands/preapprove.ts
12527
- var import_chalk54 = __toESM(require("chalk"));
12528
- var import_ora25 = __toESM(require("ora"));
12847
+ var import_chalk55 = __toESM(require("chalk"));
12848
+ var import_ora26 = __toESM(require("ora"));
12529
12849
  async function runPreapprove(who, opts) {
12530
12850
  const tenant2 = await resolveProducerTenant(opts.tenant);
12531
12851
  if (opts.list) {
12532
12852
  const rules = await listPreapprovalRules(tenant2);
12533
12853
  if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
12534
12854
  if (!rules.length) {
12535
- console.log(import_chalk54.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
12536
- console.log(import_chalk54.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
12855
+ console.log(import_chalk55.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
12856
+ console.log(import_chalk55.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
12537
12857
  return;
12538
12858
  }
12539
- console.log(import_chalk54.default.dim(`Pre-approved for ${import_chalk54.default.bold(tenant2)}:`));
12859
+ console.log(import_chalk55.default.dim(`Pre-approved for ${import_chalk55.default.bold(tenant2)}:`));
12540
12860
  for (const r of rules) {
12541
- const tag = r.kind === "domain" ? import_chalk54.default.cyan("@" + r.value + " (whole domain)") : r.value;
12542
- const grp = r.groups?.length ? import_chalk54.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
12861
+ const tag = r.kind === "domain" ? import_chalk55.default.cyan("@" + r.value + " (whole domain)") : r.value;
12862
+ const grp = r.groups?.length ? import_chalk55.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
12543
12863
  console.log(` ${tag}${grp}`);
12544
12864
  }
12545
12865
  return;
@@ -12548,19 +12868,19 @@ async function runPreapprove(who, opts) {
12548
12868
  throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
12549
12869
  }
12550
12870
  if (opts.remove) {
12551
- const spinner2 = (0, import_ora25.default)(`Removing ${who} from ${tenant2}\u2026`).start();
12871
+ const spinner2 = (0, import_ora26.default)(`Removing ${who} from ${tenant2}\u2026`).start();
12552
12872
  const { removed, value } = await removePreapprovalRule(tenant2, who);
12553
- if (removed) spinner2.succeed(`${import_chalk54.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
12554
- else spinner2.warn(`No pre-approval rule for ${import_chalk54.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
12873
+ if (removed) spinner2.succeed(`${import_chalk55.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
12874
+ else spinner2.warn(`No pre-approval rule for ${import_chalk55.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
12555
12875
  return;
12556
12876
  }
12557
- const spinner = (0, import_ora25.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
12877
+ const spinner = (0, import_ora26.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
12558
12878
  try {
12559
12879
  const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
12560
12880
  const what = kind === "domain" ? `Anyone @${value}` : value;
12561
- spinner.succeed(`${import_chalk54.default.bold(what)} can now sign in to ${import_chalk54.default.bold(tenant2)}.`);
12562
- if (opts.group?.length) console.log(import_chalk54.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
12563
- console.log(import_chalk54.default.dim(` See the full list: apiblaze preapprove --list`));
12881
+ spinner.succeed(`${import_chalk55.default.bold(what)} can now sign in to ${import_chalk55.default.bold(tenant2)}.`);
12882
+ if (opts.group?.length) console.log(import_chalk55.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
12883
+ console.log(import_chalk55.default.dim(` See the full list: apiblaze preapprove --list`));
12564
12884
  } catch (err) {
12565
12885
  spinner.fail("Could not add the rule.");
12566
12886
  throw err;
@@ -12568,8 +12888,8 @@ async function runPreapprove(who, opts) {
12568
12888
  }
12569
12889
 
12570
12890
  // src/commands/consumer.ts
12571
- var import_chalk55 = __toESM(require("chalk"));
12572
- var import_ora26 = __toESM(require("ora"));
12891
+ var import_chalk56 = __toESM(require("chalk"));
12892
+ var import_ora27 = __toESM(require("ora"));
12573
12893
  init_admin();
12574
12894
  init_resolve();
12575
12895
  init_caller();
@@ -12591,7 +12911,7 @@ async function consumerFetch(creds, suffix, init) {
12591
12911
  function requireConsumer2() {
12592
12912
  const c = loadConsumer();
12593
12913
  if (!c) {
12594
- console.error(import_chalk55.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
12914
+ console.error(import_chalk56.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
12595
12915
  process.exit(1);
12596
12916
  }
12597
12917
  return c;
@@ -12602,7 +12922,7 @@ async function runConsumerLogin(opts) {
12602
12922
  let clientId = opts.client;
12603
12923
  if (clientId) {
12604
12924
  if (!tenant2) {
12605
- console.error(import_chalk55.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
12925
+ console.error(import_chalk56.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
12606
12926
  process.exit(1);
12607
12927
  }
12608
12928
  } else {
@@ -12614,24 +12934,24 @@ async function runConsumerLogin(opts) {
12614
12934
  if (!picked) process.exit(1);
12615
12935
  tenant2 = picked;
12616
12936
  }
12617
- const s2 = (0, import_ora26.default)("Finding the login app...").start();
12937
+ const s2 = (0, import_ora27.default)("Finding the login app...").start();
12618
12938
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
12619
12939
  s2.stop();
12620
12940
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
12621
12941
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
12622
12942
  if (!pick2) {
12623
- console.error(import_chalk55.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
12943
+ console.error(import_chalk56.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
12624
12944
  process.exit(1);
12625
12945
  }
12626
12946
  clientId = pick2.client_id ?? pick2.clientId;
12627
12947
  }
12628
- console.log(`${import_chalk55.default.cyan("\u2192")} Logging in to ${import_chalk55.default.bold(tenant2)} as a consumer...`);
12948
+ console.log(`${import_chalk56.default.cyan("\u2192")} Logging in to ${import_chalk56.default.bold(tenant2)} as a consumer...`);
12629
12949
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
12630
12950
  console.log(`
12631
- Open: ${import_chalk55.default.underline(verificationUri)}`);
12632
- console.log(` Code: ${import_chalk55.default.bold(userCode)}
12951
+ Open: ${import_chalk56.default.underline(verificationUri)}`);
12952
+ console.log(` Code: ${import_chalk56.default.bold(userCode)}
12633
12953
  `);
12634
- console.log(import_chalk55.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
12954
+ console.log(import_chalk56.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
12635
12955
  });
12636
12956
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
12637
12957
  const creds = {
@@ -12646,7 +12966,7 @@ async function runConsumerLogin(opts) {
12646
12966
  obtainedAt: Date.now()
12647
12967
  };
12648
12968
  saveConsumer(creds);
12649
- console.log(import_chalk55.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
12969
+ console.log(import_chalk56.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
12650
12970
  }
12651
12971
  async function runConsumerTokens(opts) {
12652
12972
  const creds = requireConsumer2();
@@ -12659,28 +12979,28 @@ async function runConsumerTokens(opts) {
12659
12979
  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));
12660
12980
  return;
12661
12981
  }
12662
- console.log(`${import_chalk55.default.cyan("Consumer")} ${import_chalk55.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk55.default.bold(fresh.tenant)}
12982
+ console.log(`${import_chalk56.default.cyan("Consumer")} ${import_chalk56.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk56.default.bold(fresh.tenant)}
12663
12983
  `);
12664
- console.log(`${import_chalk55.default.bold("access_token")} ${import_chalk55.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
12984
+ console.log(`${import_chalk56.default.bold("access_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
12665
12985
  ${fresh.accessToken}
12666
12986
  `);
12667
- if (fresh.idToken) console.log(`${import_chalk55.default.bold("id_token")} ${import_chalk55.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
12987
+ if (fresh.idToken) console.log(`${import_chalk56.default.bold("id_token")} ${import_chalk56.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
12668
12988
  ${fresh.idToken}
12669
12989
  `);
12670
- if (fresh.refreshToken) console.log(`${import_chalk55.default.bold("refresh_token")}
12990
+ if (fresh.refreshToken) console.log(`${import_chalk56.default.bold("refresh_token")}
12671
12991
  ${fresh.refreshToken}
12672
12992
  `);
12673
- console.log(import_chalk55.default.dim("These are your own tokens \u2014 keep them secret."));
12993
+ console.log(import_chalk56.default.dim("These are your own tokens \u2014 keep them secret."));
12674
12994
  }
12675
12995
  async function runConsumerApikeys(opts) {
12676
12996
  const creds = requireConsumer2();
12677
12997
  const { default: inquirer3 } = await import("inquirer");
12678
- const spinner = (0, import_ora26.default)("Loading your API keys...").start();
12998
+ const spinner = (0, import_ora27.default)("Loading your API keys...").start();
12679
12999
  const list = await consumerFetch(creds, withPage("/apikeys", null));
12680
13000
  if (list.status >= 400) {
12681
13001
  spinner.stop();
12682
- console.error(import_chalk55.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
12683
- if (list.status === 401) console.error(import_chalk55.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
13002
+ console.error(import_chalk56.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
13003
+ if (list.status === 401) console.error(import_chalk56.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
12684
13004
  process.exit(1);
12685
13005
  }
12686
13006
  let walkCreds = list.creds;
@@ -12709,16 +13029,16 @@ async function runConsumerApikeys(opts) {
12709
13029
  if (opts.json) {
12710
13030
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
12711
13031
  } else if (!keys.length) {
12712
- console.log(import_chalk55.default.yellow("No API keys yet."));
13032
+ console.log(import_chalk56.default.yellow("No API keys yet."));
12713
13033
  } else {
12714
13034
  for (const k of keys) {
12715
13035
  const clear = revealMap[k.environment]?.key;
12716
- const shown = clear ? import_chalk55.default.green(clear) : import_chalk55.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
12717
- const exp = k.expires_at ? import_chalk55.default.dim(`exp ${k.expires_at}`) : import_chalk55.default.dim("no expiry");
12718
- console.log(` ${import_chalk55.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk55.default.dim(k.description ?? "")}`);
13036
+ const shown = clear ? import_chalk56.default.green(clear) : import_chalk56.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
13037
+ const exp = k.expires_at ? import_chalk56.default.dim(`exp ${k.expires_at}`) : import_chalk56.default.dim("no expiry");
13038
+ console.log(` ${import_chalk56.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk56.default.dim(k.description ?? "")}`);
12719
13039
  }
12720
13040
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
12721
- console.log(import_chalk55.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
13041
+ console.log(import_chalk56.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
12722
13042
  }
12723
13043
  }
12724
13044
  if (opts.json) return;
@@ -12732,7 +13052,7 @@ async function runConsumerApikeys(opts) {
12732
13052
  const body = { environment: answers.environment };
12733
13053
  if (answers.description) body.description = answers.description;
12734
13054
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
12735
- const s2 = (0, import_ora26.default)("Creating key...").start();
13055
+ const s2 = (0, import_ora27.default)("Creating key...").start();
12736
13056
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
12737
13057
  if (created.status >= 400) {
12738
13058
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -12740,13 +13060,13 @@ async function runConsumerApikeys(opts) {
12740
13060
  }
12741
13061
  s2.succeed("Key created.");
12742
13062
  const key = created.data?.key ?? created.data?.fullKey;
12743
- if (key) console.log(` ${import_chalk55.default.green(key)} ${import_chalk55.default.dim("(shown once \u2014 store it now)")}`);
12744
- else console.log(import_chalk55.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
13063
+ if (key) console.log(` ${import_chalk56.default.green(key)} ${import_chalk56.default.dim("(shown once \u2014 store it now)")}`);
13064
+ else console.log(import_chalk56.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
12745
13065
  }
12746
13066
 
12747
13067
  // src/commands/sidecar.ts
12748
- var import_chalk56 = __toESM(require("chalk"));
12749
- var import_ora27 = __toESM(require("ora"));
13068
+ var import_chalk57 = __toESM(require("chalk"));
13069
+ var import_ora28 = __toESM(require("ora"));
12750
13070
  var fs13 = __toESM(require("fs"));
12751
13071
  var path10 = __toESM(require("path"));
12752
13072
  init_admin();
@@ -12788,18 +13108,18 @@ function upsertEnvLocal(root, token) {
12788
13108
  }
12789
13109
  function installSidecarPackage(root) {
12790
13110
  if (fs13.existsSync(path10.join(root, "node_modules", "apiblaze", "package.json"))) {
12791
- console.log(` ${import_chalk56.default.green("\u2713")} apiblaze package already installed`);
13111
+ console.log(` ${import_chalk57.default.green("\u2713")} apiblaze package already installed`);
12792
13112
  return;
12793
13113
  }
12794
13114
  const has = (f) => fs13.existsSync(path10.join(root, f));
12795
13115
  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" };
12796
- const spinner = (0, import_ora27.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
13116
+ const spinner = (0, import_ora28.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
12797
13117
  try {
12798
13118
  const { execSync } = require("child_process");
12799
13119
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
12800
13120
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
12801
13121
  } catch {
12802
- 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")}.`);
13122
+ 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")}.`);
12803
13123
  }
12804
13124
  }
12805
13125
  function readEnvKey(root) {
@@ -12938,7 +13258,7 @@ async function runAnonymousInit(root, router, opts) {
12938
13258
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
12939
13259
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
12940
13260
  if (opts.newSession) clearAnonCred2();
12941
- const spinner = (0, import_ora27.default)("Setting up a sidecar (no login needed)...").start();
13261
+ const spinner = (0, import_ora28.default)("Setting up a sidecar (no login needed)...").start();
12942
13262
  let out;
12943
13263
  try {
12944
13264
  out = await sidecarInitAnonymous2();
@@ -12950,29 +13270,29 @@ async function runAnonymousInit(root, router, opts) {
12950
13270
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
12951
13271
  const envState = upsertEnvLocal(root, out.token);
12952
13272
  ensureGitignored(root);
12953
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
12954
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13273
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13274
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
12955
13275
  installSidecarPackage(root);
12956
13276
  let inspectorPath = null;
12957
13277
  if (!opts.noInspector) {
12958
13278
  inspectorPath = generateInspector(root, router);
12959
- if (inspectorPath) console.log(` ${import_chalk56.default.green("\u2713")} inspector at ${inspectorPath}`);
13279
+ if (inspectorPath) console.log(` ${import_chalk57.default.green("\u2713")} inspector at ${inspectorPath}`);
12960
13280
  }
12961
13281
  console.log("");
12962
- console.log(import_chalk56.default.bold("Done (no account needed). What happens next:"));
12963
- console.log(` 1. ${import_chalk56.default.cyan("npm run dev")} and use your app.`);
13282
+ console.log(import_chalk57.default.bold("Done (no account needed). What happens next:"));
13283
+ console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app.`);
12964
13284
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
12965
- console.log(` ${import_chalk56.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
13285
+ console.log(` ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
12966
13286
  console.log("");
12967
- console.log(import_chalk56.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
12968
- console.log(` ${import_chalk56.default.cyan("apiblaze login")} then ${import_chalk56.default.cyan("apiblaze claim")} ${import_chalk56.default.dim("(no code needed here)")}`);
12969
- console.log(import_chalk56.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
13287
+ console.log(import_chalk57.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
13288
+ console.log(` ${import_chalk57.default.cyan("apiblaze login")} then ${import_chalk57.default.cyan("apiblaze claim")} ${import_chalk57.default.dim("(no code needed here)")}`);
13289
+ console.log(import_chalk57.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
12970
13290
  }
12971
13291
  async function runSidecar(opts) {
12972
13292
  const root = path10.resolve(opts.dir ?? process.cwd());
12973
13293
  const detected2 = detectNextProject(root);
12974
13294
  if (!detected2.found) {
12975
- console.log(import_chalk56.default.yellow(`No Next.js project detected in ${root}.`));
13295
+ console.log(import_chalk57.default.yellow(`No Next.js project detected in ${root}.`));
12976
13296
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
12977
13297
  return;
12978
13298
  }
@@ -12983,10 +13303,10 @@ async function runSidecar(opts) {
12983
13303
  if (!loadCredentials()) {
12984
13304
  upsertEnvLocal(root, readEnvKey(root));
12985
13305
  ensureGitignored(root);
12986
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
12987
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
13306
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
13307
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
12988
13308
  installSidecarPackage(root);
12989
- console.log(import_chalk56.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
13309
+ console.log(import_chalk57.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
12990
13310
  return;
12991
13311
  }
12992
13312
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -12995,7 +13315,7 @@ async function runSidecar(opts) {
12995
13315
  const mustMint = !existingKey || opts.rotate || switchingTeam;
12996
13316
  let token = existingKey ?? "";
12997
13317
  if (mustMint) {
12998
- 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();
13318
+ 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();
12999
13319
  try {
13000
13320
  const out = await admin({
13001
13321
  method: "POST",
@@ -13009,39 +13329,39 @@ async function runSidecar(opts) {
13009
13329
  throw err;
13010
13330
  }
13011
13331
  } else {
13012
- 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).`));
13332
+ 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).`));
13013
13333
  }
13014
13334
  const envState = upsertEnvLocal(root, token);
13015
13335
  ensureGitignored(root);
13016
- console.log(` ${import_chalk56.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13336
+ console.log(` ${import_chalk57.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
13017
13337
  const wireState = wireInstrumentation(root);
13018
- console.log(` ${import_chalk56.default.green("\u2713")} instrumentation.ts ${wireState}`);
13338
+ console.log(` ${import_chalk57.default.green("\u2713")} instrumentation.ts ${wireState}`);
13019
13339
  installSidecarPackage(root);
13020
13340
  let inspectorPath = null;
13021
13341
  if (!opts.noInspector) {
13022
13342
  inspectorPath = generateInspector(root, detected2.router);
13023
- 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}`);
13024
13344
  }
13025
13345
  console.log("");
13026
- console.log(import_chalk56.default.bold("Done. What happens next:"));
13027
- console.log(` 1. ${import_chalk56.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
13028
- console.log(` 2. The origins your app calls appear as ${import_chalk56.default.bold("candidates")} \u2014 list them: ${import_chalk56.default.cyan("apiblaze sidecar")}`);
13029
- console.log(` 3. Approve the ones to route: ${import_chalk56.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
13346
+ console.log(import_chalk57.default.bold("Done. What happens next:"));
13347
+ console.log(` 1. ${import_chalk57.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
13348
+ console.log(` 2. The origins your app calls appear as ${import_chalk57.default.bold("candidates")} \u2014 list them: ${import_chalk57.default.cyan("apiblaze sidecar")}`);
13349
+ console.log(` 3. Approve the ones to route: ${import_chalk57.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
13030
13350
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
13031
- 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)`);
13032
- 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>\`.`));
13351
+ 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)`);
13352
+ 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>\`.`));
13033
13353
  console.log("");
13034
- console.log(import_chalk56.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
13035
- console.log(import_chalk56.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
13036
- console.log(import_chalk56.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
13354
+ console.log(import_chalk57.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
13355
+ console.log(import_chalk57.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
13356
+ console.log(import_chalk57.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
13037
13357
  console.log("");
13038
- 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."));
13039
- console.log(import_chalk56.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
13358
+ 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."));
13359
+ console.log(import_chalk57.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
13040
13360
  }
13041
13361
 
13042
13362
  // src/commands/origins.ts
13043
- var import_chalk57 = __toESM(require("chalk"));
13044
- var import_ora28 = __toESM(require("ora"));
13363
+ var import_chalk58 = __toESM(require("chalk"));
13364
+ var import_ora29 = __toESM(require("ora"));
13045
13365
  init_admin();
13046
13366
  init_resolve();
13047
13367
  init_auth();
@@ -13051,7 +13371,7 @@ async function runOriginsList(opts) {
13051
13371
  if (!loadCredentials()) {
13052
13372
  const cred = loadAnonCred();
13053
13373
  if (!cred) {
13054
- console.log(import_chalk57.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
13374
+ console.log(import_chalk58.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
13055
13375
  return;
13056
13376
  }
13057
13377
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -13069,30 +13389,30 @@ async function runOriginsList(opts) {
13069
13389
  }
13070
13390
  const routed = out.routed ?? [];
13071
13391
  const candidates = out.candidates ?? [];
13072
- console.log(import_chalk57.default.bold(`
13392
+ console.log(import_chalk58.default.bold(`
13073
13393
  Routed through APIblaze (${routed.length})`));
13074
- if (!routed.length) console.log(import_chalk57.default.dim(" none yet"));
13075
- for (const r of routed) console.log(` ${import_chalk57.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk57.default.dim(`\u2192 ${r.project_id}`)}`);
13076
- console.log(import_chalk57.default.bold(`
13394
+ if (!routed.length) console.log(import_chalk58.default.dim(" none yet"));
13395
+ for (const r of routed) console.log(` ${import_chalk58.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk58.default.dim(`\u2192 ${r.project_id}`)}`);
13396
+ console.log(import_chalk58.default.bold(`
13077
13397
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
13078
- if (!candidates.length) console.log(import_chalk57.default.dim(" none \u2014 run your app to discover the origins it calls"));
13398
+ if (!candidates.length) console.log(import_chalk58.default.dim(" none \u2014 run your app to discover the origins it calls"));
13079
13399
  for (const c of candidates) {
13080
- console.log(` ${import_chalk57.default.yellow("\u25CB")} ${c.origin} ${import_chalk57.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
13400
+ console.log(` ${import_chalk58.default.yellow("\u25CB")} ${c.origin} ${import_chalk58.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
13081
13401
  }
13082
13402
  if (candidates.length) {
13083
- console.log(import_chalk57.default.dim(`
13403
+ console.log(import_chalk58.default.dim(`
13084
13404
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
13085
- console.log(import_chalk57.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
13405
+ console.log(import_chalk58.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
13086
13406
  }
13087
13407
  }
13088
13408
  async function runOriginsApprove(origin, opts) {
13089
13409
  if (!loadCredentials()) {
13090
13410
  const cred = loadAnonCred();
13091
13411
  if (!cred) {
13092
- console.error(import_chalk57.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
13412
+ console.error(import_chalk58.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
13093
13413
  process.exit(1);
13094
13414
  }
13095
- const spinner2 = (0, import_ora28.default)(`Approving ${origin} (anonymous)...`).start();
13415
+ const spinner2 = (0, import_ora29.default)(`Approving ${origin} (anonymous)...`).start();
13096
13416
  try {
13097
13417
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
13098
13418
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -13103,7 +13423,7 @@ async function runOriginsApprove(origin, opts) {
13103
13423
  return;
13104
13424
  }
13105
13425
  const { teamId } = await resolveTeam(opts.team);
13106
- const spinner = (0, import_ora28.default)(`Approving ${origin}...`).start();
13426
+ const spinner = (0, import_ora29.default)(`Approving ${origin}...`).start();
13107
13427
  try {
13108
13428
  const out = await admin({
13109
13429
  method: "POST",
@@ -13120,7 +13440,7 @@ async function runOriginsApprove(origin, opts) {
13120
13440
  }
13121
13441
  async function runOriginsDeny(origin, opts) {
13122
13442
  const { teamId } = await resolveTeam(opts.team);
13123
- const spinner = (0, import_ora28.default)(`Dismissing ${origin}...`).start();
13443
+ const spinner = (0, import_ora29.default)(`Dismissing ${origin}...`).start();
13124
13444
  try {
13125
13445
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
13126
13446
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -13131,7 +13451,7 @@ async function runOriginsDeny(origin, opts) {
13131
13451
  }
13132
13452
  async function runOriginsRemove(origin, opts) {
13133
13453
  const { teamId } = await resolveTeam(opts.team);
13134
- const spinner = (0, import_ora28.default)(`Removing the proxy for ${origin}...`).start();
13454
+ const spinner = (0, import_ora29.default)(`Removing the proxy for ${origin}...`).start();
13135
13455
  try {
13136
13456
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
13137
13457
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -13142,7 +13462,7 @@ async function runOriginsRemove(origin, opts) {
13142
13462
  }
13143
13463
 
13144
13464
  // src/commands/op.ts
13145
- var import_chalk62 = __toESM(require("chalk"));
13465
+ var import_chalk63 = __toESM(require("chalk"));
13146
13466
  init_auth();
13147
13467
 
13148
13468
  // src/lib/op-call.ts
@@ -13171,7 +13491,7 @@ async function opCall(call2) {
13171
13491
  }
13172
13492
 
13173
13493
  // src/commands/op-billing.ts
13174
- var import_chalk58 = __toESM(require("chalk"));
13494
+ var import_chalk59 = __toESM(require("chalk"));
13175
13495
  init_admin();
13176
13496
  var SANDBOX = {
13177
13497
  teamId: "team_1782844865835_zujrf",
@@ -13230,7 +13550,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
13230
13550
  }
13231
13551
  function printDoors(data) {
13232
13552
  const checks = [];
13233
- console.log(import_chalk58.default.bold("\n Doors \u2014 is every way in metered?\n"));
13553
+ console.log(import_chalk59.default.bold("\n Doors \u2014 is every way in metered?\n"));
13234
13554
  const doors = data?.doors ?? [];
13235
13555
  const metered = doors.filter((d) => d.verdict === "metered");
13236
13556
  const allowed = doors.filter((d) => d.verdict === "allowed-free");
@@ -13239,29 +13559,29 @@ function printDoors(data) {
13239
13559
  const errs = data?.errors ?? [];
13240
13560
  const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
13241
13561
  const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
13242
- console.log(import_chalk58.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
13243
- for (const d of metered) console.log(import_chalk58.default.green(` \u2713 ${d.pattern}`));
13244
- console.log(import_chalk58.default.dim(`
13562
+ console.log(import_chalk59.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
13563
+ for (const d of metered) console.log(import_chalk59.default.green(` \u2713 ${d.pattern}`));
13564
+ console.log(import_chalk59.default.dim(`
13245
13565
  ${allowed.length} route(s) free ON PURPOSE`));
13246
13566
  for (const d of allowed) {
13247
- console.log(import_chalk58.default.cyan(` \u2022 ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13248
- console.log(import_chalk58.default.dim(` ${d.why}`));
13567
+ console.log(import_chalk59.default.cyan(` \u2022 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13568
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13249
13569
  }
13250
13570
  if (known.length) {
13251
- console.log(import_chalk58.default.yellow(`
13571
+ console.log(import_chalk59.default.yellow(`
13252
13572
  ${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
13253
13573
  for (const d of known) {
13254
- console.log(import_chalk58.default.yellow(` ! ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13255
- console.log(import_chalk58.default.dim(` ${d.why}`));
13574
+ console.log(import_chalk59.default.yellow(` ! ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13575
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13256
13576
  }
13257
13577
  checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
13258
13578
  }
13259
13579
  if (stray.length) {
13260
- console.log(import_chalk58.default.red(`
13580
+ console.log(import_chalk59.default.red(`
13261
13581
  ${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
13262
13582
  for (const d of stray) {
13263
- console.log(import_chalk58.default.red(` \u2717 ${d.pattern}`) + import_chalk58.default.dim(` \u2192 ${d.script}`));
13264
- console.log(import_chalk58.default.dim(` ${d.why}`));
13583
+ console.log(import_chalk59.default.red(` \u2717 ${d.pattern}`) + import_chalk59.default.dim(` \u2192 ${d.script}`));
13584
+ console.log(import_chalk59.default.dim(` ${d.why}`));
13265
13585
  }
13266
13586
  checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
13267
13587
  } else if (routeAuditBroke) {
@@ -13272,22 +13592,22 @@ function printDoors(data) {
13272
13592
  const wd = data?.workers_dev ?? {};
13273
13593
  const open = wd.enabled ?? [];
13274
13594
  if (open.length) {
13275
- console.log(import_chalk58.default.red(`
13595
+ console.log(import_chalk59.default.red(`
13276
13596
  ${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
13277
- 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})`));
13278
- console.log(import_chalk58.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
13597
+ 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})`));
13598
+ console.log(import_chalk59.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
13279
13599
  checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
13280
13600
  } else if (devAuditBroke || !wd.total) {
13281
13601
  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." });
13282
13602
  } else {
13283
- console.log(import_chalk58.default.green(`
13603
+ console.log(import_chalk59.default.green(`
13284
13604
  \u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
13285
13605
  checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
13286
13606
  }
13287
- if (data?.how_to_fix) console.log(import_chalk58.default.yellow(`
13288
- ${data.reason}`) + import_chalk58.default.dim(`
13607
+ if (data?.how_to_fix) console.log(import_chalk59.default.yellow(`
13608
+ ${data.reason}`) + import_chalk59.default.dim(`
13289
13609
  ${data.how_to_fix}`));
13290
- else for (const e of errs) console.log(import_chalk58.default.red(`
13610
+ else for (const e of errs) console.log(import_chalk59.default.red(`
13291
13611
  audit error: ${e}`));
13292
13612
  if (errs.length) {
13293
13613
  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` });
@@ -13297,9 +13617,9 @@ function printDoors(data) {
13297
13617
  async function runMeter(readLedger, opts) {
13298
13618
  const checks = [];
13299
13619
  const N = Math.max(1, Math.min(10, opts.count ?? 3));
13300
- console.log(import_chalk58.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
13620
+ console.log(import_chalk59.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
13301
13621
  const snap = await readLedger();
13302
- 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
13622
+ 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
13303
13623
  `));
13304
13624
  const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
13305
13625
  const headers = opts.key ? { "X-API-Key": opts.key } : {};
@@ -13381,27 +13701,27 @@ async function runMeter(readLedger, opts) {
13381
13701
  return checks;
13382
13702
  }
13383
13703
  function printChecks(checks) {
13384
- console.log(import_chalk58.default.bold("\n Results\n"));
13385
- 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") };
13704
+ console.log(import_chalk59.default.bold("\n Results\n"));
13705
+ 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") };
13386
13706
  for (const ch of checks) {
13387
- console.log(` ${mark[ch.status]} ${import_chalk58.default.bold(ch.name)}`);
13388
- console.log(import_chalk58.default.dim(` ${ch.detail}`));
13707
+ console.log(` ${mark[ch.status]} ${import_chalk59.default.bold(ch.name)}`);
13708
+ console.log(import_chalk59.default.dim(` ${ch.detail}`));
13389
13709
  }
13390
13710
  const fails = checks.filter((c) => c.status === "FAIL").length;
13391
13711
  const skips = checks.filter((c) => c.status === "SKIP").length;
13392
13712
  const known = checks.filter((c) => c.status === "KNOWN").length;
13393
13713
  console.log("");
13394
13714
  const passes = checks.filter((c) => c.status === "PASS").length;
13395
- if (fails) console.log(import_chalk58.default.red(` ${fails} check(s) FAILED.`));
13396
- else if (passes) console.log(import_chalk58.default.green(` ${passes} check(s) passed, 0 failed.`));
13397
- else console.log(import_chalk58.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
13398
- if (known) console.log(import_chalk58.default.yellow(` ${known} known-open issue(s) still outstanding.`));
13399
- if (skips) console.log(import_chalk58.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
13715
+ if (fails) console.log(import_chalk59.default.red(` ${fails} check(s) FAILED.`));
13716
+ else if (passes) console.log(import_chalk59.default.green(` ${passes} check(s) passed, 0 failed.`));
13717
+ else console.log(import_chalk59.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
13718
+ if (known) console.log(import_chalk59.default.yellow(` ${known} known-open issue(s) still outstanding.`));
13719
+ if (skips) console.log(import_chalk59.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
13400
13720
  console.log("");
13401
13721
  }
13402
13722
 
13403
13723
  // src/commands/op-test.ts
13404
- var import_chalk59 = __toESM(require("chalk"));
13724
+ var import_chalk60 = __toESM(require("chalk"));
13405
13725
  init_auth();
13406
13726
  init_types();
13407
13727
  var DASHBOARD_BASE9 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
@@ -13426,10 +13746,10 @@ async function scrubSandboxKeys(timeoutS = 150) {
13426
13746
  try {
13427
13747
  const r = await runnerCall({ action: "gut", id: TEARDOWN_GUT_ID }, timeoutS);
13428
13748
  const first = r.results?.[0]?.detail ?? r.error ?? "";
13429
- log(import_chalk59.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
13749
+ log(import_chalk60.default.dim(`teardown: ${first || (r.ok ? "sandbox fixture keys scrubbed" : "scrub reported a problem")}`));
13430
13750
  return r.ok;
13431
13751
  } catch (e) {
13432
- 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`));
13752
+ 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`));
13433
13753
  return false;
13434
13754
  }
13435
13755
  }
@@ -13451,23 +13771,23 @@ async function runOpTest(o = {}) {
13451
13771
  const guts = o.skipGuts ? [] : listing.guts.filter((g) => g.id !== TEARDOWN_GUT_ID && matches(g.id, void 0, { ...o, group: void 0 }));
13452
13772
  const total = specs.length + guts.length;
13453
13773
  if (total === 0) {
13454
- log(import_chalk59.default.yellow("nothing matched"));
13774
+ log(import_chalk60.default.yellow("nothing matched"));
13455
13775
  process.exitCode = 2;
13456
13776
  return;
13457
13777
  }
13458
- log(import_chalk59.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
13778
+ log(import_chalk60.default.bold(`apiblaze op test \u2014 ${specs.length} spec(s) + ${guts.length} gut check(s) on sandbox ${listing.sandbox_team}`));
13459
13779
  const results = [];
13460
13780
  let failed = 0;
13461
13781
  const started = Date.now();
13462
13782
  const record = (r) => {
13463
13783
  results.push(r);
13464
13784
  if (!r.ok) failed += 1;
13465
- const mark = r.ok ? import_chalk59.default.green("PASS") : import_chalk59.default.red("FAIL");
13785
+ const mark = r.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
13466
13786
  log(`[${mark}] ${r.id} \u2014 ${r.label} (${ms(r.duration_ms)})`);
13467
13787
  if (!r.ok) {
13468
- if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk59.default.red(` \u2717 ${c.name}: ${c.detail}`));
13469
- else if (r.error) log(import_chalk59.default.red(` \u2717 ${r.error}`));
13470
- 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)}`));
13788
+ if (r.kind === "spec") for (const c of r.cases.filter((c2) => !c2.ok)) log(import_chalk60.default.red(` \u2717 ${c.name}: ${c.detail}`));
13789
+ else if (r.error) log(import_chalk60.default.red(` \u2717 ${r.error}`));
13790
+ 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)}`));
13471
13791
  }
13472
13792
  };
13473
13793
  try {
@@ -13498,13 +13818,13 @@ async function runOpTest(o = {}) {
13498
13818
  }
13499
13819
  const summary = { ran: results.length, passed: results.length - failed, failed, duration_ms: Date.now() - started, sandbox_team: listing.sandbox_team };
13500
13820
  log("");
13501
- 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)})`));
13821
+ 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)})`));
13502
13822
  if (o.json) process.stdout.write(JSON.stringify({ ...summary, results }, null, 2) + "\n");
13503
13823
  if (failed) process.exitCode = 1;
13504
13824
  }
13505
13825
 
13506
13826
  // src/commands/op-consumer.ts
13507
- var import_chalk60 = __toESM(require("chalk"));
13827
+ var import_chalk61 = __toESM(require("chalk"));
13508
13828
  var SANDBOX_TENANT = "portalteststenant";
13509
13829
  var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
13510
13830
  function proxyUrlFor(spec2, path11 = "/get") {
@@ -13546,9 +13866,9 @@ async function runOpConsumer(o = {}) {
13546
13866
  const creds = loadConsumer();
13547
13867
  const done = (r) => {
13548
13868
  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 };
13549
- const mark = out.skipped ? import_chalk60.default.yellow("SKIP") : out.ok ? import_chalk60.default.green("PASS") : import_chalk60.default.red("FAIL");
13869
+ const mark = out.skipped ? import_chalk61.default.yellow("SKIP") : out.ok ? import_chalk61.default.green("PASS") : import_chalk61.default.red("FAIL");
13550
13870
  log2(`[${mark}] ${out.id} \u2014 ${out.label}${out.tenant ? ` (tenant ${out.tenant})` : ""}${out.url ? ` \u2192 ${out.url}` : ""}`);
13551
- 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}`));
13871
+ 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}`));
13552
13872
  if (o.json) process.stdout.write(JSON.stringify(out, null, 2) + "\n");
13553
13873
  if (!out.ok && !out.skipped) process.exitCode = 1;
13554
13874
  if (out.skipped) process.exitCode = 2;
@@ -13588,7 +13908,7 @@ async function runOpConsumer(o = {}) {
13588
13908
  }
13589
13909
 
13590
13910
  // src/commands/op-perf.ts
13591
- var import_chalk61 = __toESM(require("chalk"));
13911
+ var import_chalk62 = __toESM(require("chalk"));
13592
13912
  var RPS_DEFAULT = 100;
13593
13913
  var RPS_CAP = 300;
13594
13914
  var SECONDS_DEFAULT = 60;
@@ -13772,10 +14092,10 @@ async function readGateway(proxy, sent, signal) {
13772
14092
  }
13773
14093
  const left = deadline - Date.now();
13774
14094
  if (left <= 0) {
13775
- 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})` : ""}`));
14095
+ 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})` : ""}`));
13776
14096
  return null;
13777
14097
  }
13778
- log3(import_chalk61.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
14098
+ log3(import_chalk62.default.dim(` waiting for the latency ledger\u2026 ${lastSeen}/${sent} rows for ${proxy}${lastErr ? ` (${lastErr})` : ""}`));
13779
14099
  await sleep2(Math.min(GATEWAY_POLL_MS, left), signal);
13780
14100
  }
13781
14101
  }
@@ -13784,7 +14104,7 @@ async function runOpPerf(o = {}) {
13784
14104
  const thresholds = thresholdsFrom(o);
13785
14105
  const ctrl = new AbortController();
13786
14106
  const onSigint = () => {
13787
- log3(import_chalk61.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
14107
+ log3(import_chalk62.default.yellow("\ninterrupted \u2014 stopping the load and tearing down the fixture"));
13788
14108
  ctrl.abort();
13789
14109
  };
13790
14110
  process.once("SIGINT", onSigint);
@@ -13793,7 +14113,7 @@ async function runOpPerf(o = {}) {
13793
14113
  if (!o.key) throw new Error("--url needs --key (the fixture proxy's API key)");
13794
14114
  fixture = { url: o.url, key: o.key, proxy: proxyFromUrl(o.url), provisioned: false };
13795
14115
  } else {
13796
- log3(import_chalk61.default.dim("provisioning the perf fixture through the runner\u2026"));
14116
+ log3(import_chalk62.default.dim("provisioning the perf fixture through the runner\u2026"));
13797
14117
  const f = await runnerCall({ action: "perf-fixture" }, 120);
13798
14118
  if (!f?.url || !f?.key) throw new Error(`perf-fixture returned no url/key: ${JSON.stringify(f).slice(0, 200)}`);
13799
14119
  fixture = { url: f.url, key: f.key, proxy: f.proxy || proxyFromUrl(f.url), team: f.team, provisioned: true };
@@ -13801,7 +14121,7 @@ async function runOpPerf(o = {}) {
13801
14121
  try {
13802
14122
  const total = Math.round(rps * seconds);
13803
14123
  const intervalMs = 1e3 / rps;
13804
- 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})` : ""}`));
14124
+ 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})` : ""}`));
13805
14125
  const samples = [];
13806
14126
  const inflight = /* @__PURE__ */ new Set();
13807
14127
  let fired = 0;
@@ -13809,7 +14129,7 @@ async function runOpPerf(o = {}) {
13809
14129
  const progress = setInterval(() => {
13810
14130
  const m = summarize(samples, Date.now() - t0);
13811
14131
  const secs = (Date.now() - t0) / 1e3;
13812
- 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)`));
14132
+ 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)`));
13813
14133
  }, PROGRESS_EVERY_MS);
13814
14134
  const { sent, elapsedMs } = await schedule({
13815
14135
  total,
@@ -13823,21 +14143,21 @@ async function runOpPerf(o = {}) {
13823
14143
  }
13824
14144
  });
13825
14145
  clearInterval(progress);
13826
- 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`));
14146
+ 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`));
13827
14147
  await Promise.all([...inflight]);
13828
14148
  const client = summarize(samples, elapsedMs);
13829
14149
  log3("");
13830
- 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}`);
14150
+ 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}`);
13831
14151
  const gateway = ctrl.signal.aborted ? null : await readGateway(fixture.proxy, sent, ctrl.signal);
13832
14152
  if (gateway) {
13833
14153
  const extras = [
13834
14154
  gateway.kv_per_req !== void 0 ? `kv/req ${gateway.kv_per_req}` : "",
13835
14155
  gateway.redis_per_req !== void 0 ? `redis/req ${gateway.redis_per_req}` : ""
13836
14156
  ].filter(Boolean).join(" ");
13837
- 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}` : ""})`)}`);
13838
- 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}`));
14157
+ 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}` : ""})`)}`);
14158
+ 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}`));
13839
14159
  } else {
13840
- log3(` ${import_chalk61.default.bold("gateway")} ${import_chalk61.default.yellow("not available")} ${import_chalk61.default.dim("(gateway thresholds not evaluated)")}`);
14160
+ log3(` ${import_chalk62.default.bold("gateway")} ${import_chalk62.default.yellow("not available")} ${import_chalk62.default.dim("(gateway thresholds not evaluated)")}`);
13841
14161
  }
13842
14162
  const breaches = evaluate(client, gateway, thresholds);
13843
14163
  const aborted = ctrl.signal.aborted;
@@ -13845,7 +14165,7 @@ async function runOpPerf(o = {}) {
13845
14165
  log3("");
13846
14166
  const row = (metric, value, limit, unit) => {
13847
14167
  const hit = breaches.find((b) => b.metric === metric);
13848
- log3(`[${hit ? import_chalk61.default.red("FAIL") : import_chalk61.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
14168
+ log3(`[${hit ? import_chalk62.default.red("FAIL") : import_chalk62.default.green("PASS")}] ${metric.padEnd(11)} ${String(value)}${unit} ${hit ? ">" : "\u2264"} ${limit}${unit}`);
13849
14169
  };
13850
14170
  row("client_p50", client.p50, thresholds.p50_ms, "ms");
13851
14171
  row("client_p95", client.p95, thresholds.p95_ms, "ms");
@@ -13854,12 +14174,12 @@ async function runOpPerf(o = {}) {
13854
14174
  row("gateway_p50", gateway.p50, thresholds.gateway_p50_ms, "ms");
13855
14175
  row("gateway_p95", gateway.p95, thresholds.gateway_p95_ms, "ms");
13856
14176
  } else {
13857
- log3(`[${import_chalk61.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
14177
+ log3(`[${import_chalk62.default.yellow("SKIP")}] gateway_p50 / gateway_p95 \u2014 ledger rows not available`);
13858
14178
  }
13859
14179
  log3("");
13860
- if (aborted) log3(import_chalk61.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
13861
- else if (ok) log3(import_chalk61.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
13862
- else log3(import_chalk61.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
14180
+ if (aborted) log3(import_chalk62.default.red(`FAIL \u2014 interrupted after ${sent}/${total} requests`));
14181
+ else if (ok) log3(import_chalk62.default.green(`PASS \u2014 ${client.sent} requests at ${client.rps_achieved} rps, no threshold breached`));
14182
+ else log3(import_chalk62.default.red(`FAIL \u2014 ${breaches.length} threshold(s) breached: ${breaches.map((b) => `${b.metric} ${b.value} > ${b.limit}`).join(", ")}`));
13863
14183
  if (o.json) {
13864
14184
  const doc = {
13865
14185
  kind: "perf",
@@ -13897,9 +14217,9 @@ async function runOpPerf(o = {}) {
13897
14217
  if (fixture.provisioned) {
13898
14218
  try {
13899
14219
  await runnerCall({ action: "perf-teardown", proxy: fixture.proxy }, 120);
13900
- log3(import_chalk61.default.dim(`fixture ${fixture.proxy} torn down`));
14220
+ log3(import_chalk62.default.dim(`fixture ${fixture.proxy} torn down`));
13901
14221
  } catch (e) {
13902
- 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`));
14222
+ 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`));
13903
14223
  process.exitCode = 1;
13904
14224
  }
13905
14225
  await scrubSandboxKeys(120);
@@ -13933,93 +14253,93 @@ var OP_COMMANDS = [
13933
14253
  function renderOpCommands() {
13934
14254
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
13935
14255
  const lines = OP_COMMANDS.map((c) => {
13936
- const left = ` ${import_chalk62.default.cyan(`apiblaze ${c.cmd}`)}`;
14256
+ const left = ` ${import_chalk63.default.cyan(`apiblaze ${c.cmd}`)}`;
13937
14257
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
13938
- return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk62.default.dim(`(${c.extra})`) : ""}`;
14258
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk63.default.dim(`(${c.extra})`) : ""}`;
13939
14259
  });
13940
14260
  return [
13941
- import_chalk62.default.bold("Operator commands"),
14261
+ import_chalk63.default.bold("Operator commands"),
13942
14262
  ...lines,
13943
14263
  "",
13944
- import_chalk62.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
13945
- import_chalk62.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
13946
- import_chalk62.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
14264
+ import_chalk63.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
14265
+ import_chalk63.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
14266
+ import_chalk63.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
13947
14267
  "",
13948
- import_chalk62.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
13949
- import_chalk62.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
13950
- import_chalk62.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
14268
+ import_chalk63.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
14269
+ import_chalk63.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
14270
+ import_chalk63.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
13951
14271
  ].join("\n");
13952
14272
  }
13953
14273
  function printResidue(report, applied) {
13954
14274
  const up = report?.upstash ?? {};
13955
14275
  const fga = report?.fga ?? {};
13956
14276
  const ghosts = report?.ghosts ?? {};
13957
- console.log(import_chalk62.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
13958
- console.log(import_chalk62.default.bold("\n Upstash"));
14277
+ console.log(import_chalk63.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
14278
+ console.log(import_chalk63.default.bold("\n Upstash"));
13959
14279
  const orphans = up.orphans ?? [];
13960
- if (orphans.length === 0) console.log(import_chalk62.default.green(" no orphaned keys"));
13961
- for (const o of orphans) console.log(` ${import_chalk62.default.yellow(o.key)} ${import_chalk62.default.dim(`\u2014 ${o.reason}`)}`);
13962
- console.log(import_chalk62.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
14280
+ if (orphans.length === 0) console.log(import_chalk63.default.green(" no orphaned keys"));
14281
+ for (const o of orphans) console.log(` ${import_chalk63.default.yellow(o.key)} ${import_chalk63.default.dim(`\u2014 ${o.reason}`)}`);
14282
+ console.log(import_chalk63.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
13963
14283
  if (up.anon_wallet_detail) {
13964
14284
  const d = up.anon_wallet_detail;
13965
- 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)"}`));
14285
+ 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)"}`));
13966
14286
  }
13967
14287
  if (up.keyspace_census) {
13968
14288
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
13969
- console.log(import_chalk62.default.dim(` keyspace: ${census}`));
14289
+ console.log(import_chalk63.default.dim(` keyspace: ${census}`));
13970
14290
  }
13971
- if (up.unknown?.length) console.log(import_chalk62.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
13972
- if (applied) console.log(` ${import_chalk62.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
13973
- for (const e of up.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
13974
- console.log(import_chalk62.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
14291
+ if (up.unknown?.length) console.log(import_chalk63.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
14292
+ if (applied) console.log(` ${import_chalk63.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
14293
+ for (const e of up.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14294
+ console.log(import_chalk63.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
13975
14295
  if (applied) {
13976
14296
  const swept = fga?.swept ?? [];
13977
- if (swept.length === 0) console.log(import_chalk62.default.green(" no orphaned stores"));
14297
+ if (swept.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
13978
14298
  for (const s of swept) {
13979
14299
  console.log(
13980
- ` ${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`)}`
14300
+ ` ${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`)}`
13981
14301
  );
13982
14302
  }
13983
- if (fga?.remaining) console.log(import_chalk62.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
14303
+ if (fga?.remaining) console.log(import_chalk63.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
13984
14304
  const st = fga?.side_tables;
13985
- 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})` : ""}`));
14305
+ 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})` : ""}`));
13986
14306
  } else {
13987
14307
  const fgaOrphans = fga?.orphans ?? [];
13988
- if (fgaOrphans.length === 0) console.log(import_chalk62.default.green(" no orphaned stores"));
14308
+ if (fgaOrphans.length === 0) console.log(import_chalk63.default.green(" no orphaned stores"));
13989
14309
  for (const s of fgaOrphans) {
13990
14310
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
13991
- 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)`)}`);
14311
+ 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)`)}`);
13992
14312
  }
13993
- console.log(import_chalk62.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
14313
+ console.log(import_chalk63.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
13994
14314
  const st = fga?.side_tables;
13995
- 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`));
14315
+ 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`));
13996
14316
  }
13997
- for (const e of fga?.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
13998
- console.log(import_chalk62.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
14317
+ for (const e of fga?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14318
+ console.log(import_chalk63.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
13999
14319
  if (applied) {
14000
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk62.default.green(" no ghost tuples"));
14001
- 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)`)}`);
14320
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk63.default.green(" no ghost tuples"));
14321
+ 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)`)}`);
14002
14322
  } else {
14003
14323
  const n = ghosts?.ghost_count ?? 0;
14004
- 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)`)}`));
14324
+ 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)`)}`));
14005
14325
  else {
14006
- console.log(import_chalk62.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
14326
+ console.log(import_chalk63.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
14007
14327
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
14008
- console.log(import_chalk62.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
14328
+ console.log(import_chalk63.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
14009
14329
  }
14010
- if (n > 20) console.log(import_chalk62.default.dim(` \u2026 and ${n - 20} more`));
14330
+ if (n > 20) console.log(import_chalk63.default.dim(` \u2026 and ${n - 20} more`));
14011
14331
  }
14012
14332
  }
14013
- for (const e of ghosts?.errors ?? []) console.log(import_chalk62.default.red(` error: ${e}`));
14333
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk63.default.red(` error: ${e}`));
14014
14334
  console.log();
14015
14335
  }
14016
14336
  async function runOp(sub, opts = {}, view) {
14017
14337
  if (!loadCredentials()) {
14018
- console.log(import_chalk62.default.dim("Not logged in. Run `apiblaze login`."));
14338
+ console.log(import_chalk63.default.dim("Not logged in. Run `apiblaze login`."));
14019
14339
  return;
14020
14340
  }
14021
14341
  if (!isOperatorLogin()) {
14022
- console.log(import_chalk62.default.dim("`apiblaze op` is only available to platform operators."));
14342
+ console.log(import_chalk63.default.dim("`apiblaze op` is only available to platform operators."));
14023
14343
  return;
14024
14344
  }
14025
14345
  switch (sub) {
@@ -14075,17 +14395,17 @@ async function runOp(sub, opts = {}, view) {
14075
14395
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
14076
14396
  printResidue(report, false);
14077
14397
  if (nUp + nFga + nGhost + nSide === 0) {
14078
- console.log(import_chalk62.default.green("Nothing to sweep."));
14398
+ console.log(import_chalk63.default.green("Nothing to sweep."));
14079
14399
  return;
14080
14400
  }
14081
14401
  if (!opts.yes) {
14082
14402
  const readline3 = await import("readline/promises");
14083
14403
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
14084
14404
  const answer = await rl.question(
14085
- 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: `)
14405
+ 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: `)
14086
14406
  );
14087
14407
  rl.close();
14088
- if (answer.trim() !== "sweep") return void console.log(import_chalk62.default.dim("Aborted."));
14408
+ if (answer.trim() !== "sweep") return void console.log(import_chalk63.default.dim("Aborted."));
14089
14409
  }
14090
14410
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
14091
14411
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -14095,25 +14415,25 @@ async function runOp(sub, opts = {}, view) {
14095
14415
  case "mark": {
14096
14416
  const label3 = (view ?? "").trim();
14097
14417
  if (!label3) {
14098
- console.log(import_chalk62.default.red("Give the change a name:") + import_chalk62.default.cyan(' apiblaze op mark "cached tenant count"'));
14418
+ console.log(import_chalk63.default.red("Give the change a name:") + import_chalk63.default.cyan(' apiblaze op mark "cached tenant count"'));
14099
14419
  return;
14100
14420
  }
14101
14421
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
14102
14422
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
14103
- console.log(import_chalk62.default.green(`
14104
- Marked: `) + import_chalk62.default.bold(label3));
14105
- console.log(import_chalk62.default.dim(` ${ts}`));
14106
- console.log(import_chalk62.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk62.default.cyan("apiblaze op latency compare") + "\n");
14423
+ console.log(import_chalk63.default.green(`
14424
+ Marked: `) + import_chalk63.default.bold(label3));
14425
+ console.log(import_chalk63.default.dim(` ${ts}`));
14426
+ console.log(import_chalk63.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk63.default.cyan("apiblaze op latency compare") + "\n");
14107
14427
  return;
14108
14428
  }
14109
14429
  case "credits": {
14110
14430
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
14111
14431
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14112
14432
  const accounts = data?.accounts ?? [];
14113
- if (accounts.length === 0) return void console.log(import_chalk62.default.dim("No credit wallets."));
14433
+ if (accounts.length === 0) return void console.log(import_chalk63.default.dim("No credit wallets."));
14114
14434
  for (const a of accounts) {
14115
14435
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
14116
- 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") : ""}`);
14436
+ 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") : ""}`);
14117
14437
  }
14118
14438
  return;
14119
14439
  }
@@ -14124,7 +14444,7 @@ async function runOp(sub, opts = {}, view) {
14124
14444
  case "billing": {
14125
14445
  const which = (view ?? "").trim().toLowerCase();
14126
14446
  if (which && which !== "doors" && which !== "meter") {
14127
- return void console.log(import_chalk62.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
14447
+ return void console.log(import_chalk63.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
14128
14448
  }
14129
14449
  const checks = [];
14130
14450
  let doorsData = null;
@@ -14193,29 +14513,29 @@ async function runOp(sub, opts = {}, view) {
14193
14513
  const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
14194
14514
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14195
14515
  const t = data.thresholds_ms;
14196
- 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)"));
14197
- 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+
14516
+ 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)"));
14517
+ 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+
14198
14518
  `));
14199
- console.log(import_chalk62.default.dim(" date reqs excellent okay bad terrible"));
14519
+ console.log(import_chalk63.default.dim(" date reqs excellent okay bad terrible"));
14200
14520
  for (const d of data.days ?? []) {
14201
14521
  const p = d.pct;
14202
- const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk62.default.dim(`${String(v).padStart(5)}%`);
14522
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk63.default.dim(`${String(v).padStart(5)}%`);
14203
14523
  console.log(
14204
- ` ${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)}`
14524
+ ` ${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)}`
14205
14525
  );
14206
14526
  }
14207
14527
  const cul = data.culprits ?? [];
14208
14528
  if (cul.length) {
14209
- console.log(import_chalk62.default.bold("\n Who caused the bad and terrible ones\n"));
14210
- console.log(import_chalk62.default.dim(" bad terrible feature \u2192 dependency"));
14529
+ console.log(import_chalk63.default.bold("\n Who caused the bad and terrible ones\n"));
14530
+ console.log(import_chalk63.default.dim(" bad terrible feature \u2192 dependency"));
14211
14531
  for (const r of cul.slice(0, 12)) {
14212
14532
  if (!r.bad && !r.terrible) continue;
14213
14533
  console.log(
14214
- ` ${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)}`
14534
+ ` ${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)}`
14215
14535
  );
14216
14536
  }
14217
14537
  }
14218
- if (data.caveat) console.log(import_chalk62.default.dim(`
14538
+ if (data.caveat) console.log(import_chalk63.default.dim(`
14219
14539
  \u26A0 ${data.caveat}
14220
14540
  `));
14221
14541
  return;
@@ -14224,24 +14544,24 @@ async function runOp(sub, opts = {}, view) {
14224
14544
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
14225
14545
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14226
14546
  const b = data.before, a = data.after, d = data.delta;
14227
- console.log(import_chalk62.default.bold(`
14228
- Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
14229
- console.log(import_chalk62.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
14547
+ console.log(import_chalk63.default.bold(`
14548
+ Before vs after: `) + import_chalk63.default.cyan(data.marker.label));
14549
+ console.log(import_chalk63.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
14230
14550
  `));
14231
14551
  const row = (name, before, after, delta) => {
14232
14552
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
14233
14553
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
14234
- console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk62.default.green(txt) : import_chalk62.default.red(txt) : import_chalk62.default.dim(txt)}`);
14554
+ console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk63.default.green(txt) : import_chalk63.default.red(txt) : import_chalk63.default.dim(txt)}`);
14235
14555
  };
14236
- console.log(import_chalk62.default.dim(" metric before after change"));
14556
+ console.log(import_chalk63.default.dim(" metric before after change"));
14237
14557
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
14238
14558
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
14239
14559
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
14240
14560
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
14241
- console.log(import_chalk62.default.dim(`
14561
+ console.log(import_chalk63.default.dim(`
14242
14562
  requests: ${b.requests} before \xB7 ${a.requests} after`));
14243
14563
  for (const w of data.warnings ?? []) {
14244
- console.log((data.trustworthy ? import_chalk62.default.dim : import_chalk62.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
14564
+ console.log((data.trustworthy ? import_chalk63.default.dim : import_chalk63.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
14245
14565
  }
14246
14566
  console.log("");
14247
14567
  return;
@@ -14250,19 +14570,19 @@ Before vs after: `) + import_chalk62.default.cyan(data.marker.label));
14250
14570
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
14251
14571
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14252
14572
  const rows2 = data?.rows ?? [];
14253
- if (!rows2.length) return void console.log(import_chalk62.default.dim("No requests over the threshold in that window."));
14254
- console.log(import_chalk62.default.bold(`
14573
+ if (!rows2.length) return void console.log(import_chalk63.default.dim("No requests over the threshold in that window."));
14574
+ console.log(import_chalk63.default.bold(`
14255
14575
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
14256
14576
  `));
14257
- console.log(import_chalk62.default.dim(" total ours theirs blame request id"));
14577
+ console.log(import_chalk63.default.dim(" total ours theirs blame request id"));
14258
14578
  for (const r of rows2.slice(0, 30)) {
14259
14579
  console.log(
14260
- ` ${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 || "")}`
14580
+ ` ${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 || "")}`
14261
14581
  );
14262
14582
  }
14263
- console.log(import_chalk62.default.dim(`
14583
+ console.log(import_chalk63.default.dim(`
14264
14584
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
14265
- console.log(import_chalk62.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
14585
+ console.log(import_chalk63.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
14266
14586
  `));
14267
14587
  return;
14268
14588
  }
@@ -14270,18 +14590,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
14270
14590
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
14271
14591
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
14272
14592
  const rows2 = data?.rows ?? [];
14273
- if (!rows2.length) return void console.log(import_chalk62.default.dim("No LLM traffic in that window."));
14274
- console.log(import_chalk62.default.bold(`
14593
+ if (!rows2.length) return void console.log(import_chalk63.default.dim("No LLM traffic in that window."));
14594
+ console.log(import_chalk63.default.bold(`
14275
14595
  LLM timing \u2014 last ${data.window_hours}h
14276
14596
  `));
14277
- console.log(import_chalk62.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
14597
+ console.log(import_chalk63.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
14278
14598
  for (const r of rows2) {
14279
14599
  const n = r.turns ?? r.requests ?? 0;
14280
14600
  console.log(
14281
14601
  ` ${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 || "-"}`
14282
14602
  );
14283
14603
  }
14284
- console.log(import_chalk62.default.dim(`
14604
+ console.log(import_chalk63.default.dim(`
14285
14605
  ${data.note}
14286
14606
  `));
14287
14607
  return;
@@ -14292,26 +14612,26 @@ LLM timing \u2014 last ${data.window_hours}h
14292
14612
  ]);
14293
14613
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
14294
14614
  const ov = summary?.apiblaze_overhead_ms ?? {};
14295
- console.log(import_chalk62.default.bold(`
14615
+ console.log(import_chalk63.default.bold(`
14296
14616
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
14297
14617
  `));
14298
- 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")}`);
14299
- 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")}`);
14300
- console.log(import_chalk62.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
14618
+ 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")}`);
14619
+ 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")}`);
14620
+ console.log(import_chalk63.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
14301
14621
  `));
14302
14622
  const rows = blame?.blame ?? [];
14303
- if (!rows.length) return void console.log(import_chalk62.default.dim("No latency rows in that window."));
14304
- console.log(import_chalk62.default.bold(" Which feature ate the time, and what inside it\n"));
14305
- console.log(import_chalk62.default.dim(" share p95 feature \u2192 dependency"));
14623
+ if (!rows.length) return void console.log(import_chalk63.default.dim("No latency rows in that window."));
14624
+ console.log(import_chalk63.default.bold(" Which feature ate the time, and what inside it\n"));
14625
+ console.log(import_chalk63.default.dim(" share p95 feature \u2192 dependency"));
14306
14626
  for (const r of rows.slice(0, 15)) {
14307
14627
  const share = `${(r.share * 100).toFixed(1)}%`;
14308
- 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)}`);
14628
+ 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)}`);
14309
14629
  }
14310
14630
  console.log("");
14311
14631
  return;
14312
14632
  }
14313
14633
  default:
14314
- console.log(import_chalk62.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
14634
+ console.log(import_chalk63.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
14315
14635
  }
14316
14636
  }
14317
14637
 
@@ -14362,8 +14682,8 @@ agent.command("openapi").description("Chat to build your API spec from real traf
14362
14682
  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)));
14363
14683
  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) => {
14364
14684
  const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs5.default.existsSync(project));
14365
- const target = opts.target ?? (looksLikeSource ? project : void 0);
14366
- return runApichat({ ...opts, target, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
14685
+ const target2 = opts.target ?? (looksLikeSource ? project : void 0);
14686
+ return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
14367
14687
  }));
14368
14688
  var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
14369
14689
  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)));
@@ -14381,7 +14701,7 @@ program.command("dev").description(`Put the code running on this machine behind
14381
14701
  try {
14382
14702
  const resolved = parseInt(port ?? opts.port, 10);
14383
14703
  if (Number.isNaN(resolved)) {
14384
- console.error(import_chalk63.default.red(`Invalid port: ${port ?? opts.port}`));
14704
+ console.error(import_chalk64.default.red(`Invalid port: ${port ?? opts.port}`));
14385
14705
  process.exit(1);
14386
14706
  }
14387
14707
  await runDev({ ...opts, port: resolved });
@@ -14444,13 +14764,25 @@ var logs = program.command("logs").description("API Producer view \u2014 stream
14444
14764
  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())));
14445
14765
  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)));
14446
14766
  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)));
14447
- 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)));
14767
+ 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)));
14768
+ 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");
14769
+ 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)));
14770
+ 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", `
14771
+ Example \u2014 lock your backend to the proxy:
14772
+ export TARGET_SERVER_SECRET=$(openssl rand -hex 24) # also give it to your backend
14773
+ apiblaze transform set-header shop x-target-api-key --value-env TARGET_SERVER_SECRET --secret`).action(action((project, header, opts) => runTransformSetHeader(project, header, opts)));
14774
+ 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)));
14448
14775
  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)));
14449
- var domain = program.command("domain").description("Use your own domain for a proxy");
14450
- domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("<project>", "Project name or id").requiredOption("--domain <host>", "Custom hostname to add").option("--tenant <slug>", "Bind the domain to one of the project's tenants (per-tenant scope)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainAdd(project, opts)));
14451
- domain.command("list").description("List custom domains for a proxy").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) => runDomainList(project, opts)));
14452
- domain.command("status").description("Check a custom domain's validation status").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainStatus(project, opts)));
14453
- domain.command("rm").description("Remove a custom domain").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
14776
+ var domain = program.command("domain").description("Use your own domain for your API, MCP server, developer portal or login pages (billed monthly)");
14777
+ 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", `
14778
+ Examples:
14779
+ apiblaze domain add shop --domain api.acme.com # the API
14780
+ apiblaze domain add shop --domain mcp.acme.com --kind mcp # the MCP server
14781
+ apiblaze domain add --tenant acme --kind portal --domain developers.acme.com
14782
+ apiblaze domain add --tenant acme --kind auth --domain login.acme.com`).action(action((project, opts) => runDomainAdd(project, opts)));
14783
+ domain.command("list").description("List a project's custom domains, or a tenant's portal/login domains").argument("[project]", "Project name or id (omit with --tenant)").option("--tenant <slug>", "List the tenant's portal / auth domains").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainList(project, opts)));
14784
+ domain.command("status").description("Check a custom domain's validation status (and show the DNS records still needed)").argument("[project]", "Project name or id (omit with --tenant)").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--tenant <slug>", "The tenant, for a portal / auth domain").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainStatus(project, opts)));
14785
+ domain.command("rm").description("Remove a custom domain (billing for it stops)").argument("[project]", "Project name or id (omit with --tenant)").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--tenant <slug>", "The tenant, for a portal / auth domain").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
14454
14786
  domain.command("set-base").description("Choose which version/environment your main URL serves").argument("<project>", "Project name or id").option("--env <env>", "Environment (default: prod)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainSetBase(project, opts)));
14455
14787
  var group = program.command("group").description("Manage a tenant's users & groups \u2014 bare command lists groups").action(action(() => runGroupList({})));
14456
14788
  group.command("list").description("List groups").option("--tenant <slug>", "Tenant").option("--json", "Machine-readable output").action(action((opts) => runGroupList(opts)));
@@ -14480,11 +14812,11 @@ admins.command("remove").description("Remove a tenant admin").argument("<email>"
14480
14812
  program.addCommand(admins);
14481
14813
  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)));
14482
14814
  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)));
14483
- 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)));
14815
+ 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)));
14484
14816
  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 })));
14485
14817
  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 })));
14486
14818
  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 })));
14487
- program.addCommand(apikeys, { hidden: true });
14819
+ program.addCommand(apikeys);
14488
14820
  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", () => `
14489
14821
  ${renderOpCommands()}
14490
14822
  `);
@@ -14495,10 +14827,10 @@ spec.command("set").description("Replace the stored OpenAPI spec from a local fi
14495
14827
  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)));
14496
14828
  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)));
14497
14829
  var HELP_GROUPS = [
14498
- { title: "Chat", commands: ["docs", "apichat", "agent"] },
14499
- { title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout"] },
14830
+ { title: "Chat", commands: ["docs", "apichat", "agent", "llm"] },
14831
+ { title: "Setup", commands: ["dev", "create", "integration", "skill", "login", "init", "sidecar", "claim", "team", "whoami", "logout", "flush"] },
14500
14832
  { title: "Recipes \u2014 install a working setup, or publish yours", commands: ["search", "show", "install", "publish", "withdraw"] },
14501
- { title: "Control plane commands", commands: ["config", "projects", "logs", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
14833
+ { 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"] },
14502
14834
  { title: "Data plane commands", commands: [
14503
14835
  { parent: "consumer", sub: "login" },
14504
14836
  { parent: "consumer", sub: "apikeys" },
@@ -14524,7 +14856,7 @@ function groupedCommandHelp() {
14524
14856
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
14525
14857
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
14526
14858
  }).filter(Boolean).join("\n");
14527
- return `${import_chalk63.default.bold(g.title)}
14859
+ return `${import_chalk64.default.bold(g.title)}
14528
14860
  ${rows}`;
14529
14861
  }).join("\n\n");
14530
14862
  }
@@ -14535,7 +14867,7 @@ program.configureHelp({
14535
14867
  }
14536
14868
  });
14537
14869
  program.addHelpText("after", () => `
14538
- ${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.`)}
14870
+ ${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.`)}
14539
14871
 
14540
14872
  ${groupedCommandHelp()}
14541
14873
 
@@ -14571,14 +14903,14 @@ async function recoverStaleTeam() {
14571
14903
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
14572
14904
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
14573
14905
  if (!linked) {
14574
- console.error(import_chalk63.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
14906
+ console.error(import_chalk64.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
14575
14907
  return;
14576
14908
  }
14577
14909
  if (linked.teamId === creds.teamId) return;
14578
14910
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
14579
14911
  delete next.activeTenant;
14580
14912
  saveCredentials(next);
14581
- 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.`));
14913
+ 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.`));
14582
14914
  } catch {
14583
14915
  }
14584
14916
  }
@@ -14586,16 +14918,33 @@ async function printError(err) {
14586
14918
  if (err instanceof ApiError) {
14587
14919
  const data = err.body;
14588
14920
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
14589
- console.error(import_chalk63.default.red(`
14921
+ console.error(import_chalk64.default.red(`
14590
14922
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
14591
14923
  if (err.status === 403 || err.status === 404) {
14592
14924
  await recoverStaleTeam();
14593
14925
  }
14594
14926
  } else if (err instanceof Error) {
14595
- console.error(import_chalk63.default.red(`
14927
+ console.error(import_chalk64.default.red(`
14596
14928
  Error: ${err.message}`));
14597
14929
  } else {
14598
- console.error(import_chalk63.default.red("\nUnknown error"));
14930
+ console.error(import_chalk64.default.red("\nUnknown error"));
14599
14931
  }
14600
14932
  }
14933
+ function hintStaleCacheOnUnknownOption(cmd) {
14934
+ cmd.exitOverride((err) => {
14935
+ if (err.code === "commander.unknownOption") {
14936
+ console.error(import_chalk64.default.yellow(
14937
+ `
14938
+ This is ${B.cli} v${version}. If that option is documented but this build
14939
+ does not have it, your npx cache is serving an older copy \u2014 re-run as:
14940
+ `
14941
+ ));
14942
+ console.error(import_chalk64.default.cyan(` npx ${B.cli}@latest ${process.argv.slice(2).join(" ")}
14943
+ `));
14944
+ }
14945
+ process.exit(err.exitCode);
14946
+ });
14947
+ for (const sub of cmd.commands) hintStaleCacheOnUnknownOption(sub);
14948
+ }
14949
+ hintStaleCacheOnUnknownOption(program);
14601
14950
  program.parse(process.argv);