apiblaze 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +184 -62
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -51,6 +51,14 @@ var init_types = __esm({
51
51
  });
52
52
 
53
53
  // src/lib/auth.ts
54
+ var auth_exports = {};
55
+ __export(auth_exports, {
56
+ clearCredentials: () => clearCredentials,
57
+ getAccessToken: () => getAccessToken,
58
+ getApiblazeDir: () => getApiblazeDir,
59
+ loadCredentials: () => loadCredentials,
60
+ saveCredentials: () => saveCredentials
61
+ });
54
62
  function saveCredentials(creds) {
55
63
  fs.mkdirSync(APIBLAZE_DIR, { recursive: true });
56
64
  fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
@@ -82,6 +90,9 @@ function getAccessToken() {
82
90
  }
83
91
  return creds.accessToken;
84
92
  }
93
+ function getApiblazeDir() {
94
+ return APIBLAZE_DIR;
95
+ }
85
96
  var fs, os, path, APIBLAZE_DIR, CREDENTIALS_PATH;
86
97
  var init_auth = __esm({
87
98
  "src/lib/auth.ts"() {
@@ -232,6 +243,65 @@ var init_api = __esm({
232
243
  }
233
244
  });
234
245
 
246
+ // src/lib/team.ts
247
+ var team_exports = {};
248
+ __export(team_exports, {
249
+ resolveLinkedTeam: () => resolveLinkedTeam
250
+ });
251
+ async function resolveLinkedTeam(opts) {
252
+ let teams;
253
+ try {
254
+ teams = await getTeams();
255
+ } catch {
256
+ return opts.preferredId ? { teamId: opts.preferredId } : null;
257
+ }
258
+ if (teams.length === 0) return null;
259
+ const preferred = opts.preferredId ? teams.find((t) => t.teamId === opts.preferredId) : void 0;
260
+ if (preferred && !(opts.promptWhenMultiple && opts.interactive && teams.length > 1)) {
261
+ return { teamId: preferred.teamId, teamName: preferred.name };
262
+ }
263
+ if (preferred) {
264
+ const { default: inquirer3 } = await import("inquirer");
265
+ const { chosen: chosen2 } = await inquirer3.prompt([{
266
+ type: "list",
267
+ name: "chosen",
268
+ message: "Which team do you want to work in?",
269
+ default: preferred.teamId,
270
+ choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
271
+ }]);
272
+ const picked2 = teams.find((t) => t.teamId === chosen2);
273
+ return { teamId: picked2.teamId, teamName: picked2.name };
274
+ }
275
+ if (opts.preferredId) {
276
+ console.log(import_chalk.default.yellow("\nYour previously linked team is no longer available."));
277
+ }
278
+ if (teams.length === 1) {
279
+ console.log(`${import_chalk.default.cyan("\u2192")} Linking to your team ${import_chalk.default.bold(teams[0].name)}.`);
280
+ return { teamId: teams[0].teamId, teamName: teams[0].name };
281
+ }
282
+ if (!opts.interactive) {
283
+ console.log(import_chalk.default.yellow(`Linking to "${teams[0].name}" \u2014 pass --team to choose another.`));
284
+ return { teamId: teams[0].teamId, teamName: teams[0].name };
285
+ }
286
+ const { default: inquirer2 } = await import("inquirer");
287
+ const { chosen } = await inquirer2.prompt([{
288
+ type: "list",
289
+ name: "chosen",
290
+ message: "Which team do you want to link to?",
291
+ choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
292
+ }]);
293
+ const picked = teams.find((t) => t.teamId === chosen);
294
+ return { teamId: picked.teamId, teamName: picked.name };
295
+ }
296
+ var import_chalk;
297
+ var init_team = __esm({
298
+ "src/lib/team.ts"() {
299
+ "use strict";
300
+ import_chalk = __toESM(require("chalk"));
301
+ init_api();
302
+ }
303
+ });
304
+
235
305
  // src/lib/anon-cred.ts
236
306
  var anon_cred_exports = {};
237
307
  __export(anon_cred_exports, {
@@ -492,7 +562,7 @@ var import_commander = require("commander");
492
562
  var import_chalk34 = __toESM(require("chalk"));
493
563
 
494
564
  // package.json
495
- var version = "0.12.0";
565
+ var version = "0.14.0";
496
566
 
497
567
  // src/index.ts
498
568
  init_types();
@@ -501,43 +571,7 @@ init_types();
501
571
  var import_chalk2 = __toESM(require("chalk"));
502
572
  var import_ora = __toESM(require("ora"));
503
573
  init_auth();
504
-
505
- // src/lib/team.ts
506
- var import_chalk = __toESM(require("chalk"));
507
- init_api();
508
- async function resolveLinkedTeam(opts) {
509
- let teams;
510
- try {
511
- teams = await getTeams();
512
- } catch {
513
- return opts.preferredId ? { teamId: opts.preferredId } : null;
514
- }
515
- if (teams.length === 0) return null;
516
- const preferred = opts.preferredId ? teams.find((t) => t.teamId === opts.preferredId) : void 0;
517
- if (preferred) return { teamId: preferred.teamId, teamName: preferred.name };
518
- if (opts.preferredId) {
519
- console.log(import_chalk.default.yellow("\nYour previously linked team is no longer available."));
520
- }
521
- if (teams.length === 1) {
522
- console.log(`${import_chalk.default.cyan("\u2192")} Linking to your team ${import_chalk.default.bold(teams[0].name)}.`);
523
- return { teamId: teams[0].teamId, teamName: teams[0].name };
524
- }
525
- if (!opts.interactive) {
526
- console.log(import_chalk.default.yellow(`Linking to "${teams[0].name}" \u2014 pass --team to choose another.`));
527
- return { teamId: teams[0].teamId, teamName: teams[0].name };
528
- }
529
- const { default: inquirer2 } = await import("inquirer");
530
- const { chosen } = await inquirer2.prompt([{
531
- type: "list",
532
- name: "chosen",
533
- message: "Which team do you want to link to?",
534
- choices: teams.map((t) => ({ name: t.name, value: t.teamId }))
535
- }]);
536
- const picked = teams.find((t) => t.teamId === chosen);
537
- return { teamId: picked.teamId, teamName: picked.name };
538
- }
539
-
540
- // src/commands/login.ts
574
+ init_team();
541
575
  var DASHBOARD_BASE2 = "https://dashboard.apiblaze.com";
542
576
  function openBrowser(url) {
543
577
  const { exec } = require("child_process");
@@ -613,7 +647,12 @@ async function runLogin() {
613
647
  }
614
648
  let teamId = defaultTeamId ?? void 0;
615
649
  let teamName;
616
- const linked = await resolveLinkedTeam({ preferredId: defaultTeamId ?? void 0, interactive: !!process.stdin.isTTY });
650
+ const linked = await resolveLinkedTeam({
651
+ preferredId: defaultTeamId ?? void 0,
652
+ interactive: !!process.stdin.isTTY,
653
+ // >1 team → ASK (JWT default is just the cursor). One team → link silently.
654
+ promptWhenMultiple: true
655
+ });
617
656
  if (linked) {
618
657
  teamId = linked.teamId;
619
658
  teamName = linked.teamName;
@@ -838,6 +877,9 @@ function formatCapturedRequest(req, note) {
838
877
  ].join("\n");
839
878
  }
840
879
 
880
+ // src/commands/dev.ts
881
+ init_team();
882
+
841
883
  // src/lib/random-name.ts
842
884
  var ADJECTIVES = [
843
885
  "amber",
@@ -2729,10 +2771,12 @@ async function runTenantUse(query, opts) {
2729
2771
  }
2730
2772
  async function runTenantList(opts) {
2731
2773
  const { teamId, teamName } = await resolveTeam(opts.team);
2774
+ const limit = opts.limit ?? (opts.q ? "50" : void 0);
2775
+ const qs = `?detail=1${opts.q ? `&q=${encodeURIComponent(opts.q)}` : ""}${limit ? `&limit=${encodeURIComponent(limit)}` : ""}`;
2732
2776
  const out = await admin({
2733
2777
  method: "GET",
2734
- path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`,
2735
- summary: `List tenants for team ${teamName ?? teamId}`
2778
+ path: `/teams/${encodeURIComponent(teamId)}/tenants${qs}`,
2779
+ summary: opts.q ? `Search tenants matching "${opts.q}"` : `List tenants for team ${teamName ?? teamId}`
2736
2780
  });
2737
2781
  const tenants = out?.tenants ?? [];
2738
2782
  if (opts.json) {
@@ -2740,7 +2784,7 @@ async function runTenantList(opts) {
2740
2784
  return;
2741
2785
  }
2742
2786
  if (!tenants.length) {
2743
- console.log(import_chalk23.default.yellow("No tenants."));
2787
+ console.log(import_chalk23.default.yellow(opts.q ? `No tenants matching "${opts.q}".` : "No tenants."));
2744
2788
  return;
2745
2789
  }
2746
2790
  for (const t of tenants) {
@@ -2748,6 +2792,10 @@ async function runTenantList(opts) {
2748
2792
  const display = typeof t === "string" ? "" : import_chalk23.default.dim(` ${t.display_name ?? ""}`);
2749
2793
  console.log(` ${import_chalk23.default.bold(name)}${display}`);
2750
2794
  }
2795
+ const total = out?.total ?? tenants.length;
2796
+ if (total > tenants.length) {
2797
+ console.log(import_chalk23.default.dim(` \u2026 showing ${tenants.length} of ${total} \u2014 narrow with --q <search>`));
2798
+ }
2751
2799
  }
2752
2800
  async function runTenantCreate(opts) {
2753
2801
  if (!opts.name) {
@@ -2852,13 +2900,28 @@ var trailingComma = /\s*,\s*/;
2852
2900
  var parseList = (s) => s.split(trailingComma).map((x) => x.trim()).filter(Boolean);
2853
2901
  async function runTenantManage(query, opts) {
2854
2902
  const { teamId } = await resolveTeam(opts.team);
2855
- const slug = opts.tenant ?? loadCredentialsTenant(query) ?? await pickTenant(teamId, { message: "Manage which tenant?", initialQuery: query, allowCreate: true });
2903
+ const slug = opts.tenant ?? await validScopedTenant(teamId, query) ?? await pickTenant(teamId, { message: "Manage which tenant?", initialQuery: query, allowCreate: true });
2856
2904
  if (!slug) return;
2857
2905
  await tenantHome(teamId, slug);
2858
2906
  }
2859
- function loadCredentialsTenant(query) {
2907
+ async function validScopedTenant(teamId, query) {
2860
2908
  if (query) return void 0;
2861
- return loadCredentials()?.activeTenant ?? void 0;
2909
+ const creds = loadCredentials();
2910
+ const scoped = creds?.activeTenant;
2911
+ if (!scoped) return void 0;
2912
+ const out = await admin({
2913
+ method: "GET",
2914
+ path: `/teams/${encodeURIComponent(teamId)}/tenants?q=${encodeURIComponent(scoped)}&limit=15`,
2915
+ summary: `Verify tenant scope ${scoped}`
2916
+ }).catch(() => null);
2917
+ const names = (out?.tenants ?? []).map((t) => typeof t === "string" ? t : t.tenant_name);
2918
+ if (names.includes(scoped)) return scoped;
2919
+ const { saveCredentials: saveCredentials2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
2920
+ const next = { ...creds };
2921
+ delete next.activeTenant;
2922
+ saveCredentials2(next);
2923
+ console.log(import_chalk24.default.yellow(`Tenant scope "${scoped}" no longer exists in this team \u2014 cleared.`));
2924
+ return void 0;
2862
2925
  }
2863
2926
  async function tenantHome(teamId, tenant2) {
2864
2927
  const { default: inquirer2 } = await import("inquirer");
@@ -5044,16 +5107,25 @@ async function opCall(call) {
5044
5107
  function printResidue(report, applied) {
5045
5108
  const up = report?.upstash ?? {};
5046
5109
  const fga = report?.fga ?? {};
5110
+ const ghosts = report?.ghosts ?? {};
5047
5111
  console.log(import_chalk33.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
5048
5112
  console.log(import_chalk33.default.bold("\n Upstash"));
5049
5113
  const orphans = up.orphans ?? [];
5050
5114
  if (orphans.length === 0) console.log(import_chalk33.default.green(" no orphaned keys"));
5051
5115
  for (const o of orphans) console.log(` ${import_chalk33.default.yellow(o.key)} ${import_chalk33.default.dim(`\u2014 ${o.reason}`)}`);
5052
5116
  console.log(import_chalk33.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
5117
+ if (up.anon_wallet_detail) {
5118
+ const d = up.anon_wallet_detail;
5119
+ console.log(import_chalk33.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)"}`));
5120
+ }
5121
+ if (up.keyspace_census) {
5122
+ const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
5123
+ console.log(import_chalk33.default.dim(` keyspace: ${census}`));
5124
+ }
5053
5125
  if (up.unknown?.length) console.log(import_chalk33.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
5054
5126
  if (applied) console.log(` ${import_chalk33.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
5055
5127
  for (const e of up.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
5056
- console.log(import_chalk33.default.bold("\n OpenFGA / Neon"));
5128
+ console.log(import_chalk33.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
5057
5129
  if (applied) {
5058
5130
  const swept = fga?.swept ?? [];
5059
5131
  if (swept.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
@@ -5063,6 +5135,8 @@ function printResidue(report, applied) {
5063
5135
  );
5064
5136
  }
5065
5137
  if (fga?.remaining) console.log(import_chalk33.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
5138
+ const st = fga?.side_tables;
5139
+ if (st) console.log(import_chalk33.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})` : ""}`));
5066
5140
  } else {
5067
5141
  const fgaOrphans = fga?.orphans ?? [];
5068
5142
  if (fgaOrphans.length === 0) console.log(import_chalk33.default.green(" no orphaned stores"));
@@ -5071,8 +5145,26 @@ function printResidue(report, applied) {
5071
5145
  console.log(` ${import_chalk33.default.yellow(s.store_id)} ${import_chalk33.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
5072
5146
  }
5073
5147
  console.log(import_chalk33.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
5148
+ const st = fga?.side_tables;
5149
+ if (st) console.log(import_chalk33.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`));
5074
5150
  }
5075
5151
  for (const e of fga?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
5152
+ console.log(import_chalk33.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
5153
+ if (applied) {
5154
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk33.default.green(" no ghost tuples"));
5155
+ else console.log(` ${import_chalk33.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk33.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
5156
+ } else {
5157
+ const n = ghosts?.ghost_count ?? 0;
5158
+ if (n === 0) console.log(import_chalk33.default.green(` no ghost tuples ${import_chalk33.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
5159
+ else {
5160
+ console.log(import_chalk33.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
5161
+ for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
5162
+ console.log(import_chalk33.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
5163
+ }
5164
+ if (n > 20) console.log(import_chalk33.default.dim(` \u2026 and ${n - 20} more`));
5165
+ }
5166
+ }
5167
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk33.default.red(` error: ${e}`));
5076
5168
  console.log();
5077
5169
  }
5078
5170
  async function runOp(sub, opts = {}) {
@@ -5090,8 +5182,9 @@ async function runOp(sub, opts = {}) {
5090
5182
  console.log(import_chalk33.default.bold("\nOperator menu"));
5091
5183
  console.log(` ${import_chalk33.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
5092
5184
  console.log(` ${import_chalk33.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk33.default.dim("-y to skip")})`);
5093
- console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets
5094
- `);
5185
+ console.log(` ${import_chalk33.default.cyan("apiblaze op credits")} list credit wallets`);
5186
+ console.log(import_chalk33.default.dim(` (to prune all non-CP data: run scripts/nuke-but-cp.sh --apply --sweep in the repo)
5187
+ `));
5095
5188
  return;
5096
5189
  }
5097
5190
  case "residue": {
@@ -5104,15 +5197,20 @@ async function runOp(sub, opts = {}) {
5104
5197
  const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
5105
5198
  const nUp = report?.upstash?.orphans?.length ?? 0;
5106
5199
  const nFga = report?.fga?.orphans?.length ?? 0;
5200
+ const nGhost = report?.ghosts?.ghost_count ?? 0;
5201
+ const st = report?.fga?.side_tables ?? {};
5202
+ const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
5107
5203
  printResidue(report, false);
5108
- if (nUp + nFga === 0) {
5204
+ if (nUp + nFga + nGhost + nSide === 0) {
5109
5205
  console.log(import_chalk33.default.green("Nothing to sweep."));
5110
5206
  return;
5111
5207
  }
5112
5208
  if (!opts.yes) {
5113
5209
  const readline2 = await import("readline/promises");
5114
5210
  const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
5115
- const answer = await rl.question(import_chalk33.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
5211
+ const answer = await rl.question(
5212
+ import_chalk33.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: `)
5213
+ );
5116
5214
  rl.close();
5117
5215
  if (answer.trim() !== "sweep") return void console.log(import_chalk33.default.dim("Aborted."));
5118
5216
  }
@@ -5139,6 +5237,7 @@ async function runOp(sub, opts = {}) {
5139
5237
 
5140
5238
  // src/index.ts
5141
5239
  init_trace();
5240
+ init_auth();
5142
5241
  var program = new import_commander.Command();
5143
5242
  program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
5144
5243
  program.hook("preAction", () => {
@@ -5152,7 +5251,7 @@ function action(fn) {
5152
5251
  renderTrace();
5153
5252
  } catch (err) {
5154
5253
  renderTrace();
5155
- printError(err);
5254
+ await printError(err);
5156
5255
  process.exit(1);
5157
5256
  }
5158
5257
  };
@@ -5161,7 +5260,7 @@ program.command("login").description("Authenticate with APIblaze").action(async
5161
5260
  try {
5162
5261
  await runLogin();
5163
5262
  } catch (err) {
5164
- printError(err);
5263
+ await printError(err);
5165
5264
  process.exit(1);
5166
5265
  }
5167
5266
  });
@@ -5169,7 +5268,7 @@ program.command("create").description("Create a new API proxy (no login needed \
5169
5268
  try {
5170
5269
  await runCreate(opts);
5171
5270
  } catch (err) {
5172
- printError(err);
5271
+ await printError(err);
5173
5272
  process.exit(1);
5174
5273
  }
5175
5274
  });
@@ -5194,7 +5293,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
5194
5293
  }
5195
5294
  await runDev({ port: resolved, captureFile: opts.captureFile });
5196
5295
  } catch (err) {
5197
- printError(err);
5296
+ await printError(err);
5198
5297
  process.exit(1);
5199
5298
  }
5200
5299
  });
@@ -5207,7 +5306,7 @@ program.command("whoami").description("Show who you are \u2014 both API Producer
5207
5306
  try {
5208
5307
  await runWhoami(opts);
5209
5308
  } catch (err) {
5210
- printError(err);
5309
+ await printError(err);
5211
5310
  process.exit(1);
5212
5311
  }
5213
5312
  });
@@ -5215,7 +5314,7 @@ program.command("claim").description("Claim your anonymous workspace into your a
5215
5314
  try {
5216
5315
  await runClaim(code, opts);
5217
5316
  } catch (err) {
5218
- printError(err);
5317
+ await printError(err);
5219
5318
  process.exit(1);
5220
5319
  }
5221
5320
  });
@@ -5223,7 +5322,7 @@ program.command("team").description("Switch the active team").argument("[team]",
5223
5322
  try {
5224
5323
  await runTeam(team);
5225
5324
  } catch (err) {
5226
- printError(err);
5325
+ await printError(err);
5227
5326
  process.exit(1);
5228
5327
  }
5229
5328
  });
@@ -5231,7 +5330,7 @@ program.command("projects").description("List the projects in your team").action
5231
5330
  try {
5232
5331
  await runProjects();
5233
5332
  } catch (err) {
5234
- printError(err);
5333
+ await printError(err);
5235
5334
  process.exit(1);
5236
5335
  }
5237
5336
  });
@@ -5247,10 +5346,10 @@ domain.command("list").description("List custom domains for a proxy").argument("
5247
5346
  domain.command("status").description("Check a custom domain's validation status").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainStatus(project, opts)));
5248
5347
  domain.command("rm").description("Remove a custom domain").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
5249
5348
  domain.command("set-base").description("Choose which version/environment your main URL serves").argument("<project>", "Project name or id").option("--env <env>", "Environment (default: prod)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainSetBase(project, opts)));
5250
- var tenant = program.command("tenant").description("Manage tenants \u2014 separate groups of your API's users");
5349
+ var tenant = program.command("tenant").description("Manage tenants \u2014 bare command opens the interactive picker (settings, app clients, providers)").action(action(() => runTenantManage(void 0, {})));
5251
5350
  tenant.command("manage").description("Browse & edit one tenant: settings, login app clients, providers, issuers (search-first picker)").argument("[query]", "Search by tenant name/display name (omit to use your tenant scope or pick)").option("--tenant <slug>", "Exact tenant slug (skips the picker)").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantManage(query, opts)));
5252
5351
  tenant.command("use").description("Set the sticky tenant scope for future commands (search-first; --clear to unset)").argument("[query]", "Search by tenant name/display name").option("--clear", "Clear the tenant scope").option("--team <id|name>", "Team (defaults to active team)").action(action((query, opts) => runTenantUse(query, opts)));
5253
- tenant.command("list").description("List tenants in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
5352
+ tenant.command("list").description("List tenants in your team (--q searches server-side)").option("--q <search>", "Filter by tenant name or display name").option("--limit <n>", "Page size (max 200)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
5254
5353
  tenant.command("create").description("Create a tenant in your team").requiredOption("--name <display>", "Display name").option("--slug <tenant_name>", "Explicit tenant slug (generated if omitted)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
5255
5354
  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)));
5256
5355
  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)));
@@ -5313,10 +5412,33 @@ Examples:
5313
5412
  $ npx apiblaze throttle myapi --rate 50 --verbose # configure + show the API call
5314
5413
  $ npx apiblaze consumer login # act as a consumer of your API
5315
5414
  `);
5316
- function printError(err) {
5415
+ async function recoverStaleTeam() {
5416
+ try {
5417
+ const creds = loadCredentials();
5418
+ if (!creds?.teamId) return;
5419
+ const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
5420
+ const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
5421
+ if (!linked) {
5422
+ console.error(import_chalk34.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
5423
+ return;
5424
+ }
5425
+ if (linked.teamId === creds.teamId) return;
5426
+ const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
5427
+ delete next.activeTenant;
5428
+ saveCredentials(next);
5429
+ console.error(import_chalk34.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk34.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
5430
+ } catch {
5431
+ }
5432
+ }
5433
+ async function printError(err) {
5317
5434
  if (err instanceof ApiError) {
5435
+ const data = err.body;
5436
+ const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
5318
5437
  console.error(import_chalk34.default.red(`
5319
- API error (${err.status}): ${err.message}`));
5438
+ API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
5439
+ if (err.status === 403 || err.status === 404) {
5440
+ await recoverStaleTeam();
5441
+ }
5320
5442
  } else if (err instanceof Error) {
5321
5443
  console.error(import_chalk34.default.red(`
5322
5444
  Error: ${err.message}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.12.0",
3
+ "version": "0.14.0",
4
4
  "description": "APIblaze CLI + sidecar — manage API proxies, run dev tunnels, and route a Next.js app's egress through APIblaze with one command",
5
5
  "keywords": [
6
6
  "apiblaze",