apiblaze 0.6.1 → 0.8.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.
package/dist/index.js CHANGED
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
9
16
  var __copyProps = (to, from, except, desc) => {
10
17
  if (from && typeof from === "object" || typeof from === "function") {
11
18
  for (let key of __getOwnPropNames(from))
@@ -23,33 +30,27 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
30
  mod
24
31
  ));
25
32
 
26
- // src/index.ts
27
- var import_commander = require("commander");
28
- var import_chalk30 = __toESM(require("chalk"));
29
-
30
- // package.json
31
- var version = "0.6.1";
32
-
33
33
  // src/types.ts
34
- var ApiError = class extends Error {
35
- constructor(status, message, body) {
36
- super(message);
37
- this.status = status;
38
- this.body = body;
39
- this.name = "ApiError";
34
+ var types_exports = {};
35
+ __export(types_exports, {
36
+ ApiError: () => ApiError
37
+ });
38
+ var ApiError;
39
+ var init_types = __esm({
40
+ "src/types.ts"() {
41
+ "use strict";
42
+ ApiError = class extends Error {
43
+ constructor(status, message, body) {
44
+ super(message);
45
+ this.status = status;
46
+ this.body = body;
47
+ this.name = "ApiError";
48
+ }
49
+ };
40
50
  }
41
- };
42
-
43
- // src/commands/login.ts
44
- var import_chalk2 = __toESM(require("chalk"));
45
- var import_ora = __toESM(require("ora"));
51
+ });
46
52
 
47
53
  // src/lib/auth.ts
48
- var fs = __toESM(require("fs"));
49
- var os = __toESM(require("os"));
50
- var path = __toESM(require("path"));
51
- var APIBLAZE_DIR = path.join(os.homedir(), ".apiblaze");
52
- var CREDENTIALS_PATH = path.join(APIBLAZE_DIR, "credentials.json");
53
54
  function saveCredentials(creds) {
54
55
  fs.mkdirSync(APIBLAZE_DIR, { recursive: true });
55
56
  fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
@@ -81,13 +82,34 @@ function getAccessToken() {
81
82
  }
82
83
  return creds.accessToken;
83
84
  }
84
-
85
- // src/lib/team.ts
86
- var import_chalk = __toESM(require("chalk"));
85
+ var fs, os, path, APIBLAZE_DIR, CREDENTIALS_PATH;
86
+ var init_auth = __esm({
87
+ "src/lib/auth.ts"() {
88
+ "use strict";
89
+ fs = __toESM(require("fs"));
90
+ os = __toESM(require("os"));
91
+ path = __toESM(require("path"));
92
+ APIBLAZE_DIR = path.join(os.homedir(), ".apiblaze");
93
+ CREDENTIALS_PATH = path.join(APIBLAZE_DIR, "credentials.json");
94
+ }
95
+ });
87
96
 
88
97
  // src/lib/api.ts
89
- var DASHBOARD_BASE = "https://dashboard.apiblaze.com";
90
- var PUBLIC_API_BASE = "https://api.apiblaze.com";
98
+ var api_exports = {};
99
+ __export(api_exports, {
100
+ agentCall: () => agentCall,
101
+ checkProxyName: () => checkProxyName,
102
+ claimProxy: () => claimProxy,
103
+ claimTeam: () => claimTeam,
104
+ createProxy: () => createProxy,
105
+ createProxyAnonymous: () => createProxyAnonymous,
106
+ deleteDevTunnel: () => deleteDevTunnel,
107
+ getLocalhostTargets: () => getLocalhostTargets,
108
+ getProjects: () => getProjects,
109
+ getTeams: () => getTeams,
110
+ putDevTunnel: () => putDevTunnel,
111
+ sidecarInitAnonymous: () => sidecarInitAnonymous
112
+ });
91
113
  async function createProxyAnonymous(body) {
92
114
  const res = await fetch(`${PUBLIC_API_BASE}/proxy`, {
93
115
  method: "POST",
@@ -105,9 +127,9 @@ async function createProxyAnonymous(body) {
105
127
  }
106
128
  return res.json();
107
129
  }
108
- async function apiFetch(path4, options = {}) {
130
+ async function apiFetch(path5, options = {}) {
109
131
  const token = getAccessToken();
110
- const url = `${DASHBOARD_BASE}${path4}`;
132
+ const url = `${DASHBOARD_BASE}${path5}`;
111
133
  const res = await fetch(url, {
112
134
  ...options,
113
135
  headers: {
@@ -121,8 +143,9 @@ async function apiFetch(path4, options = {}) {
121
143
  let parsed;
122
144
  try {
123
145
  parsed = await res.json();
146
+ const pick2 = (v) => typeof v === "string" ? v : v && typeof v === "object" ? pick2(v.message) ?? pick2(v.error) : void 0;
124
147
  const b = parsed;
125
- message = b.message ?? b.error ?? message;
148
+ message = pick2(b.message) ?? pick2(b.error) ?? pick2(b.details) ?? message;
126
149
  } catch {
127
150
  }
128
151
  throw new ApiError(res.status, message, parsed);
@@ -132,12 +155,12 @@ async function apiFetch(path4, options = {}) {
132
155
  }
133
156
  return res.json();
134
157
  }
135
- async function agentCall(path4, method, body) {
158
+ async function agentCall(path5, method, body) {
136
159
  const token = getAccessToken();
137
160
  const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
138
161
  method: "POST",
139
162
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
140
- body: JSON.stringify({ path: path4, method, body })
163
+ body: JSON.stringify({ path: path5, method, body })
141
164
  });
142
165
  let data = null;
143
166
  try {
@@ -177,6 +200,22 @@ async function claimProxy(claimCode, teamId) {
177
200
  body: JSON.stringify({ claimCode, team_id: teamId })
178
201
  });
179
202
  }
203
+ async function claimTeam(code) {
204
+ return apiFetch("/api/cli/claim-team", {
205
+ method: "POST",
206
+ body: JSON.stringify({ code })
207
+ });
208
+ }
209
+ async function sidecarInitAnonymous() {
210
+ const res = await fetch(`${PUBLIC_API_BASE}/sidecar/init`, {
211
+ method: "POST",
212
+ headers: { "content-type": "application/json" },
213
+ body: "{}"
214
+ });
215
+ const data = await res.json().catch(() => ({}));
216
+ if (!res.ok) throw new ApiError(res.status, data?.error ?? "init_failed", data);
217
+ return data;
218
+ }
180
219
  async function putDevTunnel(payload) {
181
220
  return apiFetch("/api/cli/dev-tunnel", {
182
221
  method: "PUT",
@@ -189,8 +228,103 @@ async function deleteDevTunnel(restore) {
189
228
  body: JSON.stringify({ restore })
190
229
  });
191
230
  }
231
+ var DASHBOARD_BASE, PUBLIC_API_BASE;
232
+ var init_api = __esm({
233
+ "src/lib/api.ts"() {
234
+ "use strict";
235
+ init_auth();
236
+ init_types();
237
+ DASHBOARD_BASE = "https://dashboard.apiblaze.com";
238
+ PUBLIC_API_BASE = "https://api.apiblaze.com";
239
+ }
240
+ });
241
+
242
+ // src/lib/anon-cred.ts
243
+ var anon_cred_exports = {};
244
+ __export(anon_cred_exports, {
245
+ clearAnonCred: () => clearAnonCred,
246
+ cpFetch: () => cpFetch,
247
+ loadAnonCred: () => loadAnonCred,
248
+ saveAnonCred: () => saveAnonCred
249
+ });
250
+ function loadAnonCred() {
251
+ try {
252
+ const c = JSON.parse(fs2.readFileSync(FILE, "utf8"));
253
+ return c?.cp_key ? c : null;
254
+ } catch {
255
+ return null;
256
+ }
257
+ }
258
+ function saveAnonCred(cp_key, team_id, claim_code) {
259
+ try {
260
+ fs2.mkdirSync(DIR, { recursive: true });
261
+ const prev = loadAnonCred();
262
+ const merged = {
263
+ cp_key,
264
+ team_id,
265
+ claim_code: claim_code ?? prev?.claim_code,
266
+ created_at: prev?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
267
+ };
268
+ fs2.writeFileSync(FILE, JSON.stringify(merged, null, 2));
269
+ } catch {
270
+ }
271
+ }
272
+ function clearAnonCred() {
273
+ try {
274
+ fs2.unlinkSync(FILE);
275
+ } catch {
276
+ }
277
+ }
278
+ async function cpFetch(cpKey, apiPath, opts = {}) {
279
+ const res = await fetch(`${CP_EDGE}${apiPath}`, {
280
+ ...opts,
281
+ headers: { "Content-Type": "application/json", "X-API-Key": cpKey, ...opts.headers ?? {} }
282
+ });
283
+ const text = await res.text();
284
+ let parsed;
285
+ try {
286
+ parsed = text ? JSON.parse(text) : void 0;
287
+ } catch {
288
+ parsed = text;
289
+ }
290
+ if (!res.ok) {
291
+ const msg = parsed && typeof parsed === "object" && (parsed.details ?? parsed.error) || res.statusText;
292
+ throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), parsed);
293
+ }
294
+ return parsed;
295
+ }
296
+ var fs2, path2, os2, DIR, FILE, CP_EDGE;
297
+ var init_anon_cred = __esm({
298
+ "src/lib/anon-cred.ts"() {
299
+ "use strict";
300
+ fs2 = __toESM(require("fs"));
301
+ path2 = __toESM(require("path"));
302
+ os2 = __toESM(require("os"));
303
+ init_types();
304
+ DIR = path2.join(os2.homedir(), ".apiblaze");
305
+ FILE = path2.join(DIR, "anon.json");
306
+ CP_EDGE = process.env.APIBLAZE_API_BASE ? `${process.env.APIBLAZE_API_BASE}/1.0.0/prod` : "https://api.apiblaze.com/1.0.0/prod";
307
+ }
308
+ });
309
+
310
+ // src/index.ts
311
+ var import_commander = require("commander");
312
+ var import_chalk30 = __toESM(require("chalk"));
313
+
314
+ // package.json
315
+ var version = "0.8.0";
316
+
317
+ // src/index.ts
318
+ init_types();
319
+
320
+ // src/commands/login.ts
321
+ var import_chalk2 = __toESM(require("chalk"));
322
+ var import_ora = __toESM(require("ora"));
323
+ init_auth();
192
324
 
193
325
  // src/lib/team.ts
326
+ var import_chalk = __toESM(require("chalk"));
327
+ init_api();
194
328
  async function resolveLinkedTeam(opts) {
195
329
  let teams;
196
330
  try {
@@ -319,6 +453,37 @@ ${import_chalk2.default.cyan("\u2192")} Team: ${import_chalk2.default.bold(teamN
319
453
  teamName
320
454
  });
321
455
  console.log(import_chalk2.default.green("\n\u2714 Logged in successfully!"));
456
+ await offerClaimPendingAnon();
457
+ }
458
+ async function offerClaimPendingAnon() {
459
+ const { loadAnonCred: loadAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
460
+ const cred = loadAnonCred2();
461
+ if (!cred?.claim_code) return;
462
+ let doClaim = true;
463
+ if (process.stdin.isTTY) {
464
+ const { default: inquirer2 } = await import("inquirer");
465
+ const { yes } = await inquirer2.prompt([{
466
+ type: "confirm",
467
+ name: "yes",
468
+ message: "You created proxies anonymously before logging in. Claim them into this account?",
469
+ default: true
470
+ }]);
471
+ doClaim = yes;
472
+ }
473
+ if (!doClaim) {
474
+ console.log(import_chalk2.default.dim(" Left unclaimed \u2014 run `apiblaze claim` anytime to keep them (they GC after inactivity)."));
475
+ return;
476
+ }
477
+ try {
478
+ const { claimTeam: claimTeam2 } = await Promise.resolve().then(() => (init_api(), api_exports));
479
+ const t = await claimTeam2(cred.claim_code);
480
+ clearAnonCred2();
481
+ console.log(import_chalk2.default.green(` \u2714 Claimed your anonymous proxies into your account (team ${import_chalk2.default.bold(t.team_id)}).`));
482
+ } catch (err) {
483
+ const { ApiError: ApiError2 } = await Promise.resolve().then(() => (init_types(), types_exports));
484
+ const why = err instanceof ApiError2 ? err.message : err instanceof Error ? err.message : "error";
485
+ console.log(import_chalk2.default.yellow(` Couldn't auto-claim (${why}). Run \`apiblaze claim\` to retry.`));
486
+ }
322
487
  }
323
488
 
324
489
  // src/commands/dev.ts
@@ -326,6 +491,8 @@ var import_fs = __toESM(require("fs"));
326
491
  var import_chalk4 = __toESM(require("chalk"));
327
492
  var import_ora2 = __toESM(require("ora"));
328
493
  var import_inquirer = __toESM(require("inquirer"));
494
+ init_auth();
495
+ init_api();
329
496
 
330
497
  // src/lib/traffic.ts
331
498
  var import_chalk3 = __toESM(require("chalk"));
@@ -420,11 +587,11 @@ function decodeJwt(token) {
420
587
  return null;
421
588
  }
422
589
  }
423
- function maskPath(path4) {
424
- const q = path4.indexOf("?");
425
- if (q < 0) return path4;
426
- const base = path4.slice(0, q);
427
- const query = path4.slice(q + 1);
590
+ function maskPath(path5) {
591
+ const q = path5.indexOf("?");
592
+ if (q < 0) return path5;
593
+ const base = path5.slice(0, q);
594
+ const query = path5.slice(q + 1);
428
595
  const masked = query.split("&").map((pair) => {
429
596
  const eq = pair.indexOf("=");
430
597
  if (eq < 0) return pair;
@@ -1018,6 +1185,8 @@ Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
1018
1185
  // src/commands/projects.ts
1019
1186
  var import_chalk5 = __toESM(require("chalk"));
1020
1187
  var import_ora3 = __toESM(require("ora"));
1188
+ init_auth();
1189
+ init_api();
1021
1190
  async function runProjects() {
1022
1191
  const creds = loadCredentials();
1023
1192
  if (!creds) {
@@ -1077,6 +1246,8 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
1077
1246
  var import_fs2 = __toESM(require("fs"));
1078
1247
  var import_chalk6 = __toESM(require("chalk"));
1079
1248
  var import_ora4 = __toESM(require("ora"));
1249
+ init_auth();
1250
+ init_api();
1080
1251
  function normalizeName(raw) {
1081
1252
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
1082
1253
  }
@@ -1345,10 +1516,23 @@ async function runAnonymousCreate(opts) {
1345
1516
  if (opts.displayName) body.display_name = opts.displayName;
1346
1517
  if (opts.apiversion) body.api_version = opts.apiversion;
1347
1518
  if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
1348
- const spinner = !opts.json ? (0, import_ora4.default)("Creating proxy...").start() : null;
1519
+ const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
1520
+ if (opts.newSession) clearAnonCred2();
1521
+ const cred = loadAnonCred2();
1522
+ const spinner = !opts.json ? (0, import_ora4.default)(cred ? "Creating proxy (in your anonymous workspace)..." : "Creating proxy...").start() : null;
1349
1523
  let result;
1350
1524
  try {
1351
- result = await createProxyAnonymous(body);
1525
+ if (cred) {
1526
+ result = await cpFetch2(cred.cp_key, "/projects", { method: "POST", body: JSON.stringify(body) });
1527
+ if (Array.isArray(result.endpoints)) {
1528
+ result.endpoints = result.endpoints.map((e) => e.replace(/:\/\/[^/]+/, `://${result.project_id}.tryabz.run`));
1529
+ }
1530
+ } else {
1531
+ result = await createProxyAnonymous(body);
1532
+ if (result.cp_key && result.team_id) {
1533
+ saveAnonCred2(result.cp_key, result.team_id, result.team_claim_code);
1534
+ }
1535
+ }
1352
1536
  spinner?.succeed(import_chalk6.default.green("Proxy created!"));
1353
1537
  } catch (err) {
1354
1538
  spinner?.fail("Failed to create proxy.");
@@ -1380,17 +1564,27 @@ async function runAnonymousCreate(opts) {
1380
1564
  console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
1381
1565
  }
1382
1566
  if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
1383
- if (result.claim_url) {
1567
+ const claimCode = result.team_claim_code || cred?.claim_code;
1568
+ if (claimCode) {
1569
+ console.log();
1570
+ console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous \u2014 claim within 30 days or it expires.")} All your anonymous proxies`);
1571
+ console.log(` on this machine share ONE workspace. To keep them all:`);
1572
+ console.log(` ${import_chalk6.default.cyan("apiblaze login")} ${import_chalk6.default.dim("(prompts to claim them into your account)")}`);
1573
+ console.log(import_chalk6.default.dim(` From another machine: apiblaze claim ${claimCode}`));
1574
+ } else if (result.claim_url) {
1384
1575
  console.log();
1385
1576
  console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
1386
1577
  console.log(` ${import_chalk6.default.bold(result.claim_url)}`);
1387
- console.log(import_chalk6.default.dim(" (or run `apiblaze login`, then re-run `apiblaze create` to create it under your account)"));
1388
1578
  }
1389
1579
  console.log();
1390
1580
  }
1391
1581
 
1392
1582
  // src/commands/claim.ts
1393
1583
  var import_chalk7 = __toESM(require("chalk"));
1584
+ init_auth();
1585
+ init_anon_cred();
1586
+ init_api();
1587
+ init_types();
1394
1588
  function fail2(message) {
1395
1589
  console.error(import_chalk7.default.red(`Error: ${message}`));
1396
1590
  process.exit(1);
@@ -1401,6 +1595,14 @@ async function runClaim(claimCodeArg, opts) {
1401
1595
  fail2("Not logged in. Run `apiblaze login` first, then claim.");
1402
1596
  }
1403
1597
  let claimCode = (claimCodeArg ?? "").trim();
1598
+ let fromLocalSession = false;
1599
+ if (!claimCode) {
1600
+ const sess = loadAnonCred();
1601
+ if (sess?.claim_code) {
1602
+ claimCode = sess.claim_code;
1603
+ fromLocalSession = true;
1604
+ }
1605
+ }
1404
1606
  if (!claimCode && process.stdin.isTTY) {
1405
1607
  const { default: inquirer2 } = await import("inquirer");
1406
1608
  const { code } = await inquirer2.prompt([{
@@ -1411,7 +1613,30 @@ async function runClaim(claimCodeArg, opts) {
1411
1613
  claimCode = (code ?? "").trim();
1412
1614
  }
1413
1615
  if (!claimCode) {
1414
- fail2("A claim code is required: `apiblaze claim <code>`.");
1616
+ fail2("Nothing to claim on this machine. Run `apiblaze claim <code>` with a code, or `apiblaze create`/`init` first.");
1617
+ }
1618
+ try {
1619
+ const t = await claimTeam(claimCode);
1620
+ if (fromLocalSession) clearAnonCred();
1621
+ if (opts.json) {
1622
+ console.log(JSON.stringify(t, null, 2));
1623
+ return;
1624
+ }
1625
+ console.log(import_chalk7.default.green(`
1626
+ \u2714 Claimed the whole team into your account.`));
1627
+ console.log(` Team: ${import_chalk7.default.bold(t.team_id)}`);
1628
+ console.log(` Its proxies + keys are now yours. Switch to it with ${import_chalk7.default.cyan(`apiblaze team ${t.team_id}`)}.`);
1629
+ console.log(`
1630
+ Manage it at ${import_chalk7.default.cyan("https://dashboard.apiblaze.com/dashboard")}`);
1631
+ return;
1632
+ } catch (err) {
1633
+ if (err instanceof ApiError && err.status !== 404) {
1634
+ const code = typeof err.body === "object" && err.body ? err.body.error : void 0;
1635
+ if (code === "already_claimed") fail2("That claim code has already been used.");
1636
+ if (code === "expired") fail2("That claim code has expired. Create a fresh setup with `apiblaze init`.");
1637
+ if (code === "not_claimable") fail2("That team can no longer be claimed (it already belongs to an account).");
1638
+ fail2(`Could not claim: ${err.message}`);
1639
+ }
1415
1640
  }
1416
1641
  const teams = await getTeams().catch(() => []);
1417
1642
  if (teams.length === 0) {
@@ -1443,7 +1668,13 @@ async function runClaim(claimCodeArg, opts) {
1443
1668
  fail2(`Specify a team: \`apiblaze claim ${claimCode} --team <name|id>\`. Available: ${teams.map((t) => t.name).join(", ")}.`);
1444
1669
  }
1445
1670
  const teamName = teams.find((t) => t.teamId === teamId)?.name ?? teamId;
1446
- const result = await claimProxy(claimCode, teamId);
1671
+ let result;
1672
+ try {
1673
+ result = await claimProxy(claimCode, teamId);
1674
+ } catch (err) {
1675
+ if (err instanceof ApiError) fail2(`That claim code isn't valid \u2014 it may be mistyped, already claimed, or expired.`);
1676
+ throw err;
1677
+ }
1447
1678
  if (opts.json) {
1448
1679
  console.log(JSON.stringify(result, null, 2));
1449
1680
  return;
@@ -1461,32 +1692,33 @@ async function runClaim(claimCodeArg, opts) {
1461
1692
 
1462
1693
  // src/commands/logout.ts
1463
1694
  var import_chalk8 = __toESM(require("chalk"));
1695
+ init_auth();
1464
1696
 
1465
1697
  // src/lib/consumer-auth.ts
1466
- var fs4 = __toESM(require("fs"));
1467
- var os2 = __toESM(require("os"));
1468
- var path2 = __toESM(require("path"));
1698
+ var fs5 = __toESM(require("fs"));
1699
+ var os3 = __toESM(require("os"));
1700
+ var path3 = __toESM(require("path"));
1469
1701
  var crypto = __toESM(require("crypto"));
1470
1702
  var import_child_process = require("child_process");
1471
1703
  var AUTH_BASE = process.env.APIBLAZE_AUTH_BASE || "https://auth.apiblaze.com";
1472
- var APIBLAZE_DIR2 = path2.join(os2.homedir(), ".apiblaze");
1473
- var CONSUMER_PATH = path2.join(APIBLAZE_DIR2, "consumer.json");
1704
+ var APIBLAZE_DIR2 = path3.join(os3.homedir(), ".apiblaze");
1705
+ var CONSUMER_PATH = path3.join(APIBLAZE_DIR2, "consumer.json");
1474
1706
  var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
1475
1707
  function loadConsumer() {
1476
1708
  try {
1477
- return JSON.parse(fs4.readFileSync(CONSUMER_PATH, "utf-8"));
1709
+ return JSON.parse(fs5.readFileSync(CONSUMER_PATH, "utf-8"));
1478
1710
  } catch {
1479
1711
  return null;
1480
1712
  }
1481
1713
  }
1482
1714
  function saveConsumer(creds) {
1483
- fs4.mkdirSync(APIBLAZE_DIR2, { recursive: true });
1484
- fs4.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
1485
- fs4.chmodSync(CONSUMER_PATH, 384);
1715
+ fs5.mkdirSync(APIBLAZE_DIR2, { recursive: true });
1716
+ fs5.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
1717
+ fs5.chmodSync(CONSUMER_PATH, 384);
1486
1718
  }
1487
1719
  function clearConsumer() {
1488
1720
  try {
1489
- fs4.unlinkSync(CONSUMER_PATH);
1721
+ fs5.unlinkSync(CONSUMER_PATH);
1490
1722
  return true;
1491
1723
  } catch {
1492
1724
  return false;
@@ -1628,6 +1860,7 @@ async function runLogout(opts = {}) {
1628
1860
 
1629
1861
  // src/commands/whoami.ts
1630
1862
  var import_chalk9 = __toESM(require("chalk"));
1863
+ init_auth();
1631
1864
  async function runWhoami(opts = {}) {
1632
1865
  const creds = loadCredentials();
1633
1866
  const consumer2 = loadConsumer();
@@ -1668,6 +1901,8 @@ async function runWhoami(opts = {}) {
1668
1901
 
1669
1902
  // src/commands/team.ts
1670
1903
  var import_chalk10 = __toESM(require("chalk"));
1904
+ init_auth();
1905
+ init_api();
1671
1906
  function fail3(message) {
1672
1907
  console.error(import_chalk10.default.red(`Error: ${message}`));
1673
1908
  process.exit(1);
@@ -1712,10 +1947,13 @@ async function runTeam(arg) {
1712
1947
 
1713
1948
  // src/commands/authz.ts
1714
1949
  var import_chalk12 = __toESM(require("chalk"));
1950
+ init_auth();
1951
+ init_api();
1715
1952
 
1716
1953
  // src/lib/agent-chat.ts
1717
1954
  var import_readline = __toESM(require("readline"));
1718
1955
  var import_chalk11 = __toESM(require("chalk"));
1956
+ init_api();
1719
1957
  async function runAgentChatRepl(opts) {
1720
1958
  const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
1721
1959
  console.log("\n" + import_chalk11.default.cyan.bold(title));
@@ -1885,6 +2123,8 @@ async function runAuthz(projectArg, apiVersionArg) {
1885
2123
 
1886
2124
  // src/commands/openapi.ts
1887
2125
  var import_chalk13 = __toESM(require("chalk"));
2126
+ init_auth();
2127
+ init_api();
1888
2128
  async function runOpenapi(projectArg, apiVersionArg) {
1889
2129
  const creds = loadCredentials();
1890
2130
  if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
@@ -1949,6 +2189,8 @@ async function runOpenapi(projectArg, apiVersionArg) {
1949
2189
 
1950
2190
  // src/commands/mcp.ts
1951
2191
  var import_chalk14 = __toESM(require("chalk"));
2192
+ init_auth();
2193
+ init_api();
1952
2194
  async function runMcp(projectArg, apiVersionArg, opts) {
1953
2195
  const creds = loadCredentials();
1954
2196
  if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
@@ -1994,6 +2236,7 @@ var import_ora5 = __toESM(require("ora"));
1994
2236
 
1995
2237
  // src/lib/admin.ts
1996
2238
  var import_chalk16 = __toESM(require("chalk"));
2239
+ init_auth();
1997
2240
 
1998
2241
  // src/lib/trace.ts
1999
2242
  var import_chalk15 = __toESM(require("chalk"));
@@ -2036,6 +2279,7 @@ function renderTrace() {
2036
2279
  }
2037
2280
 
2038
2281
  // src/lib/admin.ts
2282
+ init_types();
2039
2283
  var DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2040
2284
  async function admin(call) {
2041
2285
  const token = getAccessToken();
@@ -2068,6 +2312,8 @@ function maybePrintBilling(data) {
2068
2312
 
2069
2313
  // src/lib/resolve.ts
2070
2314
  var import_chalk17 = __toESM(require("chalk"));
2315
+ init_auth();
2316
+ init_api();
2071
2317
  function requireAuth() {
2072
2318
  const creds = loadCredentials();
2073
2319
  if (!creds) {
@@ -2175,6 +2421,7 @@ async function runDelete(project, version2, opts) {
2175
2421
  // src/commands/export.ts
2176
2422
  var import_chalk19 = __toESM(require("chalk"));
2177
2423
  var import_fs3 = require("fs");
2424
+ init_auth();
2178
2425
  var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2179
2426
  async function runExport(projectArg, versionArg, opts) {
2180
2427
  const token = getAccessToken();
@@ -2581,7 +2828,7 @@ async function runKeyRevoke(keyId, opts) {
2581
2828
  }
2582
2829
 
2583
2830
  // src/commands/spec.ts
2584
- var fs5 = __toESM(require("fs"));
2831
+ var fs6 = __toESM(require("fs"));
2585
2832
  var import_chalk24 = __toESM(require("chalk"));
2586
2833
  var import_ora10 = __toESM(require("ora"));
2587
2834
  async function runSpecGet(project, opts) {
@@ -2601,7 +2848,7 @@ async function runSpecSet(project, opts) {
2601
2848
  }
2602
2849
  let specContent;
2603
2850
  try {
2604
- specContent = fs5.readFileSync(opts.file, "utf-8");
2851
+ specContent = fs6.readFileSync(opts.file, "utf-8");
2605
2852
  } catch {
2606
2853
  console.error(import_chalk24.default.red(`Cannot read file: ${opts.file}`));
2607
2854
  process.exit(1);
@@ -2627,9 +2874,11 @@ async function runSpecSet(project, opts) {
2627
2874
  // src/commands/agent.ts
2628
2875
  var import_chalk26 = __toESM(require("chalk"));
2629
2876
  var import_ora11 = __toESM(require("ora"));
2877
+ init_auth();
2630
2878
 
2631
2879
  // src/lib/tools.ts
2632
2880
  var import_chalk25 = __toESM(require("chalk"));
2881
+ init_api();
2633
2882
  async function proj(teamId, name, version2) {
2634
2883
  return resolveProject(teamId, name, version2);
2635
2884
  }
@@ -2779,6 +3028,7 @@ function findTool(name) {
2779
3028
  }
2780
3029
 
2781
3030
  // src/commands/agent.ts
3031
+ init_types();
2782
3032
  var DASHBOARD_BASE5 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2783
3033
  var MAX_TOOL_STEPS = 6;
2784
3034
  async function callAgent(messages, teamId) {
@@ -3040,44 +3290,59 @@ async function runConsumerApikeys(opts) {
3040
3290
  // src/commands/sidecar.ts
3041
3291
  var import_chalk28 = __toESM(require("chalk"));
3042
3292
  var import_ora13 = __toESM(require("ora"));
3043
- var fs6 = __toESM(require("fs"));
3044
- var path3 = __toESM(require("path"));
3293
+ var fs7 = __toESM(require("fs"));
3294
+ var path4 = __toESM(require("path"));
3295
+ init_auth();
3045
3296
  function detectNextProject(root) {
3046
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs6.existsSync(path3.join(root, f)));
3297
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs7.existsSync(path4.join(root, f)));
3047
3298
  let hasDep = false;
3048
3299
  try {
3049
- const pkg = JSON.parse(fs6.readFileSync(path3.join(root, "package.json"), "utf8"));
3300
+ const pkg = JSON.parse(fs7.readFileSync(path4.join(root, "package.json"), "utf8"));
3050
3301
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
3051
3302
  } catch {
3052
3303
  }
3053
- const appDir = fs6.existsSync(path3.join(root, "app")) || fs6.existsSync(path3.join(root, "src", "app"));
3054
- const pagesDir = fs6.existsSync(path3.join(root, "pages")) || fs6.existsSync(path3.join(root, "src", "pages"));
3304
+ const appDir = fs7.existsSync(path4.join(root, "app")) || fs7.existsSync(path4.join(root, "src", "app"));
3305
+ const pagesDir = fs7.existsSync(path4.join(root, "pages")) || fs7.existsSync(path4.join(root, "src", "pages"));
3055
3306
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
3056
3307
  }
3057
3308
  function upsertEnvLocal(root, token) {
3058
- const p = path3.join(root, ".env.local");
3309
+ const p = path4.join(root, ".env.local");
3059
3310
  let existing = "";
3060
3311
  try {
3061
- existing = fs6.readFileSync(p, "utf8");
3312
+ existing = fs7.readFileSync(p, "utf8");
3062
3313
  } catch {
3063
3314
  }
3064
- const had = /^APIBLAZE_TOKEN=/m.test(existing);
3065
- const next = had ? existing.replace(/^APIBLAZE_TOKEN=.*$/m, `APIBLAZE_TOKEN=${token}`) : (existing && !existing.endsWith("\n") ? existing + "\n" : existing) + `APIBLAZE_TOKEN=${token}
3315
+ const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
3316
+ let next = existing.replace(/^APIBLAZE_TOKEN=.*\n?/m, "");
3317
+ next = /^APIBLAZE_API_KEY=/m.test(next) ? next.replace(/^APIBLAZE_API_KEY=.*$/m, `APIBLAZE_API_KEY=${token}`) : (next && !next.endsWith("\n") ? next + "\n" : next) + `APIBLAZE_API_KEY=${token}
3318
+ `;
3319
+ if (!/^APIBLAZE_SIDECAR=/m.test(next)) {
3320
+ next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR=on
3066
3321
  `;
3067
- fs6.writeFileSync(p, next);
3322
+ }
3323
+ fs7.writeFileSync(p, next);
3068
3324
  return had ? "rotated" : "created";
3069
3325
  }
3326
+ function readEnvKey(root) {
3327
+ try {
3328
+ const s = fs7.readFileSync(path4.join(root, ".env.local"), "utf8");
3329
+ const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
3330
+ return m ? m[1].trim() : null;
3331
+ } catch {
3332
+ return null;
3333
+ }
3334
+ }
3070
3335
  function ensureGitignored(root) {
3071
- const p = path3.join(root, ".gitignore");
3336
+ const p = path4.join(root, ".gitignore");
3072
3337
  let c = "";
3073
3338
  try {
3074
- c = fs6.readFileSync(p, "utf8");
3339
+ c = fs7.readFileSync(p, "utf8");
3075
3340
  } catch {
3076
3341
  }
3077
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs6.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
3342
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs7.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
3078
3343
  }
3079
3344
  function wireInstrumentation(root) {
3080
- const existing = ["instrumentation.ts", "instrumentation.js", path3.join("src", "instrumentation.ts")].map((c) => path3.join(root, c)).find((f) => fs6.existsSync(f));
3345
+ const existing = ["instrumentation.ts", "instrumentation.js", path4.join("src", "instrumentation.ts")].map((c) => path4.join(root, c)).find((f) => fs7.existsSync(f));
3081
3346
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
3082
3347
 
3083
3348
  export function register() {
@@ -3085,18 +3350,18 @@ export function register() {
3085
3350
  }
3086
3351
  `;
3087
3352
  if (!existing) {
3088
- fs6.writeFileSync(path3.join(root, "instrumentation.ts"), body);
3353
+ fs7.writeFileSync(path4.join(root, "instrumentation.ts"), body);
3089
3354
  return "created";
3090
3355
  }
3091
- const cur = fs6.readFileSync(existing, "utf8");
3356
+ const cur = fs7.readFileSync(existing, "utf8");
3092
3357
  if (cur.includes("apiblaze/sidecar")) return "present";
3093
3358
  if (/export\s+function\s+register\s*\(/.test(cur)) {
3094
- fs6.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3359
+ fs7.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3095
3360
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
3096
3361
  apiblaze();`));
3097
3362
  return "patched";
3098
3363
  }
3099
- fs6.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3364
+ fs7.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3100
3365
  ${cur}
3101
3366
  // call apiblaze() inside your register() export.
3102
3367
  `);
@@ -3132,51 +3397,99 @@ export default async function Page() {
3132
3397
  function generateInspector(root, router) {
3133
3398
  try {
3134
3399
  if (router === "pages") {
3135
- const dir2 = fs6.existsSync(path3.join(root, "src", "pages")) ? path3.join(root, "src", "pages") : path3.join(root, "pages");
3136
- const f2 = path3.join(dir2, "abz-inspector.tsx");
3137
- fs6.writeFileSync(f2, INSPECTOR_PAGE);
3138
- return path3.relative(root, f2);
3139
- }
3140
- const base = fs6.existsSync(path3.join(root, "src", "app")) ? path3.join(root, "src", "app") : path3.join(root, "app");
3141
- const dir = path3.join(base, "abz-inspector");
3142
- fs6.mkdirSync(dir, { recursive: true });
3143
- const f = path3.join(dir, "page.tsx");
3144
- fs6.writeFileSync(f, INSPECTOR_PAGE);
3145
- return path3.relative(root, f);
3400
+ const dir2 = fs7.existsSync(path4.join(root, "src", "pages")) ? path4.join(root, "src", "pages") : path4.join(root, "pages");
3401
+ const f2 = path4.join(dir2, "abz-inspector.tsx");
3402
+ fs7.writeFileSync(f2, INSPECTOR_PAGE);
3403
+ return path4.relative(root, f2);
3404
+ }
3405
+ const base = fs7.existsSync(path4.join(root, "src", "app")) ? path4.join(root, "src", "app") : path4.join(root, "app");
3406
+ const dir = path4.join(base, "abz-inspector");
3407
+ fs7.mkdirSync(dir, { recursive: true });
3408
+ const f = path4.join(dir, "page.tsx");
3409
+ fs7.writeFileSync(f, INSPECTOR_PAGE);
3410
+ return path4.relative(root, f);
3146
3411
  } catch {
3147
3412
  return null;
3148
3413
  }
3149
3414
  }
3150
- async function runSidecar(opts) {
3151
- const root = path3.resolve(opts.dir ?? process.cwd());
3152
- if (!loadCredentials()) {
3153
- console.log(import_chalk28.default.dim("Not logged in \u2014 starting APIblaze login..."));
3154
- await runLogin();
3415
+ async function runAnonymousInit(root, router, opts) {
3416
+ const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
3417
+ const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
3418
+ if (opts.newSession) clearAnonCred2();
3419
+ const spinner = (0, import_ora13.default)("Setting up a sidecar (no login needed)...").start();
3420
+ let out;
3421
+ try {
3422
+ out = await sidecarInitAnonymous2();
3423
+ spinner.succeed("Sidecar ready (anonymous trial).");
3424
+ } catch (err) {
3425
+ spinner.fail("Setup failed.");
3426
+ throw err;
3427
+ }
3428
+ if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
3429
+ const envState = upsertEnvLocal(root, out.token);
3430
+ ensureGitignored(root);
3431
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3432
+ console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
3433
+ let inspectorPath = null;
3434
+ if (!opts.noInspector) {
3435
+ inspectorPath = generateInspector(root, router);
3436
+ if (inspectorPath) console.log(` ${import_chalk28.default.green("\u2713")} inspector at ${inspectorPath}`);
3155
3437
  }
3438
+ console.log("");
3439
+ console.log(import_chalk28.default.bold("Done (no account needed). What happens next:"));
3440
+ console.log(` 1. ${import_chalk28.default.cyan("npm install apiblaze")} then ${import_chalk28.default.cyan("npm run dev")} and use your app.`);
3441
+ console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
3442
+ console.log(` ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (no login needed)`);
3443
+ console.log("");
3444
+ console.log(import_chalk28.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
3445
+ console.log(` ${import_chalk28.default.cyan("apiblaze login")} then ${import_chalk28.default.cyan("apiblaze claim")} ${import_chalk28.default.dim("(no code needed here)")}`);
3446
+ console.log(import_chalk28.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
3447
+ }
3448
+ async function runSidecar(opts) {
3449
+ const root = path4.resolve(opts.dir ?? process.cwd());
3156
3450
  const detected = detectNextProject(root);
3157
3451
  if (!detected.found) {
3158
3452
  console.log(import_chalk28.default.yellow(`No Next.js project detected in ${root}.`));
3159
3453
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
3160
3454
  return;
3161
3455
  }
3456
+ if (!loadCredentials() && !readEnvKey(root)) {
3457
+ await runAnonymousInit(root, detected.router, opts);
3458
+ return;
3459
+ }
3460
+ if (!loadCredentials()) {
3461
+ upsertEnvLocal(root, readEnvKey(root));
3462
+ ensureGitignored(root);
3463
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
3464
+ console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
3465
+ console.log(import_chalk28.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
3466
+ return;
3467
+ }
3162
3468
  const { teamId, teamName } = await resolveTeam(opts.team);
3163
- const spinner = (0, import_ora13.default)("Setting up the sidecar (tenant + non-expiring invoke key)...").start();
3164
- let token;
3165
- try {
3166
- const out = await admin({
3167
- method: "POST",
3168
- path: `/teams/${encodeURIComponent(teamId)}/sidecar/setup`,
3169
- summary: `Set up sidecar for team ${teamName ?? teamId}`
3170
- });
3171
- token = out.token;
3172
- spinner.succeed("Sidecar ready.");
3173
- } catch (err) {
3174
- spinner.fail("Setup failed.");
3175
- throw err;
3469
+ const existingKey = readEnvKey(root);
3470
+ const switchingTeam = !!opts.team;
3471
+ const mustMint = !existingKey || opts.rotate || switchingTeam;
3472
+ let token = existingKey ?? "";
3473
+ if (mustMint) {
3474
+ const spinner = (0, import_ora13.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
3475
+ try {
3476
+ const out = await admin({
3477
+ method: "POST",
3478
+ path: `/teams/${encodeURIComponent(teamId)}/sidecar/setup`,
3479
+ summary: `Set up sidecar for team ${teamName ?? teamId}`
3480
+ });
3481
+ token = out.token;
3482
+ spinner.succeed(existingKey ? `Sidecar re-established on team ${teamName ?? teamId}.` : "Sidecar ready.");
3483
+ } catch (err) {
3484
+ spinner.fail("Setup failed.");
3485
+ throw err;
3486
+ }
3487
+ } else {
3488
+ console.log(import_chalk28.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
3176
3489
  }
3177
3490
  const envState = upsertEnvLocal(root, token);
3178
3491
  ensureGitignored(root);
3179
- console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_TOKEN) \u2014 gitignored`);
3492
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3180
3493
  const wireState = wireInstrumentation(root);
3181
3494
  console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireState}`);
3182
3495
  let inspectorPath = null;
@@ -3191,22 +3504,39 @@ async function runSidecar(opts) {
3191
3504
  console.log(` 3. The origins your app calls appear as ${import_chalk28.default.bold("candidates")} \u2014 list them: ${import_chalk28.default.cyan("apiblaze origins")}`);
3192
3505
  console.log(` 4. Approve the ones to route: ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (or in the dashboard)`);
3193
3506
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
3194
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk28.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path3.dirname(inspectorPath)} before shipping)`);
3507
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk28.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
3508
+ if (switchingTeam) console.log(import_chalk28.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze origins approve <origin>\`.`));
3509
+ console.log("");
3510
+ console.log(import_chalk28.default.dim(" Manage: apiblaze origins (list/approve/deny/remove)"));
3511
+ console.log(import_chalk28.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
3512
+ console.log(import_chalk28.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
3195
3513
  console.log("");
3196
- console.log(import_chalk28.default.yellow(" \u26A0 APIBLAZE_TOKEN is long-lived and lets a holder call your team's proxies. Never commit it."));
3514
+ console.log(import_chalk28.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
3197
3515
  console.log(import_chalk28.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
3198
3516
  }
3199
3517
 
3200
3518
  // src/commands/origins.ts
3201
3519
  var import_chalk29 = __toESM(require("chalk"));
3202
3520
  var import_ora14 = __toESM(require("ora"));
3521
+ init_auth();
3522
+ init_anon_cred();
3203
3523
  async function runOriginsList(opts) {
3204
- const { teamId, teamName } = await resolveTeam(opts.team);
3205
- const out = await admin({
3206
- method: "GET",
3207
- path: `/teams/${encodeURIComponent(teamId)}/sidecar/candidates`,
3208
- summary: `List sidecar origins for team ${teamName ?? teamId}`
3209
- });
3524
+ let out;
3525
+ if (!loadCredentials()) {
3526
+ const cred = loadAnonCred();
3527
+ if (!cred) {
3528
+ console.log(import_chalk29.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
3529
+ return;
3530
+ }
3531
+ out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
3532
+ } else {
3533
+ const { teamId, teamName } = await resolveTeam(opts.team);
3534
+ out = await admin({
3535
+ method: "GET",
3536
+ path: `/teams/${encodeURIComponent(teamId)}/sidecar/candidates`,
3537
+ summary: `List sidecar origins for team ${teamName ?? teamId}`
3538
+ });
3539
+ }
3210
3540
  if (opts.json) {
3211
3541
  console.log(JSON.stringify(out));
3212
3542
  return;
@@ -3230,6 +3560,22 @@ Candidates \u2014 going direct, not yet approved (${candidates.length})`));
3230
3560
  }
3231
3561
  }
3232
3562
  async function runOriginsApprove(origin, opts) {
3563
+ if (!loadCredentials()) {
3564
+ const cred = loadAnonCred();
3565
+ if (!cred) {
3566
+ console.error(import_chalk29.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
3567
+ process.exit(1);
3568
+ }
3569
+ const spinner2 = (0, import_ora14.default)(`Approving ${origin} (anonymous)...`).start();
3570
+ try {
3571
+ const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
3572
+ spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
3573
+ } catch (err) {
3574
+ spinner2.fail("Approve failed.");
3575
+ throw err;
3576
+ }
3577
+ return;
3578
+ }
3233
3579
  const { teamId } = await resolveTeam(opts.team);
3234
3580
  const spinner = (0, import_ora14.default)(`Approving ${origin}...`).start();
3235
3581
  try {
@@ -3257,6 +3603,17 @@ async function runOriginsDeny(origin, opts) {
3257
3603
  throw err;
3258
3604
  }
3259
3605
  }
3606
+ async function runOriginsRemove(origin, opts) {
3607
+ const { teamId } = await resolveTeam(opts.team);
3608
+ const spinner = (0, import_ora14.default)(`Removing the proxy for ${origin}...`).start();
3609
+ try {
3610
+ await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
3611
+ spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
3612
+ } catch (err) {
3613
+ spinner.fail("Remove failed.");
3614
+ throw err;
3615
+ }
3616
+ }
3260
3617
 
3261
3618
  // src/index.ts
3262
3619
  var program = new import_commander.Command();
@@ -3285,7 +3642,7 @@ program.command("login").description("Authenticate with APIblaze").action(async
3285
3642
  process.exit(1);
3286
3643
  }
3287
3644
  });
3288
- program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "Target URL to forward requests to").option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
3645
+ program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "Target URL to forward requests to").option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
3289
3646
  try {
3290
3647
  await runCreate(opts);
3291
3648
  } catch (err) {
@@ -3297,10 +3654,11 @@ var agent = program.command("agent").description("Chat with an assistant that bu
3297
3654
  agent.command("authz").description("Chat to design and turn on access rules for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runAuthz(project, apiVersion)));
3298
3655
  agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
3299
3656
  agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
3300
- program.command("init").aliases(["sidecar"]).description("Wire a Next.js app to route its external fetches through APIblaze (one command)").option("--team <id|name>", "Team to set up under (defaults to your active team)").option("--dir <path>", "Project directory (defaults to cwd)").option("--no-inspector", "Skip generating the dev-only /abz-inspector page").option("-y, --yes", "Skip prompts").action(action((opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false })));
3657
+ program.command("init").aliases(["sidecar"]).description("Wire a Next.js app to route its external fetches through APIblaze (one command)").option("--team <id|name>", "Team to set up under (defaults to your active team)").option("--dir <path>", "Project directory (defaults to cwd)").option("--no-inspector", "Skip generating the dev-only /abz-inspector page").option("--rotate", "Mint a fresh invoke key even if one already exists").option("--new-session", "Start a fresh anonymous session (logged-out only)").option("-y, --yes", "Skip prompts").action(action((opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false, rotate: opts.rotate, newSession: opts.newSession })));
3301
3658
  var origins = program.command("origins").description("See which external origins your app calls; approve the ones to route through APIblaze").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((opts) => runOriginsList(opts)));
3302
3659
  origins.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)));
3303
3660
  origins.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)));
3661
+ origins.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)));
3304
3662
  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) => {
3305
3663
  try {
3306
3664
  const resolved = parseInt(port ?? opts.port, 10);
@@ -17,6 +17,8 @@ interface SidecarOptions {
17
17
  exclude?: string[];
18
18
  include?: string[];
19
19
  quiet?: boolean;
20
+ /** Explicit on/off. Overrides the APIBLAZE_SIDECAR env toggle when set. */
21
+ enabled?: boolean;
20
22
  }
21
23
  declare function register(opts?: SidecarOptions): void;
22
24
  declare function withApiblaze<T extends Record<string, unknown>>(nextConfig?: T): T;
@@ -17,6 +17,8 @@ interface SidecarOptions {
17
17
  exclude?: string[];
18
18
  include?: string[];
19
19
  quiet?: boolean;
20
+ /** Explicit on/off. Overrides the APIBLAZE_SIDECAR env toggle when set. */
21
+ enabled?: boolean;
20
22
  }
21
23
  declare function register(opts?: SidecarOptions): void;
22
24
  declare function withApiblaze<T extends Record<string, unknown>>(nextConfig?: T): T;
@@ -53,6 +53,7 @@ var NOISE_DENYLIST = [
53
53
  "registry.npmjs.org",
54
54
  "pypi.org"
55
55
  ];
56
+ var OFF_VALUES = /* @__PURE__ */ new Set(["off", "false", "0", "no", "disabled"]);
56
57
  var installed = false;
57
58
  function canonicalOrigin(input) {
58
59
  try {
@@ -83,9 +84,14 @@ function isPrivateOrLocal(host) {
83
84
  }
84
85
  function register(opts) {
85
86
  if (installed) return;
86
- const token = opts?.token ?? process.env.APIBLAZE_TOKEN;
87
+ const off = opts?.enabled === false || opts?.enabled === void 0 && OFF_VALUES.has((process.env.APIBLAZE_SIDECAR ?? "").trim().toLowerCase());
88
+ if (off) {
89
+ if (!opts?.quiet) console.log("[apiblaze/sidecar] off (APIBLAZE_SIDECAR=off) \u2014 fetches go direct.");
90
+ return;
91
+ }
92
+ const token = opts?.token ?? process.env.APIBLAZE_API_KEY ?? process.env.APIBLAZE_TOKEN;
87
93
  if (!token) {
88
- if (!opts?.quiet) console.warn("[apiblaze/sidecar] APIBLAZE_TOKEN not set \u2014 sidecar OFF (fetches pass through unchanged).");
94
+ if (!opts?.quiet) console.log("[apiblaze/sidecar] off (no APIBLAZE_API_KEY set) \u2014 fetches go direct.");
89
95
  return;
90
96
  }
91
97
  const original = globalThis.fetch;
@@ -27,6 +27,7 @@ var NOISE_DENYLIST = [
27
27
  "registry.npmjs.org",
28
28
  "pypi.org"
29
29
  ];
30
+ var OFF_VALUES = /* @__PURE__ */ new Set(["off", "false", "0", "no", "disabled"]);
30
31
  var installed = false;
31
32
  function canonicalOrigin(input) {
32
33
  try {
@@ -57,9 +58,14 @@ function isPrivateOrLocal(host) {
57
58
  }
58
59
  function register(opts) {
59
60
  if (installed) return;
60
- const token = opts?.token ?? process.env.APIBLAZE_TOKEN;
61
+ const off = opts?.enabled === false || opts?.enabled === void 0 && OFF_VALUES.has((process.env.APIBLAZE_SIDECAR ?? "").trim().toLowerCase());
62
+ if (off) {
63
+ if (!opts?.quiet) console.log("[apiblaze/sidecar] off (APIBLAZE_SIDECAR=off) \u2014 fetches go direct.");
64
+ return;
65
+ }
66
+ const token = opts?.token ?? process.env.APIBLAZE_API_KEY ?? process.env.APIBLAZE_TOKEN;
61
67
  if (!token) {
62
- if (!opts?.quiet) console.warn("[apiblaze/sidecar] APIBLAZE_TOKEN not set \u2014 sidecar OFF (fetches pass through unchanged).");
68
+ if (!opts?.quiet) console.log("[apiblaze/sidecar] off (no APIBLAZE_API_KEY set) \u2014 fetches go direct.");
63
69
  return;
64
70
  }
65
71
  const original = globalThis.fetch;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.6.1",
3
+ "version": "0.8.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",