apiblaze 0.19.1 → 0.19.3

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 +422 -318
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -577,10 +577,10 @@ async function resolveProjectVia(caller, teamId, nameOrId, version2) {
577
577
  summary: `List projects for ${teamId}`
578
578
  });
579
579
  const rows = out?.projects ?? [];
580
- const candidates = rows.filter((r) => r.project_id === nameOrId || r.project_name === nameOrId);
580
+ const candidates = rows.filter((r) => r.project_id === nameOrId || r.project_display_name === nameOrId || r.display_name === nameOrId);
581
581
  if (!candidates.length) {
582
582
  throw new Error(
583
- `Project "${nameOrId}" not found in this team.` + (rows.length ? ` Known: ${rows.map((r) => r.project_name).join(", ")}` : "")
583
+ `Project "${nameOrId}" not found in this team.` + (rows.length ? ` Known: ${rows.map((r) => r.project_id).join(", ")}` : "")
584
584
  );
585
585
  }
586
586
  const chosen = version2 ? candidates.find((r) => r.api_version === version2) : candidates[0];
@@ -590,11 +590,12 @@ async function resolveProjectVia(caller, teamId, nameOrId, version2) {
590
590
  );
591
591
  }
592
592
  return {
593
+ // A proxy is addressed by its project_id (that IS its name); display_name is cosmetic.
593
594
  projectId: chosen.project_id,
594
- projectName: chosen.project_name,
595
+ projectName: chosen.project_display_name ?? chosen.display_name ?? chosen.project_id,
595
596
  apiVersion: chosen.api_version,
596
597
  teamId,
597
- tenant: chosen.tenant ?? chosen.config?.tenant
598
+ tenant: chosen.config?.tenant ?? chosen.config?.default_tenant
598
599
  };
599
600
  }
600
601
  var init_caller = __esm({
@@ -626,15 +627,16 @@ async function runIamToggle(project, state, opts) {
626
627
  if (!tenant2) {
627
628
  throw new Error("Could not resolve the tenant for this project. Pass --tenant <name>.");
628
629
  }
629
- const spinner = (0, import_ora4.default)(`Turning IAM ${state} for tenant ${tenant2}...`).start();
630
+ const spinner = opts.quiet ? null : (0, import_ora4.default)(`Turning IAM ${state} for tenant ${tenant2}...`).start();
630
631
  try {
631
632
  const out = await producer(caller, {
632
633
  method: "PATCH",
633
634
  path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/iam`,
634
- body: { iam_enabled: enabled },
635
+ body: { enabled },
635
636
  summary: `IAM ${state} for tenant ${tenant2}`
636
637
  });
637
- spinner.succeed(`IAM is ${import_chalk9.default.bold(state)} for tenant ${tenant2}.`);
638
+ if (opts.quiet) return;
639
+ spinner?.succeed(`IAM is ${import_chalk9.default.bold(state)} for tenant ${tenant2}.`);
638
640
  if (opts.json) console.log(JSON.stringify(out ?? { iam_enabled: enabled }));
639
641
  else if (enabled) {
640
642
  console.log(import_chalk9.default.dim(" Groups now apply to identified calls. Identify callers with"));
@@ -642,7 +644,7 @@ async function runIamToggle(project, state, opts) {
642
644
  console.log(import_chalk9.default.dim(` \`apiblaze identified ${project} require\` to reject unattributed traffic.`));
643
645
  }
644
646
  } catch (err) {
645
- spinner.fail(`IAM ${state} failed.`);
647
+ spinner?.fail(`IAM ${state} failed.`);
646
648
  throw err;
647
649
  }
648
650
  }
@@ -654,7 +656,7 @@ async function runIdentifiedToggle(project, mode, opts) {
654
656
  const caller = requireCaller();
655
657
  const { teamId } = await resolveActingTeam(caller, opts.team);
656
658
  const proj2 = await resolveProjectVia(caller, teamId, project, opts.apiversion);
657
- const spinner = (0, import_ora4.default)(
659
+ const spinner = opts.quiet ? null : (0, import_ora4.default)(
658
660
  require2 ? "Requiring identified traffic..." : "Allowing unattributed traffic..."
659
661
  ).start();
660
662
  try {
@@ -681,12 +683,13 @@ async function runIdentifiedToggle(project, mode, opts) {
681
683
  body: { requests_policy: merged },
682
684
  summary: `Set identified-traffic policy (${mode}) on ${proj2.projectName}`
683
685
  });
684
- spinner.succeed(
686
+ if (opts.quiet) return;
687
+ spinner?.succeed(
685
688
  require2 ? `${proj2.projectName} now rejects calls that don't identify an end user.` : `${proj2.projectName} accepts unattributed calls again (they pass with no groups).`
686
689
  );
687
690
  if (opts.json) console.log(JSON.stringify(out ?? { identified_traffic_only: require2 }));
688
691
  } catch (err) {
689
- spinner.fail("Policy update failed.");
692
+ spinner?.fail("Policy update failed.");
690
693
  throw err;
691
694
  }
692
695
  }
@@ -736,7 +739,7 @@ async function createTenantRow(teamId) {
736
739
  }
737
740
  async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
738
741
  const { default: inquirer3 } = await import("inquirer");
739
- const base = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
742
+ const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
740
743
  const { provider } = await inquirer3.prompt([{
741
744
  type: "list",
742
745
  name: "provider",
@@ -781,9 +784,9 @@ async function addApiblazeHostedLogin(teamId, tenant2, opts = {}) {
781
784
  }
782
785
  const spinner = (0, import_ora10.default)("Setting up login...").start();
783
786
  try {
784
- const client = await admin({ method: "POST", path: `${base}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
787
+ const client = await admin({ method: "POST", path: `${base2}/app-clients`, body: clientBody, summary: `Create login for ${tenant2}` });
785
788
  const clientId = client?.clientId ?? client?.client_id;
786
- await admin({ method: "POST", path: `${base}/app-clients/${encodeURIComponent(clientId)}/providers`, body: providerBody, summary: `Add ${provider} provider` });
789
+ await admin({ method: "POST", path: `${base2}/app-clients/${encodeURIComponent(clientId)}/providers`, body: providerBody, summary: `Add ${provider} provider` });
787
790
  spinner.succeed(opt.own ? `${opt.label} login is ready.` : "APIblaze-hosted GitHub login is ready.");
788
791
  return true;
789
792
  } catch (err) {
@@ -927,10 +930,10 @@ var init_tenant_pick = __esm({
927
930
 
928
931
  // src/index.ts
929
932
  var import_commander = require("commander");
930
- var import_chalk43 = __toESM(require("chalk"));
933
+ var import_chalk44 = __toESM(require("chalk"));
931
934
 
932
935
  // package.json
933
- var version = "0.19.1";
936
+ var version = "0.19.3";
934
937
 
935
938
  // src/index.ts
936
939
  init_types();
@@ -1183,7 +1186,7 @@ function decodeJwt(token) {
1183
1186
  function maskPath(path8) {
1184
1187
  const q = path8.indexOf("?");
1185
1188
  if (q < 0) return path8;
1186
- const base = path8.slice(0, q);
1189
+ const base2 = path8.slice(0, q);
1187
1190
  const query = path8.slice(q + 1);
1188
1191
  const masked = query.split("&").map((pair) => {
1189
1192
  const eq = pair.indexOf("=");
@@ -1195,7 +1198,7 @@ function maskPath(path8) {
1195
1198
  }
1196
1199
  return pair;
1197
1200
  }).join("&");
1198
- return `${base}?${masked}`;
1201
+ return `${base2}?${masked}`;
1199
1202
  }
1200
1203
  function formatHeaderLines(name, value) {
1201
1204
  const lower = name.toLowerCase();
@@ -1665,7 +1668,22 @@ async function runDev(options) {
1665
1668
  }
1666
1669
  }
1667
1670
  let selectedTargets;
1668
- if (targets.length === 0) {
1671
+ if (options.project) {
1672
+ const want = options.project.toLowerCase();
1673
+ const match = targets.find(
1674
+ (t) => t.projectId?.toLowerCase() === want || t.projectName?.toLowerCase() === want
1675
+ );
1676
+ if (!match) {
1677
+ console.error(import_chalk4.default.red(`No localhost project named "${options.project}" found.`));
1678
+ if (targets.length) {
1679
+ console.error(import_chalk4.default.dim(" Projects pointing at this machine: " + targets.map((t) => t.projectName).join(", ")));
1680
+ } else {
1681
+ console.error(import_chalk4.default.dim(" No projects point at localhost yet \u2014 create one with `apiblaze create --target http://localhost:<port>`."));
1682
+ }
1683
+ process.exit(1);
1684
+ }
1685
+ selectedTargets = [match];
1686
+ } else if (targets.length === 0) {
1669
1687
  const created = await offerAutoCreate(teamId, options.port);
1670
1688
  if (!created) {
1671
1689
  console.log("Set a project's upstream target to localhost or a private IP, then try again.");
@@ -1673,17 +1691,25 @@ async function runDev(options) {
1673
1691
  }
1674
1692
  selectedTargets = [created];
1675
1693
  } else if (targets.length === 1) {
1676
- const { confirmed } = await import_inquirer.default.prompt([{
1677
- type: "confirm",
1678
- name: "confirmed",
1679
- message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
1680
- default: true
1681
- }]);
1682
- if (!confirmed) {
1683
- console.log("Aborted.");
1684
- process.exit(0);
1694
+ if (options.yes || !process.stdin.isTTY) {
1695
+ selectedTargets = targets;
1696
+ } else {
1697
+ const { confirmed } = await import_inquirer.default.prompt([{
1698
+ type: "confirm",
1699
+ name: "confirmed",
1700
+ message: `Found 1 project with an internal target \u2014 tunnel "${import_chalk4.default.bold(targets[0].projectName)}" (${targets[0].tenantName})?`,
1701
+ default: true
1702
+ }]);
1703
+ if (!confirmed) {
1704
+ console.log("Aborted.");
1705
+ process.exit(0);
1706
+ }
1707
+ selectedTargets = targets;
1685
1708
  }
1686
- selectedTargets = targets;
1709
+ } else if (!process.stdin.isTTY) {
1710
+ console.error(import_chalk4.default.red(`Found ${targets.length} projects pointing at localhost \u2014 pass --project <name> to choose one non-interactively.`));
1711
+ console.error(import_chalk4.default.dim(" " + targets.map((t) => t.projectName).join(", ")));
1712
+ process.exit(1);
1687
1713
  } else {
1688
1714
  const ALL = "__all__";
1689
1715
  const { chosen } = await import_inquirer.default.prompt([{
@@ -2010,6 +2036,7 @@ async function runCreate(opts = {}) {
2010
2036
  const adminKey = keys.dev ?? Object.values(keys)[0];
2011
2037
  const proxyUrl = `https://${name}.abz.run/${version2}/dev`;
2012
2038
  const devPortal = result.devPortal ? stripTenantFromPortal(result.devPortal) : void 0;
2039
+ await applyCreateToggles(name, opts);
2013
2040
  if (opts.json) {
2014
2041
  process.stdout.write(JSON.stringify({
2015
2042
  project_id: result.project_id,
@@ -2037,17 +2064,13 @@ async function runCreate(opts = {}) {
2037
2064
  }
2038
2065
  printCurlExample(proxyUrl, auth, adminKey, devPortal);
2039
2066
  console.log();
2040
- await applyCreateToggles(name, opts);
2041
2067
  }
2042
2068
  async function applyCreateToggles(name, opts) {
2043
2069
  if (!opts.identified && !opts.iam) return;
2044
2070
  const { runIamToggle: runIamToggle2, runIdentifiedToggle: runIdentifiedToggle2 } = await Promise.resolve().then(() => (init_iam(), iam_exports));
2045
- if (opts.identified) {
2046
- await runIdentifiedToggle2(name, "require", { team: opts.team, json: opts.json });
2047
- }
2048
- if (opts.iam) {
2049
- await runIamToggle2(name, "on", { team: opts.team, json: opts.json });
2050
- }
2071
+ const toggleOpts = { team: opts.team, json: opts.json, quiet: opts.json };
2072
+ if (opts.identified) await runIdentifiedToggle2(name, "require", toggleOpts);
2073
+ if (opts.iam) await runIamToggle2(name, "on", toggleOpts);
2051
2074
  }
2052
2075
  async function runAnonymousCreate(opts) {
2053
2076
  const interactive = !!process.stdin.isTTY && !opts.json;
@@ -2149,6 +2172,7 @@ async function runAnonymousCreate(opts) {
2149
2172
  const keys = result.api_keys ?? {};
2150
2173
  const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
2151
2174
  const prodEndpoint = (result.endpoints || []).find((e) => e.endsWith("/prod")) || (result.endpoints || [])[0];
2175
+ if (name) await applyCreateToggles(name, opts);
2152
2176
  if (opts.json) {
2153
2177
  process.stdout.write(JSON.stringify({
2154
2178
  project_id: result.project_id,
@@ -2182,7 +2206,6 @@ async function runAnonymousCreate(opts) {
2182
2206
  console.log(` ${import_chalk10.default.bold(result.claim_url)}`);
2183
2207
  }
2184
2208
  console.log();
2185
- if (name) await applyCreateToggles(name, opts);
2186
2209
  }
2187
2210
 
2188
2211
  // src/commands/claim.ts
@@ -3422,19 +3445,19 @@ async function validScopedTenant(teamId, query) {
3422
3445
  }
3423
3446
  async function tenantHome(teamId, tenant2) {
3424
3447
  const { default: inquirer3 } = await import("inquirer");
3425
- const base = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
3448
+ const base2 = `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}`;
3426
3449
  console.log(import_chalk29.default.bold(`
3427
3450
  Tenant ${tenant2}`));
3428
3451
  console.log(import_chalk29.default.dim("Tenant auth/settings are SHARED: changes apply to every proxy this tenant serves.\n"));
3429
3452
  for (; ; ) {
3430
3453
  const spinner = (0, import_ora13.default)("Reading tenant state...").start();
3431
3454
  const [iam, cors, emails, issuers, opaque, clients] = await Promise.all([
3432
- admin({ method: "GET", path: `${base}/iam`, summary: "Read IAM toggle" }).catch(() => null),
3433
- admin({ method: "GET", path: `${base}/cors`, summary: "Read tenant CORS" }).catch(() => null),
3434
- admin({ method: "GET", path: `${base}/admin-emails`, summary: "List consumer-admin emails" }).catch(() => null),
3435
- admin({ method: "GET", path: `${base}/external-issuers`, summary: "List external issuers" }).catch(() => null),
3436
- admin({ method: "GET", path: `${base}/opaque`, summary: "Read opaque validator" }).catch(() => null),
3437
- admin({ method: "GET", path: `${base}/app-clients`, summary: "List app clients" }).catch(() => [])
3455
+ admin({ method: "GET", path: `${base2}/iam`, summary: "Read IAM toggle" }).catch(() => null),
3456
+ admin({ method: "GET", path: `${base2}/cors`, summary: "Read tenant CORS" }).catch(() => null),
3457
+ admin({ method: "GET", path: `${base2}/admin-emails`, summary: "List consumer-admin emails" }).catch(() => null),
3458
+ admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List external issuers" }).catch(() => null),
3459
+ admin({ method: "GET", path: `${base2}/opaque`, summary: "Read opaque validator" }).catch(() => null),
3460
+ admin({ method: "GET", path: `${base2}/app-clients`, summary: "List app clients" }).catch(() => [])
3438
3461
  ]).finally(() => spinner.stop());
3439
3462
  const nEmails = (emails?.admin_emails ?? []).length;
3440
3463
  const nIssuers = (issuers?.external_issuers ?? []).length;
@@ -3460,11 +3483,11 @@ Tenant ${tenant2}`));
3460
3483
  case "back":
3461
3484
  return;
3462
3485
  case "login":
3463
- await loginMethodsMenu(teamId, tenant2, base);
3486
+ await loginMethodsMenu(teamId, tenant2, base2);
3464
3487
  break;
3465
3488
  case "iam": {
3466
3489
  const { v } = await inquirer3.prompt([{ type: "confirm", name: "v", message: "Enable Users & groups?", default: !!iam?.iam_enabled }]);
3467
- await admin({ method: "PATCH", path: `${base}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
3490
+ await admin({ method: "PATCH", path: `${base2}/iam`, body: { enabled: v }, summary: `IAM enforcement \u2192 ${v ? "on" : "off"}` });
3468
3491
  console.log(import_chalk29.default.green(` Users & groups ${v ? "enabled" : "disabled"}.`));
3469
3492
  break;
3470
3493
  }
@@ -3481,24 +3504,24 @@ Tenant ${tenant2}`));
3481
3504
  console.log(import_chalk29.default.yellow(" Not valid JSON \u2014 unchanged."));
3482
3505
  break;
3483
3506
  }
3484
- await admin({ method: "PUT", path: `${base}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
3507
+ await admin({ method: "PUT", path: `${base2}/cors`, body: { cors: parsed }, summary: "Set tenant CORS" });
3485
3508
  console.log(import_chalk29.default.green(" CORS updated."));
3486
3509
  break;
3487
3510
  }
3488
3511
  case "emails":
3489
- await emailsMenu(base, emails?.admin_emails ?? []);
3512
+ await emailsMenu(base2, emails?.admin_emails ?? []);
3490
3513
  break;
3491
3514
  }
3492
3515
  }
3493
3516
  }
3494
- async function loginMethodsMenu(teamId, tenant2, base) {
3517
+ async function loginMethodsMenu(teamId, tenant2, base2) {
3495
3518
  const { default: inquirer3 } = await import("inquirer");
3496
3519
  for (; ; ) {
3497
3520
  const spinner = (0, import_ora13.default)("Loading login methods...").start();
3498
3521
  const [rawClients, rawIssuers, rawOpaque] = await Promise.all([
3499
- admin({ method: "GET", path: `${base}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
3500
- admin({ method: "GET", path: `${base}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
3501
- admin({ method: "GET", path: `${base}/opaque`, summary: "Read opaque login" }).catch(() => null)
3522
+ admin({ method: "GET", path: `${base2}/app-clients`, summary: "List APIblaze-hosted logins" }).catch(() => []),
3523
+ admin({ method: "GET", path: `${base2}/external-issuers`, summary: "List your-own-JWT logins" }).catch(() => null),
3524
+ admin({ method: "GET", path: `${base2}/opaque`, summary: "Read opaque login" }).catch(() => null)
3502
3525
  ]).finally(() => spinner.stop());
3503
3526
  const appClients = Array.isArray(rawClients) ? rawClients : [];
3504
3527
  const issuers = rawIssuers?.external_issuers ?? [];
@@ -3529,24 +3552,24 @@ async function loginMethodsMenu(teamId, tenant2, base) {
3529
3552
  }]);
3530
3553
  if (pick2.kind === "back") return;
3531
3554
  if (pick2.kind === "add") {
3532
- await addLoginMethod(teamId, tenant2, base);
3555
+ await addLoginMethod(teamId, tenant2, base2);
3533
3556
  continue;
3534
3557
  }
3535
3558
  if (pick2.kind === "client") {
3536
- await clientHome(base, pick2.item);
3559
+ await clientHome(base2, pick2.item);
3537
3560
  continue;
3538
3561
  }
3539
3562
  if (pick2.kind === "issuer") {
3540
- await issuerHome(base, pick2.item);
3563
+ await issuerHome(base2, pick2.item);
3541
3564
  continue;
3542
3565
  }
3543
3566
  if (pick2.kind === "opaque") {
3544
- await opaqueHome(base, pick2.item);
3567
+ await opaqueHome(base2, pick2.item);
3545
3568
  continue;
3546
3569
  }
3547
3570
  }
3548
3571
  }
3549
- async function addLoginMethod(teamId, tenant2, base) {
3572
+ async function addLoginMethod(teamId, tenant2, base2) {
3550
3573
  const { default: inquirer3 } = await import("inquirer");
3551
3574
  const { kind } = await inquirer3.prompt([{
3552
3575
  type: "list",
@@ -3565,9 +3588,9 @@ async function addLoginMethod(teamId, tenant2, base) {
3565
3588
  const { addApiblazeHostedLogin: addApiblazeHostedLogin2 } = await Promise.resolve().then(() => (init_tenant_create(), tenant_create_exports));
3566
3589
  await addApiblazeHostedLogin2(teamId, tenant2);
3567
3590
  } else if (kind === "jwt") {
3568
- await addIssuer(base);
3591
+ await addIssuer(base2);
3569
3592
  } else {
3570
- await setOpaque(base, null);
3593
+ await setOpaque(base2, null);
3571
3594
  }
3572
3595
  }
3573
3596
  function safeJson(s) {
@@ -3577,7 +3600,7 @@ function safeJson(s) {
3577
3600
  return void 0;
3578
3601
  }
3579
3602
  }
3580
- async function emailsMenu(base, emails) {
3603
+ async function emailsMenu(base2, emails) {
3581
3604
  const { default: inquirer3 } = await import("inquirer");
3582
3605
  console.log();
3583
3606
  if (!emails.length) console.log(import_chalk29.default.dim(" No consumer-admin emails."));
@@ -3595,7 +3618,7 @@ async function emailsMenu(base, emails) {
3595
3618
  if (act === "back") return;
3596
3619
  if (act === "add") {
3597
3620
  const { email } = await inquirer3.prompt([{ type: "input", name: "email", message: "Email:", validate: (s) => /.+@.+\..+/.test(s) || "not an email" }]);
3598
- await admin({ method: "POST", path: `${base}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
3621
+ await admin({ method: "POST", path: `${base2}/admin-emails`, body: { email }, summary: `Add consumer-admin ${email}` });
3599
3622
  console.log(import_chalk29.default.green(` ${email} added.`));
3600
3623
  } else {
3601
3624
  const { e } = await inquirer3.prompt([{
@@ -3605,11 +3628,11 @@ async function emailsMenu(base, emails) {
3605
3628
  choices: [...emails.map((x) => ({ name: x.email ?? String(x), value: x.email ?? String(x) })), { name: "\u2190 Back", value: null }]
3606
3629
  }]);
3607
3630
  if (!e) return;
3608
- await admin({ method: "DELETE", path: `${base}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
3631
+ await admin({ method: "DELETE", path: `${base2}/admin-emails/${encodeURIComponent(e)}`, summary: `Remove consumer-admin ${e}` });
3609
3632
  console.log(import_chalk29.default.green(` ${e} removed.`));
3610
3633
  }
3611
3634
  }
3612
- async function addIssuer(base) {
3635
+ async function addIssuer(base2) {
3613
3636
  const { default: inquirer3 } = await import("inquirer");
3614
3637
  const a = await inquirer3.prompt([
3615
3638
  { type: "input", name: "iss", message: "Issuer URL (iss):", validate: (s) => !!s.trim() || "required" },
@@ -3623,13 +3646,13 @@ async function addIssuer(base) {
3623
3646
  const claim = a.sem === "extract_from_claim" ? (await inquirer3.prompt([{ type: "input", name: "c", message: "Claim name:", validate: (s) => !!s.trim() || "required" }])).c : void 0;
3624
3647
  await admin({
3625
3648
  method: "POST",
3626
- path: `${base}/external-issuers`,
3649
+ path: `${base2}/external-issuers`,
3627
3650
  body: { iss: a.iss.trim(), aud: a.aud.trim(), jwks_url: a.jwks.trim() || null, sub_semantics: a.sem, ...claim ? { claim_name: claim } : {} },
3628
3651
  summary: `Add external issuer ${a.iss.trim()}`
3629
3652
  });
3630
3653
  console.log(import_chalk29.default.green(" JWT login method saved."));
3631
3654
  }
3632
- async function issuerHome(base, issuer) {
3655
+ async function issuerHome(base2, issuer) {
3633
3656
  const { default: inquirer3 } = await import("inquirer");
3634
3657
  console.log(`
3635
3658
  ${import_chalk29.default.bold(issuer.iss)} ${import_chalk29.default.dim(`aud=${issuer.aud} \xB7 ${issuer.sub_semantics ?? ""}`)}`);
@@ -3645,28 +3668,28 @@ async function issuerHome(base, issuer) {
3645
3668
  }]);
3646
3669
  if (act === "back") return;
3647
3670
  if (act === "edit") {
3648
- await addIssuer(base);
3671
+ await addIssuer(base2);
3649
3672
  return;
3650
3673
  }
3651
3674
  const { sure } = await inquirer3.prompt([{ type: "confirm", name: "sure", message: `Delete the JWT login method for ${issuer.iss}? Consumers using it can no longer sign in.`, default: false }]);
3652
3675
  if (!sure) return;
3653
3676
  await admin({
3654
3677
  method: "DELETE",
3655
- path: `${base}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
3678
+ path: `${base2}/external-issuers?iss=${encodeURIComponent(issuer.iss)}&aud=${encodeURIComponent(issuer.aud)}`,
3656
3679
  summary: `Delete issuer ${issuer.iss}`
3657
3680
  });
3658
3681
  console.log(import_chalk29.default.green(" Deleted."));
3659
3682
  }
3660
- async function setOpaque(base, cur) {
3683
+ async function setOpaque(base2, cur) {
3661
3684
  const { default: inquirer3 } = await import("inquirer");
3662
3685
  const a = await inquirer3.prompt([
3663
3686
  { type: "input", name: "endpoint", message: "Introspection endpoint (https):", default: cur?.endpoint, validate: (s) => s.startsWith("https://") || "must be https" },
3664
3687
  { type: "list", name: "method", message: "HTTP method:", choices: ["GET", "POST"], default: cur?.method ?? "GET" }
3665
3688
  ]);
3666
- await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
3689
+ await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: { endpoint: a.endpoint, method: a.method } }, summary: "Set opaque validator" });
3667
3690
  console.log(import_chalk29.default.green(" Opaque login method set."));
3668
3691
  }
3669
- async function opaqueHome(base, cur) {
3692
+ async function opaqueHome(base2, cur) {
3670
3693
  const { default: inquirer3 } = await import("inquirer");
3671
3694
  console.log(`
3672
3695
  ${import_chalk29.default.bold(cur.endpoint)} ${import_chalk29.default.dim(cur.method ?? "GET")}`);
@@ -3682,16 +3705,16 @@ async function opaqueHome(base, cur) {
3682
3705
  }]);
3683
3706
  if (act === "back") return;
3684
3707
  if (act === "edit") {
3685
- await setOpaque(base, cur);
3708
+ await setOpaque(base2, cur);
3686
3709
  return;
3687
3710
  }
3688
- await admin({ method: "PUT", path: `${base}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
3711
+ await admin({ method: "PUT", path: `${base2}/opaque`, body: { opaque: null }, summary: "Clear opaque validator" });
3689
3712
  console.log(import_chalk29.default.green(" Deleted."));
3690
3713
  }
3691
- async function clientHome(base, summary) {
3714
+ async function clientHome(base2, summary) {
3692
3715
  const { default: inquirer3 } = await import("inquirer");
3693
3716
  const id = summary.clientId ?? summary.client_id;
3694
- const cBase = `${base}/app-clients/${encodeURIComponent(id)}`;
3717
+ const cBase = `${base2}/app-clients/${encodeURIComponent(id)}`;
3695
3718
  for (; ; ) {
3696
3719
  const spinner = (0, import_ora13.default)("Reading app client...").start();
3697
3720
  const c = await admin({ method: "GET", path: cBase, summary: `Read app client ${id}` }).catch(() => summary);
@@ -4839,9 +4862,9 @@ function showCondition(cond) {
4839
4862
  }
4840
4863
  async function transformsMenu(proj2) {
4841
4864
  const { default: inquirer3 } = await import("inquirer");
4842
- const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
4865
+ const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
4843
4866
  for (; ; ) {
4844
- const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
4867
+ const out = await admin({ method: "GET", path: base2, summary: "List transform rules" });
4845
4868
  const rules = out?.rules ?? [];
4846
4869
  console.log();
4847
4870
  if (!rules.length) console.log(import_chalk33.default.dim(" No transform rules yet."));
@@ -4877,7 +4900,7 @@ async function transformsMenu(proj2) {
4877
4900
  console.log(import_chalk33.default.yellow(" Not a JSON object \u2014 skipped."));
4878
4901
  continue;
4879
4902
  }
4880
- await admin({ method: "POST", path: base, body, summary: "Create transform rule (raw JSON)" });
4903
+ await admin({ method: "POST", path: base2, body, summary: "Create transform rule (raw JSON)" });
4881
4904
  console.log(import_chalk33.default.green(" Rule created."));
4882
4905
  continue;
4883
4906
  }
@@ -4927,7 +4950,7 @@ async function transformsMenu(proj2) {
4927
4950
  try {
4928
4951
  await admin({
4929
4952
  method: "POST",
4930
- path: base,
4953
+ path: base2,
4931
4954
  body: { name: ans.name, phase: ans.phase, enabled: true, action: action2, ...condition ? { condition } : {} },
4932
4955
  summary: `Create transform "${ans.name}"`
4933
4956
  });
@@ -4946,10 +4969,10 @@ async function transformsMenu(proj2) {
4946
4969
  if (!rule) continue;
4947
4970
  if (act === "toggle") {
4948
4971
  const flipped = { ...rule, enabled: rule.enabled === false };
4949
- await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
4972
+ await admin({ method: "PUT", path: `${base2}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
4950
4973
  console.log(import_chalk33.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
4951
4974
  } else {
4952
- await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
4975
+ await admin({ method: "DELETE", path: `${base2}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
4953
4976
  console.log(import_chalk33.default.green(` ${rule.name} deleted.`));
4954
4977
  }
4955
4978
  }
@@ -4957,9 +4980,9 @@ async function transformsMenu(proj2) {
4957
4980
  }
4958
4981
  async function mappingsMenu(proj2) {
4959
4982
  const { default: inquirer3 } = await import("inquirer");
4960
- const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
4983
+ const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
4961
4984
  for (; ; ) {
4962
- const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
4985
+ const out = await admin({ method: "GET", path: base2, summary: "List mapping tables" });
4963
4986
  const tables = out?.mappings ?? out?.tables ?? [];
4964
4987
  console.log();
4965
4988
  if (!tables.length) console.log(import_chalk33.default.dim(" No mapping tables yet."));
@@ -4987,7 +5010,7 @@ async function mappingsMenu(proj2) {
4987
5010
  console.log(import_chalk33.default.yellow(" Entries must be a JSON array \u2014 not created."));
4988
5011
  continue;
4989
5012
  }
4990
- await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
5013
+ await admin({ method: "POST", path: base2, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
4991
5014
  console.log(import_chalk33.default.green(` Table "${a.name}" created.`));
4992
5015
  } else {
4993
5016
  const { table } = await inquirer3.prompt([{
@@ -4997,16 +5020,16 @@ async function mappingsMenu(proj2) {
4997
5020
  choices: [...tables.map((t) => ({ name: t.name, value: t })), { name: "\u2190 Back", value: null }]
4998
5021
  }]);
4999
5022
  if (!table) continue;
5000
- await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
5023
+ await admin({ method: "DELETE", path: `${base2}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
5001
5024
  console.log(import_chalk33.default.green(` ${table.name} deleted.`));
5002
5025
  }
5003
5026
  }
5004
5027
  }
5005
5028
  async function tenantsMenu(proj2, opts) {
5006
5029
  const { default: inquirer3 } = await import("inquirer");
5007
- const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
5030
+ const base2 = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
5008
5031
  for (; ; ) {
5009
- const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
5032
+ const out = await admin({ method: "GET", path: base2, summary: "List attached tenants" });
5010
5033
  const tenants = out?.tenants ?? [];
5011
5034
  console.log();
5012
5035
  if (!tenants.length) console.log(import_chalk33.default.dim(" No tenants attached (consumers use the default tenant)."));
@@ -5038,7 +5061,7 @@ async function tenantsMenu(proj2, opts) {
5038
5061
  choices: [...tenants.map((x) => ({ name: x.tenant_name ?? x.name, value: x })), { name: "\u2190 Back", value: null }]
5039
5062
  }]);
5040
5063
  if (!t) continue;
5041
- await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
5064
+ await admin({ method: "DELETE", path: `${base2}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
5042
5065
  console.log(import_chalk33.default.green(` Detached ${t.tenant_name ?? t.name}.`));
5043
5066
  }
5044
5067
  }
@@ -5418,9 +5441,85 @@ async function runKeyRevoke(keyId, opts) {
5418
5441
  // src/index.ts
5419
5442
  init_iam();
5420
5443
 
5421
- // src/commands/preapprove.ts
5444
+ // src/commands/admins.ts
5422
5445
  var import_chalk36 = __toESM(require("chalk"));
5423
5446
  var import_ora19 = __toESM(require("ora"));
5447
+ init_caller();
5448
+ function requireTenant(opts) {
5449
+ const t = (opts.tenant ?? "").trim();
5450
+ if (!t) {
5451
+ throw new Error("--tenant <slug> is required (e.g. --tenant nino).");
5452
+ }
5453
+ return t;
5454
+ }
5455
+ function base(teamId, tenant2) {
5456
+ return `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/admin-emails`;
5457
+ }
5458
+ async function runAdminsList(opts) {
5459
+ const caller = requireCaller();
5460
+ const { teamId } = await resolveActingTeam(caller, opts.team);
5461
+ const tenant2 = requireTenant(opts);
5462
+ const out = await producer(caller, {
5463
+ method: "GET",
5464
+ path: base(teamId, tenant2),
5465
+ summary: `List admins for tenant ${tenant2}`
5466
+ });
5467
+ const admins2 = out?.admin_emails ?? out?.admins ?? [];
5468
+ if (opts.json) {
5469
+ console.log(JSON.stringify(admins2));
5470
+ return;
5471
+ }
5472
+ if (!admins2.length) {
5473
+ console.log(import_chalk36.default.yellow(`No admins for tenant ${tenant2} yet.`));
5474
+ return;
5475
+ }
5476
+ for (const a of admins2) {
5477
+ const email = typeof a === "string" ? a : a.email;
5478
+ const flags = [a.active === false ? "pending" : null, a.pinned ? "pinned" : null].filter(Boolean).join(", ");
5479
+ console.log(` ${import_chalk36.default.bold(email)}${flags ? import_chalk36.default.dim(` (${flags})`) : ""}`);
5480
+ }
5481
+ }
5482
+ async function runAdminsAdd(email, opts) {
5483
+ const caller = requireCaller();
5484
+ const { teamId } = await resolveActingTeam(caller, opts.team);
5485
+ const tenant2 = requireTenant(opts);
5486
+ const spinner = opts.json ? null : (0, import_ora19.default)(`Adding ${email} as admin of ${tenant2}...`).start();
5487
+ try {
5488
+ const out = await producer(caller, {
5489
+ method: "POST",
5490
+ path: base(teamId, tenant2),
5491
+ body: { email },
5492
+ summary: `Add ${email} to tenant ${tenant2} admins`
5493
+ });
5494
+ spinner?.succeed(`${import_chalk36.default.bold(email)} is now an admin of ${import_chalk36.default.bold(tenant2)}.`);
5495
+ if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
5496
+ else console.log(import_chalk36.default.dim(" Reload the Users & Groups widget \u2014 access flips from \u201Cpending\u201D to ready."));
5497
+ } catch (err) {
5498
+ spinner?.fail("Add failed.");
5499
+ throw err;
5500
+ }
5501
+ }
5502
+ async function runAdminsRemove(email, opts) {
5503
+ const caller = requireCaller();
5504
+ const { teamId } = await resolveActingTeam(caller, opts.team);
5505
+ const tenant2 = requireTenant(opts);
5506
+ const spinner = opts.json ? null : (0, import_ora19.default)(`Removing ${email} from ${tenant2} admins...`).start();
5507
+ try {
5508
+ await producer(caller, {
5509
+ method: "DELETE",
5510
+ path: `${base(teamId, tenant2)}/${encodeURIComponent(email)}`,
5511
+ summary: `Remove ${email} from tenant ${tenant2} admins`
5512
+ });
5513
+ spinner?.succeed(`Removed ${import_chalk36.default.bold(email)} from ${import_chalk36.default.bold(tenant2)} admins.`);
5514
+ } catch (err) {
5515
+ spinner?.fail("Remove failed.");
5516
+ throw err;
5517
+ }
5518
+ }
5519
+
5520
+ // src/commands/preapprove.ts
5521
+ var import_chalk37 = __toESM(require("chalk"));
5522
+ var import_ora20 = __toESM(require("ora"));
5424
5523
 
5425
5524
  // src/lib/preapproval.ts
5426
5525
  init_auth();
@@ -5484,14 +5583,14 @@ async function runPreapprove(who, opts) {
5484
5583
  const rules = await listPreapprovalRules(tenant2);
5485
5584
  if (opts.json) return void console.log(JSON.stringify(rules, null, 2));
5486
5585
  if (!rules.length) {
5487
- console.log(import_chalk36.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
5488
- console.log(import_chalk36.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
5586
+ console.log(import_chalk37.default.dim(`No pre-approval rules in ${tenant2}. Anyone who signs in can access (unless access is restricted).`));
5587
+ console.log(import_chalk37.default.dim(`Add one: apiblaze preapprove someone@acme.com`));
5489
5588
  return;
5490
5589
  }
5491
- console.log(import_chalk36.default.dim(`Pre-approved for ${import_chalk36.default.bold(tenant2)}:`));
5590
+ console.log(import_chalk37.default.dim(`Pre-approved for ${import_chalk37.default.bold(tenant2)}:`));
5492
5591
  for (const r of rules) {
5493
- const tag = r.kind === "domain" ? import_chalk36.default.cyan("@" + r.value + " (whole domain)") : r.value;
5494
- const grp = r.groups?.length ? import_chalk36.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
5592
+ const tag = r.kind === "domain" ? import_chalk37.default.cyan("@" + r.value + " (whole domain)") : r.value;
5593
+ const grp = r.groups?.length ? import_chalk37.default.dim(` \u2192 groups: ${r.groups.join(", ")}`) : "";
5495
5594
  console.log(` ${tag}${grp}`);
5496
5595
  }
5497
5596
  return;
@@ -5500,19 +5599,19 @@ async function runPreapprove(who, opts) {
5500
5599
  throw new Error("Who? Pass an email or a domain: `apiblaze preapprove someone@acme.com` (or `apiblaze preapprove --list`).");
5501
5600
  }
5502
5601
  if (opts.remove) {
5503
- const spinner2 = (0, import_ora19.default)(`Removing ${who} from ${tenant2}\u2026`).start();
5602
+ const spinner2 = (0, import_ora20.default)(`Removing ${who} from ${tenant2}\u2026`).start();
5504
5603
  const { removed, value } = await removePreapprovalRule(tenant2, who);
5505
- if (removed) spinner2.succeed(`${import_chalk36.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
5506
- else spinner2.warn(`No pre-approval rule for ${import_chalk36.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
5604
+ if (removed) spinner2.succeed(`${import_chalk37.default.bold(value)} is no longer pre-approved for ${tenant2}.`);
5605
+ else spinner2.warn(`No pre-approval rule for ${import_chalk37.default.bold(value)} in ${tenant2} \u2014 nothing to remove.`);
5507
5606
  return;
5508
5607
  }
5509
- const spinner = (0, import_ora19.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
5608
+ const spinner = (0, import_ora20.default)(`Pre-approving ${who} for ${tenant2}\u2026`).start();
5510
5609
  try {
5511
5610
  const { kind, value } = await addPreapprovalRule(tenant2, who, opts.group);
5512
5611
  const what = kind === "domain" ? `Anyone @${value}` : value;
5513
- spinner.succeed(`${import_chalk36.default.bold(what)} can now sign in to ${import_chalk36.default.bold(tenant2)}.`);
5514
- if (opts.group?.length) console.log(import_chalk36.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
5515
- console.log(import_chalk36.default.dim(` See the full list: apiblaze preapprove --list`));
5612
+ spinner.succeed(`${import_chalk37.default.bold(what)} can now sign in to ${import_chalk37.default.bold(tenant2)}.`);
5613
+ if (opts.group?.length) console.log(import_chalk37.default.dim(` They'll join group(s) ${opts.group.join(", ")} on first sign-in.`));
5614
+ console.log(import_chalk37.default.dim(` See the full list: apiblaze preapprove --list`));
5516
5615
  } catch (err) {
5517
5616
  spinner.fail("Could not add the rule.");
5518
5617
  throw err;
@@ -5523,8 +5622,8 @@ async function runPreapprove(who, opts) {
5523
5622
  var fs9 = __toESM(require("fs"));
5524
5623
  var path6 = __toESM(require("path"));
5525
5624
  var crypto2 = __toESM(require("crypto"));
5526
- var import_chalk38 = __toESM(require("chalk"));
5527
- var import_ora20 = __toESM(require("ora"));
5625
+ var import_chalk39 = __toESM(require("chalk"));
5626
+ var import_ora21 = __toESM(require("ora"));
5528
5627
  var import_yaml = require("yaml");
5529
5628
  init_auth();
5530
5629
  init_anon_cred();
@@ -5534,7 +5633,7 @@ init_admin();
5534
5633
  // src/commands/llm.ts
5535
5634
  var fs8 = __toESM(require("fs"));
5536
5635
  var path5 = __toESM(require("path"));
5537
- var import_chalk37 = __toESM(require("chalk"));
5636
+ var import_chalk38 = __toESM(require("chalk"));
5538
5637
  var import_inquirer2 = __toESM(require("inquirer"));
5539
5638
  init_auth();
5540
5639
  var LLM_PATH = path5.join(getApiblazeDir(), "llm.json");
@@ -5578,27 +5677,27 @@ async function runLlmSetKey(keyArg, opts) {
5578
5677
  }
5579
5678
  const existing = loadLlmConfig();
5580
5679
  saveLlmConfig({ key, provider, model: opts.model ?? existing?.model });
5581
- console.log(`${import_chalk37.default.green("\u2713")} Saved ${import_chalk37.default.bold(provider)} key ${maskSecret(key)}`);
5582
- console.log(import_chalk37.default.gray(` Stored locally at ${LLM_PATH} (0600) \u2014 never sent to APIblaze except per chat turn.`));
5583
- if (opts.model) console.log(import_chalk37.default.gray(` Model: ${opts.model}`));
5680
+ console.log(`${import_chalk38.default.green("\u2713")} Saved ${import_chalk38.default.bold(provider)} key ${maskSecret(key)}`);
5681
+ console.log(import_chalk38.default.gray(` Stored locally at ${LLM_PATH} (0600) \u2014 never sent to APIblaze except per chat turn.`));
5682
+ if (opts.model) console.log(import_chalk38.default.gray(` Model: ${opts.model}`));
5584
5683
  }
5585
5684
  async function runLlmShow() {
5586
5685
  const cfg = loadLlmConfig();
5587
5686
  if (!cfg) {
5588
- console.log(import_chalk37.default.gray("No LLM key set. `apiblaze llm set-key sk-...` to add one (optional \u2014 chat works without it)."));
5687
+ console.log(import_chalk38.default.gray("No LLM key set. `apiblaze llm set-key sk-...` to add one (optional \u2014 chat works without it)."));
5589
5688
  return;
5590
5689
  }
5591
- console.log(`Provider: ${import_chalk37.default.bold(cfg.provider)}`);
5690
+ console.log(`Provider: ${import_chalk38.default.bold(cfg.provider)}`);
5592
5691
  console.log(`Key: ${maskSecret(cfg.key)}`);
5593
5692
  if (cfg.model) console.log(`Model: ${cfg.model}`);
5594
- console.log(import_chalk37.default.gray(`Stored at ${LLM_PATH}`));
5693
+ console.log(import_chalk38.default.gray(`Stored at ${LLM_PATH}`));
5595
5694
  }
5596
5695
  async function runLlmClearKey() {
5597
5696
  try {
5598
5697
  fs8.unlinkSync(LLM_PATH);
5599
- console.log(`${import_chalk37.default.green("\u2713")} Removed local LLM key.`);
5698
+ console.log(`${import_chalk38.default.green("\u2713")} Removed local LLM key.`);
5600
5699
  } catch {
5601
- console.log(import_chalk37.default.gray("No LLM key was set."));
5700
+ console.log(import_chalk38.default.gray("No LLM key was set."));
5602
5701
  }
5603
5702
  }
5604
5703
 
@@ -5606,9 +5705,9 @@ async function runLlmClearKey() {
5606
5705
  init_trace();
5607
5706
  init_types();
5608
5707
  function fail4(message, hint) {
5609
- console.error(import_chalk38.default.red(`
5708
+ console.error(import_chalk39.default.red(`
5610
5709
  Error: ${message}`));
5611
- if (hint) console.error(import_chalk38.default.dim(hint));
5710
+ if (hint) console.error(import_chalk39.default.dim(hint));
5612
5711
  process.exit(1);
5613
5712
  }
5614
5713
  function normalizeName2(raw) {
@@ -5650,9 +5749,9 @@ async function fetchText(url) {
5650
5749
  }
5651
5750
  }
5652
5751
  async function discoverSpec(target) {
5653
- const base = target.replace(/\/+$/, "");
5752
+ const base2 = target.replace(/\/+$/, "");
5654
5753
  for (const suffix of ["/openapi.json", "/openapi.yaml", "/swagger.json"]) {
5655
- const url = base + suffix;
5754
+ const url = base2 + suffix;
5656
5755
  const text = await fetchText(url);
5657
5756
  if (text) {
5658
5757
  try {
@@ -5756,7 +5855,7 @@ async function resolveTargetAuth(spec2, opts) {
5756
5855
  "Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
5757
5856
  );
5758
5857
  }
5759
- if (sawOAuth) console.log(import_chalk38.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
5858
+ if (sawOAuth) console.log(import_chalk39.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
5760
5859
  return null;
5761
5860
  }
5762
5861
  if (candidates.length === 1 && !noneAllowed) return candidates[0];
@@ -5821,28 +5920,28 @@ async function provision(spec2, target, opts) {
5821
5920
  const loggedIn = !!loadCredentials();
5822
5921
  const anon = !loggedIn;
5823
5922
  const salt = () => Math.random().toString(36).slice(2, 6);
5824
- let base = opts.name ? normalizeName2(opts.name) : "";
5825
- if (!base) {
5923
+ let base2 = opts.name ? normalizeName2(opts.name) : "";
5924
+ if (!base2) {
5826
5925
  try {
5827
5926
  const host = new URL(target).hostname;
5828
- base = normalizeName2(host.split(".")[0]);
5829
- if (base.length < 3) base = normalizeName2(host);
5927
+ base2 = normalizeName2(host.split(".")[0]);
5928
+ if (base2.length < 3) base2 = normalizeName2(host);
5830
5929
  } catch {
5831
5930
  }
5832
5931
  }
5833
- if (!base && spec2.info && typeof spec2.info.title === "string") base = normalizeName2(spec2.info.title);
5834
- if (!base || base.length < 3) base = "apichat";
5835
- let name = opts.name ? base : `${base}${salt()}`;
5932
+ if (!base2 && spec2.info && typeof spec2.info.title === "string") base2 = normalizeName2(spec2.info.title);
5933
+ if (!base2 || base2.length < 3) base2 = "apichat";
5934
+ let name = opts.name ? base2 : `${base2}${salt()}`;
5836
5935
  const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
5837
5936
  if (anon && opts.access === "invite") {
5838
- console.log(import_chalk38.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
5937
+ console.log(import_chalk39.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
5839
5938
  }
5840
5939
  const DUAL_AUTH = {
5841
5940
  mode: "authenticate",
5842
5941
  methods: ["api_key", "jwt"],
5843
5942
  ...access === "invite" ? { preapproved_users_only: true } : {}
5844
5943
  };
5845
- const spinner = (0, import_ora20.default)("Provisioning an api_key proxy...").start();
5944
+ const spinner = (0, import_ora21.default)("Provisioning an api_key proxy...").start();
5846
5945
  let result;
5847
5946
  for (let attempt = 0; attempt < 4; attempt++) {
5848
5947
  try {
@@ -5874,13 +5973,13 @@ async function provision(spec2, target, opts) {
5874
5973
  if (result.cp_key && result.team_id) saveAnonCred(result.cp_key, result.team_id, result.claim_code);
5875
5974
  }
5876
5975
  }
5877
- spinner.succeed(`Proxy provisioned${name !== base ? ` as "${name}"` : ""}.`);
5976
+ spinner.succeed(`Proxy provisioned${name !== base2 ? ` as "${name}"` : ""}.`);
5878
5977
  break;
5879
5978
  } catch (err) {
5880
5979
  const status = err instanceof ApiError ? err.status : void 0;
5881
5980
  const collision = err instanceof ApiError && (err.status === 409 || /exist|taken|available/i.test(err.message));
5882
5981
  if (collision && attempt < 3) {
5883
- name = `${base}${salt()}`;
5982
+ name = `${base2}${salt()}`;
5884
5983
  continue;
5885
5984
  }
5886
5985
  if (status === 401) {
@@ -5928,14 +6027,14 @@ async function provision(spec2, target, opts) {
5928
6027
  try {
5929
6028
  await addPreapprovalRule(tenant2, email);
5930
6029
  } catch {
5931
- console.log(import_chalk38.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
6030
+ console.log(import_chalk39.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
5932
6031
  }
5933
6032
  }
5934
6033
  }
5935
6034
  return { projectId, version: version2, environment, dpKey, mcpHost, proxyUrl, anon, access, tenant: tenant2 };
5936
6035
  }
5937
6036
  async function writeTargetAuth(p, auth, secret) {
5938
- const spinner = (0, import_ora20.default)("Storing target credentials (encrypted)...").start();
6037
+ const spinner = (0, import_ora21.default)("Storing target credentials (encrypted)...").start();
5939
6038
  try {
5940
6039
  await cpPost(
5941
6040
  p.anon,
@@ -5957,7 +6056,7 @@ async function writeTargetAuth(p, auth, secret) {
5957
6056
  }
5958
6057
  }
5959
6058
  async function uploadSpec(p, specText, opts) {
5960
- const spinner = (0, import_ora20.default)("Uploading the spec...").start();
6059
+ const spinner = (0, import_ora21.default)("Uploading the spec...").start();
5961
6060
  let out;
5962
6061
  try {
5963
6062
  out = await cpPost(
@@ -5972,7 +6071,7 @@ async function uploadSpec(p, specText, opts) {
5972
6071
  throw err;
5973
6072
  }
5974
6073
  if (out && out.reused === true) {
5975
- console.log(import_chalk38.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
6074
+ console.log(import_chalk39.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
5976
6075
  } else if (out && out.changed === true && out.previous_spec_hash) {
5977
6076
  const interactive = !!process.stdin.isTTY && !opts.yes;
5978
6077
  if (interactive) {
@@ -5980,12 +6079,12 @@ async function uploadSpec(p, specText, opts) {
5980
6079
  const { go } = await inquirer3.prompt([
5981
6080
  { type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
5982
6081
  ]);
5983
- if (!go) console.log(import_chalk38.default.dim(" Keeping the existing MCP catalogue."));
6082
+ if (!go) console.log(import_chalk39.default.dim(" Keeping the existing MCP catalogue."));
5984
6083
  }
5985
6084
  }
5986
6085
  }
5987
6086
  async function publishMcp(p, spec2) {
5988
- const spinner = (0, import_ora20.default)("Publishing the MCP catalogue...").start();
6087
+ const spinner = (0, import_ora21.default)("Publishing the MCP catalogue...").start();
5989
6088
  try {
5990
6089
  const url = `https://${p.mcpHost}/${p.version}/${p.environment}/mcp/generate`;
5991
6090
  const res = await fetch(url, {
@@ -6021,14 +6120,14 @@ var revealAuth = false;
6021
6120
  function renderToolEvents(events, dpKey) {
6022
6121
  for (const e of events ?? []) {
6023
6122
  const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
6024
- const mark = ok ? import_chalk38.default.green("\u2713") : import_chalk38.default.red("\u2717");
6025
- console.log(` ${mark} ${import_chalk38.default.cyan(e.name)} ${import_chalk38.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
6123
+ const mark = ok ? import_chalk39.default.green("\u2713") : import_chalk39.default.red("\u2717");
6124
+ console.log(` ${mark} ${import_chalk39.default.cyan(e.name)} ${import_chalk39.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
6026
6125
  if (isVerbose() && e.method && e.url) {
6027
- console.log(import_chalk38.default.dim(` curl -sS -X ${e.method} '${e.url}'${dpKey ? " \\" : ""}`));
6126
+ console.log(import_chalk39.default.dim(` curl -sS -X ${e.method} '${e.url}'${dpKey ? " \\" : ""}`));
6028
6127
  if (dpKey) {
6029
6128
  const keyLine = ` -H 'X-API-Key: ${revealAuth ? dpKey : maskKey(dpKey)}'`;
6030
- const hint = revealAuth ? "" : import_chalk38.default.yellow(" \u2190 /showauth will reveal this");
6031
- console.log(import_chalk38.default.dim(keyLine) + hint);
6129
+ const hint = revealAuth ? "" : import_chalk39.default.yellow(" \u2190 /showauth will reveal this");
6130
+ console.log(import_chalk39.default.dim(keyLine) + hint);
6032
6131
  }
6033
6132
  }
6034
6133
  }
@@ -6037,9 +6136,9 @@ function billingLine(billing) {
6037
6136
  if (!billing || typeof billing.cents !== "number") return null;
6038
6137
  if (typeof billing.free_turns_remaining === "number") return null;
6039
6138
  const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
6040
- let line = import_chalk38.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk38.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
6139
+ let line = import_chalk39.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk39.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
6041
6140
  if (typeof billing.credits_remaining === "number") {
6042
- line += import_chalk38.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
6141
+ line += import_chalk39.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
6043
6142
  }
6044
6143
  return line;
6045
6144
  }
@@ -6047,21 +6146,21 @@ function freeBudgetWarning(billing, anon) {
6047
6146
  if (!anon || !billing) return null;
6048
6147
  if (typeof billing.free_turns_remaining === "number") {
6049
6148
  const left2 = billing.free_turns_remaining;
6050
- if (left2 <= 0) return import_chalk38.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
6051
- return import_chalk38.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
6149
+ if (left2 <= 0) return import_chalk39.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
6150
+ return import_chalk39.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
6052
6151
  }
6053
6152
  if (typeof billing.free_remaining_cents !== "number") return null;
6054
6153
  const perTurn = Math.max(billing.cents || 0, 0.02);
6055
6154
  const left = Math.floor(billing.free_remaining_cents / perTurn);
6056
6155
  if (left > 8) return null;
6057
- if (left <= 0) return import_chalk38.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
6058
- return import_chalk38.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
6156
+ if (left <= 0) return import_chalk39.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
6157
+ return import_chalk39.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
6059
6158
  }
6060
6159
  function printAssistant(delta) {
6061
6160
  for (let i = delta.length - 1; i >= 0; i--) {
6062
6161
  const m = delta[i];
6063
6162
  if (m && m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
6064
- console.log("\n" + import_chalk38.default.green("assistant \u203A ") + m.content + "\n");
6163
+ console.log("\n" + import_chalk39.default.green("assistant \u203A ") + m.content + "\n");
6065
6164
  return;
6066
6165
  }
6067
6166
  }
@@ -6071,7 +6170,7 @@ async function replTurn(p, messages, userText) {
6071
6170
  const llm2 = loadLlmConfig();
6072
6171
  const turnId = crypto2.randomUUID();
6073
6172
  for (let round = 0; round < CLIENT_ROUND_CAP; round++) {
6074
- const spinner = (0, import_ora20.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
6173
+ const spinner = (0, import_ora21.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
6075
6174
  const body = {
6076
6175
  turn_id: turnId,
6077
6176
  messages,
@@ -6087,7 +6186,7 @@ async function replTurn(p, messages, userText) {
6087
6186
  });
6088
6187
  } catch (err) {
6089
6188
  spinner.fail("Network error.");
6090
- console.log(import_chalk38.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
6189
+ console.log(import_chalk39.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
6091
6190
  return;
6092
6191
  }
6093
6192
  let data = null;
@@ -6105,12 +6204,12 @@ async function replTurn(p, messages, userText) {
6105
6204
  return;
6106
6205
  }
6107
6206
  if (res.status === 401) {
6108
- console.log(import_chalk38.default.red(" The proxy rejected the API key (401). The key may have been revoked; re-run `apiblaze apichat` to re-provision."));
6207
+ console.log(import_chalk39.default.red(" The proxy rejected the API key (401). The key may have been revoked; re-run `apiblaze apichat` to re-provision."));
6109
6208
  return;
6110
6209
  }
6111
6210
  if (!res.ok || !data) {
6112
6211
  const err = data && data.error || `HTTP ${res.status}`;
6113
- console.log(import_chalk38.default.red(` Chat error: ${err}`));
6212
+ console.log(import_chalk39.default.red(` Chat error: ${err}`));
6114
6213
  return;
6115
6214
  }
6116
6215
  if (Array.isArray(data.delta)) {
@@ -6124,25 +6223,25 @@ async function replTurn(p, messages, userText) {
6124
6223
  if (warn) console.log(warn);
6125
6224
  if (!data.continue) return;
6126
6225
  }
6127
- console.log(import_chalk38.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
6226
+ console.log(import_chalk39.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
6128
6227
  }
6129
6228
  function renderUpsell(p, upsell) {
6130
6229
  const loggedIn = !!loadCredentials();
6131
6230
  if (upsell.reason === "CAPPED" && !loggedIn) {
6132
- console.log("\n" + import_chalk38.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
6133
- console.log(import_chalk38.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
6231
+ console.log("\n" + import_chalk39.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
6232
+ console.log(import_chalk39.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
6134
6233
  console.log();
6135
6234
  return;
6136
6235
  }
6137
- console.log("\n" + import_chalk38.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
6236
+ console.log("\n" + import_chalk39.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
6138
6237
  if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "PAUSED") {
6139
6238
  if (!loggedIn) {
6140
- console.log(import_chalk38.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
6239
+ console.log(import_chalk39.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
6141
6240
  } else {
6142
- console.log(import_chalk38.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
6241
+ console.log(import_chalk39.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
6143
6242
  }
6144
6243
  } else if (upsell.reason === "INFLIGHT") {
6145
- console.log(import_chalk38.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
6244
+ console.log(import_chalk39.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
6146
6245
  }
6147
6246
  console.log();
6148
6247
  }
@@ -6211,9 +6310,9 @@ async function openServerProxy(project) {
6211
6310
  const prior = loadApichats().find((a) => a.projectId === project.projectId && a.dpKey);
6212
6311
  let dpKey = prior?.dpKey;
6213
6312
  if (!dpKey) {
6214
- console.log(import_chalk38.default.dim(` Minting an API key for tenant ${import_chalk38.default.bold(tenant2)} to query project ${import_chalk38.default.bold(project.projectName)}\u2026`));
6313
+ console.log(import_chalk39.default.dim(` Minting an API key for tenant ${import_chalk39.default.bold(tenant2)} to query project ${import_chalk39.default.bold(project.projectName)}\u2026`));
6215
6314
  dpKey = await mintDurableProxyKey(project.teamId, tenant2);
6216
- console.log(` ${import_chalk38.default.green("\u2714")} API key: ${import_chalk38.default.dim(maskKey(dpKey))}`);
6315
+ console.log(` ${import_chalk39.default.green("\u2714")} API key: ${import_chalk39.default.dim(maskKey(dpKey))}`);
6217
6316
  }
6218
6317
  const p = {
6219
6318
  projectId: project.projectId,
@@ -6226,7 +6325,7 @@ async function openServerProxy(project) {
6226
6325
  // Reusing an owned proxy: logged-in apichat doors default to invite-only.
6227
6326
  access: "invite"
6228
6327
  };
6229
- const spinner = (0, import_ora20.default)("Preparing the chat\u2026").start();
6328
+ const spinner = (0, import_ora21.default)("Preparing the chat\u2026").start();
6230
6329
  try {
6231
6330
  const raw = await admin({
6232
6331
  method: "GET",
@@ -6238,7 +6337,7 @@ async function openServerProxy(project) {
6238
6337
  if (spec2 && (spec2.paths || spec2.openapi)) {
6239
6338
  await publishMcp(p, spec2);
6240
6339
  } else {
6241
- console.log(import_chalk38.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
6340
+ console.log(import_chalk39.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
6242
6341
  }
6243
6342
  } catch (err) {
6244
6343
  spinner.fail("Could not open the proxy.");
@@ -6291,7 +6390,7 @@ async function noArgsMenu(opts) {
6291
6390
  const me = loadCredentials()?.apiblazeUserId;
6292
6391
  const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
6293
6392
  const choices = saved.map((a) => ({
6294
- name: `Chat with ${import_chalk38.default.bold(a.name)} ${import_chalk38.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
6393
+ name: `Chat with ${import_chalk39.default.bold(a.name)} ${import_chalk39.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
6295
6394
  value: { type: "existing", a }
6296
6395
  }));
6297
6396
  const creds = loadCredentials();
@@ -6301,14 +6400,14 @@ async function noArgsMenu(opts) {
6301
6400
  const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
6302
6401
  for (const pr of proxies) {
6303
6402
  choices.push({
6304
- name: `Chat with ${import_chalk38.default.bold(pr.projectName)} ${import_chalk38.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
6403
+ name: `Chat with ${import_chalk39.default.bold(pr.projectName)} ${import_chalk39.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
6305
6404
  value: { type: "server", project: pr }
6306
6405
  });
6307
6406
  }
6308
6407
  } catch {
6309
6408
  }
6310
6409
  }
6311
- choices.push({ name: import_chalk38.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
6410
+ choices.push({ name: import_chalk39.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
6312
6411
  const { pick: pick2 } = await inquirer3.prompt([
6313
6412
  { type: "list", name: "pick", message: "What would you like to do?", choices }
6314
6413
  ]);
@@ -6382,36 +6481,36 @@ async function noArgsMenu(opts) {
6382
6481
  async function runRepl(p, initialMessages) {
6383
6482
  const { default: inquirer3 } = await import("inquirer");
6384
6483
  const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
6385
- console.log("\n" + import_chalk38.default.cyan.bold("Chat with your API") + import_chalk38.default.dim(` \xB7 ${p.mcpHost}`));
6386
- if (messages.length) console.log(import_chalk38.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
6484
+ console.log("\n" + import_chalk39.default.cyan.bold("Chat with your API") + import_chalk39.default.dim(` \xB7 ${p.mcpHost}`));
6485
+ if (messages.length) console.log(import_chalk39.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
6387
6486
  const llm2 = loadLlmConfig();
6388
6487
  console.log(
6389
- import_chalk38.default.dim(
6488
+ import_chalk39.default.dim(
6390
6489
  llm2 ? `Using your local ${llm2.provider} key for the model. Type a question, or /exit. /login /claim manage your workspace.` : "Ask a question in plain English. /exit to quit \xB7 /login for more free chats \xB7 /claim to keep this workspace \xB7 `apiblaze llm set-key` for BYO models."
6391
6490
  )
6392
6491
  );
6393
6492
  for (; ; ) {
6394
- const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk38.default.green("you \u203A") }]);
6493
+ const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk39.default.green("you \u203A") }]);
6395
6494
  const text = (input ?? "").trim();
6396
6495
  if (!text) continue;
6397
6496
  if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
6398
6497
  if (text === "/login") {
6399
6498
  try {
6400
6499
  await runLogin();
6401
- console.log(import_chalk38.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
6500
+ console.log(import_chalk39.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
6402
6501
  } catch (err) {
6403
- console.log(import_chalk38.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
6502
+ console.log(import_chalk39.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
6404
6503
  }
6405
6504
  continue;
6406
6505
  }
6407
6506
  if (text === "/claim") {
6408
6507
  const justLoggedIn = !loadCredentials();
6409
6508
  if (justLoggedIn) {
6410
- console.log(import_chalk38.default.dim(" Logging in to claim your workspace\u2026"));
6509
+ console.log(import_chalk39.default.dim(" Logging in to claim your workspace\u2026"));
6411
6510
  try {
6412
6511
  await runLogin();
6413
6512
  } catch (err) {
6414
- console.log(import_chalk38.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
6513
+ console.log(import_chalk39.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
6415
6514
  continue;
6416
6515
  }
6417
6516
  if (!loadCredentials()) continue;
@@ -6422,30 +6521,30 @@ async function runRepl(p, initialMessages) {
6422
6521
  p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
6423
6522
  p.anon = false;
6424
6523
  claimApichat(p, loadCredentials()?.apiblazeUserId);
6425
- console.log(import_chalk38.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
6524
+ console.log(import_chalk39.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
6426
6525
  }
6427
6526
  } catch (err) {
6428
- console.log(import_chalk38.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
6527
+ console.log(import_chalk39.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
6429
6528
  }
6430
6529
  continue;
6431
6530
  }
6432
6531
  if (text === "/showauth") {
6433
6532
  revealAuth = !revealAuth;
6434
- console.log(import_chalk38.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
6533
+ console.log(import_chalk39.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
6435
6534
  continue;
6436
6535
  }
6437
6536
  if (text.startsWith("/")) {
6438
- console.log(import_chalk38.default.dim(" Commands: /login /claim /showauth /exit"));
6537
+ console.log(import_chalk39.default.dim(" Commands: /login /claim /showauth /exit"));
6439
6538
  continue;
6440
6539
  }
6441
6540
  await replTurn(p, messages, text);
6442
6541
  saveTranscript(p, messages);
6443
6542
  }
6444
- console.log(import_chalk38.default.dim("\nBye."));
6543
+ console.log(import_chalk39.default.dim("\nBye."));
6445
6544
  }
6446
6545
  async function runApichat(opts) {
6447
6546
  setVerbose(opts.verbose !== false);
6448
- console.log(import_chalk38.default.bold("\napichat \u2014 turn any API into a chat\n"));
6547
+ console.log(import_chalk39.default.bold("\napichat \u2014 turn any API into a chat\n"));
6449
6548
  if (!opts.openapispec && !opts.target) {
6450
6549
  if (!process.stdin.isTTY) {
6451
6550
  fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
@@ -6458,7 +6557,7 @@ async function runApichat(opts) {
6458
6557
  }
6459
6558
  const { spec: spec2, sourceUrl } = await loadSpec(opts);
6460
6559
  const target = resolveTarget(spec2, opts, sourceUrl);
6461
- console.log(` ${import_chalk38.default.dim("Target:")} ${import_chalk38.default.bold(target)}`);
6560
+ console.log(` ${import_chalk39.default.dim("Target:")} ${import_chalk39.default.bold(target)}`);
6462
6561
  const auth = await resolveTargetAuth(spec2, opts);
6463
6562
  if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
6464
6563
  fail4(
@@ -6467,7 +6566,7 @@ async function runApichat(opts) {
6467
6566
  );
6468
6567
  }
6469
6568
  const p = await provision(spec2, target, opts);
6470
- console.log(` ${import_chalk38.default.dim("Proxy: ")} ${import_chalk38.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
6569
+ console.log(` ${import_chalk39.default.dim("Proxy: ")} ${import_chalk39.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
6471
6570
  upsertApichat({
6472
6571
  name: p.projectId,
6473
6572
  target,
@@ -6486,31 +6585,31 @@ async function runApichat(opts) {
6486
6585
  const secret = await captureTargetSecret(auth, opts);
6487
6586
  if (secret) await writeTargetAuth(p, auth, secret);
6488
6587
  } else {
6489
- console.log(import_chalk38.default.dim(" Target auth: none required."));
6588
+ console.log(import_chalk39.default.dim(" Target auth: none required."));
6490
6589
  }
6491
6590
  const specText = JSON.stringify(spec2);
6492
6591
  await uploadSpec(p, specText, opts);
6493
6592
  const mcpUrl = await publishMcp(p, spec2);
6494
6593
  console.log();
6495
- if (p.proxyUrl) console.log(` ${import_chalk38.default.green("\u2713")} proxy ${import_chalk38.default.bold(p.proxyUrl)}`);
6594
+ if (p.proxyUrl) console.log(` ${import_chalk39.default.green("\u2713")} proxy ${import_chalk39.default.bold(p.proxyUrl)}`);
6496
6595
  if (mcpUrl) {
6497
- console.log(` ${import_chalk38.default.green("\u2713")} mcp ${import_chalk38.default.bold(mcpUrl)}`);
6596
+ console.log(` ${import_chalk39.default.green("\u2713")} mcp ${import_chalk39.default.bold(mcpUrl)}`);
6498
6597
  if (p.access === "invite") {
6499
- console.log(import_chalk38.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
6500
- console.log(import_chalk38.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
6598
+ console.log(import_chalk39.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
6599
+ console.log(import_chalk39.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
6501
6600
  } else {
6502
- console.log(import_chalk38.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
6601
+ console.log(import_chalk39.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
6503
6602
  }
6504
6603
  }
6505
6604
  if (p.anon) {
6506
- console.log(import_chalk38.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
6605
+ console.log(import_chalk39.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
6507
6606
  }
6508
6607
  await runRepl(p);
6509
6608
  }
6510
6609
 
6511
6610
  // src/commands/consumer.ts
6512
- var import_chalk39 = __toESM(require("chalk"));
6513
- var import_ora21 = __toESM(require("ora"));
6611
+ var import_chalk40 = __toESM(require("chalk"));
6612
+ var import_ora22 = __toESM(require("ora"));
6514
6613
  init_admin();
6515
6614
  init_resolve();
6516
6615
  var DEFAULT_SCOPE = "openid email profile offline_access";
@@ -6531,7 +6630,7 @@ async function consumerFetch(creds, suffix, init) {
6531
6630
  function requireConsumer() {
6532
6631
  const c = loadConsumer();
6533
6632
  if (!c) {
6534
- console.error(import_chalk39.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
6633
+ console.error(import_chalk40.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
6535
6634
  process.exit(1);
6536
6635
  }
6537
6636
  return c;
@@ -6542,7 +6641,7 @@ async function runConsumerLogin(opts) {
6542
6641
  let clientId = opts.client;
6543
6642
  if (clientId) {
6544
6643
  if (!tenant2) {
6545
- console.error(import_chalk39.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
6644
+ console.error(import_chalk40.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
6546
6645
  process.exit(1);
6547
6646
  }
6548
6647
  } else {
@@ -6554,25 +6653,25 @@ async function runConsumerLogin(opts) {
6554
6653
  if (!picked) process.exit(1);
6555
6654
  tenant2 = picked;
6556
6655
  }
6557
- const s2 = (0, import_ora21.default)("Finding the login app...").start();
6656
+ const s2 = (0, import_ora22.default)("Finding the login app...").start();
6558
6657
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
6559
6658
  s2.stop();
6560
6659
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
6561
6660
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
6562
6661
  if (!pick2) {
6563
- console.error(import_chalk39.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
6662
+ console.error(import_chalk40.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
6564
6663
  process.exit(1);
6565
6664
  }
6566
6665
  clientId = pick2.client_id ?? pick2.clientId;
6567
6666
  }
6568
6667
  const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
6569
- console.log(`${import_chalk39.default.cyan("\u2192")} Logging in to ${import_chalk39.default.bold(tenant2)} as a consumer...`);
6668
+ console.log(`${import_chalk40.default.cyan("\u2192")} Logging in to ${import_chalk40.default.bold(tenant2)} as a consumer...`);
6570
6669
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
6571
6670
  console.log(`
6572
- Open: ${import_chalk39.default.underline(verificationUri)}`);
6573
- console.log(` Code: ${import_chalk39.default.bold(userCode)}
6671
+ Open: ${import_chalk40.default.underline(verificationUri)}`);
6672
+ console.log(` Code: ${import_chalk40.default.bold(userCode)}
6574
6673
  `);
6575
- console.log(import_chalk39.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
6674
+ console.log(import_chalk40.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
6576
6675
  }, portalResource);
6577
6676
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
6578
6677
  const creds = {
@@ -6587,7 +6686,7 @@ async function runConsumerLogin(opts) {
6587
6686
  obtainedAt: Date.now()
6588
6687
  };
6589
6688
  saveConsumer(creds);
6590
- console.log(import_chalk39.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
6689
+ console.log(import_chalk40.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
6591
6690
  }
6592
6691
  async function runConsumerTokens(opts) {
6593
6692
  const creds = requireConsumer();
@@ -6600,29 +6699,29 @@ async function runConsumerTokens(opts) {
6600
6699
  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));
6601
6700
  return;
6602
6701
  }
6603
- console.log(`${import_chalk39.default.cyan("Consumer")} ${import_chalk39.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk39.default.bold(fresh.tenant)}
6702
+ console.log(`${import_chalk40.default.cyan("Consumer")} ${import_chalk40.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk40.default.bold(fresh.tenant)}
6604
6703
  `);
6605
- console.log(`${import_chalk39.default.bold("access_token")} ${import_chalk39.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
6704
+ console.log(`${import_chalk40.default.bold("access_token")} ${import_chalk40.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
6606
6705
  ${fresh.accessToken}
6607
6706
  `);
6608
- if (fresh.idToken) console.log(`${import_chalk39.default.bold("id_token")} ${import_chalk39.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
6707
+ if (fresh.idToken) console.log(`${import_chalk40.default.bold("id_token")} ${import_chalk40.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
6609
6708
  ${fresh.idToken}
6610
6709
  `);
6611
- if (fresh.refreshToken) console.log(`${import_chalk39.default.bold("refresh_token")}
6710
+ if (fresh.refreshToken) console.log(`${import_chalk40.default.bold("refresh_token")}
6612
6711
  ${fresh.refreshToken}
6613
6712
  `);
6614
- console.log(import_chalk39.default.dim("These are your own tokens \u2014 keep them secret."));
6713
+ console.log(import_chalk40.default.dim("These are your own tokens \u2014 keep them secret."));
6615
6714
  }
6616
6715
  async function runConsumerApikeys(opts) {
6617
6716
  const creds = requireConsumer();
6618
6717
  const { default: inquirer3 } = await import("inquirer");
6619
- const spinner = (0, import_ora21.default)("Loading your API keys...").start();
6718
+ const spinner = (0, import_ora22.default)("Loading your API keys...").start();
6620
6719
  const list = await consumerFetch(creds, "/apikeys");
6621
6720
  const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
6622
6721
  spinner.stop();
6623
6722
  if (list.status >= 400) {
6624
- console.error(import_chalk39.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
6625
- if (list.status === 401) console.error(import_chalk39.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
6723
+ console.error(import_chalk40.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
6724
+ if (list.status === 401) console.error(import_chalk40.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
6626
6725
  process.exit(1);
6627
6726
  }
6628
6727
  const keys = list.data?.keys ?? [];
@@ -6630,16 +6729,16 @@ async function runConsumerApikeys(opts) {
6630
6729
  if (opts.json) {
6631
6730
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
6632
6731
  } else if (!keys.length) {
6633
- console.log(import_chalk39.default.yellow("No API keys yet."));
6732
+ console.log(import_chalk40.default.yellow("No API keys yet."));
6634
6733
  } else {
6635
6734
  for (const k of keys) {
6636
6735
  const clear = revealMap[k.environment]?.key;
6637
- const shown = clear ? import_chalk39.default.green(clear) : import_chalk39.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
6638
- const exp = k.expires_at ? import_chalk39.default.dim(`exp ${k.expires_at}`) : import_chalk39.default.dim("no expiry");
6639
- console.log(` ${import_chalk39.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk39.default.dim(k.description ?? "")}`);
6736
+ const shown = clear ? import_chalk40.default.green(clear) : import_chalk40.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
6737
+ const exp = k.expires_at ? import_chalk40.default.dim(`exp ${k.expires_at}`) : import_chalk40.default.dim("no expiry");
6738
+ console.log(` ${import_chalk40.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk40.default.dim(k.description ?? "")}`);
6640
6739
  }
6641
6740
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
6642
- console.log(import_chalk39.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
6741
+ console.log(import_chalk40.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
6643
6742
  }
6644
6743
  }
6645
6744
  if (opts.json) return;
@@ -6653,7 +6752,7 @@ async function runConsumerApikeys(opts) {
6653
6752
  const body = { environment: answers.environment };
6654
6753
  if (answers.description) body.description = answers.description;
6655
6754
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
6656
- const s2 = (0, import_ora21.default)("Creating key...").start();
6755
+ const s2 = (0, import_ora22.default)("Creating key...").start();
6657
6756
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
6658
6757
  if (created.status >= 400) {
6659
6758
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -6661,13 +6760,13 @@ async function runConsumerApikeys(opts) {
6661
6760
  }
6662
6761
  s2.succeed("Key created.");
6663
6762
  const key = created.data?.key ?? created.data?.fullKey;
6664
- if (key) console.log(` ${import_chalk39.default.green(key)} ${import_chalk39.default.dim("(shown once \u2014 store it now)")}`);
6665
- else console.log(import_chalk39.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
6763
+ if (key) console.log(` ${import_chalk40.default.green(key)} ${import_chalk40.default.dim("(shown once \u2014 store it now)")}`);
6764
+ else console.log(import_chalk40.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
6666
6765
  }
6667
6766
 
6668
6767
  // src/commands/sidecar.ts
6669
- var import_chalk40 = __toESM(require("chalk"));
6670
- var import_ora22 = __toESM(require("ora"));
6768
+ var import_chalk41 = __toESM(require("chalk"));
6769
+ var import_ora23 = __toESM(require("ora"));
6671
6770
  var fs10 = __toESM(require("fs"));
6672
6771
  var path7 = __toESM(require("path"));
6673
6772
  init_admin();
@@ -6709,18 +6808,18 @@ function upsertEnvLocal(root, token) {
6709
6808
  }
6710
6809
  function installSidecarPackage(root) {
6711
6810
  if (fs10.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
6712
- console.log(` ${import_chalk40.default.green("\u2713")} apiblaze package already installed`);
6811
+ console.log(` ${import_chalk41.default.green("\u2713")} apiblaze package already installed`);
6713
6812
  return;
6714
6813
  }
6715
6814
  const has = (f) => fs10.existsSync(path7.join(root, f));
6716
6815
  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" };
6717
- const spinner = (0, import_ora22.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
6816
+ const spinner = (0, import_ora23.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
6718
6817
  try {
6719
6818
  const { execSync } = require("child_process");
6720
6819
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
6721
6820
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
6722
6821
  } catch {
6723
- spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk40.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk40.default.cyan("npm run dev")}.`);
6822
+ spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk41.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk41.default.cyan("npm run dev")}.`);
6724
6823
  }
6725
6824
  }
6726
6825
  function readEnvKey(root) {
@@ -6845,8 +6944,8 @@ function generateInspector(root, router) {
6845
6944
  fs10.writeFileSync(f2, INSPECTOR_PAGE);
6846
6945
  return path7.relative(root, f2);
6847
6946
  }
6848
- const base = fs10.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
6849
- const dir = path7.join(base, "abz-inspector");
6947
+ const base2 = fs10.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
6948
+ const dir = path7.join(base2, "abz-inspector");
6850
6949
  fs10.mkdirSync(dir, { recursive: true });
6851
6950
  const f = path7.join(dir, "page.tsx");
6852
6951
  fs10.writeFileSync(f, INSPECTOR_PAGE);
@@ -6859,7 +6958,7 @@ async function runAnonymousInit(root, router, opts) {
6859
6958
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
6860
6959
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
6861
6960
  if (opts.newSession) clearAnonCred2();
6862
- const spinner = (0, import_ora22.default)("Setting up a sidecar (no login needed)...").start();
6961
+ const spinner = (0, import_ora23.default)("Setting up a sidecar (no login needed)...").start();
6863
6962
  let out;
6864
6963
  try {
6865
6964
  out = await sidecarInitAnonymous2();
@@ -6871,29 +6970,29 @@ async function runAnonymousInit(root, router, opts) {
6871
6970
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
6872
6971
  const envState = upsertEnvLocal(root, out.token);
6873
6972
  ensureGitignored(root);
6874
- console.log(` ${import_chalk40.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
6875
- console.log(` ${import_chalk40.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
6973
+ console.log(` ${import_chalk41.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
6974
+ console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
6876
6975
  installSidecarPackage(root);
6877
6976
  let inspectorPath = null;
6878
6977
  if (!opts.noInspector) {
6879
6978
  inspectorPath = generateInspector(root, router);
6880
- if (inspectorPath) console.log(` ${import_chalk40.default.green("\u2713")} inspector at ${inspectorPath}`);
6979
+ if (inspectorPath) console.log(` ${import_chalk41.default.green("\u2713")} inspector at ${inspectorPath}`);
6881
6980
  }
6882
6981
  console.log("");
6883
- console.log(import_chalk40.default.bold("Done (no account needed). What happens next:"));
6884
- console.log(` 1. ${import_chalk40.default.cyan("npm run dev")} and use your app.`);
6982
+ console.log(import_chalk41.default.bold("Done (no account needed). What happens next:"));
6983
+ console.log(` 1. ${import_chalk41.default.cyan("npm run dev")} and use your app.`);
6885
6984
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
6886
- console.log(` ${import_chalk40.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
6985
+ console.log(` ${import_chalk41.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
6887
6986
  console.log("");
6888
- console.log(import_chalk40.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
6889
- console.log(` ${import_chalk40.default.cyan("apiblaze login")} then ${import_chalk40.default.cyan("apiblaze claim")} ${import_chalk40.default.dim("(no code needed here)")}`);
6890
- console.log(import_chalk40.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
6987
+ console.log(import_chalk41.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
6988
+ console.log(` ${import_chalk41.default.cyan("apiblaze login")} then ${import_chalk41.default.cyan("apiblaze claim")} ${import_chalk41.default.dim("(no code needed here)")}`);
6989
+ console.log(import_chalk41.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
6891
6990
  }
6892
6991
  async function runSidecar(opts) {
6893
6992
  const root = path7.resolve(opts.dir ?? process.cwd());
6894
6993
  const detected = detectNextProject(root);
6895
6994
  if (!detected.found) {
6896
- console.log(import_chalk40.default.yellow(`No Next.js project detected in ${root}.`));
6995
+ console.log(import_chalk41.default.yellow(`No Next.js project detected in ${root}.`));
6897
6996
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
6898
6997
  return;
6899
6998
  }
@@ -6904,10 +7003,10 @@ async function runSidecar(opts) {
6904
7003
  if (!loadCredentials()) {
6905
7004
  upsertEnvLocal(root, readEnvKey(root));
6906
7005
  ensureGitignored(root);
6907
- console.log(` ${import_chalk40.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
6908
- console.log(` ${import_chalk40.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
7006
+ console.log(` ${import_chalk41.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
7007
+ console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
6909
7008
  installSidecarPackage(root);
6910
- console.log(import_chalk40.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
7009
+ console.log(import_chalk41.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
6911
7010
  return;
6912
7011
  }
6913
7012
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -6916,7 +7015,7 @@ async function runSidecar(opts) {
6916
7015
  const mustMint = !existingKey || opts.rotate || switchingTeam;
6917
7016
  let token = existingKey ?? "";
6918
7017
  if (mustMint) {
6919
- const spinner = (0, import_ora22.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
7018
+ const spinner = (0, import_ora23.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
6920
7019
  try {
6921
7020
  const out = await admin({
6922
7021
  method: "POST",
@@ -6930,39 +7029,39 @@ async function runSidecar(opts) {
6930
7029
  throw err;
6931
7030
  }
6932
7031
  } else {
6933
- console.log(import_chalk40.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
7032
+ console.log(import_chalk41.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
6934
7033
  }
6935
7034
  const envState = upsertEnvLocal(root, token);
6936
7035
  ensureGitignored(root);
6937
- console.log(` ${import_chalk40.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
7036
+ console.log(` ${import_chalk41.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
6938
7037
  const wireState = wireInstrumentation(root);
6939
- console.log(` ${import_chalk40.default.green("\u2713")} instrumentation.ts ${wireState}`);
7038
+ console.log(` ${import_chalk41.default.green("\u2713")} instrumentation.ts ${wireState}`);
6940
7039
  installSidecarPackage(root);
6941
7040
  let inspectorPath = null;
6942
7041
  if (!opts.noInspector) {
6943
7042
  inspectorPath = generateInspector(root, detected.router);
6944
- if (inspectorPath) console.log(` ${import_chalk40.default.green("\u2713")} inspector at ${inspectorPath}`);
7043
+ if (inspectorPath) console.log(` ${import_chalk41.default.green("\u2713")} inspector at ${inspectorPath}`);
6945
7044
  }
6946
7045
  console.log("");
6947
- console.log(import_chalk40.default.bold("Done. What happens next:"));
6948
- console.log(` 1. ${import_chalk40.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
6949
- console.log(` 2. The origins your app calls appear as ${import_chalk40.default.bold("candidates")} \u2014 list them: ${import_chalk40.default.cyan("apiblaze sidecar")}`);
6950
- console.log(` 3. Approve the ones to route: ${import_chalk40.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
7046
+ console.log(import_chalk41.default.bold("Done. What happens next:"));
7047
+ console.log(` 1. ${import_chalk41.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
7048
+ console.log(` 2. The origins your app calls appear as ${import_chalk41.default.bold("candidates")} \u2014 list them: ${import_chalk41.default.cyan("apiblaze sidecar")}`);
7049
+ console.log(` 3. Approve the ones to route: ${import_chalk41.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
6951
7050
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
6952
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk40.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path7.dirname(inspectorPath)} before shipping)`);
6953
- if (switchingTeam) console.log(import_chalk40.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
7051
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk41.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path7.dirname(inspectorPath)} before shipping)`);
7052
+ if (switchingTeam) console.log(import_chalk41.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
6954
7053
  console.log("");
6955
- console.log(import_chalk40.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
6956
- console.log(import_chalk40.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
6957
- console.log(import_chalk40.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
7054
+ console.log(import_chalk41.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
7055
+ console.log(import_chalk41.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
7056
+ console.log(import_chalk41.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
6958
7057
  console.log("");
6959
- console.log(import_chalk40.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
6960
- console.log(import_chalk40.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
7058
+ console.log(import_chalk41.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
7059
+ console.log(import_chalk41.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
6961
7060
  }
6962
7061
 
6963
7062
  // src/commands/origins.ts
6964
- var import_chalk41 = __toESM(require("chalk"));
6965
- var import_ora23 = __toESM(require("ora"));
7063
+ var import_chalk42 = __toESM(require("chalk"));
7064
+ var import_ora24 = __toESM(require("ora"));
6966
7065
  init_admin();
6967
7066
  init_resolve();
6968
7067
  init_auth();
@@ -6972,7 +7071,7 @@ async function runOriginsList(opts) {
6972
7071
  if (!loadCredentials()) {
6973
7072
  const cred = loadAnonCred();
6974
7073
  if (!cred) {
6975
- console.log(import_chalk41.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
7074
+ console.log(import_chalk42.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
6976
7075
  return;
6977
7076
  }
6978
7077
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -6990,30 +7089,30 @@ async function runOriginsList(opts) {
6990
7089
  }
6991
7090
  const routed = out.routed ?? [];
6992
7091
  const candidates = out.candidates ?? [];
6993
- console.log(import_chalk41.default.bold(`
7092
+ console.log(import_chalk42.default.bold(`
6994
7093
  Routed through APIblaze (${routed.length})`));
6995
- if (!routed.length) console.log(import_chalk41.default.dim(" none yet"));
6996
- for (const r of routed) console.log(` ${import_chalk41.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk41.default.dim(`\u2192 ${r.project_id}`)}`);
6997
- console.log(import_chalk41.default.bold(`
7094
+ if (!routed.length) console.log(import_chalk42.default.dim(" none yet"));
7095
+ for (const r of routed) console.log(` ${import_chalk42.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk42.default.dim(`\u2192 ${r.project_id}`)}`);
7096
+ console.log(import_chalk42.default.bold(`
6998
7097
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
6999
- if (!candidates.length) console.log(import_chalk41.default.dim(" none \u2014 run your app to discover the origins it calls"));
7098
+ if (!candidates.length) console.log(import_chalk42.default.dim(" none \u2014 run your app to discover the origins it calls"));
7000
7099
  for (const c of candidates) {
7001
- console.log(` ${import_chalk41.default.yellow("\u25CB")} ${c.origin} ${import_chalk41.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
7100
+ console.log(` ${import_chalk42.default.yellow("\u25CB")} ${c.origin} ${import_chalk42.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
7002
7101
  }
7003
7102
  if (candidates.length) {
7004
- console.log(import_chalk41.default.dim(`
7103
+ console.log(import_chalk42.default.dim(`
7005
7104
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
7006
- console.log(import_chalk41.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
7105
+ console.log(import_chalk42.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
7007
7106
  }
7008
7107
  }
7009
7108
  async function runOriginsApprove(origin, opts) {
7010
7109
  if (!loadCredentials()) {
7011
7110
  const cred = loadAnonCred();
7012
7111
  if (!cred) {
7013
- console.error(import_chalk41.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
7112
+ console.error(import_chalk42.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
7014
7113
  process.exit(1);
7015
7114
  }
7016
- const spinner2 = (0, import_ora23.default)(`Approving ${origin} (anonymous)...`).start();
7115
+ const spinner2 = (0, import_ora24.default)(`Approving ${origin} (anonymous)...`).start();
7017
7116
  try {
7018
7117
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
7019
7118
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -7024,7 +7123,7 @@ async function runOriginsApprove(origin, opts) {
7024
7123
  return;
7025
7124
  }
7026
7125
  const { teamId } = await resolveTeam(opts.team);
7027
- const spinner = (0, import_ora23.default)(`Approving ${origin}...`).start();
7126
+ const spinner = (0, import_ora24.default)(`Approving ${origin}...`).start();
7028
7127
  try {
7029
7128
  const out = await admin({
7030
7129
  method: "POST",
@@ -7041,7 +7140,7 @@ async function runOriginsApprove(origin, opts) {
7041
7140
  }
7042
7141
  async function runOriginsDeny(origin, opts) {
7043
7142
  const { teamId } = await resolveTeam(opts.team);
7044
- const spinner = (0, import_ora23.default)(`Dismissing ${origin}...`).start();
7143
+ const spinner = (0, import_ora24.default)(`Dismissing ${origin}...`).start();
7045
7144
  try {
7046
7145
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
7047
7146
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -7052,7 +7151,7 @@ async function runOriginsDeny(origin, opts) {
7052
7151
  }
7053
7152
  async function runOriginsRemove(origin, opts) {
7054
7153
  const { teamId } = await resolveTeam(opts.team);
7055
- const spinner = (0, import_ora23.default)(`Removing the proxy for ${origin}...`).start();
7154
+ const spinner = (0, import_ora24.default)(`Removing the proxy for ${origin}...`).start();
7056
7155
  try {
7057
7156
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
7058
7157
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -7063,7 +7162,7 @@ async function runOriginsRemove(origin, opts) {
7063
7162
  }
7064
7163
 
7065
7164
  // src/commands/op.ts
7066
- var import_chalk42 = __toESM(require("chalk"));
7165
+ var import_chalk43 = __toESM(require("chalk"));
7067
7166
  init_auth();
7068
7167
  init_trace();
7069
7168
  init_types();
@@ -7096,82 +7195,82 @@ function printResidue(report, applied) {
7096
7195
  const up = report?.upstash ?? {};
7097
7196
  const fga = report?.fga ?? {};
7098
7197
  const ghosts = report?.ghosts ?? {};
7099
- console.log(import_chalk42.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
7100
- console.log(import_chalk42.default.bold("\n Upstash"));
7198
+ console.log(import_chalk43.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
7199
+ console.log(import_chalk43.default.bold("\n Upstash"));
7101
7200
  const orphans = up.orphans ?? [];
7102
- if (orphans.length === 0) console.log(import_chalk42.default.green(" no orphaned keys"));
7103
- for (const o of orphans) console.log(` ${import_chalk42.default.yellow(o.key)} ${import_chalk42.default.dim(`\u2014 ${o.reason}`)}`);
7104
- console.log(import_chalk42.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
7201
+ if (orphans.length === 0) console.log(import_chalk43.default.green(" no orphaned keys"));
7202
+ for (const o of orphans) console.log(` ${import_chalk43.default.yellow(o.key)} ${import_chalk43.default.dim(`\u2014 ${o.reason}`)}`);
7203
+ console.log(import_chalk43.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
7105
7204
  if (up.anon_wallet_detail) {
7106
7205
  const d = up.anon_wallet_detail;
7107
- console.log(import_chalk42.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
7206
+ console.log(import_chalk43.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
7108
7207
  }
7109
7208
  if (up.keyspace_census) {
7110
7209
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
7111
- console.log(import_chalk42.default.dim(` keyspace: ${census}`));
7210
+ console.log(import_chalk43.default.dim(` keyspace: ${census}`));
7112
7211
  }
7113
- if (up.unknown?.length) console.log(import_chalk42.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
7114
- if (applied) console.log(` ${import_chalk42.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
7115
- for (const e of up.errors ?? []) console.log(import_chalk42.default.red(` error: ${e}`));
7116
- console.log(import_chalk42.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
7212
+ if (up.unknown?.length) console.log(import_chalk43.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
7213
+ if (applied) console.log(` ${import_chalk43.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
7214
+ for (const e of up.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
7215
+ console.log(import_chalk43.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
7117
7216
  if (applied) {
7118
7217
  const swept = fga?.swept ?? [];
7119
- if (swept.length === 0) console.log(import_chalk42.default.green(" no orphaned stores"));
7218
+ if (swept.length === 0) console.log(import_chalk43.default.green(" no orphaned stores"));
7120
7219
  for (const s of swept) {
7121
7220
  console.log(
7122
- ` ${import_chalk42.default.yellow(s.store_id)} ${import_chalk42.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
7221
+ ` ${import_chalk43.default.yellow(s.store_id)} ${import_chalk43.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
7123
7222
  );
7124
7223
  }
7125
- if (fga?.remaining) console.log(import_chalk42.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
7224
+ if (fga?.remaining) console.log(import_chalk43.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
7126
7225
  const st = fga?.side_tables;
7127
- if (st) console.log(import_chalk42.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
7226
+ if (st) console.log(import_chalk43.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
7128
7227
  } else {
7129
7228
  const fgaOrphans = fga?.orphans ?? [];
7130
- if (fgaOrphans.length === 0) console.log(import_chalk42.default.green(" no orphaned stores"));
7229
+ if (fgaOrphans.length === 0) console.log(import_chalk43.default.green(" no orphaned stores"));
7131
7230
  for (const s of fgaOrphans) {
7132
7231
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
7133
- console.log(` ${import_chalk42.default.yellow(s.store_id)} ${import_chalk42.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
7232
+ console.log(` ${import_chalk43.default.yellow(s.store_id)} ${import_chalk43.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
7134
7233
  }
7135
- console.log(import_chalk42.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
7234
+ console.log(import_chalk43.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
7136
7235
  const st = fga?.side_tables;
7137
- if (st) console.log(import_chalk42.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
7236
+ if (st) console.log(import_chalk43.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
7138
7237
  }
7139
- for (const e of fga?.errors ?? []) console.log(import_chalk42.default.red(` error: ${e}`));
7140
- console.log(import_chalk42.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
7238
+ for (const e of fga?.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
7239
+ console.log(import_chalk43.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
7141
7240
  if (applied) {
7142
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk42.default.green(" no ghost tuples"));
7143
- else console.log(` ${import_chalk42.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk42.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
7241
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk43.default.green(" no ghost tuples"));
7242
+ else console.log(` ${import_chalk43.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk43.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
7144
7243
  } else {
7145
7244
  const n = ghosts?.ghost_count ?? 0;
7146
- if (n === 0) console.log(import_chalk42.default.green(` no ghost tuples ${import_chalk42.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
7245
+ if (n === 0) console.log(import_chalk43.default.green(` no ghost tuples ${import_chalk43.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
7147
7246
  else {
7148
- console.log(import_chalk42.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
7247
+ console.log(import_chalk43.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
7149
7248
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
7150
- console.log(import_chalk42.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
7249
+ console.log(import_chalk43.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
7151
7250
  }
7152
- if (n > 20) console.log(import_chalk42.default.dim(` \u2026 and ${n - 20} more`));
7251
+ if (n > 20) console.log(import_chalk43.default.dim(` \u2026 and ${n - 20} more`));
7153
7252
  }
7154
7253
  }
7155
- for (const e of ghosts?.errors ?? []) console.log(import_chalk42.default.red(` error: ${e}`));
7254
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk43.default.red(` error: ${e}`));
7156
7255
  console.log();
7157
7256
  }
7158
7257
  async function runOp(sub, opts = {}) {
7159
7258
  if (!loadCredentials()) {
7160
- console.log(import_chalk42.default.dim("Not logged in. Run `apiblaze login`."));
7259
+ console.log(import_chalk43.default.dim("Not logged in. Run `apiblaze login`."));
7161
7260
  return;
7162
7261
  }
7163
7262
  if (!isOperatorLogin()) {
7164
- console.log(import_chalk42.default.dim("`apiblaze op` is only available to platform operators."));
7263
+ console.log(import_chalk43.default.dim("`apiblaze op` is only available to platform operators."));
7165
7264
  return;
7166
7265
  }
7167
7266
  switch (sub) {
7168
7267
  case void 0:
7169
7268
  case "menu": {
7170
- console.log(import_chalk42.default.bold("\nOperator menu"));
7171
- console.log(` ${import_chalk42.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
7172
- console.log(` ${import_chalk42.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk42.default.dim("-y to skip")})`);
7173
- console.log(` ${import_chalk42.default.cyan("apiblaze op credits")} list credit wallets`);
7174
- console.log(import_chalk42.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
7269
+ console.log(import_chalk43.default.bold("\nOperator menu"));
7270
+ console.log(` ${import_chalk43.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
7271
+ console.log(` ${import_chalk43.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk43.default.dim("-y to skip")})`);
7272
+ console.log(` ${import_chalk43.default.cyan("apiblaze op credits")} list credit wallets`);
7273
+ console.log(import_chalk43.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
7175
7274
  `));
7176
7275
  return;
7177
7276
  }
@@ -7190,17 +7289,17 @@ async function runOp(sub, opts = {}) {
7190
7289
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
7191
7290
  printResidue(report, false);
7192
7291
  if (nUp + nFga + nGhost + nSide === 0) {
7193
- console.log(import_chalk42.default.green("Nothing to sweep."));
7292
+ console.log(import_chalk43.default.green("Nothing to sweep."));
7194
7293
  return;
7195
7294
  }
7196
7295
  if (!opts.yes) {
7197
7296
  const readline2 = await import("readline/promises");
7198
7297
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
7199
7298
  const answer = await rl.question(
7200
- import_chalk42.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
7299
+ import_chalk43.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
7201
7300
  );
7202
7301
  rl.close();
7203
- if (answer.trim() !== "sweep") return void console.log(import_chalk42.default.dim("Aborted."));
7302
+ if (answer.trim() !== "sweep") return void console.log(import_chalk43.default.dim("Aborted."));
7204
7303
  }
7205
7304
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
7206
7305
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -7211,15 +7310,15 @@ async function runOp(sub, opts = {}) {
7211
7310
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
7212
7311
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
7213
7312
  const accounts = data?.accounts ?? [];
7214
- if (accounts.length === 0) return void console.log(import_chalk42.default.dim("No credit wallets."));
7313
+ if (accounts.length === 0) return void console.log(import_chalk43.default.dim("No credit wallets."));
7215
7314
  for (const a of accounts) {
7216
7315
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
7217
- console.log(` ${import_chalk42.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk42.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk42.default.dim(" \u2014 anon") : ""}`);
7316
+ console.log(` ${import_chalk43.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk43.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk43.default.dim(" \u2014 anon") : ""}`);
7218
7317
  }
7219
7318
  return;
7220
7319
  }
7221
7320
  default:
7222
- console.log(import_chalk42.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
7321
+ console.log(import_chalk43.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
7223
7322
  }
7224
7323
  }
7225
7324
 
@@ -7278,14 +7377,14 @@ withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to rou
7278
7377
  sidecar.command("approve").description("Route an origin through APIblaze (creates its proxy)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((origin, opts) => runOriginsApprove(origin, opts)));
7279
7378
  sidecar.command("deny").description("Dismiss a candidate origin so it stops being suggested").argument("<origin>", "Origin, e.g. sentry.io").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsDeny(origin, opts)));
7280
7379
  sidecar.command("remove").description("Un-route an approved origin (deletes its proxy; the app goes direct again)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsRemove(origin, opts)));
7281
- program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
7380
+ program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("--project <nameOrId>", "Tunnel this specific project (skips the picker \u2014 for scripts)").option("-y, --yes", "Skip confirmation prompts (non-interactive)").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
7282
7381
  try {
7283
7382
  const resolved = parseInt(port ?? opts.port, 10);
7284
7383
  if (Number.isNaN(resolved)) {
7285
- console.error(import_chalk43.default.red(`Invalid port: ${port ?? opts.port}`));
7384
+ console.error(import_chalk44.default.red(`Invalid port: ${port ?? opts.port}`));
7286
7385
  process.exit(1);
7287
7386
  }
7288
- await runDev({ port: resolved, captureFile: opts.captureFile });
7387
+ await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile });
7289
7388
  } catch (err) {
7290
7389
  await printError(err);
7291
7390
  process.exit(1);
@@ -7360,6 +7459,11 @@ tenant.command("create").description("Create a tenant in your team (tenant names
7360
7459
  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)));
7361
7460
  tenant.command("delete").description("Delete a tenant (full cascade)").argument("<slug>", "Tenant slug to delete").option("--team <id|name>", "Team (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").action(action((slug, opts) => runTenantDelete(slug, opts)));
7362
7461
  tenant.command("cors").description("Set the CORS allow-list for a tenant").requiredOption("--tenant <slug>", "Tenant slug").option("--origins <list>", 'Comma-separated origins (or "*"); empty clears').option("--team <id|name>", "Team (defaults to active team)").action(action((opts) => runTenantCors(opts)));
7462
+ var admins = new import_commander.Command("admins").description("Manage who can administer a tenant's users & groups (the first-admin bootstrap the widget needs)").option("--tenant <slug>", "Tenant slug (e.g. nino)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--json", "Output machine-readable JSON").action(action((opts) => runAdminsList(opts)));
7463
+ admins.command("list").description("List a tenant's admins").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((opts, cmd) => runAdminsList({ ...cmd.parent?.opts(), ...opts })));
7464
+ admins.command("add").description("Add an email as a tenant admin (e.g. yourself, to start)").argument("<email>", "Email to grant admin").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((email, opts, cmd) => runAdminsAdd(email, { ...cmd.parent?.opts(), ...opts })));
7465
+ admins.command("remove").description("Remove a tenant admin").argument("<email>", "Email to remove").option("--tenant <slug>", "Tenant slug").option("--team <id|name>", "Team").option("--json", "JSON").action(action((email, opts, cmd) => runAdminsRemove(email, { ...cmd.parent?.opts(), ...opts })));
7466
+ program.addCommand(admins);
7363
7467
  program.command("iam").description("Turn users & groups on/off for a proxy's tenant (identified calls get their user's groups applied)").argument("<project>", "Project name or id").argument("<state>", "on | off").option("--tenant <name>", "Tenant to toggle (defaults to the project's tenant)").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, state, opts) => runIamToggle(project, state, opts)));
7364
7468
  program.command("identified").description("Require calls to identify their end user (X-End-User-Id or a login token) \u2014 or allow unattributed calls again").argument("<project>", "Project name or id").argument("<mode>", "require | allow-anon").option("--team <id|name>", "Team (defaults to active team, or your anonymous workspace)").option("--apiversion <version>", "API version (defaults to the first)").option("--json", "Output machine-readable JSON").action(action((project, mode, opts) => runIdentifiedToggle(project, mode, opts)));
7365
7469
  var apikeys = new import_commander.Command("apikeys").description("Producer control-plane API keys (list, then offer to create one)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
@@ -7377,7 +7481,7 @@ spec.command("delete-rule").description("Delete the saved rules for a route (e.g
7377
7481
  var HELP_GROUPS = [
7378
7482
  { title: "Chat", commands: ["apichat", "agent"] },
7379
7483
  { title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
7380
- { title: "Control plane commands", commands: ["config", "projects", "tenant", "group", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
7484
+ { title: "Control plane commands", commands: ["config", "projects", "tenant", "group", "admins", "iam", "identified", "preapprove", "rule", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
7381
7485
  { title: "Data plane commands", commands: [
7382
7486
  { parent: "consumer", sub: "login" },
7383
7487
  { parent: "consumer", sub: "apikeys" }
@@ -7396,7 +7500,7 @@ function groupedCommandHelp() {
7396
7500
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
7397
7501
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
7398
7502
  }).filter(Boolean).join("\n");
7399
- return `${import_chalk43.default.bold(g.title)}
7503
+ return `${import_chalk44.default.bold(g.title)}
7400
7504
  ${rows}`;
7401
7505
  }).join("\n\n");
7402
7506
  }
@@ -7430,14 +7534,14 @@ async function recoverStaleTeam() {
7430
7534
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
7431
7535
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
7432
7536
  if (!linked) {
7433
- console.error(import_chalk43.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
7537
+ console.error(import_chalk44.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
7434
7538
  return;
7435
7539
  }
7436
7540
  if (linked.teamId === creds.teamId) return;
7437
7541
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
7438
7542
  delete next.activeTenant;
7439
7543
  saveCredentials(next);
7440
- console.error(import_chalk43.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk43.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
7544
+ console.error(import_chalk44.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk44.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
7441
7545
  } catch {
7442
7546
  }
7443
7547
  }
@@ -7445,16 +7549,16 @@ async function printError(err) {
7445
7549
  if (err instanceof ApiError) {
7446
7550
  const data = err.body;
7447
7551
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
7448
- console.error(import_chalk43.default.red(`
7552
+ console.error(import_chalk44.default.red(`
7449
7553
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
7450
7554
  if (err.status === 403 || err.status === 404) {
7451
7555
  await recoverStaleTeam();
7452
7556
  }
7453
7557
  } else if (err instanceof Error) {
7454
- console.error(import_chalk43.default.red(`
7558
+ console.error(import_chalk44.default.red(`
7455
7559
  Error: ${err.message}`));
7456
7560
  } else {
7457
- console.error(import_chalk43.default.red("\nUnknown error"));
7561
+ console.error(import_chalk44.default.red("\nUnknown error"));
7458
7562
  }
7459
7563
  }
7460
7564
  program.parse(process.argv);