apiblaze 0.11.0 → 0.12.0

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 +1030 -317
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -300,12 +300,199 @@ var init_anon_cred = __esm({
300
300
  }
301
301
  });
302
302
 
303
+ // src/lib/trace.ts
304
+ function setVerbose(v) {
305
+ verbose = v;
306
+ }
307
+ function recordCall(e) {
308
+ if (verbose) entries.push(e);
309
+ }
310
+ function maskBody(body) {
311
+ if (body === void 0) return void 0;
312
+ return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
313
+ }
314
+ function renderTrace() {
315
+ if (!verbose || entries.length === 0) return;
316
+ console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
317
+ console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
318
+ console.log(
319
+ import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
320
+ );
321
+ entries.forEach((e, i) => {
322
+ const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
323
+ if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
324
+ const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
325
+ const masked = maskBody(e.body);
326
+ const hasBody = e.method !== "GET" && masked !== void 0;
327
+ console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
328
+ console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
329
+ if (hasBody) {
330
+ console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
331
+ console.log(import_chalk15.default.green(` -d '${masked}'`));
332
+ }
333
+ if (i < entries.length - 1) console.log();
334
+ });
335
+ entries.length = 0;
336
+ }
337
+ var import_chalk15, CONTROL_API_VERSION, verbose, entries, SECRET_KEY;
338
+ var init_trace = __esm({
339
+ "src/lib/trace.ts"() {
340
+ "use strict";
341
+ import_chalk15 = __toESM(require("chalk"));
342
+ CONTROL_API_VERSION = "1.0.0";
343
+ verbose = false;
344
+ entries = [];
345
+ SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
346
+ }
347
+ });
348
+
349
+ // src/lib/admin.ts
350
+ async function admin(call) {
351
+ const token = getAccessToken();
352
+ const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
353
+ method: "POST",
354
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
355
+ body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
356
+ });
357
+ let data = null;
358
+ try {
359
+ data = await res.json();
360
+ } catch {
361
+ }
362
+ recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
363
+ maybePrintBilling(data);
364
+ if (!res.ok) {
365
+ const msg = data?.details ?? data?.error ?? res.statusText;
366
+ throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
367
+ }
368
+ return data;
369
+ }
370
+ function maybePrintBilling(data) {
371
+ const b = data?.billing;
372
+ if (b && typeof b.charged_cents === "number") {
373
+ const usd = (b.charged_cents / 100).toFixed(2);
374
+ const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
375
+ console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
376
+ }
377
+ }
378
+ var import_chalk16, DASHBOARD_BASE3;
379
+ var init_admin = __esm({
380
+ "src/lib/admin.ts"() {
381
+ "use strict";
382
+ import_chalk16 = __toESM(require("chalk"));
383
+ init_auth();
384
+ init_trace();
385
+ init_types();
386
+ DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
387
+ }
388
+ });
389
+
390
+ // src/lib/tenant-pick.ts
391
+ var tenant_pick_exports = {};
392
+ __export(tenant_pick_exports, {
393
+ pickTenant: () => pickTenant
394
+ });
395
+ async function fetchPage(teamId, q) {
396
+ const out = await admin({
397
+ method: "GET",
398
+ path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1&limit=${PAGE}${q ? `&q=${encodeURIComponent(q)}` : ""}`,
399
+ summary: q ? `Search tenants matching "${q}"` : "List tenants (first page)"
400
+ });
401
+ const rows = (out?.tenants ?? []).map(
402
+ (t) => typeof t === "string" ? { tenant_name: t } : t
403
+ );
404
+ return { rows, total: out?.total ?? rows.length, defaultTenant: out?.default_tenant ?? null };
405
+ }
406
+ function label(t, defaultTenant, active) {
407
+ const tags = [
408
+ t.tenant_name === active ? import_chalk22.default.cyan("active scope") : "",
409
+ t.tenant_name === defaultTenant ? import_chalk22.default.dim("team default") : ""
410
+ ].filter(Boolean).join(", ");
411
+ const disp = t.display_name && t.display_name !== t.tenant_name ? import_chalk22.default.dim(` ${t.display_name}`) : "";
412
+ return `${t.tenant_name}${disp}${tags ? ` (${tags})` : ""}`;
413
+ }
414
+ async function pickTenant(teamId, opts = {}) {
415
+ const { default: inquirer2 } = await import("inquirer");
416
+ const active = loadCredentials()?.activeTenant;
417
+ let q = opts.initialQuery ?? "";
418
+ for (; ; ) {
419
+ const spinner = (0, import_ora8.default)(q ? `Searching tenants for "${q}"...` : "Loading tenants...").start();
420
+ const page = await fetchPage(teamId, q).finally(() => spinner.stop());
421
+ if (!page.total && !q) {
422
+ if (opts.allowCreate) {
423
+ const { make } = await inquirer2.prompt([{ type: "confirm", name: "make", message: "No tenants yet \u2014 create one?", default: true }]);
424
+ if (make) return await createTenantInline(teamId);
425
+ }
426
+ console.error(import_chalk22.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
427
+ return null;
428
+ }
429
+ const truncated = page.total > page.rows.length;
430
+ const choices = page.rows.map((t) => ({
431
+ name: label(t, page.defaultTenant, active),
432
+ value: t.tenant_name
433
+ }));
434
+ if (truncated || q) {
435
+ choices.push(new inquirer2.Separator(import_chalk22.default.dim(
436
+ truncated ? `showing ${page.rows.length} of ${page.total}${q ? ` matching "${q}"` : ""} \u2014 search to narrow` : `matches for "${q}"`
437
+ )));
438
+ choices.push({ name: `\u{1F50D} Search${q ? " again" : ""}\u2026`, value: "\0search" });
439
+ }
440
+ if (q) choices.push({ name: "\u21BA Show all (clear search)", value: "\0clear" });
441
+ if (opts.allowCreate) choices.push({ name: "\uFF0B Create a new tenant\u2026", value: "\0create" });
442
+ if (opts.allowBack) choices.push({ name: "\u2190 Back", value: "\0back" });
443
+ const { picked } = await inquirer2.prompt([{
444
+ type: "list",
445
+ name: "picked",
446
+ pageSize: PAGE + 5,
447
+ message: opts.message ?? "Which tenant?",
448
+ default: active && page.rows.some((t) => t.tenant_name === active) ? active : void 0,
449
+ choices
450
+ }]);
451
+ if (picked === "\0back") return null;
452
+ if (picked === "\0clear") {
453
+ q = "";
454
+ continue;
455
+ }
456
+ if (picked === "\0create") return await createTenantInline(teamId);
457
+ if (picked === "\0search") {
458
+ const { nq } = await inquirer2.prompt([{ type: "input", name: "nq", message: "Search (name or display name):", default: q }]);
459
+ q = String(nq ?? "").trim();
460
+ continue;
461
+ }
462
+ return picked;
463
+ }
464
+ }
465
+ async function createTenantInline(teamId) {
466
+ const { default: inquirer2 } = await import("inquirer");
467
+ const { display } = await inquirer2.prompt([{ type: "input", name: "display", message: "Display name for the new tenant:", validate: (s) => !!s.trim() || "required" }]);
468
+ const out = await admin({
469
+ method: "POST",
470
+ path: `/teams/${encodeURIComponent(teamId)}/tenants`,
471
+ body: { display_name: display.trim() },
472
+ summary: `Create tenant "${display.trim()}"`
473
+ });
474
+ const slug = out?.tenant_name ?? out?.tenant?.tenant_name;
475
+ console.log(import_chalk22.default.green(` Tenant ${import_chalk22.default.bold(slug)} created.`));
476
+ return slug;
477
+ }
478
+ var import_chalk22, import_ora8, PAGE;
479
+ var init_tenant_pick = __esm({
480
+ "src/lib/tenant-pick.ts"() {
481
+ "use strict";
482
+ import_chalk22 = __toESM(require("chalk"));
483
+ import_ora8 = __toESM(require("ora"));
484
+ init_admin();
485
+ init_auth();
486
+ PAGE = 15;
487
+ }
488
+ });
489
+
303
490
  // src/index.ts
304
491
  var import_commander = require("commander");
305
- var import_chalk32 = __toESM(require("chalk"));
492
+ var import_chalk34 = __toESM(require("chalk"));
306
493
 
307
494
  // package.json
308
- var version = "0.11.0";
495
+ var version = "0.12.0";
309
496
 
310
497
  // src/index.ts
311
498
  init_types();
@@ -1009,9 +1196,9 @@ function printTunnelEndpoints(restore, targets) {
1009
1196
  if (restore.length === 0) return;
1010
1197
  console.log(import_chalk4.default.bold("\nYour proxy is live at:"));
1011
1198
  for (const r of restore) {
1012
- const label = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
1199
+ const label2 = targets.find((t) => t.projectId === r.projectId)?.projectName ?? r.projectId;
1013
1200
  console.log(`
1014
- ${import_chalk4.default.bold(label)}`);
1201
+ ${import_chalk4.default.bold(label2)}`);
1015
1202
  const internalEnvs = Object.keys(r.environments ?? {}).filter((e) => isInternalTarget(r.environments[e]?.target));
1016
1203
  const envs = internalEnvs.includes("dev") ? ["dev"] : internalEnvs.length ? internalEnvs : ["dev"];
1017
1204
  for (const env of envs) {
@@ -1835,6 +2022,7 @@ async function runWhoami(opts = {}) {
1835
2022
  console.log(` ${import_chalk9.default.cyan("Signed in as")} ${import_chalk9.default.bold(who)}`);
1836
2023
  if (creds.email && creds.email !== who) console.log(` Email: ${creds.email}`);
1837
2024
  if (creds.teamName || creds.teamId) console.log(` Team: ${import_chalk9.default.bold(creds.teamName ?? creds.teamId)}`);
2025
+ if (creds.activeTenant) console.log(` Tenant scope: ${import_chalk9.default.bold(creds.activeTenant)} ${import_chalk9.default.dim("(clear: `apiblaze tenant use --clear`)")}`);
1838
2026
  if (Date.now() >= creds.expiresAt) console.log(import_chalk9.default.yellow(" \u26A0 Session expired \u2014 run `apiblaze login`."));
1839
2027
  }
1840
2028
  console.log(import_chalk9.default.bold("\nAPI Consumer"));
@@ -2181,82 +2369,7 @@ async function runMcp(projectArg, apiVersionArg, opts) {
2181
2369
  // src/commands/delete.ts
2182
2370
  var import_chalk18 = __toESM(require("chalk"));
2183
2371
  var import_ora5 = __toESM(require("ora"));
2184
-
2185
- // src/lib/admin.ts
2186
- var import_chalk16 = __toESM(require("chalk"));
2187
- init_auth();
2188
-
2189
- // src/lib/trace.ts
2190
- var import_chalk15 = __toESM(require("chalk"));
2191
- var CONTROL_API_VERSION = "1.0.0";
2192
- var verbose = false;
2193
- var entries = [];
2194
- function setVerbose(v) {
2195
- verbose = v;
2196
- }
2197
- function recordCall(e) {
2198
- if (verbose) entries.push(e);
2199
- }
2200
- var SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
2201
- function maskBody(body) {
2202
- if (body === void 0) return void 0;
2203
- return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
2204
- }
2205
- function renderTrace() {
2206
- if (!verbose || entries.length === 0) return;
2207
- console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
2208
- console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
2209
- console.log(
2210
- import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
2211
- );
2212
- entries.forEach((e, i) => {
2213
- const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
2214
- if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
2215
- const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
2216
- const masked = maskBody(e.body);
2217
- const hasBody = e.method !== "GET" && masked !== void 0;
2218
- console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
2219
- console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_CONTROLPLANE_APIKEY"' + (hasBody ? " \\" : "")));
2220
- if (hasBody) {
2221
- console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
2222
- console.log(import_chalk15.default.green(` -d '${masked}'`));
2223
- }
2224
- if (i < entries.length - 1) console.log();
2225
- });
2226
- entries.length = 0;
2227
- }
2228
-
2229
- // src/lib/admin.ts
2230
- init_types();
2231
- var DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2232
- async function admin(call) {
2233
- const token = getAccessToken();
2234
- const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
2235
- method: "POST",
2236
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
2237
- body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
2238
- });
2239
- let data = null;
2240
- try {
2241
- data = await res.json();
2242
- } catch {
2243
- }
2244
- recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
2245
- maybePrintBilling(data);
2246
- if (!res.ok) {
2247
- const msg = data?.details ?? data?.error ?? res.statusText;
2248
- throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
2249
- }
2250
- return data;
2251
- }
2252
- function maybePrintBilling(data) {
2253
- const b = data?.billing;
2254
- if (b && typeof b.charged_cents === "number") {
2255
- const usd = (b.charged_cents / 100).toFixed(2);
2256
- const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
2257
- console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
2258
- }
2259
- }
2372
+ init_admin();
2260
2373
 
2261
2374
  // src/lib/resolve.ts
2262
2375
  var import_chalk17 = __toESM(require("chalk"));
@@ -2369,6 +2482,7 @@ async function runDelete(project, version2, opts) {
2369
2482
  // src/commands/export.ts
2370
2483
  var import_chalk19 = __toESM(require("chalk"));
2371
2484
  var import_fs3 = require("fs");
2485
+ init_admin();
2372
2486
  init_auth();
2373
2487
  var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2374
2488
  async function runExport(projectArg, versionArg, opts) {
@@ -2425,6 +2539,7 @@ async function runExport(projectArg, versionArg, opts) {
2425
2539
  // src/commands/config.ts
2426
2540
  var import_chalk20 = __toESM(require("chalk"));
2427
2541
  var import_ora6 = __toESM(require("ora"));
2542
+ init_admin();
2428
2543
  async function patchConfig(project, opts, body, summary) {
2429
2544
  const { teamId } = await resolveTeam(opts.team);
2430
2545
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
@@ -2478,13 +2593,15 @@ async function runRename(project, opts) {
2478
2593
  }
2479
2594
 
2480
2595
  // src/commands/config-browse.ts
2481
- var import_chalk26 = __toESM(require("chalk"));
2482
- var import_ora11 = __toESM(require("ora"));
2596
+ var import_chalk28 = __toESM(require("chalk"));
2597
+ var import_ora13 = __toESM(require("ora"));
2598
+ init_admin();
2483
2599
  init_auth();
2484
2600
 
2485
2601
  // src/commands/domain.ts
2486
2602
  var import_chalk21 = __toESM(require("chalk"));
2487
2603
  var import_ora7 = __toESM(require("ora"));
2604
+ init_admin();
2488
2605
  async function runDomainAdd(project, opts) {
2489
2606
  if (!opts.domain) {
2490
2607
  console.error(import_chalk21.default.red("--domain is required."));
@@ -2587,8 +2704,29 @@ async function runDomainSetBase(project, opts) {
2587
2704
  }
2588
2705
 
2589
2706
  // src/commands/tenant.ts
2590
- var import_chalk22 = __toESM(require("chalk"));
2591
- var import_ora8 = __toESM(require("ora"));
2707
+ var import_chalk23 = __toESM(require("chalk"));
2708
+ var import_ora9 = __toESM(require("ora"));
2709
+ init_admin();
2710
+ init_auth();
2711
+ init_tenant_pick();
2712
+ async function runTenantUse(query, opts) {
2713
+ const creds = loadCredentials();
2714
+ if (!creds) {
2715
+ console.error(import_chalk23.default.red("Not logged in. Run `apiblaze login` first."));
2716
+ process.exit(1);
2717
+ }
2718
+ if (opts.clear) {
2719
+ delete creds.activeTenant;
2720
+ saveCredentials(creds);
2721
+ console.log(import_chalk23.default.green("Tenant scope cleared."));
2722
+ return;
2723
+ }
2724
+ const { teamId } = await resolveTeam(opts.team);
2725
+ const slug = await pickTenant(teamId, { message: "Scope future commands to which tenant?", initialQuery: query });
2726
+ if (!slug) process.exit(1);
2727
+ saveCredentials({ ...creds, activeTenant: slug });
2728
+ console.log(import_chalk23.default.green(`Tenant scope set to ${import_chalk23.default.bold(slug)}.`) + import_chalk23.default.dim(" (clear with `apiblaze tenant use --clear`)"));
2729
+ }
2592
2730
  async function runTenantList(opts) {
2593
2731
  const { teamId, teamName } = await resolveTeam(opts.team);
2594
2732
  const out = await admin({
@@ -2602,22 +2740,22 @@ async function runTenantList(opts) {
2602
2740
  return;
2603
2741
  }
2604
2742
  if (!tenants.length) {
2605
- console.log(import_chalk22.default.yellow("No tenants."));
2743
+ console.log(import_chalk23.default.yellow("No tenants."));
2606
2744
  return;
2607
2745
  }
2608
2746
  for (const t of tenants) {
2609
2747
  const name = typeof t === "string" ? t : t.tenant_name;
2610
- const display = typeof t === "string" ? "" : import_chalk22.default.dim(` ${t.display_name ?? ""}`);
2611
- console.log(` ${import_chalk22.default.bold(name)}${display}`);
2748
+ const display = typeof t === "string" ? "" : import_chalk23.default.dim(` ${t.display_name ?? ""}`);
2749
+ console.log(` ${import_chalk23.default.bold(name)}${display}`);
2612
2750
  }
2613
2751
  }
2614
2752
  async function runTenantCreate(opts) {
2615
2753
  if (!opts.name) {
2616
- console.error(import_chalk22.default.red("--name (display name) is required."));
2754
+ console.error(import_chalk23.default.red("--name (display name) is required."));
2617
2755
  process.exit(1);
2618
2756
  }
2619
2757
  const { teamId } = await resolveTeam(opts.team);
2620
- const spinner = (0, import_ora8.default)("Creating tenant...").start();
2758
+ const spinner = (0, import_ora9.default)("Creating tenant...").start();
2621
2759
  try {
2622
2760
  const out = await admin({
2623
2761
  method: "POST",
@@ -2625,7 +2763,7 @@ async function runTenantCreate(opts) {
2625
2763
  body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
2626
2764
  summary: `Create tenant "${opts.name}"`
2627
2765
  });
2628
- spinner.succeed(`Created tenant ${import_chalk22.default.bold(out?.tenant_name ?? opts.name)}.`);
2766
+ spinner.succeed(`Created tenant ${import_chalk23.default.bold(out?.tenant_name ?? opts.name)}.`);
2629
2767
  if (opts.json) console.log(JSON.stringify(out));
2630
2768
  } catch (err) {
2631
2769
  spinner.fail("Tenant create failed.");
@@ -2634,12 +2772,12 @@ async function runTenantCreate(opts) {
2634
2772
  }
2635
2773
  async function runTenantAttach(project, opts) {
2636
2774
  if (!opts.tenant) {
2637
- console.error(import_chalk22.default.red("--tenant <slug> is required."));
2775
+ console.error(import_chalk23.default.red("--tenant <slug> is required."));
2638
2776
  process.exit(1);
2639
2777
  }
2640
2778
  const { teamId } = await resolveTeam(opts.team);
2641
2779
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
2642
- const spinner = (0, import_ora8.default)("Attaching tenant...").start();
2780
+ const spinner = (0, import_ora9.default)("Attaching tenant...").start();
2643
2781
  try {
2644
2782
  const out = await admin({
2645
2783
  method: "POST",
@@ -2662,11 +2800,11 @@ async function runTenantDelete(slug, opts) {
2662
2800
  { type: "confirm", name: "confirm", message: `Permanently delete tenant "${slug}" and everything under it? This cannot be undone.`, default: false }
2663
2801
  ]);
2664
2802
  if (!confirm) {
2665
- console.log(import_chalk22.default.dim("Aborted."));
2803
+ console.log(import_chalk23.default.dim("Aborted."));
2666
2804
  return;
2667
2805
  }
2668
2806
  }
2669
- const spinner = (0, import_ora8.default)("Deleting tenant...").start();
2807
+ const spinner = (0, import_ora9.default)("Deleting tenant...").start();
2670
2808
  try {
2671
2809
  await admin({
2672
2810
  method: "DELETE",
@@ -2681,13 +2819,13 @@ async function runTenantDelete(slug, opts) {
2681
2819
  }
2682
2820
  async function runTenantCors(opts) {
2683
2821
  if (!opts.tenant) {
2684
- console.error(import_chalk22.default.red("--tenant <slug> is required."));
2822
+ console.error(import_chalk23.default.red("--tenant <slug> is required."));
2685
2823
  process.exit(1);
2686
2824
  }
2687
2825
  const { teamId } = await resolveTeam(opts.team);
2688
2826
  const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2689
2827
  const cors = origins.length ? { allowed_origins: origins } : null;
2690
- const spinner = (0, import_ora8.default)("Updating CORS...").start();
2828
+ const spinner = (0, import_ora9.default)("Updating CORS...").start();
2691
2829
  try {
2692
2830
  await admin({
2693
2831
  method: "PUT",
@@ -2702,10 +2840,458 @@ async function runTenantCors(opts) {
2702
2840
  }
2703
2841
  }
2704
2842
 
2843
+ // src/commands/tenant-drill.ts
2844
+ var import_chalk24 = __toESM(require("chalk"));
2845
+ var import_ora10 = __toESM(require("ora"));
2846
+ var import_crypto = require("crypto");
2847
+ init_admin();
2848
+ init_auth();
2849
+ init_tenant_pick();
2850
+ init_api();
2851
+ var trailingComma = /\s*,\s*/;
2852
+ var parseList = (s) => s.split(trailingComma).map((x) => x.trim()).filter(Boolean);
2853
+ async function runTenantManage(query, opts) {
2854
+ const { teamId } = await resolveTeam(opts.team);
2855
+ const slug = opts.tenant ?? loadCredentialsTenant(query) ?? await pickTenant(teamId, { message: "Manage which tenant?", initialQuery: query, allowCreate: true });
2856
+ if (!slug) return;
2857
+ await tenantHome(teamId, slug);
2858
+ }
2859
+ function loadCredentialsTenant(query) {
2860
+ if (query) return void 0;
2861
+ return loadCredentials()?.activeTenant ?? void 0;
2862
+ }
2863
+ async function tenantHome(teamId, tenant2) {
2864
+ const { default: inquirer2 } = await import("inquirer");
2865
+ const base = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
2866
+ console.log(import_chalk24.default.bold(`
2867
+ Tenant ${tenant2}`));
2868
+ console.log(import_chalk24.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
2869
+ for (; ; ) {
2870
+ const spinner = (0, import_ora10.default)("Reading tenant state...").start();
2871
+ const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
2872
+ admin({ method: "GET", path: `${base}/iam`, summary: "Read IAM toggle" }).catch(() => null),
2873
+ admin({ method: "GET", path: `${base}/cors`, summary: "Read tenant CORS" }).catch(() => null),
2874
+ admin({ method: "GET", path: `${base}/admin-emails`, summary: "List consumer-admin emails" }).catch(() => null),
2875
+ admin({ method: "GET", path: `${base}/external-issuers`, summary: "List external issuers" }).catch(() => null),
2876
+ admin({ method: "GET", path: `${base}/opaque`, summary: "Read opaque validator" }).catch(() => null),
2877
+ admin({ method: "GET", path: `${base}/app-clients`, summary: "List app clients" }).catch(() => [])
2878
+ ]).finally(() => spinner.stop());
2879
+ const nEmails = (emails?.admin_emails ?? []).length;
2880
+ const nIssuers = (issuers?.external_issuers ?? []).length;
2881
+ const nClients = Array.isArray(clients) ? clients.length : 0;
2882
+ const onOff = (b) => b ? import_chalk24.default.green("on") : import_chalk24.default.dim("off");
2883
+ const { pick: pick2 } = await inquirer2.prompt([{
2884
+ type: "list",
2885
+ name: "pick",
2886
+ message: `Tenant ${tenant2}:`,
2887
+ pageSize: 12,
2888
+ choices: [
2889
+ { name: `App clients (${nClients}) ${import_chalk24.default.dim("OAuth clients your consumers log in through \u2014 providers live inside")}`, value: "clients" },
2890
+ { name: `IAM enforcement: ${onOff(iam?.iam_enabled)} ${import_chalk24.default.dim("key/identity enforcement for this tenant")}`, value: "iam" },
2891
+ { name: `CORS override: ${cors?.cors ? import_chalk24.default.cyan(JSON.stringify(cors.cors)) : import_chalk24.default.dim("(unset)")}`, value: "cors" },
2892
+ { name: `Consumer-admin emails (${nEmails}) ${import_chalk24.default.dim("who may administer the tenant portal")}`, value: "emails" },
2893
+ { name: `External JWT issuers (${nIssuers}) ${import_chalk24.default.dim("bring-your-own auth: trust tokens you already mint")}`, value: "issuers" },
2894
+ { name: `Opaque-token validator: ${opaque?.opaque?.endpoint ? import_chalk24.default.cyan(opaque.opaque.endpoint) : import_chalk24.default.dim("(unset)")}`, value: "opaque" },
2895
+ { name: "\u2190 Back", value: "back" }
2896
+ ]
2897
+ }]);
2898
+ switch (pick2) {
2899
+ case "back":
2900
+ return;
2901
+ case "clients":
2902
+ await clientsMenu(teamId, tenant2, base);
2903
+ break;
2904
+ case "iam": {
2905
+ const { v } = await inquirer2.prompt([{ type: "confirm", name: "v", message: "Enable IAM enforcement?", default: !!iam?.iam_enabled }]);
2906
+ await admin({ method: "PATCH", path: `${base}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
2907
+ console.log(import_chalk24.default.green(` IAM enforcement ${v ? "enabled" : "disabled"}.`));
2908
+ break;
2909
+ }
2910
+ case "cors": {
2911
+ const { v } = await inquirer2.prompt([{
2912
+ type: "input",
2913
+ name: "v",
2914
+ message: 'CORS JSON (e.g. {"allow_all_origins":true} \u2014 "null" clears):',
2915
+ default: cors?.cors ? JSON.stringify(cors.cors) : ""
2916
+ }]);
2917
+ if (v === "") break;
2918
+ const parsed = v === "null" ? null : safeJson(v);
2919
+ if (parsed === void 0) {
2920
+ console.log(import_chalk24.default.yellow(" Not valid JSON \u2014 unchanged."));
2921
+ break;
2922
+ }
2923
+ await admin({ method: "PUT", path: `${base}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
2924
+ console.log(import_chalk24.default.green(" CORS updated."));
2925
+ break;
2926
+ }
2927
+ case "emails":
2928
+ await emailsMenu(base, emails?.admin_emails ?? []);
2929
+ break;
2930
+ case "issuers":
2931
+ await issuersMenu(base, issuers?.external_issuers ?? []);
2932
+ break;
2933
+ case "opaque": {
2934
+ const cur = opaque?.opaque;
2935
+ const { mode } = await inquirer2.prompt([{
2936
+ type: "list",
2937
+ name: "mode",
2938
+ message: "Opaque-token validator:",
2939
+ choices: [
2940
+ { name: cur ? "Replace it" : "Set one up", value: "set" },
2941
+ ...cur ? [{ name: "Clear it", value: "clear" }] : [],
2942
+ { name: "\u2190 Back", value: "back" }
2943
+ ]
2944
+ }]);
2945
+ if (mode === "back") break;
2946
+ if (mode === "clear") {
2947
+ await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
2948
+ console.log(import_chalk24.default.green(" Cleared."));
2949
+ break;
2950
+ }
2951
+ const a = await inquirer2.prompt([
2952
+ { type: "input", name: "endpoint", message: "Introspection endpoint (https):", default: cur?.endpoint, validate: (s) => s.startsWith("https://") || "must be https" },
2953
+ { type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
2954
+ ]);
2955
+ await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
2956
+ console.log(import_chalk24.default.green(" Opaque validator set."));
2957
+ break;
2958
+ }
2959
+ }
2960
+ }
2961
+ }
2962
+ function safeJson(s) {
2963
+ try {
2964
+ return JSON.parse(s);
2965
+ } catch {
2966
+ return void 0;
2967
+ }
2968
+ }
2969
+ async function emailsMenu(base, emails) {
2970
+ const { default: inquirer2 } = await import("inquirer");
2971
+ console.log();
2972
+ if (!emails.length) console.log(import_chalk24.default.dim(" No consumer-admin emails."));
2973
+ for (const e of emails) console.log(` ${import_chalk24.default.bold(e.email ?? e)} ${import_chalk24.default.dim(e.status ?? "")}`);
2974
+ const { act } = await inquirer2.prompt([{
2975
+ type: "list",
2976
+ name: "act",
2977
+ message: "Consumer-admin emails:",
2978
+ choices: [
2979
+ { name: "Add an email", value: "add" },
2980
+ ...emails.length ? [{ name: "Remove an email", value: "rm" }] : [],
2981
+ { name: "\u2190 Back", value: "back" }
2982
+ ]
2983
+ }]);
2984
+ if (act === "back") return;
2985
+ if (act === "add") {
2986
+ const { email } = await inquirer2.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
2987
+ await admin({ method: "POST", path: `${base}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
2988
+ console.log(import_chalk24.default.green(` ${email} added.`));
2989
+ } else {
2990
+ const { e } = await inquirer2.prompt([{
2991
+ type: "list",
2992
+ name: "e",
2993
+ message: "Remove which?",
2994
+ choices: [...emails.map((x) => ({ name: x.email ?? String(x), value: x.email ?? String(x) })), { name: "\u2190 Back", value: null }]
2995
+ }]);
2996
+ if (!e) return;
2997
+ await admin({ method: "DELETE", path: `${base}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
2998
+ console.log(import_chalk24.default.green(` ${e} removed.`));
2999
+ }
3000
+ }
3001
+ async function issuersMenu(base, issuers) {
3002
+ const { default: inquirer2 } = await import("inquirer");
3003
+ console.log();
3004
+ if (!issuers.length) console.log(import_chalk24.default.dim(" No external issuers \u2014 consumers use APIblaze-issued tokens."));
3005
+ for (const i of issuers) console.log(` ${import_chalk24.default.bold(i.iss)} aud=${i.aud} ${import_chalk24.default.dim(i.sub_semantics ?? "")}`);
3006
+ const { act } = await inquirer2.prompt([{
3007
+ type: "list",
3008
+ name: "act",
3009
+ message: "External JWT issuers:",
3010
+ choices: [
3011
+ { name: "Add / replace one issuer", value: "add" },
3012
+ ...issuers.length ? [{ name: "Delete an issuer", value: "rm" }] : [],
3013
+ { name: "\u2190 Back", value: "back" }
3014
+ ]
3015
+ }]);
3016
+ if (act === "back") return;
3017
+ if (act === "add") {
3018
+ const a = await inquirer2.prompt([
3019
+ { type: "input", name: "iss", message: "Issuer URL (iss):", validate: (s) => !!s.trim() || "required" },
3020
+ { type: "input", name: "aud", message: "Audience (aud):", validate: (s) => !!s.trim() || "required" },
3021
+ { type: "input", name: "jwks", message: "JWKS URL (empty = derive from issuer):" },
3022
+ { type: "list", name: "sem", message: "Where is the end-user id?", choices: [
3023
+ { name: "The token sub IS the end user (tenant-owned)", value: "tenant_owned" },
3024
+ { name: "Extract it from a claim\u2026", value: "extract_from_claim" }
3025
+ ] }
3026
+ ]);
3027
+ const claim = a.sem === "extract_from_claim" ? (await inquirer2.prompt([{ type: "input", name: "c", message: "Claim name:", validate: (s) => !!s.trim() || "required" }])).c : void 0;
3028
+ await admin({
3029
+ method: "POST",
3030
+ path: `${base}/external-issuers`,
3031
+ body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
3032
+ summary: `Add external issuer ${a.iss.trim()}`
3033
+ });
3034
+ console.log(import_chalk24.default.green(" Issuer saved."));
3035
+ } else {
3036
+ const { i } = await inquirer2.prompt([{
3037
+ type: "list",
3038
+ name: "i",
3039
+ message: "Delete which issuer?",
3040
+ choices: [...issuers.map((x) => ({ name: `${x.iss} (aud=${x.aud})`, value: x })), { name: "\u2190 Back", value: null }]
3041
+ }]);
3042
+ if (!i) return;
3043
+ await admin({
3044
+ method: "DELETE",
3045
+ path: `${base}/external-issuers?iss=${encodeURIComponent(i.iss)}&aud=${encodeURIComponent(i.aud)}`,
3046
+ summary: `Delete issuer ${i.iss}`
3047
+ });
3048
+ console.log(import_chalk24.default.green(" Issuer deleted."));
3049
+ }
3050
+ }
3051
+ async function clientsMenu(teamId, tenant2, base) {
3052
+ const { default: inquirer2 } = await import("inquirer");
3053
+ for (; ; ) {
3054
+ const spinner = (0, import_ora10.default)("Loading app clients...").start();
3055
+ const raw = await admin({ method: "GET", path: `${base}/app-clients`, summary: "List app clients" }).catch(() => []);
3056
+ spinner.stop();
3057
+ const clients = Array.isArray(raw) ? raw : [];
3058
+ const { pick: pick2 } = await inquirer2.prompt([{
3059
+ type: "list",
3060
+ name: "pick",
3061
+ message: `App clients of ${tenant2}:`,
3062
+ pageSize: 15,
3063
+ choices: [
3064
+ ...clients.map((c) => ({
3065
+ name: `${import_chalk24.default.bold(c.name ?? c.clientId)} ${import_chalk24.default.dim(`${c.clientId}${c.projectName ? ` \xB7 ${c.projectName}` : ""}`)}`,
3066
+ value: c
3067
+ })),
3068
+ ...clients.length ? [] : [new inquirer2.Separator(import_chalk24.default.dim(" no app clients yet"))],
3069
+ { name: "\uFF0B Create an app client\u2026", value: " create" },
3070
+ { name: "\u2190 Back", value: " back" }
3071
+ ]
3072
+ }]);
3073
+ if (pick2 === " back") return;
3074
+ if (pick2 === " create") {
3075
+ const projects = await getProjects(teamId).catch(() => []);
3076
+ if (!projects.length) {
3077
+ console.log(import_chalk24.default.yellow(" No projects in this team \u2014 create a proxy first."));
3078
+ continue;
3079
+ }
3080
+ const a = await inquirer2.prompt([
3081
+ { type: "input", name: "name", message: "Client name:", validate: (s) => !!s.trim() || "required" },
3082
+ { type: "list", name: "proj", message: "For which project?", choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk24.default.dim("v" + p.apiVersion)}`, value: p })) },
3083
+ { type: "input", name: "callbacks", message: "Callback URLs (comma-separated, empty = none):" }
3084
+ ]);
3085
+ const created = await admin({
3086
+ method: "POST",
3087
+ path: `${base}/app-clients`,
3088
+ body: {
3089
+ name: a.name.trim(),
3090
+ projectName: a.proj.projectName,
3091
+ apiVersion: a.proj.apiVersion,
3092
+ ...a.callbacks.trim() ? { authorizedCallbackUrls: parseList(a.callbacks) } : {}
3093
+ },
3094
+ summary: `Create app client "${a.name.trim()}"`
3095
+ });
3096
+ console.log(import_chalk24.default.green(` App client created${created?.clientId ? ` (${created.clientId})` : ""}.`));
3097
+ continue;
3098
+ }
3099
+ await clientHome(base, pick2);
3100
+ }
3101
+ }
3102
+ async function clientHome(base, summary) {
3103
+ const { default: inquirer2 } = await import("inquirer");
3104
+ const id = summary.clientId ?? summary.client_id;
3105
+ const cBase = `${base}/app-clients/${encodeURIComponent(id)}`;
3106
+ for (; ; ) {
3107
+ const spinner = (0, import_ora10.default)("Reading app client...").start();
3108
+ const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
3109
+ spinner.stop();
3110
+ const cb = c.authorizedCallbackUrls ?? c.authorized_callback_urls ?? [];
3111
+ const scopes = c.scopes ?? [];
3112
+ const nProviders = (c.providers ?? []).length;
3113
+ const { pick: pick2 } = await inquirer2.prompt([{
3114
+ type: "list",
3115
+ name: "pick",
3116
+ message: `${c.name ?? id}:`,
3117
+ pageSize: 12,
3118
+ choices: [
3119
+ { name: `Login providers${nProviders ? ` (${nProviders})` : ""} ${import_chalk24.default.dim("google/github/microsoft/\u2026 \u2014 how consumers sign in")}`, value: "providers" },
3120
+ { name: `Callback URLs: ${cb.length ? import_chalk24.default.cyan(cb.join(", ")) : import_chalk24.default.dim("(none)")}`, value: "callbacks" },
3121
+ { name: `Scopes: ${scopes.length ? import_chalk24.default.cyan(scopes.join(" ")) : import_chalk24.default.dim("(defaults)")}`, value: "scopes" },
3122
+ { name: `Token expiries: access ${c.accessTokenExpiry ?? 3600}s \xB7 id ${c.idTokenExpiry ?? 3600}s \xB7 refresh ${c.refreshTokenExpiry ?? 2592e3}s`, value: "expiries" },
3123
+ { name: "Reveal client secret", value: "secret" },
3124
+ { name: "Rotate client secret", value: "rotate" },
3125
+ { name: import_chalk24.default.red("Delete this app client"), value: "delete" },
3126
+ { name: "\u2190 Back", value: "back" }
3127
+ ]
3128
+ }]);
3129
+ switch (pick2) {
3130
+ case "back":
3131
+ return;
3132
+ case "providers":
3133
+ await providersMenu(cBase, c.name ?? id);
3134
+ break;
3135
+ case "callbacks": {
3136
+ const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: "Callback URLs (comma-separated):", default: cb.join(", ") }]);
3137
+ await admin({ method: "PATCH", path: cBase, body: { authorizedCallbackUrls: parseList(v) }, summary: "Update callback URLs" });
3138
+ console.log(import_chalk24.default.green(" Callbacks updated."));
3139
+ break;
3140
+ }
3141
+ case "scopes": {
3142
+ const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: "Scopes (space/comma-separated):", default: scopes.join(" ") }]);
3143
+ await admin({ method: "PATCH", path: cBase, body: { scopes: v.split(/[\s,]+/).filter(Boolean) }, summary: "Update scopes" });
3144
+ console.log(import_chalk24.default.green(" Scopes updated."));
3145
+ break;
3146
+ }
3147
+ case "expiries": {
3148
+ const a = await inquirer2.prompt([
3149
+ { type: "input", name: "access", message: "Access token expiry (seconds):", default: String(c.accessTokenExpiry ?? 3600) },
3150
+ { type: "input", name: "id", message: "ID token expiry (seconds):", default: String(c.idTokenExpiry ?? 3600) },
3151
+ { type: "input", name: "refresh", message: "Refresh token expiry (seconds):", default: String(c.refreshTokenExpiry ?? 2592e3) }
3152
+ ]);
3153
+ await admin({
3154
+ method: "PATCH",
3155
+ path: cBase,
3156
+ body: { accessTokenExpiry: Number(a.access), idTokenExpiry: Number(a.id), refreshTokenExpiry: Number(a.refresh) },
3157
+ summary: "Update token expiries"
3158
+ });
3159
+ console.log(import_chalk24.default.green(" Expiries updated."));
3160
+ break;
3161
+ }
3162
+ case "secret": {
3163
+ const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: "Print the client secret to this terminal?", default: false }]);
3164
+ if (!sure) break;
3165
+ const s = await admin({ method: "GET", path: `${cBase}/secret`, summary: "Reveal client secret" });
3166
+ console.log(` ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
3167
+ break;
3168
+ }
3169
+ case "rotate": {
3170
+ const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: "Rotate the secret? Existing integrations using it will break.", default: false }]);
3171
+ if (!sure) break;
3172
+ const fresh = randomSecret();
3173
+ await admin({ method: "PATCH", path: cBase, body: { clientSecret: fresh }, summary: "Rotate client secret" });
3174
+ console.log(` New ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(fresh)} ${import_chalk24.default.dim("(store it now)")}`);
3175
+ break;
3176
+ }
3177
+ case "delete": {
3178
+ const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: `Delete app client "${c.name ?? id}"? Consumers logged in through it will lose access.`, default: false }]);
3179
+ if (!sure) break;
3180
+ await admin({ method: "DELETE", path: cBase, summary: `Delete app client ${id}` });
3181
+ console.log(import_chalk24.default.green(" App client deleted."));
3182
+ return;
3183
+ }
3184
+ }
3185
+ }
3186
+ }
3187
+ function randomSecret() {
3188
+ const bytes = new Uint8Array(24);
3189
+ (0, import_crypto.randomFillSync)(bytes);
3190
+ return Buffer.from(bytes).toString("base64url");
3191
+ }
3192
+ var PROVIDER_TYPES = ["google", "github", "microsoft", "facebook", "auth0", "other"];
3193
+ var DEFAULT_SCOPES = {
3194
+ google: "openid email profile",
3195
+ microsoft: "openid email profile",
3196
+ github: "read:user user:email",
3197
+ facebook: "public_profile email"
3198
+ };
3199
+ async function providersMenu(cBase, clientLabel) {
3200
+ const { default: inquirer2 } = await import("inquirer");
3201
+ for (; ; ) {
3202
+ const spinner = (0, import_ora10.default)("Loading providers...").start();
3203
+ const raw = await admin({ method: "GET", path: `${cBase}/providers`, summary: "List login providers" }).catch(() => []);
3204
+ spinner.stop();
3205
+ const providers = Array.isArray(raw) ? raw : [];
3206
+ console.log();
3207
+ for (const p of providers) {
3208
+ console.log(` ${import_chalk24.default.bold(p.type)} ${import_chalk24.default.dim(`${p.clientId || "(managed)"} \xB7 identity=${p.tokenType ?? "apiblaze"} \xB7 to-upstream=${p.targetServerToken ?? "apiblaze"}${p.isApiblazeDefault ? " \xB7 apiblaze-managed" : ""}`)}`);
3209
+ }
3210
+ if (!providers.length) console.log(import_chalk24.default.dim(" No login providers \u2014 consumers cannot sign in to this client yet."));
3211
+ const { act } = await inquirer2.prompt([{
3212
+ type: "list",
3213
+ name: "act",
3214
+ message: `Login providers of ${clientLabel}:`,
3215
+ choices: [
3216
+ { name: "\uFF0B Add a provider", value: "add" },
3217
+ ...providers.length ? [
3218
+ { name: "Reveal a provider secret", value: "secret" },
3219
+ { name: "Remove a provider", value: "rm" }
3220
+ ] : [],
3221
+ { name: "\u2190 Back", value: "back" }
3222
+ ]
3223
+ }]);
3224
+ if (act === "back") return;
3225
+ if (act === "add") {
3226
+ const { type } = await inquirer2.prompt([{ type: "list", name: "type", message: "Provider:", choices: PROVIDER_TYPES }]);
3227
+ let body;
3228
+ if (type === "github") {
3229
+ const { managed } = await inquirer2.prompt([{
3230
+ type: "confirm",
3231
+ name: "managed",
3232
+ default: true,
3233
+ message: "Use the APIblaze-managed GitHub app (no credentials needed)?"
3234
+ }]);
3235
+ if (managed) body = { type, managed: true };
3236
+ }
3237
+ if (!body) {
3238
+ const a = await inquirer2.prompt([
3239
+ { type: "input", name: "clientId", message: `${type} OAuth client id:`, validate: (s) => !!s.trim() || "required" },
3240
+ { type: "password", name: "clientSecret", mask: "*", message: `${type} OAuth client secret:`, validate: (s) => s.length >= 6 && s.length <= 200 || "6\u2013200 chars" },
3241
+ ...type === "auth0" || type === "other" ? [{ type: "input", name: "domain", message: "Issuer / domain (e.g. your-tenant.auth0.com):" }] : [],
3242
+ { type: "input", name: "scopes", message: "Scopes:", default: DEFAULT_SCOPES[type] ?? "" }
3243
+ ]);
3244
+ body = {
3245
+ type,
3246
+ clientId: a.clientId.trim(),
3247
+ clientSecret: a.clientSecret,
3248
+ ...a.domain?.trim() ? { domain: a.domain.trim() } : {},
3249
+ scopes: String(a.scopes).split(/[\s,]+/).filter(Boolean)
3250
+ };
3251
+ }
3252
+ const { routing } = await inquirer2.prompt([{
3253
+ type: "list",
3254
+ name: "routing",
3255
+ message: "What does your upstream receive?",
3256
+ choices: [
3257
+ { name: "APIblaze token (recommended \u2014 provider stays an identity source)", value: null },
3258
+ { name: `The ${type} access token`, value: "third_party_access_token" },
3259
+ { name: `The ${type} id token`, value: "third_party_id_token" },
3260
+ { name: "Nothing (strip auth)", value: "none" }
3261
+ ]
3262
+ }]);
3263
+ if (routing) {
3264
+ body.tokenType = "thirdParty";
3265
+ body.targetServerToken = routing;
3266
+ }
3267
+ await admin({ method: "POST", path: `${cBase}/providers`, body, summary: `Add ${type} login provider` });
3268
+ console.log(import_chalk24.default.green(` ${type} provider added.`));
3269
+ } else {
3270
+ const { p } = await inquirer2.prompt([{
3271
+ type: "list",
3272
+ name: "p",
3273
+ message: act === "rm" ? "Remove which provider?" : "Reveal which secret?",
3274
+ choices: [...providers.map((x) => ({ name: `${x.type} ${import_chalk24.default.dim(x.clientId || "(managed)")}`, value: x })), { name: "\u2190 Back", value: null }]
3275
+ }]);
3276
+ if (!p) continue;
3277
+ if (act === "rm") {
3278
+ const { sure } = await inquirer2.prompt([{ type: "confirm", name: "sure", message: `Remove the ${p.type} provider? Consumers using it can no longer sign in.`, default: false }]);
3279
+ if (!sure) continue;
3280
+ await admin({ method: "DELETE", path: `${cBase}/providers/${encodeURIComponent(p.id)}`, summary: `Remove ${p.type} provider` });
3281
+ console.log(import_chalk24.default.green(` ${p.type} removed.`));
3282
+ } else {
3283
+ const s = await admin({ method: "GET", path: `${cBase}/providers/${encodeURIComponent(p.id)}/secret`, summary: `Reveal ${p.type} provider secret` });
3284
+ console.log(` ${import_chalk24.default.bold("client_secret")}: ${import_chalk24.default.green(s?.clientSecret ?? s?.client_secret ?? JSON.stringify(s))}`);
3285
+ }
3286
+ }
3287
+ }
3288
+ }
3289
+
2705
3290
  // src/commands/spec.ts
2706
3291
  var fs6 = __toESM(require("fs"));
2707
- var import_chalk23 = __toESM(require("chalk"));
2708
- var import_ora9 = __toESM(require("ora"));
3292
+ var import_chalk25 = __toESM(require("chalk"));
3293
+ var import_ora11 = __toESM(require("ora"));
3294
+ init_admin();
2709
3295
  async function runSpecGet(project, opts) {
2710
3296
  const { teamId } = await resolveTeam(opts.team);
2711
3297
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
@@ -2718,19 +3304,19 @@ async function runSpecGet(project, opts) {
2718
3304
  }
2719
3305
  async function runSpecSet(project, opts) {
2720
3306
  if (!opts.file) {
2721
- console.error(import_chalk23.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
3307
+ console.error(import_chalk25.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
2722
3308
  process.exit(1);
2723
3309
  }
2724
3310
  let specContent;
2725
3311
  try {
2726
3312
  specContent = fs6.readFileSync(opts.file, "utf-8");
2727
3313
  } catch {
2728
- console.error(import_chalk23.default.red(`Cannot read file: ${opts.file}`));
3314
+ console.error(import_chalk25.default.red(`Cannot read file: ${opts.file}`));
2729
3315
  process.exit(1);
2730
3316
  }
2731
3317
  const { teamId } = await resolveTeam(opts.team);
2732
3318
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
2733
- const spinner = (0, import_ora9.default)("Uploading spec...").start();
3319
+ const spinner = (0, import_ora11.default)("Uploading spec...").start();
2734
3320
  try {
2735
3321
  const out = await admin({
2736
3322
  method: "POST",
@@ -2747,12 +3333,13 @@ async function runSpecSet(project, opts) {
2747
3333
  }
2748
3334
 
2749
3335
  // src/commands/agent.ts
2750
- var import_chalk25 = __toESM(require("chalk"));
2751
- var import_ora10 = __toESM(require("ora"));
3336
+ var import_chalk27 = __toESM(require("chalk"));
3337
+ var import_ora12 = __toESM(require("ora"));
2752
3338
  init_auth();
2753
3339
 
2754
3340
  // src/lib/tools.ts
2755
- var import_chalk24 = __toESM(require("chalk"));
3341
+ var import_chalk26 = __toESM(require("chalk"));
3342
+ init_admin();
2756
3343
  init_api();
2757
3344
  async function proj(teamId, name, version2) {
2758
3345
  return resolveProject(teamId, name, version2);
@@ -2770,15 +3357,15 @@ var TOOLS = [
2770
3357
  const key = keys.dev ?? Object.values(keys)[0];
2771
3358
  const url = `https://${a.name}.abz.run/${version2}/dev`;
2772
3359
  const tryIt = buildTryItCurl(url, auth, key);
2773
- const lines = [` ${import_chalk24.default.dim("Proxy URL:")} ${import_chalk24.default.bold(url)}`];
2774
- if (res.devPortal) lines.push(` ${import_chalk24.default.dim("Dev portal:")} ${res.devPortal}`);
3360
+ const lines = [` ${import_chalk26.default.dim("Proxy URL:")} ${import_chalk26.default.bold(url)}`];
3361
+ if (res.devPortal) lines.push(` ${import_chalk26.default.dim("Dev portal:")} ${res.devPortal}`);
2775
3362
  const envs = Object.keys(keys);
2776
3363
  if (envs.length) {
2777
- lines.push("", ` ${import_chalk24.default.bold("API keys")} ${import_chalk24.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
3364
+ lines.push("", ` ${import_chalk26.default.bold("API keys")} ${import_chalk26.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
2778
3365
  const w = Math.max(...envs.map((e) => e.length));
2779
- for (const env of envs) lines.push(` ${import_chalk24.default.cyan(env.padEnd(w))} ${import_chalk24.default.green(keys[env])}`);
3366
+ for (const env of envs) lines.push(` ${import_chalk26.default.cyan(env.padEnd(w))} ${import_chalk26.default.green(keys[env])}`);
2780
3367
  }
2781
- if (tryIt) lines.push("", ` ${import_chalk24.default.dim("Try it:")}`, ` ${import_chalk24.default.cyan(tryIt)}`);
3368
+ if (tryIt) lines.push("", ` ${import_chalk26.default.dim("Try it:")}`, ` ${import_chalk26.default.cyan(tryIt)}`);
2782
3369
  return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
2783
3370
  }
2784
3371
  },
@@ -2903,6 +3490,7 @@ function findTool(name) {
2903
3490
  }
2904
3491
 
2905
3492
  // src/commands/agent.ts
3493
+ init_trace();
2906
3494
  init_types();
2907
3495
  var DASHBOARD_BASE5 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2908
3496
  var MAX_TOOL_STEPS = 6;
@@ -2933,23 +3521,23 @@ function truncate(value, max = 1500) {
2933
3521
  }
2934
3522
  function printCost(llm) {
2935
3523
  const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
2936
- console.log(import_chalk25.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk25.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
3524
+ console.log(import_chalk27.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk27.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
2937
3525
  }
2938
3526
  async function runAgent(opts) {
2939
3527
  requireAuth();
2940
3528
  const { teamId, teamName } = await resolveTeam(opts.team);
2941
3529
  const { default: inquirer2 } = await import("inquirer");
2942
- console.log(import_chalk25.default.bold("APIblaze agent") + import_chalk25.default.dim(` \xB7 team ${teamName ?? teamId}`));
2943
- console.log(import_chalk25.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
3530
+ console.log(import_chalk27.default.bold("APIblaze agent") + import_chalk27.default.dim(` \xB7 team ${teamName ?? teamId}`));
3531
+ console.log(import_chalk27.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
2944
3532
  const history = [];
2945
3533
  while (true) {
2946
- const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk25.default.cyan("you") + " \u203A" }]);
3534
+ const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk27.default.cyan("you") + " \u203A" }]);
2947
3535
  const text = (input ?? "").trim();
2948
3536
  if (!text) continue;
2949
3537
  if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
2950
3538
  history.push({ role: "user", content: text });
2951
3539
  for (let step = 0; step < MAX_TOOL_STEPS; step++) {
2952
- const spinner = (0, import_ora10.default)({ text: "thinking...", color: "magenta" }).start();
3540
+ const spinner = (0, import_ora12.default)({ text: "thinking...", color: "magenta" }).start();
2953
3541
  let resp;
2954
3542
  try {
2955
3543
  resp = await callAgent(history, teamId);
@@ -2957,21 +3545,21 @@ async function runAgent(opts) {
2957
3545
  } catch (err) {
2958
3546
  spinner.stop();
2959
3547
  if (err instanceof ApiError && err.status === 402) {
2960
- console.log(import_chalk25.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
3548
+ console.log(import_chalk27.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
2961
3549
  break;
2962
3550
  }
2963
3551
  throw err;
2964
3552
  }
2965
3553
  history.push({ role: "assistant", content: resp.raw });
2966
3554
  printCost(resp.llm);
2967
- if (resp.reply) console.log(import_chalk25.default.green("agent") + " \u203A " + resp.reply);
3555
+ if (resp.reply) console.log(import_chalk27.default.green("agent") + " \u203A " + resp.reply);
2968
3556
  if (!resp.action) break;
2969
3557
  const tool = findTool(resp.action.tool);
2970
3558
  if (!tool) {
2971
3559
  history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
2972
3560
  continue;
2973
3561
  }
2974
- const runSpinner = (0, import_ora10.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
3562
+ const runSpinner = (0, import_ora12.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
2975
3563
  try {
2976
3564
  const result = await tool.run(resp.action.args, { teamId });
2977
3565
  runSpinner.succeed(`${tool.name} \u2713`);
@@ -2989,11 +3577,11 @@ async function runAgent(opts) {
2989
3577
  }
2990
3578
  renderTrace();
2991
3579
  if (step === MAX_TOOL_STEPS - 1) {
2992
- console.log(import_chalk25.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
3580
+ console.log(import_chalk27.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
2993
3581
  }
2994
3582
  }
2995
3583
  }
2996
- console.log(import_chalk25.default.dim("\nBye."));
3584
+ console.log(import_chalk27.default.dim("\nBye."));
2997
3585
  }
2998
3586
 
2999
3587
  // src/commands/config-browse.ts
@@ -3140,7 +3728,7 @@ var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & M
3140
3728
  var FEATURES = [
3141
3729
  { go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
3142
3730
  { go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
3143
- { go: "tenants", label: "Tenants", desc: "Separate groups of your API consumers \u2014 each with its own portal, login, and keys" },
3731
+ { go: "tenants", label: "Tenants", desc: "Consumer groups \u2014 portal, login app clients & providers, issuers, IAM, CORS" },
3144
3732
  { go: "domains", label: "Custom domains", desc: "Serve the proxy on your own hostname + choose what the bare URL serves" },
3145
3733
  { go: "spec", label: "OpenAPI spec & traffic", desc: "View the stored spec, refresh it from source, or build it from captured traffic" },
3146
3734
  { go: "agents", label: "AI agents", desc: "Chat to build your spec, design access rules, or publish an MCP server (billed per turn)" },
@@ -3156,11 +3744,11 @@ function dig(blob, dotted) {
3156
3744
  }
3157
3745
  var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
3158
3746
  function show(v) {
3159
- if (v === void 0) return import_chalk26.default.dim("(unset)");
3160
- if (v === null) return import_chalk26.default.dim("null");
3161
- if (typeof v === "object") return import_chalk26.default.cyan(JSON.stringify(v));
3162
- if (typeof v === "boolean") return v ? import_chalk26.default.green("on") : import_chalk26.default.red("off");
3163
- return import_chalk26.default.cyan(String(v));
3747
+ if (v === void 0) return import_chalk28.default.dim("(unset)");
3748
+ if (v === null) return import_chalk28.default.dim("null");
3749
+ if (typeof v === "object") return import_chalk28.default.cyan(JSON.stringify(v));
3750
+ if (typeof v === "boolean") return v ? import_chalk28.default.green("on") : import_chalk28.default.red("off");
3751
+ return import_chalk28.default.cyan(String(v));
3164
3752
  }
3165
3753
  function parseValue(raw) {
3166
3754
  if (raw === "true") return true;
@@ -3187,7 +3775,7 @@ async function fetchConfigBlob(proj2) {
3187
3775
  }
3188
3776
  async function patchSetting(proj2, s, value, cfg) {
3189
3777
  const body = s.toPatch(value, cfg);
3190
- const spinner = (0, import_ora11.default)(`Set ${s.key}...`).start();
3778
+ const spinner = (0, import_ora13.default)(`Set ${s.key}...`).start();
3191
3779
  try {
3192
3780
  await admin({
3193
3781
  method: "PATCH",
@@ -3202,10 +3790,10 @@ async function patchSetting(proj2, s, value, cfg) {
3202
3790
  }
3203
3791
  }
3204
3792
  var loginFirst = (what) => {
3205
- console.log(import_chalk26.default.yellow(`
3793
+ console.log(import_chalk28.default.yellow(`
3206
3794
  Log in first to ${what}.`));
3207
- console.log(import_chalk26.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
3208
- console.log(import_chalk26.default.dim(" anonymously and want to bring it into your account.\n"));
3795
+ console.log(import_chalk28.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
3796
+ console.log(import_chalk28.default.dim(" anonymously and want to bring it into your account.\n"));
3209
3797
  };
3210
3798
  async function runConfig(project, key, value, opts) {
3211
3799
  const creds = loadCredentials();
@@ -3222,9 +3810,9 @@ async function runConfig(project, key, value, opts) {
3222
3810
  }
3223
3811
  const setting = SETTINGS.find((s) => s.key === key);
3224
3812
  if (!setting) {
3225
- console.error(import_chalk26.default.red(`Unknown setting "${key}".`));
3226
- console.error(import_chalk26.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
3227
- console.error(import_chalk26.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
3813
+ console.error(import_chalk28.default.red(`Unknown setting "${key}".`));
3814
+ console.error(import_chalk28.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
3815
+ console.error(import_chalk28.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
3228
3816
  process.exit(1);
3229
3817
  }
3230
3818
  if (value === void 0) {
@@ -3239,7 +3827,7 @@ async function pickProject(teamId) {
3239
3827
  const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
3240
3828
  const projects = await getProjects2(teamId).catch(() => []);
3241
3829
  if (!projects.length) {
3242
- console.error(import_chalk26.default.red("No projects in this team. Create one: `npx apiblaze create`."));
3830
+ console.error(import_chalk28.default.red("No projects in this team. Create one: `npx apiblaze create`."));
3243
3831
  process.exit(1);
3244
3832
  }
3245
3833
  const { default: inquirer2 } = await import("inquirer");
@@ -3247,7 +3835,7 @@ async function pickProject(teamId) {
3247
3835
  type: "list",
3248
3836
  name: "picked",
3249
3837
  message: "Which project?",
3250
- choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk26.default.dim("v" + p.apiVersion)}`, value: p }))
3838
+ choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk28.default.dim("v" + p.apiVersion)}`, value: p }))
3251
3839
  }]);
3252
3840
  return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
3253
3841
  }
@@ -3258,25 +3846,25 @@ function printAll(proj2, cfg, json) {
3258
3846
  console.log(JSON.stringify(out, null, 2));
3259
3847
  return;
3260
3848
  }
3261
- console.log(import_chalk26.default.bold(`
3849
+ console.log(import_chalk28.default.bold(`
3262
3850
  ${proj2.projectName} v${proj2.apiVersion} \u2014 settings
3263
3851
  `));
3264
3852
  for (const group of SETTING_GROUPS) {
3265
- console.log(import_chalk26.default.bold(group));
3853
+ console.log(import_chalk28.default.bold(group));
3266
3854
  for (const s of SETTINGS.filter((x) => x.group === group)) {
3267
- console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk26.default.dim(s.desc)}`);
3855
+ console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk28.default.dim(s.desc)}`);
3268
3856
  }
3269
3857
  console.log();
3270
3858
  }
3271
- console.log(import_chalk26.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
3859
+ console.log(import_chalk28.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
3272
3860
  }
3273
3861
  async function discoveryMenu(project) {
3274
3862
  const { default: inquirer2 } = await import("inquirer");
3275
- console.log(import_chalk26.default.bold(`
3863
+ console.log(import_chalk28.default.bold(`
3276
3864
  APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
3277
3865
  `));
3278
- console.log(import_chalk26.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
3279
- console.log(import_chalk26.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
3866
+ console.log(import_chalk28.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
3867
+ console.log(import_chalk28.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
3280
3868
  for (; ; ) {
3281
3869
  const { pick: pick2 } = await inquirer2.prompt([{
3282
3870
  type: "list",
@@ -3284,13 +3872,13 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
3284
3872
  message: "Explore:",
3285
3873
  pageSize: 20,
3286
3874
  choices: [
3287
- new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
3875
+ new inquirer2.Separator(import_chalk28.default.bold("\u2014 Settings \u2014")),
3288
3876
  ...SETTING_GROUPS.map((g) => ({
3289
- name: `${g} ${import_chalk26.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
3877
+ name: `${g} ${import_chalk28.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
3290
3878
  value: { kind: "settings", g }
3291
3879
  })),
3292
- new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
3293
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: "feature", f } })),
3880
+ new inquirer2.Separator(import_chalk28.default.bold("\u2014 Features \u2014")),
3881
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk28.default.dim(f.desc)}`, value: { kind: "feature", f } })),
3294
3882
  new inquirer2.Separator(),
3295
3883
  { name: "Exit", value: { kind: "exit" } }
3296
3884
  ]
@@ -3299,24 +3887,24 @@ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
3299
3887
  if (pick2.kind === "settings") {
3300
3888
  console.log();
3301
3889
  for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
3302
- console.log(` ${import_chalk26.default.bold(s.label.padEnd(28))} ${import_chalk26.default.dim(s.desc)}`);
3303
- console.log(` ${import_chalk26.default.dim(" key: " + s.key)}`);
3890
+ console.log(` ${import_chalk28.default.bold(s.label.padEnd(28))} ${import_chalk28.default.dim(s.desc)}`);
3891
+ console.log(` ${import_chalk28.default.dim(" key: " + s.key)}`);
3304
3892
  }
3305
3893
  loginFirst("view or change these settings");
3306
3894
  } else {
3307
3895
  const f = pick2.f;
3308
3896
  console.log(`
3309
- ${import_chalk26.default.bold(f.label)} \u2014 ${f.desc}`);
3897
+ ${import_chalk28.default.bold(f.label)} \u2014 ${f.desc}`);
3310
3898
  loginFirst(`use ${f.label.toLowerCase()}`);
3311
3899
  }
3312
3900
  }
3313
3901
  }
3314
3902
  async function navigator(proj2, cfg, opts) {
3315
3903
  const { default: inquirer2 } = await import("inquirer");
3316
- console.log(import_chalk26.default.bold(`
3904
+ console.log(import_chalk28.default.bold(`
3317
3905
  ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
3318
3906
  `));
3319
- console.log(import_chalk26.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
3907
+ console.log(import_chalk28.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
3320
3908
  let blob = cfg;
3321
3909
  for (; ; ) {
3322
3910
  const { pick: pick2 } = await inquirer2.prompt([{
@@ -3325,10 +3913,10 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
3325
3913
  message: "Where to?",
3326
3914
  pageSize: 20,
3327
3915
  choices: [
3328
- new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
3916
+ new inquirer2.Separator(import_chalk28.default.bold("\u2014 Settings \u2014")),
3329
3917
  ...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
3330
- new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
3331
- ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: f.go } })),
3918
+ new inquirer2.Separator(import_chalk28.default.bold("\u2014 Features \u2014")),
3919
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk28.default.dim(f.desc)}`, value: { kind: f.go } })),
3332
3920
  new inquirer2.Separator(),
3333
3921
  { name: "Show all settings", value: { kind: "list" } },
3334
3922
  { name: "Exit", value: { kind: "exit" } }
@@ -3368,7 +3956,7 @@ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
3368
3956
  }
3369
3957
  }
3370
3958
  } catch (err) {
3371
- console.error(import_chalk26.default.red(` ${err instanceof Error ? err.message : String(err)}`));
3959
+ console.error(import_chalk28.default.red(` ${err instanceof Error ? err.message : String(err)}`));
3372
3960
  }
3373
3961
  }
3374
3962
  }
@@ -3382,7 +3970,7 @@ async function settingsGroup(proj2, cfg, group) {
3382
3970
  message: group + ":",
3383
3971
  pageSize: 16,
3384
3972
  choices: [
3385
- ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk26.default.dim(s2.desc)}`, value: s2 })),
3973
+ ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk28.default.dim(s2.desc)}`, value: s2 })),
3386
3974
  new inquirer2.Separator(),
3387
3975
  { name: "\u2190 Back", value: null }
3388
3976
  ]
@@ -3400,7 +3988,7 @@ async function settingsGroup(proj2, cfg, group) {
3400
3988
  } else if (s.type === "number") {
3401
3989
  const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
3402
3990
  if (v === "" || Number.isNaN(Number(v))) {
3403
- console.log(import_chalk26.default.yellow(" Not a number \u2014 unchanged."));
3991
+ console.log(import_chalk28.default.yellow(" Not a number \u2014 unchanged."));
3404
3992
  continue;
3405
3993
  }
3406
3994
  value = Number(v);
@@ -3418,6 +4006,102 @@ async function settingsGroup(proj2, cfg, group) {
3418
4006
  cfg = await fetchConfigBlob(proj2);
3419
4007
  }
3420
4008
  }
4009
+ var FN_CHOICES = [
4010
+ { name: "uppercase", value: "uppercase" },
4011
+ { name: "lowercase", value: "lowercase" },
4012
+ { name: "trim", value: "trim" },
4013
+ { name: "url_encode", value: "url_encode" },
4014
+ { name: "url_decode", value: "url_decode" },
4015
+ { name: "base64url_encode", value: "base64url_encode" },
4016
+ { name: "base64url_decode", value: "base64url_decode" },
4017
+ { name: "hash \u2014 sha256/md5 digest", value: "hash" },
4018
+ { name: "regex_extract \u2014 pull out a capture group", value: "regex_extract" },
4019
+ { name: "concat \u2014 prepend/append text", value: "concat" }
4020
+ ];
4021
+ async function buildFns(which) {
4022
+ const { default: inquirer2 } = await import("inquirer");
4023
+ const fns = [];
4024
+ for (; ; ) {
4025
+ const { fn } = await inquirer2.prompt([{
4026
+ type: "list",
4027
+ name: "fn",
4028
+ message: fns.length ? `${which}: ${fns.map((f) => f.fn).join(" \u2192 ")} \u2014 add another?` : `${which} \u2014 add a function?`,
4029
+ choices: [{ name: fns.length ? "\u2713 Done" : "No functions", value: null }, ...FN_CHOICES]
4030
+ }]);
4031
+ if (!fn) return fns;
4032
+ if (fn === "hash") {
4033
+ const { algorithm } = await inquirer2.prompt([{ type: "list", name: "algorithm", message: "Algorithm:", choices: ["sha256", "md5"] }]);
4034
+ fns.push({ fn, algorithm });
4035
+ } else if (fn === "regex_extract") {
4036
+ const a = await inquirer2.prompt([
4037
+ { type: "input", name: "pattern", message: "Regex pattern:", validate: (s) => {
4038
+ try {
4039
+ new RegExp(s);
4040
+ return true;
4041
+ } catch {
4042
+ return "invalid regex";
4043
+ }
4044
+ } },
4045
+ { type: "input", name: "group", message: "Capture group (default 1):", default: "1" }
4046
+ ]);
4047
+ fns.push({ fn, pattern: a.pattern, ...Number(a.group) !== 1 ? { group: Number(a.group) } : {} });
4048
+ } else if (fn === "concat") {
4049
+ const a = await inquirer2.prompt([
4050
+ { type: "input", name: "before", message: "Text before (empty = none):" },
4051
+ { type: "input", name: "after", message: "Text after (empty = none):" }
4052
+ ]);
4053
+ fns.push({ fn, ...a.before ? { before: a.before } : {}, ...a.after ? { after: a.after } : {} });
4054
+ } else {
4055
+ fns.push({ fn });
4056
+ }
4057
+ }
4058
+ }
4059
+ async function buildCondition(phase) {
4060
+ const { default: inquirer2 } = await import("inquirer");
4061
+ const { want } = await inquirer2.prompt([{
4062
+ type: "confirm",
4063
+ name: "want",
4064
+ default: false,
4065
+ message: "Only apply when a condition matches?"
4066
+ }]);
4067
+ if (!want) return void 0;
4068
+ const srcHint = phase === "response" ? "(header:x-foo, bodyvar:user.id, status:)" : "(header:x-foo, param:limit, bodyvar:user.id, jwt:sub)";
4069
+ const items = [];
4070
+ for (; ; ) {
4071
+ const a = await inquirer2.prompt([
4072
+ { type: "input", name: "source", message: `Condition field ${import_chalk28.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
4073
+ { type: "list", name: "operator", message: "Operator:", choices: [
4074
+ "eq",
4075
+ "neq",
4076
+ "contains",
4077
+ "starts_with",
4078
+ "ends_with",
4079
+ "exists",
4080
+ "not_exists",
4081
+ "regex"
4082
+ ] }
4083
+ ]);
4084
+ let value;
4085
+ if (a.operator !== "exists" && a.operator !== "not_exists") {
4086
+ const v = await inquirer2.prompt([{ type: "input", name: "value", message: a.operator === "regex" ? "Pattern:" : "Value:" }]);
4087
+ value = v.value;
4088
+ }
4089
+ items.push({ id: `c${items.length + 1}`, openParen: false, closeParen: false, source: a.source, operator: a.operator, ...value !== void 0 ? { value } : {} });
4090
+ const { more } = await inquirer2.prompt([{
4091
+ type: "list",
4092
+ name: "more",
4093
+ message: "Combine with another condition?",
4094
+ choices: [{ name: "\u2713 Done", value: null }, { name: "AND \u2026", value: "AND" }, { name: "OR \u2026", value: "OR" }]
4095
+ }]);
4096
+ if (!more) return items;
4097
+ items[items.length - 1].logicOp = more;
4098
+ }
4099
+ }
4100
+ function showCondition(cond) {
4101
+ if (!Array.isArray(cond) || !cond.length) return "";
4102
+ const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
4103
+ return import_chalk28.default.dim(` when ${s}`);
4104
+ }
3421
4105
  async function transformsMenu(proj2) {
3422
4106
  const { default: inquirer2 } = await import("inquirer");
3423
4107
  const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
@@ -3425,11 +4109,12 @@ async function transformsMenu(proj2) {
3425
4109
  const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
3426
4110
  const rules = out?.rules ?? [];
3427
4111
  console.log();
3428
- if (!rules.length) console.log(import_chalk26.default.dim(" No transform rules yet."));
4112
+ if (!rules.length) console.log(import_chalk28.default.dim(" No transform rules yet."));
3429
4113
  for (const r of rules) {
3430
4114
  const a = r.action ?? {};
3431
- const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}`;
3432
- console.log(` ${r.enabled ? import_chalk26.default.green("\u25CF") : import_chalk26.default.dim("\u25CB")} ${import_chalk26.default.bold(r.name)} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)} ${what}`);
4115
+ const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
4116
+ 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_chalk28.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
4117
+ console.log(` ${r.enabled ? import_chalk28.default.green("\u25CF") : import_chalk28.default.dim("\u25CB")} ${import_chalk28.default.bold(r.name)} ${import_chalk28.default.dim(`[${r.phase ?? "request"}]`)} ${what}${showCondition(r.condition)}`);
3433
4118
  }
3434
4119
  const { act } = await inquirer2.prompt([{
3435
4120
  type: "list",
@@ -3441,10 +4126,26 @@ async function transformsMenu(proj2) {
3441
4126
  { name: "Enable/disable a rule", value: "toggle" },
3442
4127
  { name: "Delete a rule", value: "delete" }
3443
4128
  ] : [],
4129
+ { name: import_chalk28.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
3444
4130
  { name: "\u2190 Back", value: "back" }
3445
4131
  ]
3446
4132
  }]);
3447
4133
  if (act === "back") return;
4134
+ if (act === "raw") {
4135
+ const { raw } = await inquirer2.prompt([{
4136
+ type: "input",
4137
+ name: "raw",
4138
+ message: "Rule JSON ({name, phase, enabled, action, condition?}):"
4139
+ }]);
4140
+ const body = parseValue(raw);
4141
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
4142
+ console.log(import_chalk28.default.yellow(" Not a JSON object \u2014 skipped."));
4143
+ continue;
4144
+ }
4145
+ await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
4146
+ console.log(import_chalk28.default.green(" Rule created."));
4147
+ continue;
4148
+ }
3448
4149
  if (act === "add") {
3449
4150
  const ans = await inquirer2.prompt([
3450
4151
  { type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
@@ -3458,7 +4159,7 @@ async function transformsMenu(proj2) {
3458
4159
  { name: "Remove a field", value: "remove" }
3459
4160
  ] }
3460
4161
  ]);
3461
- const fieldHint = import_chalk26.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
4162
+ const fieldHint = import_chalk28.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
3462
4163
  let action2;
3463
4164
  if (ans.kind === "hardcode") {
3464
4165
  const a = await inquirer2.prompt([
@@ -3477,11 +4178,26 @@ async function transformsMenu(proj2) {
3477
4178
  { type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
3478
4179
  { type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
3479
4180
  ]);
3480
- action2 = { type: "copy", source: a.source, destination: a.destination, ...a.strip ? { strip_source: true } : {} };
4181
+ const source_fns = await buildFns("Transform the value as it is READ (source functions)");
4182
+ const dest_fns = await buildFns("Transform the value as it is WRITTEN (destination functions)");
4183
+ action2 = {
4184
+ type: "copy",
4185
+ source: a.source,
4186
+ destination: a.destination,
4187
+ ...a.strip ? { strip_source: true } : {},
4188
+ ...source_fns.length ? { source_fns } : {},
4189
+ ...dest_fns.length ? { dest_fns } : {}
4190
+ };
3481
4191
  }
3482
- const spinner = (0, import_ora11.default)("Creating rule...").start();
4192
+ const condition = await buildCondition(ans.phase);
4193
+ const spinner = (0, import_ora13.default)("Creating rule...").start();
3483
4194
  try {
3484
- await admin({ method: "POST", path: base, body: { name: ans.name, phase: ans.phase, enabled: true, action: action2 }, summary: `Create transform "${ans.name}"` });
4195
+ await admin({
4196
+ method: "POST",
4197
+ path: base,
4198
+ body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
4199
+ summary: `Create transform "${ans.name}"`
4200
+ });
3485
4201
  spinner.succeed(`Rule "${ans.name}" created.`);
3486
4202
  } catch (err) {
3487
4203
  spinner.fail("Create failed.");
@@ -3492,16 +4208,16 @@ async function transformsMenu(proj2) {
3492
4208
  type: "list",
3493
4209
  name: "rule",
3494
4210
  message: act === "toggle" ? "Which rule?" : "Delete which rule?",
3495
- choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
4211
+ choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk28.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
3496
4212
  }]);
3497
4213
  if (!rule) continue;
3498
4214
  if (act === "toggle") {
3499
4215
  const flipped = { ...rule, enabled: rule.enabled === false };
3500
4216
  await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
3501
- console.log(import_chalk26.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
4217
+ console.log(import_chalk28.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
3502
4218
  } else {
3503
4219
  await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
3504
- console.log(import_chalk26.default.green(` ${rule.name} deleted.`));
4220
+ console.log(import_chalk28.default.green(` ${rule.name} deleted.`));
3505
4221
  }
3506
4222
  }
3507
4223
  }
@@ -3513,9 +4229,9 @@ async function mappingsMenu(proj2) {
3513
4229
  const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
3514
4230
  const tables = out?.mappings ?? out?.tables ?? [];
3515
4231
  console.log();
3516
- if (!tables.length) console.log(import_chalk26.default.dim(" No mapping tables yet."));
4232
+ if (!tables.length) console.log(import_chalk28.default.dim(" No mapping tables yet."));
3517
4233
  for (const t of tables) {
3518
- console.log(` ${import_chalk26.default.bold(t.name)} ${import_chalk26.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
4234
+ console.log(` ${import_chalk28.default.bold(t.name)} ${import_chalk28.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
3519
4235
  }
3520
4236
  const { act } = await inquirer2.prompt([{
3521
4237
  type: "list",
@@ -3535,11 +4251,11 @@ async function mappingsMenu(proj2) {
3535
4251
  ]);
3536
4252
  const entries2 = parseValue(a.entries);
3537
4253
  if (!Array.isArray(entries2)) {
3538
- console.log(import_chalk26.default.yellow(" Entries must be a JSON array \u2014 not created."));
4254
+ console.log(import_chalk28.default.yellow(" Entries must be a JSON array \u2014 not created."));
3539
4255
  continue;
3540
4256
  }
3541
4257
  await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
3542
- console.log(import_chalk26.default.green(` Table "${a.name}" created.`));
4258
+ console.log(import_chalk28.default.green(` Table "${a.name}" created.`));
3543
4259
  } else {
3544
4260
  const { table } = await inquirer2.prompt([{
3545
4261
  type: "list",
@@ -3549,7 +4265,7 @@ async function mappingsMenu(proj2) {
3549
4265
  }]);
3550
4266
  if (!table) continue;
3551
4267
  await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
3552
- console.log(import_chalk26.default.green(` ${table.name} deleted.`));
4268
+ console.log(import_chalk28.default.green(` ${table.name} deleted.`));
3553
4269
  }
3554
4270
  }
3555
4271
  }
@@ -3560,22 +4276,22 @@ async function tenantsMenu(proj2, opts) {
3560
4276
  const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
3561
4277
  const tenants = out?.tenants ?? [];
3562
4278
  console.log();
3563
- if (!tenants.length) console.log(import_chalk26.default.dim(" No tenants attached (consumers use the default tenant)."));
3564
- for (const t of tenants) console.log(` ${import_chalk26.default.bold(t.tenant_name ?? t.name)} ${import_chalk26.default.dim(t.display_name ?? "")}`);
4279
+ if (!tenants.length) console.log(import_chalk28.default.dim(" No tenants attached (consumers use the default tenant)."));
4280
+ for (const t of tenants) console.log(` ${import_chalk28.default.bold(t.tenant_name ?? t.name)} ${import_chalk28.default.dim(t.display_name ?? "")}`);
3565
4281
  const { act } = await inquirer2.prompt([{
3566
4282
  type: "list",
3567
4283
  name: "act",
3568
4284
  message: "Tenants:",
3569
4285
  choices: [
3570
- { name: "Attach a tenant", value: "attach" },
3571
- ...tenants.length ? [{ name: "Detach a tenant", value: "detach" }] : [],
3572
- { name: import_chalk26.default.dim("Manage team tenants (create/CORS/login) \u2192 `apiblaze tenant --help`"), value: "hint" },
4286
+ { name: `Manage a tenant\u2026 ${import_chalk28.default.dim("settings, login app clients, providers, issuers \u2014 affects EVERY proxy the tenant serves")}`, value: "manage" },
4287
+ { name: "Attach a tenant to this project", value: "attach" },
4288
+ ...tenants.length ? [{ name: "Detach a tenant from this project", value: "detach" }] : [],
3573
4289
  { name: "\u2190 Back", value: "back" }
3574
4290
  ]
3575
4291
  }]);
3576
4292
  if (act === "back") return;
3577
- if (act === "hint") {
3578
- console.log(import_chalk26.default.dim("\n apiblaze tenant list | create | attach | cors | delete\n"));
4293
+ if (act === "manage") {
4294
+ await runTenantManage(void 0, { team: opts.team });
3579
4295
  continue;
3580
4296
  }
3581
4297
  if (act === "attach") {
@@ -3590,7 +4306,7 @@ async function tenantsMenu(proj2, opts) {
3590
4306
  }]);
3591
4307
  if (!t) continue;
3592
4308
  await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
3593
- console.log(import_chalk26.default.green(` Detached ${t.tenant_name ?? t.name}.`));
4309
+ console.log(import_chalk28.default.green(` Detached ${t.tenant_name ?? t.name}.`));
3594
4310
  }
3595
4311
  }
3596
4312
  }
@@ -3633,7 +4349,7 @@ async function specMenu(proj2, opts) {
3633
4349
  choices: [
3634
4350
  { name: "Print the stored spec", value: "get" },
3635
4351
  { name: "Refresh the spec from its source", value: "refresh" },
3636
- { name: import_chalk26.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
4352
+ { name: import_chalk28.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
3637
4353
  { name: "\u2190 Back", value: "back" }
3638
4354
  ]
3639
4355
  }]);
@@ -3641,7 +4357,7 @@ async function specMenu(proj2, opts) {
3641
4357
  if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
3642
4358
  else if (act === "refresh") {
3643
4359
  await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
3644
- console.log(import_chalk26.default.green(" Spec refresh triggered."));
4360
+ console.log(import_chalk28.default.green(" Spec refresh triggered."));
3645
4361
  } else await runOpenapi(proj2.projectName, proj2.apiVersion);
3646
4362
  }
3647
4363
  async function agentsMenu(proj2, opts) {
@@ -3666,8 +4382,9 @@ async function agentsMenu(proj2, opts) {
3666
4382
  }
3667
4383
 
3668
4384
  // src/commands/key.ts
3669
- var import_chalk27 = __toESM(require("chalk"));
3670
- var import_ora12 = __toESM(require("ora"));
4385
+ var import_chalk29 = __toESM(require("chalk"));
4386
+ var import_ora14 = __toESM(require("ora"));
4387
+ init_admin();
3671
4388
  async function runApikeysMenu(opts) {
3672
4389
  await runKeyList(opts);
3673
4390
  if (opts.json) return;
@@ -3694,11 +4411,11 @@ async function runKeyList(opts) {
3694
4411
  return;
3695
4412
  }
3696
4413
  if (!keys.length) {
3697
- console.log(import_chalk27.default.yellow("No developer keys."));
4414
+ console.log(import_chalk29.default.yellow("No developer keys."));
3698
4415
  return;
3699
4416
  }
3700
4417
  for (const k of keys) {
3701
- console.log(` ${import_chalk27.default.bold(k.key_id ?? k.id)} ${import_chalk27.default.dim(k.description ?? "")} ${import_chalk27.default.dim(k.expires_at ?? "no expiry")}`);
4418
+ console.log(` ${import_chalk29.default.bold(k.key_id ?? k.id)} ${import_chalk29.default.dim(k.description ?? "")} ${import_chalk29.default.dim(k.expires_at ?? "no expiry")}`);
3702
4419
  }
3703
4420
  }
3704
4421
  async function runKeyMint(opts) {
@@ -3706,7 +4423,7 @@ async function runKeyMint(opts) {
3706
4423
  const body = { role: "consumer-admin" };
3707
4424
  if (opts.desc) body.description = opts.desc;
3708
4425
  if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
3709
- const spinner = (0, import_ora12.default)("Minting key...").start();
4426
+ const spinner = (0, import_ora14.default)("Minting key...").start();
3710
4427
  try {
3711
4428
  const out = await admin({
3712
4429
  method: "POST",
@@ -3719,9 +4436,9 @@ async function runKeyMint(opts) {
3719
4436
  console.log(JSON.stringify(out));
3720
4437
  return;
3721
4438
  }
3722
- console.log(` ${import_chalk27.default.bold("key_id")}: ${out?.key_id}`);
3723
- console.log(` ${import_chalk27.default.bold("key")}: ${import_chalk27.default.green(out?.key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
3724
- if (out?.expires_at) console.log(` ${import_chalk27.default.dim("expires:")} ${out.expires_at}`);
4439
+ console.log(` ${import_chalk29.default.bold("key_id")}: ${out?.key_id}`);
4440
+ console.log(` ${import_chalk29.default.bold("key")}: ${import_chalk29.default.green(out?.key)} ${import_chalk29.default.dim("(shown once \u2014 store it now)")}`);
4441
+ if (out?.expires_at) console.log(` ${import_chalk29.default.dim("expires:")} ${out.expires_at}`);
3725
4442
  } catch (err) {
3726
4443
  spinner.fail("Mint failed.");
3727
4444
  throw err;
@@ -3729,7 +4446,7 @@ async function runKeyMint(opts) {
3729
4446
  }
3730
4447
  async function runKeyRevoke(keyId, opts) {
3731
4448
  const { teamId } = await resolveTeam(opts.team);
3732
- const spinner = (0, import_ora12.default)("Revoking key...").start();
4449
+ const spinner = (0, import_ora14.default)("Revoking key...").start();
3733
4450
  try {
3734
4451
  await admin({
3735
4452
  method: "DELETE",
@@ -3744,8 +4461,9 @@ async function runKeyRevoke(keyId, opts) {
3744
4461
  }
3745
4462
 
3746
4463
  // src/commands/consumer.ts
3747
- var import_chalk28 = __toESM(require("chalk"));
3748
- var import_ora13 = __toESM(require("ora"));
4464
+ var import_chalk30 = __toESM(require("chalk"));
4465
+ var import_ora15 = __toESM(require("ora"));
4466
+ init_admin();
3749
4467
  var DEFAULT_SCOPE = "openid email profile offline_access";
3750
4468
  var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
3751
4469
  async function consumerFetch(creds, suffix, init) {
@@ -3764,7 +4482,7 @@ async function consumerFetch(creds, suffix, init) {
3764
4482
  function requireConsumer() {
3765
4483
  const c = loadConsumer();
3766
4484
  if (!c) {
3767
- console.error(import_chalk28.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
4485
+ console.error(import_chalk30.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
3768
4486
  process.exit(1);
3769
4487
  }
3770
4488
  return c;
@@ -3775,48 +4493,37 @@ async function runConsumerLogin(opts) {
3775
4493
  let clientId = opts.client;
3776
4494
  if (clientId) {
3777
4495
  if (!tenant2) {
3778
- console.error(import_chalk28.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
4496
+ console.error(import_chalk30.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
3779
4497
  process.exit(1);
3780
4498
  }
3781
4499
  } else {
3782
4500
  requireAuth();
3783
4501
  const { teamId, teamName } = await resolveTeam(opts.team);
3784
- const spinner = (0, import_ora13.default)("Loading your tenants...").start();
3785
- const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
3786
- spinner.stop();
3787
- const tenants = (tdata?.tenants ?? []).map(
3788
- (t) => typeof t === "string" ? { tenant_name: t } : t
3789
- );
3790
- if (!tenants.length) {
3791
- console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
3792
- process.exit(1);
3793
- }
3794
4502
  if (!tenant2) {
3795
- if (tenants.length === 1) tenant2 = tenants[0].tenant_name;
3796
- else {
3797
- const { chosen } = await inquirer2.prompt([{ type: "list", name: "chosen", message: "Which tenant portal?", choices: tenants.map((t) => ({ name: t.display_name ? `${t.display_name} (${t.tenant_name})` : t.tenant_name, value: t.tenant_name })) }]);
3798
- tenant2 = chosen;
3799
- }
4503
+ const { pickTenant: pickTenant2 } = await Promise.resolve().then(() => (init_tenant_pick(), tenant_pick_exports));
4504
+ const picked = await pickTenant2(teamId, { message: `Which tenant portal${teamName ? ` (team ${teamName})` : ""}?` });
4505
+ if (!picked) process.exit(1);
4506
+ tenant2 = picked;
3800
4507
  }
3801
- const s2 = (0, import_ora13.default)("Finding the login app...").start();
4508
+ const s2 = (0, import_ora15.default)("Finding the login app...").start();
3802
4509
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
3803
4510
  s2.stop();
3804
4511
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
3805
4512
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
3806
4513
  if (!pick2) {
3807
- console.error(import_chalk28.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
4514
+ console.error(import_chalk30.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
3808
4515
  process.exit(1);
3809
4516
  }
3810
4517
  clientId = pick2.client_id ?? pick2.clientId;
3811
4518
  }
3812
4519
  const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
3813
- console.log(`${import_chalk28.default.cyan("\u2192")} Logging in to ${import_chalk28.default.bold(tenant2)} as a consumer...`);
4520
+ console.log(`${import_chalk30.default.cyan("\u2192")} Logging in to ${import_chalk30.default.bold(tenant2)} as a consumer...`);
3814
4521
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
3815
4522
  console.log(`
3816
- Open: ${import_chalk28.default.underline(verificationUri)}`);
3817
- console.log(` Code: ${import_chalk28.default.bold(userCode)}
4523
+ Open: ${import_chalk30.default.underline(verificationUri)}`);
4524
+ console.log(` Code: ${import_chalk30.default.bold(userCode)}
3818
4525
  `);
3819
- console.log(import_chalk28.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
4526
+ console.log(import_chalk30.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
3820
4527
  }, portalResource);
3821
4528
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
3822
4529
  const creds = {
@@ -3831,7 +4538,7 @@ async function runConsumerLogin(opts) {
3831
4538
  obtainedAt: Date.now()
3832
4539
  };
3833
4540
  saveConsumer(creds);
3834
- console.log(import_chalk28.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
4541
+ console.log(import_chalk30.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
3835
4542
  }
3836
4543
  async function runConsumerTokens(opts) {
3837
4544
  const creds = requireConsumer();
@@ -3844,29 +4551,29 @@ async function runConsumerTokens(opts) {
3844
4551
  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));
3845
4552
  return;
3846
4553
  }
3847
- console.log(`${import_chalk28.default.cyan("Consumer")} ${import_chalk28.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk28.default.bold(fresh.tenant)}
4554
+ console.log(`${import_chalk30.default.cyan("Consumer")} ${import_chalk30.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk30.default.bold(fresh.tenant)}
3848
4555
  `);
3849
- console.log(`${import_chalk28.default.bold("access_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
4556
+ console.log(`${import_chalk30.default.bold("access_token")} ${import_chalk30.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
3850
4557
  ${fresh.accessToken}
3851
4558
  `);
3852
- if (fresh.idToken) console.log(`${import_chalk28.default.bold("id_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
4559
+ if (fresh.idToken) console.log(`${import_chalk30.default.bold("id_token")} ${import_chalk30.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
3853
4560
  ${fresh.idToken}
3854
4561
  `);
3855
- if (fresh.refreshToken) console.log(`${import_chalk28.default.bold("refresh_token")}
4562
+ if (fresh.refreshToken) console.log(`${import_chalk30.default.bold("refresh_token")}
3856
4563
  ${fresh.refreshToken}
3857
4564
  `);
3858
- console.log(import_chalk28.default.dim("These are your own tokens \u2014 keep them secret."));
4565
+ console.log(import_chalk30.default.dim("These are your own tokens \u2014 keep them secret."));
3859
4566
  }
3860
4567
  async function runConsumerApikeys(opts) {
3861
4568
  const creds = requireConsumer();
3862
4569
  const { default: inquirer2 } = await import("inquirer");
3863
- const spinner = (0, import_ora13.default)("Loading your API keys...").start();
4570
+ const spinner = (0, import_ora15.default)("Loading your API keys...").start();
3864
4571
  const list = await consumerFetch(creds, "/apikeys");
3865
4572
  const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
3866
4573
  spinner.stop();
3867
4574
  if (list.status >= 400) {
3868
- console.error(import_chalk28.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
3869
- if (list.status === 401) console.error(import_chalk28.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
4575
+ console.error(import_chalk30.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
4576
+ if (list.status === 401) console.error(import_chalk30.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
3870
4577
  process.exit(1);
3871
4578
  }
3872
4579
  const keys = list.data?.keys ?? [];
@@ -3874,16 +4581,16 @@ async function runConsumerApikeys(opts) {
3874
4581
  if (opts.json) {
3875
4582
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
3876
4583
  } else if (!keys.length) {
3877
- console.log(import_chalk28.default.yellow("No API keys yet."));
4584
+ console.log(import_chalk30.default.yellow("No API keys yet."));
3878
4585
  } else {
3879
4586
  for (const k of keys) {
3880
4587
  const clear = revealMap[k.environment]?.key;
3881
- const shown = clear ? import_chalk28.default.green(clear) : import_chalk28.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
3882
- const exp = k.expires_at ? import_chalk28.default.dim(`exp ${k.expires_at}`) : import_chalk28.default.dim("no expiry");
3883
- console.log(` ${import_chalk28.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk28.default.dim(k.description ?? "")}`);
4588
+ const shown = clear ? import_chalk30.default.green(clear) : import_chalk30.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
4589
+ const exp = k.expires_at ? import_chalk30.default.dim(`exp ${k.expires_at}`) : import_chalk30.default.dim("no expiry");
4590
+ console.log(` ${import_chalk30.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk30.default.dim(k.description ?? "")}`);
3884
4591
  }
3885
4592
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
3886
- console.log(import_chalk28.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
4593
+ console.log(import_chalk30.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
3887
4594
  }
3888
4595
  }
3889
4596
  if (opts.json) return;
@@ -3897,7 +4604,7 @@ async function runConsumerApikeys(opts) {
3897
4604
  const body = { environment: answers.environment };
3898
4605
  if (answers.description) body.description = answers.description;
3899
4606
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
3900
- const s2 = (0, import_ora13.default)("Creating key...").start();
4607
+ const s2 = (0, import_ora15.default)("Creating key...").start();
3901
4608
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
3902
4609
  if (created.status >= 400) {
3903
4610
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -3905,15 +4612,16 @@ async function runConsumerApikeys(opts) {
3905
4612
  }
3906
4613
  s2.succeed("Key created.");
3907
4614
  const key = created.data?.key ?? created.data?.fullKey;
3908
- if (key) console.log(` ${import_chalk28.default.green(key)} ${import_chalk28.default.dim("(shown once \u2014 store it now)")}`);
3909
- else console.log(import_chalk28.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
4615
+ if (key) console.log(` ${import_chalk30.default.green(key)} ${import_chalk30.default.dim("(shown once \u2014 store it now)")}`);
4616
+ else console.log(import_chalk30.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
3910
4617
  }
3911
4618
 
3912
4619
  // src/commands/sidecar.ts
3913
- var import_chalk29 = __toESM(require("chalk"));
3914
- var import_ora14 = __toESM(require("ora"));
4620
+ var import_chalk31 = __toESM(require("chalk"));
4621
+ var import_ora16 = __toESM(require("ora"));
3915
4622
  var fs7 = __toESM(require("fs"));
3916
4623
  var path4 = __toESM(require("path"));
4624
+ init_admin();
3917
4625
  init_auth();
3918
4626
  function detectNextProject(root) {
3919
4627
  const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs7.existsSync(path4.join(root, f)));
@@ -3951,18 +4659,18 @@ function upsertEnvLocal(root, token) {
3951
4659
  }
3952
4660
  function installSidecarPackage(root) {
3953
4661
  if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
3954
- console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
4662
+ console.log(` ${import_chalk31.default.green("\u2713")} apiblaze package already installed`);
3955
4663
  return;
3956
4664
  }
3957
4665
  const has = (f) => fs7.existsSync(path4.join(root, f));
3958
4666
  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" };
3959
- const spinner = (0, import_ora14.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
4667
+ const spinner = (0, import_ora16.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
3960
4668
  try {
3961
4669
  const { execSync } = require("child_process");
3962
4670
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
3963
4671
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
3964
4672
  } catch {
3965
- spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk29.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk29.default.cyan("npm run dev")}.`);
4673
+ spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk31.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk31.default.cyan("npm run dev")}.`);
3966
4674
  }
3967
4675
  }
3968
4676
  function readEnvKey(root) {
@@ -4101,7 +4809,7 @@ async function runAnonymousInit(root, router, opts) {
4101
4809
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
4102
4810
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
4103
4811
  if (opts.newSession) clearAnonCred2();
4104
- const spinner = (0, import_ora14.default)("Setting up a sidecar (no login needed)...").start();
4812
+ const spinner = (0, import_ora16.default)("Setting up a sidecar (no login needed)...").start();
4105
4813
  let out;
4106
4814
  try {
4107
4815
  out = await sidecarInitAnonymous2();
@@ -4113,29 +4821,29 @@ async function runAnonymousInit(root, router, opts) {
4113
4821
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
4114
4822
  const envState = upsertEnvLocal(root, out.token);
4115
4823
  ensureGitignored(root);
4116
- console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4117
- console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4824
+ console.log(` ${import_chalk31.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4825
+ console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4118
4826
  installSidecarPackage(root);
4119
4827
  let inspectorPath = null;
4120
4828
  if (!opts.noInspector) {
4121
4829
  inspectorPath = generateInspector(root, router);
4122
- if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
4830
+ if (inspectorPath) console.log(` ${import_chalk31.default.green("\u2713")} inspector at ${inspectorPath}`);
4123
4831
  }
4124
4832
  console.log("");
4125
- console.log(import_chalk29.default.bold("Done (no account needed). What happens next:"));
4126
- console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app.`);
4833
+ console.log(import_chalk31.default.bold("Done (no account needed). What happens next:"));
4834
+ console.log(` 1. ${import_chalk31.default.cyan("npm run dev")} and use your app.`);
4127
4835
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
4128
- console.log(` ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
4836
+ console.log(` ${import_chalk31.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
4129
4837
  console.log("");
4130
- console.log(import_chalk29.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
4131
- console.log(` ${import_chalk29.default.cyan("apiblaze login")} then ${import_chalk29.default.cyan("apiblaze claim")} ${import_chalk29.default.dim("(no code needed here)")}`);
4132
- console.log(import_chalk29.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
4838
+ console.log(import_chalk31.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
4839
+ console.log(` ${import_chalk31.default.cyan("apiblaze login")} then ${import_chalk31.default.cyan("apiblaze claim")} ${import_chalk31.default.dim("(no code needed here)")}`);
4840
+ console.log(import_chalk31.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
4133
4841
  }
4134
4842
  async function runSidecar(opts) {
4135
4843
  const root = path4.resolve(opts.dir ?? process.cwd());
4136
4844
  const detected = detectNextProject(root);
4137
4845
  if (!detected.found) {
4138
- console.log(import_chalk29.default.yellow(`No Next.js project detected in ${root}.`));
4846
+ console.log(import_chalk31.default.yellow(`No Next.js project detected in ${root}.`));
4139
4847
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
4140
4848
  return;
4141
4849
  }
@@ -4146,10 +4854,10 @@ async function runSidecar(opts) {
4146
4854
  if (!loadCredentials()) {
4147
4855
  upsertEnvLocal(root, readEnvKey(root));
4148
4856
  ensureGitignored(root);
4149
- console.log(` ${import_chalk29.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
4150
- console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4857
+ console.log(` ${import_chalk31.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
4858
+ console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4151
4859
  installSidecarPackage(root);
4152
- console.log(import_chalk29.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
4860
+ console.log(import_chalk31.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
4153
4861
  return;
4154
4862
  }
4155
4863
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -4158,7 +4866,7 @@ async function runSidecar(opts) {
4158
4866
  const mustMint = !existingKey || opts.rotate || switchingTeam;
4159
4867
  let token = existingKey ?? "";
4160
4868
  if (mustMint) {
4161
- const spinner = (0, import_ora14.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
4869
+ const spinner = (0, import_ora16.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
4162
4870
  try {
4163
4871
  const out = await admin({
4164
4872
  method: "POST",
@@ -4172,39 +4880,40 @@ async function runSidecar(opts) {
4172
4880
  throw err;
4173
4881
  }
4174
4882
  } else {
4175
- console.log(import_chalk29.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
4883
+ console.log(import_chalk31.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
4176
4884
  }
4177
4885
  const envState = upsertEnvLocal(root, token);
4178
4886
  ensureGitignored(root);
4179
- console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4887
+ console.log(` ${import_chalk31.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4180
4888
  const wireState = wireInstrumentation(root);
4181
- console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
4889
+ console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireState}`);
4182
4890
  installSidecarPackage(root);
4183
4891
  let inspectorPath = null;
4184
4892
  if (!opts.noInspector) {
4185
4893
  inspectorPath = generateInspector(root, detected.router);
4186
- if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
4894
+ if (inspectorPath) console.log(` ${import_chalk31.default.green("\u2713")} inspector at ${inspectorPath}`);
4187
4895
  }
4188
4896
  console.log("");
4189
- console.log(import_chalk29.default.bold("Done. What happens next:"));
4190
- console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
4191
- console.log(` 2. The origins your app calls appear as ${import_chalk29.default.bold("candidates")} \u2014 list them: ${import_chalk29.default.cyan("apiblaze sidecar")}`);
4192
- console.log(` 3. Approve the ones to route: ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
4897
+ console.log(import_chalk31.default.bold("Done. What happens next:"));
4898
+ console.log(` 1. ${import_chalk31.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
4899
+ console.log(` 2. The origins your app calls appear as ${import_chalk31.default.bold("candidates")} \u2014 list them: ${import_chalk31.default.cyan("apiblaze sidecar")}`);
4900
+ console.log(` 3. Approve the ones to route: ${import_chalk31.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
4193
4901
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
4194
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk29.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
4195
- if (switchingTeam) console.log(import_chalk29.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
4902
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk31.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
4903
+ if (switchingTeam) console.log(import_chalk31.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
4196
4904
  console.log("");
4197
- console.log(import_chalk29.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
4198
- console.log(import_chalk29.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
4199
- console.log(import_chalk29.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
4905
+ console.log(import_chalk31.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
4906
+ console.log(import_chalk31.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
4907
+ console.log(import_chalk31.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
4200
4908
  console.log("");
4201
- console.log(import_chalk29.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
4202
- console.log(import_chalk29.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
4909
+ console.log(import_chalk31.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
4910
+ console.log(import_chalk31.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
4203
4911
  }
4204
4912
 
4205
4913
  // src/commands/origins.ts
4206
- var import_chalk30 = __toESM(require("chalk"));
4207
- var import_ora15 = __toESM(require("ora"));
4914
+ var import_chalk32 = __toESM(require("chalk"));
4915
+ var import_ora17 = __toESM(require("ora"));
4916
+ init_admin();
4208
4917
  init_auth();
4209
4918
  init_anon_cred();
4210
4919
  async function runOriginsList(opts) {
@@ -4212,7 +4921,7 @@ async function runOriginsList(opts) {
4212
4921
  if (!loadCredentials()) {
4213
4922
  const cred = loadAnonCred();
4214
4923
  if (!cred) {
4215
- console.log(import_chalk30.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
4924
+ console.log(import_chalk32.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
4216
4925
  return;
4217
4926
  }
4218
4927
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -4230,30 +4939,30 @@ async function runOriginsList(opts) {
4230
4939
  }
4231
4940
  const routed = out.routed ?? [];
4232
4941
  const candidates = out.candidates ?? [];
4233
- console.log(import_chalk30.default.bold(`
4942
+ console.log(import_chalk32.default.bold(`
4234
4943
  Routed through APIblaze (${routed.length})`));
4235
- if (!routed.length) console.log(import_chalk30.default.dim(" none yet"));
4236
- for (const r of routed) console.log(` ${import_chalk30.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk30.default.dim(`\u2192 ${r.project_id}`)}`);
4237
- console.log(import_chalk30.default.bold(`
4944
+ if (!routed.length) console.log(import_chalk32.default.dim(" none yet"));
4945
+ for (const r of routed) console.log(` ${import_chalk32.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk32.default.dim(`\u2192 ${r.project_id}`)}`);
4946
+ console.log(import_chalk32.default.bold(`
4238
4947
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
4239
- if (!candidates.length) console.log(import_chalk30.default.dim(" none \u2014 run your app to discover the origins it calls"));
4948
+ if (!candidates.length) console.log(import_chalk32.default.dim(" none \u2014 run your app to discover the origins it calls"));
4240
4949
  for (const c of candidates) {
4241
- console.log(` ${import_chalk30.default.yellow("\u25CB")} ${c.origin} ${import_chalk30.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
4950
+ console.log(` ${import_chalk32.default.yellow("\u25CB")} ${c.origin} ${import_chalk32.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
4242
4951
  }
4243
4952
  if (candidates.length) {
4244
- console.log(import_chalk30.default.dim(`
4953
+ console.log(import_chalk32.default.dim(`
4245
4954
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
4246
- console.log(import_chalk30.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
4955
+ console.log(import_chalk32.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
4247
4956
  }
4248
4957
  }
4249
4958
  async function runOriginsApprove(origin, opts) {
4250
4959
  if (!loadCredentials()) {
4251
4960
  const cred = loadAnonCred();
4252
4961
  if (!cred) {
4253
- console.error(import_chalk30.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
4962
+ console.error(import_chalk32.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
4254
4963
  process.exit(1);
4255
4964
  }
4256
- const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
4965
+ const spinner2 = (0, import_ora17.default)(`Approving ${origin} (anonymous)...`).start();
4257
4966
  try {
4258
4967
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
4259
4968
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -4264,7 +4973,7 @@ async function runOriginsApprove(origin, opts) {
4264
4973
  return;
4265
4974
  }
4266
4975
  const { teamId } = await resolveTeam(opts.team);
4267
- const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
4976
+ const spinner = (0, import_ora17.default)(`Approving ${origin}...`).start();
4268
4977
  try {
4269
4978
  const out = await admin({
4270
4979
  method: "POST",
@@ -4281,7 +4990,7 @@ async function runOriginsApprove(origin, opts) {
4281
4990
  }
4282
4991
  async function runOriginsDeny(origin, opts) {
4283
4992
  const { teamId } = await resolveTeam(opts.team);
4284
- const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
4993
+ const spinner = (0, import_ora17.default)(`Dismissing ${origin}...`).start();
4285
4994
  try {
4286
4995
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
4287
4996
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -4292,7 +5001,7 @@ async function runOriginsDeny(origin, opts) {
4292
5001
  }
4293
5002
  async function runOriginsRemove(origin, opts) {
4294
5003
  const { teamId } = await resolveTeam(opts.team);
4295
- const spinner = (0, import_ora15.default)(`Removing the proxy for ${origin}...`).start();
5004
+ const spinner = (0, import_ora17.default)(`Removing the proxy for ${origin}...`).start();
4296
5005
  try {
4297
5006
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
4298
5007
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -4303,8 +5012,9 @@ async function runOriginsRemove(origin, opts) {
4303
5012
  }
4304
5013
 
4305
5014
  // src/commands/op.ts
4306
- var import_chalk31 = __toESM(require("chalk"));
5015
+ var import_chalk33 = __toESM(require("chalk"));
4307
5016
  init_auth();
5017
+ init_trace();
4308
5018
  init_types();
4309
5019
  var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
4310
5020
  var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
@@ -4334,53 +5044,53 @@ async function opCall(call) {
4334
5044
  function printResidue(report, applied) {
4335
5045
  const up = report?.upstash ?? {};
4336
5046
  const fga = report?.fga ?? {};
4337
- console.log(import_chalk31.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
4338
- console.log(import_chalk31.default.bold("\n Upstash"));
5047
+ console.log(import_chalk33.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
5048
+ console.log(import_chalk33.default.bold("\n Upstash"));
4339
5049
  const orphans = up.orphans ?? [];
4340
- if (orphans.length === 0) console.log(import_chalk31.default.green(" no orphaned keys"));
4341
- for (const o of orphans) console.log(` ${import_chalk31.default.yellow(o.key)} ${import_chalk31.default.dim(`\u2014 ${o.reason}`)}`);
4342
- console.log(import_chalk31.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
4343
- if (up.unknown?.length) console.log(import_chalk31.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
4344
- if (applied) console.log(` ${import_chalk31.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
4345
- for (const e of up.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
4346
- console.log(import_chalk31.default.bold("\n OpenFGA / Neon"));
5050
+ if (orphans.length === 0) console.log(import_chalk33.default.green(" no orphaned keys"));
5051
+ for (const o of orphans) console.log(` ${import_chalk33.default.yellow(o.key)} ${import_chalk33.default.dim(`\u2014 ${o.reason}`)}`);
5052
+ console.log(import_chalk33.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
5053
+ if (up.unknown?.length) console.log(import_chalk33.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
5054
+ if (applied) console.log(` ${import_chalk33.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
5055
+ for (const e of up.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
5056
+ console.log(import_chalk33.default.bold("\n OpenFGA / Neon"));
4347
5057
  if (applied) {
4348
5058
  const swept = fga?.swept ?? [];
4349
- if (swept.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
5059
+ if (swept.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
4350
5060
  for (const s of swept) {
4351
5061
  console.log(
4352
- ` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
5062
+ ` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
4353
5063
  );
4354
5064
  }
4355
- if (fga?.remaining) console.log(import_chalk31.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
5065
+ if (fga?.remaining) console.log(import_chalk33.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
4356
5066
  } else {
4357
5067
  const fgaOrphans = fga?.orphans ?? [];
4358
- if (fgaOrphans.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
5068
+ if (fgaOrphans.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
4359
5069
  for (const s of fgaOrphans) {
4360
5070
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
4361
- console.log(` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
5071
+ console.log(` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
4362
5072
  }
4363
- console.log(import_chalk31.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
5073
+ console.log(import_chalk33.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
4364
5074
  }
4365
- for (const e of fga?.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
5075
+ for (const e of fga?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
4366
5076
  console.log();
4367
5077
  }
4368
5078
  async function runOp(sub, opts = {}) {
4369
5079
  if (!loadCredentials()) {
4370
- console.log(import_chalk31.default.dim("Not logged in. Run `apiblaze login`."));
5080
+ console.log(import_chalk33.default.dim("Not logged in. Run `apiblaze login`."));
4371
5081
  return;
4372
5082
  }
4373
5083
  if (!isOperatorLogin()) {
4374
- console.log(import_chalk31.default.dim("`apiblaze op` is only available to platform operators."));
5084
+ console.log(import_chalk33.default.dim("`apiblaze op` is only available to platform operators."));
4375
5085
  return;
4376
5086
  }
4377
5087
  switch (sub) {
4378
5088
  case void 0:
4379
5089
  case "menu": {
4380
- console.log(import_chalk31.default.bold("\nOperator menu"));
4381
- console.log(` ${import_chalk31.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
4382
- console.log(` ${import_chalk31.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk31.default.dim("-y to skip")})`);
4383
- console.log(` ${import_chalk31.default.cyan("apiblaze op credits")} list credit wallets
5090
+ console.log(import_chalk33.default.bold("\nOperator menu"));
5091
+ console.log(` ${import_chalk33.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
5092
+ console.log(` ${import_chalk33.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk33.default.dim("-y to skip")})`);
5093
+ console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets
4384
5094
  `);
4385
5095
  return;
4386
5096
  }
@@ -4396,15 +5106,15 @@ async function runOp(sub, opts = {}) {
4396
5106
  const nFga = report?.fga?.orphans?.length ?? 0;
4397
5107
  printResidue(report, false);
4398
5108
  if (nUp + nFga === 0) {
4399
- console.log(import_chalk31.default.green("Nothing to sweep."));
5109
+ console.log(import_chalk33.default.green("Nothing to sweep."));
4400
5110
  return;
4401
5111
  }
4402
5112
  if (!opts.yes) {
4403
5113
  const readline2 = await import("readline/promises");
4404
5114
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
4405
- const answer = await rl.question(import_chalk31.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
5115
+ const answer = await rl.question(import_chalk33.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
4406
5116
  rl.close();
4407
- if (answer.trim() !== "sweep") return void console.log(import_chalk31.default.dim("Aborted."));
5117
+ if (answer.trim() !== "sweep") return void console.log(import_chalk33.default.dim("Aborted."));
4408
5118
  }
4409
5119
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
4410
5120
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -4415,19 +5125,20 @@ async function runOp(sub, opts = {}) {
4415
5125
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
4416
5126
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
4417
5127
  const accounts = data?.accounts ?? [];
4418
- if (accounts.length === 0) return void console.log(import_chalk31.default.dim("No credit wallets."));
5128
+ if (accounts.length === 0) return void console.log(import_chalk33.default.dim("No credit wallets."));
4419
5129
  for (const a of accounts) {
4420
5130
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
4421
- console.log(` ${import_chalk31.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk31.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk31.default.dim(" \u2014 anon") : ""}`);
5131
+ console.log(` ${import_chalk33.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk33.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk33.default.dim(" \u2014 anon") : ""}`);
4422
5132
  }
4423
5133
  return;
4424
5134
  }
4425
5135
  default:
4426
- console.log(import_chalk31.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
5136
+ console.log(import_chalk33.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
4427
5137
  }
4428
5138
  }
4429
5139
 
4430
5140
  // src/index.ts
5141
+ init_trace();
4431
5142
  var program = new import_commander.Command();
4432
5143
  program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
4433
5144
  program.hook("preAction", () => {
@@ -4478,7 +5189,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
4478
5189
  try {
4479
5190
  const resolved = parseInt(port ?? opts.port, 10);
4480
5191
  if (Number.isNaN(resolved)) {
4481
- console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
5192
+ console.error(import_chalk34.default.red(`Invalid port: ${port ?? opts.port}`));
4482
5193
  process.exit(1);
4483
5194
  }
4484
5195
  await runDev({ port: resolved, captureFile: opts.captureFile });
@@ -4537,6 +5248,8 @@ domain.command("status").description("Check a custom domain's validation status"
4537
5248
  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)));
4538
5249
  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)));
4539
5250
  var tenant = program.command("tenant").description("Manage tenants \u2014 separate groups of your API's users");
5251
+ tenant.command("manage").description("Browse & edit one tenant: settings, login app clients, providers, issuers (search-first picker)").argument("[query]", "Search by tenant name/display name (omit to use your tenant scope or pick)").option("--tenant <slug>", "Exact tenant slug (skips the picker)").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantManage(query, opts)));
5252
+ tenant.command("use").description("Set the sticky tenant scope for future commands (search-first; --clear to unset)").argument("[query]", "Search by tenant name/display name").option("--clear", "Clear the tenant scope").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantUse(query, opts)));
4540
5253
  tenant.command("list").description("List tenants in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
4541
5254
  tenant.command("create").description("Create a tenant in your team").requiredOption("--name <display>", "Display name").option("--slug <tenant_name>", "Explicit tenant slug (generated if omitted)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
4542
5255
  tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
@@ -4574,7 +5287,7 @@ function groupedCommandHelp() {
4574
5287
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
4575
5288
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
4576
5289
  }).filter(Boolean).join("\n");
4577
- return `${import_chalk32.default.bold(g.title)}
5290
+ return `${import_chalk34.default.bold(g.title)}
4578
5291
  ${rows}`;
4579
5292
  }).join("\n\n");
4580
5293
  }
@@ -4602,13 +5315,13 @@ Examples:
4602
5315
  `);
4603
5316
  function printError(err) {
4604
5317
  if (err instanceof ApiError) {
4605
- console.error(import_chalk32.default.red(`
5318
+ console.error(import_chalk34.default.red(`
4606
5319
  API error (${err.status}): ${err.message}`));
4607
5320
  } else if (err instanceof Error) {
4608
- console.error(import_chalk32.default.red(`
5321
+ console.error(import_chalk34.default.red(`
4609
5322
  Error: ${err.message}`));
4610
5323
  } else {
4611
- console.error(import_chalk32.default.red("\nUnknown error"));
5324
+ console.error(import_chalk34.default.red("\nUnknown error"));
4612
5325
  }
4613
5326
  }
4614
5327
  program.parse(process.argv);