apiblaze 0.11.1 → 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 +905 -320
  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.1";
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);
@@ -3481,7 +4069,7 @@ async function buildCondition(phase) {
3481
4069
  const items = [];
3482
4070
  for (; ; ) {
3483
4071
  const a = await inquirer2.prompt([
3484
- { type: "input", name: "source", message: `Condition field ${import_chalk26.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
4072
+ { type: "input", name: "source", message: `Condition field ${import_chalk28.default.dim(srcHint)}:`, validate: (s) => !!s || "required" },
3485
4073
  { type: "list", name: "operator", message: "Operator:", choices: [
3486
4074
  "eq",
3487
4075
  "neq",
@@ -3512,7 +4100,7 @@ async function buildCondition(phase) {
3512
4100
  function showCondition(cond) {
3513
4101
  if (!Array.isArray(cond) || !cond.length) return "";
3514
4102
  const s = cond.map((c) => `${c.source} ${c.operator}${c.value !== void 0 ? ` "${c.value}"` : ""}${c.logicOp ? ` ${c.logicOp}` : ""}`).join(" ");
3515
- return import_chalk26.default.dim(` when ${s}`);
4103
+ return import_chalk28.default.dim(` when ${s}`);
3516
4104
  }
3517
4105
  async function transformsMenu(proj2) {
3518
4106
  const { default: inquirer2 } = await import("inquirer");
@@ -3521,12 +4109,12 @@ async function transformsMenu(proj2) {
3521
4109
  const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
3522
4110
  const rules = out?.rules ?? [];
3523
4111
  console.log();
3524
- 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."));
3525
4113
  for (const r of rules) {
3526
4114
  const a = r.action ?? {};
3527
4115
  const fns = [...a.source_fns ?? [], ...a.dest_fns ?? []].map((f) => f.fn);
3528
- 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_chalk26.default.dim(` via ${fns.join("\u2192")}`) : ""}`;
3529
- 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}${showCondition(r.condition)}`);
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)}`);
3530
4118
  }
3531
4119
  const { act } = await inquirer2.prompt([{
3532
4120
  type: "list",
@@ -3538,7 +4126,7 @@ async function transformsMenu(proj2) {
3538
4126
  { name: "Enable/disable a rule", value: "toggle" },
3539
4127
  { name: "Delete a rule", value: "delete" }
3540
4128
  ] : [],
3541
- { name: import_chalk26.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
4129
+ { name: import_chalk28.default.dim("Add from raw JSON (grouped conditions, lookup tables, \u2026)"), value: "raw" },
3542
4130
  { name: "\u2190 Back", value: "back" }
3543
4131
  ]
3544
4132
  }]);
@@ -3551,11 +4139,11 @@ async function transformsMenu(proj2) {
3551
4139
  }]);
3552
4140
  const body = parseValue(raw);
3553
4141
  if (!body || typeof body !== "object" || Array.isArray(body)) {
3554
- console.log(import_chalk26.default.yellow(" Not a JSON object \u2014 skipped."));
4142
+ console.log(import_chalk28.default.yellow(" Not a JSON object \u2014 skipped."));
3555
4143
  continue;
3556
4144
  }
3557
4145
  await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
3558
- console.log(import_chalk26.default.green(" Rule created."));
4146
+ console.log(import_chalk28.default.green(" Rule created."));
3559
4147
  continue;
3560
4148
  }
3561
4149
  if (act === "add") {
@@ -3571,7 +4159,7 @@ async function transformsMenu(proj2) {
3571
4159
  { name: "Remove a field", value: "remove" }
3572
4160
  ] }
3573
4161
  ]);
3574
- 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)");
3575
4163
  let action2;
3576
4164
  if (ans.kind === "hardcode") {
3577
4165
  const a = await inquirer2.prompt([
@@ -3602,7 +4190,7 @@ async function transformsMenu(proj2) {
3602
4190
  };
3603
4191
  }
3604
4192
  const condition = await buildCondition(ans.phase);
3605
- const spinner = (0, import_ora11.default)("Creating rule...").start();
4193
+ const spinner = (0, import_ora13.default)("Creating rule...").start();
3606
4194
  try {
3607
4195
  await admin({
3608
4196
  method: "POST",
@@ -3620,16 +4208,16 @@ async function transformsMenu(proj2) {
3620
4208
  type: "list",
3621
4209
  name: "rule",
3622
4210
  message: act === "toggle" ? "Which rule?" : "Delete which rule?",
3623
- 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 }]
3624
4212
  }]);
3625
4213
  if (!rule) continue;
3626
4214
  if (act === "toggle") {
3627
4215
  const flipped = { ...rule, enabled: rule.enabled === false };
3628
4216
  await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
3629
- 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"}`));
3630
4218
  } else {
3631
4219
  await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
3632
- console.log(import_chalk26.default.green(` ${rule.name} deleted.`));
4220
+ console.log(import_chalk28.default.green(` ${rule.name} deleted.`));
3633
4221
  }
3634
4222
  }
3635
4223
  }
@@ -3641,9 +4229,9 @@ async function mappingsMenu(proj2) {
3641
4229
  const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
3642
4230
  const tables = out?.mappings ?? out?.tables ?? [];
3643
4231
  console.log();
3644
- 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."));
3645
4233
  for (const t of tables) {
3646
- 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" : ""}`)}`);
3647
4235
  }
3648
4236
  const { act } = await inquirer2.prompt([{
3649
4237
  type: "list",
@@ -3663,11 +4251,11 @@ async function mappingsMenu(proj2) {
3663
4251
  ]);
3664
4252
  const entries2 = parseValue(a.entries);
3665
4253
  if (!Array.isArray(entries2)) {
3666
- 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."));
3667
4255
  continue;
3668
4256
  }
3669
4257
  await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
3670
- console.log(import_chalk26.default.green(` Table "${a.name}" created.`));
4258
+ console.log(import_chalk28.default.green(` Table "${a.name}" created.`));
3671
4259
  } else {
3672
4260
  const { table } = await inquirer2.prompt([{
3673
4261
  type: "list",
@@ -3677,7 +4265,7 @@ async function mappingsMenu(proj2) {
3677
4265
  }]);
3678
4266
  if (!table) continue;
3679
4267
  await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
3680
- console.log(import_chalk26.default.green(` ${table.name} deleted.`));
4268
+ console.log(import_chalk28.default.green(` ${table.name} deleted.`));
3681
4269
  }
3682
4270
  }
3683
4271
  }
@@ -3688,22 +4276,22 @@ async function tenantsMenu(proj2, opts) {
3688
4276
  const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
3689
4277
  const tenants = out?.tenants ?? [];
3690
4278
  console.log();
3691
- if (!tenants.length) console.log(import_chalk26.default.dim(" No tenants attached (consumers use the default tenant)."));
3692
- 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 ?? "")}`);
3693
4281
  const { act } = await inquirer2.prompt([{
3694
4282
  type: "list",
3695
4283
  name: "act",
3696
4284
  message: "Tenants:",
3697
4285
  choices: [
3698
- { name: "Attach a tenant", value: "attach" },
3699
- ...tenants.length ? [{ name: "Detach a tenant", value: "detach" }] : [],
3700
- { 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" }] : [],
3701
4289
  { name: "\u2190 Back", value: "back" }
3702
4290
  ]
3703
4291
  }]);
3704
4292
  if (act === "back") return;
3705
- if (act === "hint") {
3706
- 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 });
3707
4295
  continue;
3708
4296
  }
3709
4297
  if (act === "attach") {
@@ -3718,7 +4306,7 @@ async function tenantsMenu(proj2, opts) {
3718
4306
  }]);
3719
4307
  if (!t) continue;
3720
4308
  await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
3721
- 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}.`));
3722
4310
  }
3723
4311
  }
3724
4312
  }
@@ -3761,7 +4349,7 @@ async function specMenu(proj2, opts) {
3761
4349
  choices: [
3762
4350
  { name: "Print the stored spec", value: "get" },
3763
4351
  { name: "Refresh the spec from its source", value: "refresh" },
3764
- { 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" },
3765
4353
  { name: "\u2190 Back", value: "back" }
3766
4354
  ]
3767
4355
  }]);
@@ -3769,7 +4357,7 @@ async function specMenu(proj2, opts) {
3769
4357
  if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
3770
4358
  else if (act === "refresh") {
3771
4359
  await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
3772
- console.log(import_chalk26.default.green(" Spec refresh triggered."));
4360
+ console.log(import_chalk28.default.green(" Spec refresh triggered."));
3773
4361
  } else await runOpenapi(proj2.projectName, proj2.apiVersion);
3774
4362
  }
3775
4363
  async function agentsMenu(proj2, opts) {
@@ -3794,8 +4382,9 @@ async function agentsMenu(proj2, opts) {
3794
4382
  }
3795
4383
 
3796
4384
  // src/commands/key.ts
3797
- var import_chalk27 = __toESM(require("chalk"));
3798
- var import_ora12 = __toESM(require("ora"));
4385
+ var import_chalk29 = __toESM(require("chalk"));
4386
+ var import_ora14 = __toESM(require("ora"));
4387
+ init_admin();
3799
4388
  async function runApikeysMenu(opts) {
3800
4389
  await runKeyList(opts);
3801
4390
  if (opts.json) return;
@@ -3822,11 +4411,11 @@ async function runKeyList(opts) {
3822
4411
  return;
3823
4412
  }
3824
4413
  if (!keys.length) {
3825
- console.log(import_chalk27.default.yellow("No developer keys."));
4414
+ console.log(import_chalk29.default.yellow("No developer keys."));
3826
4415
  return;
3827
4416
  }
3828
4417
  for (const k of keys) {
3829
- 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")}`);
3830
4419
  }
3831
4420
  }
3832
4421
  async function runKeyMint(opts) {
@@ -3834,7 +4423,7 @@ async function runKeyMint(opts) {
3834
4423
  const body = { role: "consumer-admin" };
3835
4424
  if (opts.desc) body.description = opts.desc;
3836
4425
  if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
3837
- const spinner = (0, import_ora12.default)("Minting key...").start();
4426
+ const spinner = (0, import_ora14.default)("Minting key...").start();
3838
4427
  try {
3839
4428
  const out = await admin({
3840
4429
  method: "POST",
@@ -3847,9 +4436,9 @@ async function runKeyMint(opts) {
3847
4436
  console.log(JSON.stringify(out));
3848
4437
  return;
3849
4438
  }
3850
- console.log(` ${import_chalk27.default.bold("key_id")}: ${out?.key_id}`);
3851
- console.log(` ${import_chalk27.default.bold("key")}: ${import_chalk27.default.green(out?.key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
3852
- 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}`);
3853
4442
  } catch (err) {
3854
4443
  spinner.fail("Mint failed.");
3855
4444
  throw err;
@@ -3857,7 +4446,7 @@ async function runKeyMint(opts) {
3857
4446
  }
3858
4447
  async function runKeyRevoke(keyId, opts) {
3859
4448
  const { teamId } = await resolveTeam(opts.team);
3860
- const spinner = (0, import_ora12.default)("Revoking key...").start();
4449
+ const spinner = (0, import_ora14.default)("Revoking key...").start();
3861
4450
  try {
3862
4451
  await admin({
3863
4452
  method: "DELETE",
@@ -3872,8 +4461,9 @@ async function runKeyRevoke(keyId, opts) {
3872
4461
  }
3873
4462
 
3874
4463
  // src/commands/consumer.ts
3875
- var import_chalk28 = __toESM(require("chalk"));
3876
- var import_ora13 = __toESM(require("ora"));
4464
+ var import_chalk30 = __toESM(require("chalk"));
4465
+ var import_ora15 = __toESM(require("ora"));
4466
+ init_admin();
3877
4467
  var DEFAULT_SCOPE = "openid email profile offline_access";
3878
4468
  var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
3879
4469
  async function consumerFetch(creds, suffix, init) {
@@ -3892,7 +4482,7 @@ async function consumerFetch(creds, suffix, init) {
3892
4482
  function requireConsumer() {
3893
4483
  const c = loadConsumer();
3894
4484
  if (!c) {
3895
- 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."));
3896
4486
  process.exit(1);
3897
4487
  }
3898
4488
  return c;
@@ -3903,48 +4493,37 @@ async function runConsumerLogin(opts) {
3903
4493
  let clientId = opts.client;
3904
4494
  if (clientId) {
3905
4495
  if (!tenant2) {
3906
- 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)."));
3907
4497
  process.exit(1);
3908
4498
  }
3909
4499
  } else {
3910
4500
  requireAuth();
3911
4501
  const { teamId, teamName } = await resolveTeam(opts.team);
3912
- const spinner = (0, import_ora13.default)("Loading your tenants...").start();
3913
- const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
3914
- spinner.stop();
3915
- const tenants = (tdata?.tenants ?? []).map(
3916
- (t) => typeof t === "string" ? { tenant_name: t } : t
3917
- );
3918
- if (!tenants.length) {
3919
- console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
3920
- process.exit(1);
3921
- }
3922
4502
  if (!tenant2) {
3923
- if (tenants.length === 1) tenant2 = tenants[0].tenant_name;
3924
- else {
3925
- 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 })) }]);
3926
- tenant2 = chosen;
3927
- }
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;
3928
4507
  }
3929
- const s2 = (0, import_ora13.default)("Finding the login app...").start();
4508
+ const s2 = (0, import_ora15.default)("Finding the login app...").start();
3930
4509
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
3931
4510
  s2.stop();
3932
4511
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
3933
4512
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
3934
4513
  if (!pick2) {
3935
- 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).`));
3936
4515
  process.exit(1);
3937
4516
  }
3938
4517
  clientId = pick2.client_id ?? pick2.clientId;
3939
4518
  }
3940
4519
  const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
3941
- 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...`);
3942
4521
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
3943
4522
  console.log(`
3944
- Open: ${import_chalk28.default.underline(verificationUri)}`);
3945
- console.log(` Code: ${import_chalk28.default.bold(userCode)}
4523
+ Open: ${import_chalk30.default.underline(verificationUri)}`);
4524
+ console.log(` Code: ${import_chalk30.default.bold(userCode)}
3946
4525
  `);
3947
- 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)"));
3948
4527
  }, portalResource);
3949
4528
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
3950
4529
  const creds = {
@@ -3959,7 +4538,7 @@ async function runConsumerLogin(opts) {
3959
4538
  obtainedAt: Date.now()
3960
4539
  };
3961
4540
  saveConsumer(creds);
3962
- 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}.`));
3963
4542
  }
3964
4543
  async function runConsumerTokens(opts) {
3965
4544
  const creds = requireConsumer();
@@ -3972,29 +4551,29 @@ async function runConsumerTokens(opts) {
3972
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));
3973
4552
  return;
3974
4553
  }
3975
- 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)}
3976
4555
  `);
3977
- 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) ?? "?"))}
3978
4557
  ${fresh.accessToken}
3979
4558
  `);
3980
- 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) ?? "?"))}
3981
4560
  ${fresh.idToken}
3982
4561
  `);
3983
- if (fresh.refreshToken) console.log(`${import_chalk28.default.bold("refresh_token")}
4562
+ if (fresh.refreshToken) console.log(`${import_chalk30.default.bold("refresh_token")}
3984
4563
  ${fresh.refreshToken}
3985
4564
  `);
3986
- 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."));
3987
4566
  }
3988
4567
  async function runConsumerApikeys(opts) {
3989
4568
  const creds = requireConsumer();
3990
4569
  const { default: inquirer2 } = await import("inquirer");
3991
- const spinner = (0, import_ora13.default)("Loading your API keys...").start();
4570
+ const spinner = (0, import_ora15.default)("Loading your API keys...").start();
3992
4571
  const list = await consumerFetch(creds, "/apikeys");
3993
4572
  const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
3994
4573
  spinner.stop();
3995
4574
  if (list.status >= 400) {
3996
- console.error(import_chalk28.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
3997
- 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."));
3998
4577
  process.exit(1);
3999
4578
  }
4000
4579
  const keys = list.data?.keys ?? [];
@@ -4002,16 +4581,16 @@ async function runConsumerApikeys(opts) {
4002
4581
  if (opts.json) {
4003
4582
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
4004
4583
  } else if (!keys.length) {
4005
- console.log(import_chalk28.default.yellow("No API keys yet."));
4584
+ console.log(import_chalk30.default.yellow("No API keys yet."));
4006
4585
  } else {
4007
4586
  for (const k of keys) {
4008
4587
  const clear = revealMap[k.environment]?.key;
4009
- const shown = clear ? import_chalk28.default.green(clear) : import_chalk28.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
4010
- const exp = k.expires_at ? import_chalk28.default.dim(`exp ${k.expires_at}`) : import_chalk28.default.dim("no expiry");
4011
- 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 ?? "")}`);
4012
4591
  }
4013
4592
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
4014
- 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.)"));
4015
4594
  }
4016
4595
  }
4017
4596
  if (opts.json) return;
@@ -4025,7 +4604,7 @@ async function runConsumerApikeys(opts) {
4025
4604
  const body = { environment: answers.environment };
4026
4605
  if (answers.description) body.description = answers.description;
4027
4606
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
4028
- const s2 = (0, import_ora13.default)("Creating key...").start();
4607
+ const s2 = (0, import_ora15.default)("Creating key...").start();
4029
4608
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
4030
4609
  if (created.status >= 400) {
4031
4610
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -4033,15 +4612,16 @@ async function runConsumerApikeys(opts) {
4033
4612
  }
4034
4613
  s2.succeed("Key created.");
4035
4614
  const key = created.data?.key ?? created.data?.fullKey;
4036
- if (key) console.log(` ${import_chalk28.default.green(key)} ${import_chalk28.default.dim("(shown once \u2014 store it now)")}`);
4037
- 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."));
4038
4617
  }
4039
4618
 
4040
4619
  // src/commands/sidecar.ts
4041
- var import_chalk29 = __toESM(require("chalk"));
4042
- var import_ora14 = __toESM(require("ora"));
4620
+ var import_chalk31 = __toESM(require("chalk"));
4621
+ var import_ora16 = __toESM(require("ora"));
4043
4622
  var fs7 = __toESM(require("fs"));
4044
4623
  var path4 = __toESM(require("path"));
4624
+ init_admin();
4045
4625
  init_auth();
4046
4626
  function detectNextProject(root) {
4047
4627
  const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs7.existsSync(path4.join(root, f)));
@@ -4079,18 +4659,18 @@ function upsertEnvLocal(root, token) {
4079
4659
  }
4080
4660
  function installSidecarPackage(root) {
4081
4661
  if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
4082
- console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
4662
+ console.log(` ${import_chalk31.default.green("\u2713")} apiblaze package already installed`);
4083
4663
  return;
4084
4664
  }
4085
4665
  const has = (f) => fs7.existsSync(path4.join(root, f));
4086
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" };
4087
- 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();
4088
4668
  try {
4089
4669
  const { execSync } = require("child_process");
4090
4670
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
4091
4671
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
4092
4672
  } catch {
4093
- 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")}.`);
4094
4674
  }
4095
4675
  }
4096
4676
  function readEnvKey(root) {
@@ -4229,7 +4809,7 @@ async function runAnonymousInit(root, router, opts) {
4229
4809
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
4230
4810
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
4231
4811
  if (opts.newSession) clearAnonCred2();
4232
- 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();
4233
4813
  let out;
4234
4814
  try {
4235
4815
  out = await sidecarInitAnonymous2();
@@ -4241,29 +4821,29 @@ async function runAnonymousInit(root, router, opts) {
4241
4821
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
4242
4822
  const envState = upsertEnvLocal(root, out.token);
4243
4823
  ensureGitignored(root);
4244
- console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4245
- 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)}`);
4246
4826
  installSidecarPackage(root);
4247
4827
  let inspectorPath = null;
4248
4828
  if (!opts.noInspector) {
4249
4829
  inspectorPath = generateInspector(root, router);
4250
- 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}`);
4251
4831
  }
4252
4832
  console.log("");
4253
- console.log(import_chalk29.default.bold("Done (no account needed). What happens next:"));
4254
- 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.`);
4255
4835
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
4256
- 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)`);
4257
4837
  console.log("");
4258
- console.log(import_chalk29.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
4259
- console.log(` ${import_chalk29.default.cyan("apiblaze login")} then ${import_chalk29.default.cyan("apiblaze claim")} ${import_chalk29.default.dim("(no code needed here)")}`);
4260
- 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`));
4261
4841
  }
4262
4842
  async function runSidecar(opts) {
4263
4843
  const root = path4.resolve(opts.dir ?? process.cwd());
4264
4844
  const detected = detectNextProject(root);
4265
4845
  if (!detected.found) {
4266
- 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}.`));
4267
4847
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
4268
4848
  return;
4269
4849
  }
@@ -4274,10 +4854,10 @@ async function runSidecar(opts) {
4274
4854
  if (!loadCredentials()) {
4275
4855
  upsertEnvLocal(root, readEnvKey(root));
4276
4856
  ensureGitignored(root);
4277
- console.log(` ${import_chalk29.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
4278
- 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)}`);
4279
4859
  installSidecarPackage(root);
4280
- 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."));
4281
4861
  return;
4282
4862
  }
4283
4863
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -4286,7 +4866,7 @@ async function runSidecar(opts) {
4286
4866
  const mustMint = !existingKey || opts.rotate || switchingTeam;
4287
4867
  let token = existingKey ?? "";
4288
4868
  if (mustMint) {
4289
- 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();
4290
4870
  try {
4291
4871
  const out = await admin({
4292
4872
  method: "POST",
@@ -4300,39 +4880,40 @@ async function runSidecar(opts) {
4300
4880
  throw err;
4301
4881
  }
4302
4882
  } else {
4303
- 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).`));
4304
4884
  }
4305
4885
  const envState = upsertEnvLocal(root, token);
4306
4886
  ensureGitignored(root);
4307
- 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`);
4308
4888
  const wireState = wireInstrumentation(root);
4309
- console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
4889
+ console.log(` ${import_chalk31.default.green("\u2713")} instrumentation.ts ${wireState}`);
4310
4890
  installSidecarPackage(root);
4311
4891
  let inspectorPath = null;
4312
4892
  if (!opts.noInspector) {
4313
4893
  inspectorPath = generateInspector(root, detected.router);
4314
- 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}`);
4315
4895
  }
4316
4896
  console.log("");
4317
- console.log(import_chalk29.default.bold("Done. What happens next:"));
4318
- console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
4319
- console.log(` 2. The origins your app calls appear as ${import_chalk29.default.bold("candidates")} \u2014 list them: ${import_chalk29.default.cyan("apiblaze sidecar")}`);
4320
- 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)`);
4321
4901
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
4322
- 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)`);
4323
- 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>\`.`));
4324
4904
  console.log("");
4325
- console.log(import_chalk29.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
4326
- console.log(import_chalk29.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
4327
- 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)."));
4328
4908
  console.log("");
4329
- 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."));
4330
- 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."));
4331
4911
  }
4332
4912
 
4333
4913
  // src/commands/origins.ts
4334
- var import_chalk30 = __toESM(require("chalk"));
4335
- var import_ora15 = __toESM(require("ora"));
4914
+ var import_chalk32 = __toESM(require("chalk"));
4915
+ var import_ora17 = __toESM(require("ora"));
4916
+ init_admin();
4336
4917
  init_auth();
4337
4918
  init_anon_cred();
4338
4919
  async function runOriginsList(opts) {
@@ -4340,7 +4921,7 @@ async function runOriginsList(opts) {
4340
4921
  if (!loadCredentials()) {
4341
4922
  const cred = loadAnonCred();
4342
4923
  if (!cred) {
4343
- 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."));
4344
4925
  return;
4345
4926
  }
4346
4927
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -4358,30 +4939,30 @@ async function runOriginsList(opts) {
4358
4939
  }
4359
4940
  const routed = out.routed ?? [];
4360
4941
  const candidates = out.candidates ?? [];
4361
- console.log(import_chalk30.default.bold(`
4942
+ console.log(import_chalk32.default.bold(`
4362
4943
  Routed through APIblaze (${routed.length})`));
4363
- if (!routed.length) console.log(import_chalk30.default.dim(" none yet"));
4364
- for (const r of routed) console.log(` ${import_chalk30.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk30.default.dim(`\u2192 ${r.project_id}`)}`);
4365
- 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(`
4366
4947
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
4367
- 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"));
4368
4949
  for (const c of candidates) {
4369
- 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}`)}`);
4370
4951
  }
4371
4952
  if (candidates.length) {
4372
- console.log(import_chalk30.default.dim(`
4953
+ console.log(import_chalk32.default.dim(`
4373
4954
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
4374
- 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://", "")}`));
4375
4956
  }
4376
4957
  }
4377
4958
  async function runOriginsApprove(origin, opts) {
4378
4959
  if (!loadCredentials()) {
4379
4960
  const cred = loadAnonCred();
4380
4961
  if (!cred) {
4381
- 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."));
4382
4963
  process.exit(1);
4383
4964
  }
4384
- const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
4965
+ const spinner2 = (0, import_ora17.default)(`Approving ${origin} (anonymous)...`).start();
4385
4966
  try {
4386
4967
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
4387
4968
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -4392,7 +4973,7 @@ async function runOriginsApprove(origin, opts) {
4392
4973
  return;
4393
4974
  }
4394
4975
  const { teamId } = await resolveTeam(opts.team);
4395
- const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
4976
+ const spinner = (0, import_ora17.default)(`Approving ${origin}...`).start();
4396
4977
  try {
4397
4978
  const out = await admin({
4398
4979
  method: "POST",
@@ -4409,7 +4990,7 @@ async function runOriginsApprove(origin, opts) {
4409
4990
  }
4410
4991
  async function runOriginsDeny(origin, opts) {
4411
4992
  const { teamId } = await resolveTeam(opts.team);
4412
- const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
4993
+ const spinner = (0, import_ora17.default)(`Dismissing ${origin}...`).start();
4413
4994
  try {
4414
4995
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
4415
4996
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -4420,7 +5001,7 @@ async function runOriginsDeny(origin, opts) {
4420
5001
  }
4421
5002
  async function runOriginsRemove(origin, opts) {
4422
5003
  const { teamId } = await resolveTeam(opts.team);
4423
- 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();
4424
5005
  try {
4425
5006
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
4426
5007
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -4431,8 +5012,9 @@ async function runOriginsRemove(origin, opts) {
4431
5012
  }
4432
5013
 
4433
5014
  // src/commands/op.ts
4434
- var import_chalk31 = __toESM(require("chalk"));
5015
+ var import_chalk33 = __toESM(require("chalk"));
4435
5016
  init_auth();
5017
+ init_trace();
4436
5018
  init_types();
4437
5019
  var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
4438
5020
  var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
@@ -4462,53 +5044,53 @@ async function opCall(call) {
4462
5044
  function printResidue(report, applied) {
4463
5045
  const up = report?.upstash ?? {};
4464
5046
  const fga = report?.fga ?? {};
4465
- console.log(import_chalk31.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
4466
- 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"));
4467
5049
  const orphans = up.orphans ?? [];
4468
- if (orphans.length === 0) console.log(import_chalk31.default.green(" no orphaned keys"));
4469
- for (const o of orphans) console.log(` ${import_chalk31.default.yellow(o.key)} ${import_chalk31.default.dim(`\u2014 ${o.reason}`)}`);
4470
- console.log(import_chalk31.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
4471
- if (up.unknown?.length) console.log(import_chalk31.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
4472
- if (applied) console.log(` ${import_chalk31.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
4473
- for (const e of up.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
4474
- 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"));
4475
5057
  if (applied) {
4476
5058
  const swept = fga?.swept ?? [];
4477
- 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"));
4478
5060
  for (const s of swept) {
4479
5061
  console.log(
4480
- ` ${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`)}`
4481
5063
  );
4482
5064
  }
4483
- 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`));
4484
5066
  } else {
4485
5067
  const fgaOrphans = fga?.orphans ?? [];
4486
- 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"));
4487
5069
  for (const s of fgaOrphans) {
4488
5070
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
4489
- 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)`)}`);
4490
5072
  }
4491
- 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}`));
4492
5074
  }
4493
- 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}`));
4494
5076
  console.log();
4495
5077
  }
4496
5078
  async function runOp(sub, opts = {}) {
4497
5079
  if (!loadCredentials()) {
4498
- 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`."));
4499
5081
  return;
4500
5082
  }
4501
5083
  if (!isOperatorLogin()) {
4502
- 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."));
4503
5085
  return;
4504
5086
  }
4505
5087
  switch (sub) {
4506
5088
  case void 0:
4507
5089
  case "menu": {
4508
- console.log(import_chalk31.default.bold("\nOperator menu"));
4509
- console.log(` ${import_chalk31.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
4510
- console.log(` ${import_chalk31.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk31.default.dim("-y to skip")})`);
4511
- 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
4512
5094
  `);
4513
5095
  return;
4514
5096
  }
@@ -4524,15 +5106,15 @@ async function runOp(sub, opts = {}) {
4524
5106
  const nFga = report?.fga?.orphans?.length ?? 0;
4525
5107
  printResidue(report, false);
4526
5108
  if (nUp + nFga === 0) {
4527
- console.log(import_chalk31.default.green("Nothing to sweep."));
5109
+ console.log(import_chalk33.default.green("Nothing to sweep."));
4528
5110
  return;
4529
5111
  }
4530
5112
  if (!opts.yes) {
4531
5113
  const readline2 = await import("readline/promises");
4532
5114
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
4533
- 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: `));
4534
5116
  rl.close();
4535
- 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."));
4536
5118
  }
4537
5119
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
4538
5120
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -4543,19 +5125,20 @@ async function runOp(sub, opts = {}) {
4543
5125
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
4544
5126
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
4545
5127
  const accounts = data?.accounts ?? [];
4546
- 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."));
4547
5129
  for (const a of accounts) {
4548
5130
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
4549
- 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") : ""}`);
4550
5132
  }
4551
5133
  return;
4552
5134
  }
4553
5135
  default:
4554
- 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.`));
4555
5137
  }
4556
5138
  }
4557
5139
 
4558
5140
  // src/index.ts
5141
+ init_trace();
4559
5142
  var program = new import_commander.Command();
4560
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)");
4561
5144
  program.hook("preAction", () => {
@@ -4606,7 +5189,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
4606
5189
  try {
4607
5190
  const resolved = parseInt(port ?? opts.port, 10);
4608
5191
  if (Number.isNaN(resolved)) {
4609
- console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
5192
+ console.error(import_chalk34.default.red(`Invalid port: ${port ?? opts.port}`));
4610
5193
  process.exit(1);
4611
5194
  }
4612
5195
  await runDev({ port: resolved, captureFile: opts.captureFile });
@@ -4665,6 +5248,8 @@ domain.command("status").description("Check a custom domain's validation status"
4665
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)));
4666
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)));
4667
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)));
4668
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)));
4669
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)));
4670
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)));
@@ -4702,7 +5287,7 @@ function groupedCommandHelp() {
4702
5287
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
4703
5288
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
4704
5289
  }).filter(Boolean).join("\n");
4705
- return `${import_chalk32.default.bold(g.title)}
5290
+ return `${import_chalk34.default.bold(g.title)}
4706
5291
  ${rows}`;
4707
5292
  }).join("\n\n");
4708
5293
  }
@@ -4730,13 +5315,13 @@ Examples:
4730
5315
  `);
4731
5316
  function printError(err) {
4732
5317
  if (err instanceof ApiError) {
4733
- console.error(import_chalk32.default.red(`
5318
+ console.error(import_chalk34.default.red(`
4734
5319
  API error (${err.status}): ${err.message}`));
4735
5320
  } else if (err instanceof Error) {
4736
- console.error(import_chalk32.default.red(`
5321
+ console.error(import_chalk34.default.red(`
4737
5322
  Error: ${err.message}`));
4738
5323
  } else {
4739
- console.error(import_chalk32.default.red("\nUnknown error"));
5324
+ console.error(import_chalk34.default.red("\nUnknown error"));
4740
5325
  }
4741
5326
  }
4742
5327
  program.parse(process.argv);