apiblaze 0.6.0 → 0.7.2

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,23 @@ 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.0";
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 ApiError;
35
+ var init_types = __esm({
36
+ "src/types.ts"() {
37
+ "use strict";
38
+ ApiError = class extends Error {
39
+ constructor(status, message, body) {
40
+ super(message);
41
+ this.status = status;
42
+ this.body = body;
43
+ this.name = "ApiError";
44
+ }
45
+ };
40
46
  }
41
- };
42
-
43
- // src/commands/login.ts
44
- var import_chalk2 = __toESM(require("chalk"));
45
- var import_ora = __toESM(require("ora"));
47
+ });
46
48
 
47
49
  // 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
50
  function saveCredentials(creds) {
54
51
  fs.mkdirSync(APIBLAZE_DIR, { recursive: true });
55
52
  fs.writeFileSync(CREDENTIALS_PATH, JSON.stringify(creds, null, 2), "utf-8");
@@ -81,17 +78,39 @@ function getAccessToken() {
81
78
  }
82
79
  return creds.accessToken;
83
80
  }
84
-
85
- // src/lib/team.ts
86
- var import_chalk = __toESM(require("chalk"));
81
+ var fs, os, path, APIBLAZE_DIR, CREDENTIALS_PATH;
82
+ var init_auth = __esm({
83
+ "src/lib/auth.ts"() {
84
+ "use strict";
85
+ fs = __toESM(require("fs"));
86
+ os = __toESM(require("os"));
87
+ path = __toESM(require("path"));
88
+ APIBLAZE_DIR = path.join(os.homedir(), ".apiblaze");
89
+ CREDENTIALS_PATH = path.join(APIBLAZE_DIR, "credentials.json");
90
+ }
91
+ });
87
92
 
88
93
  // src/lib/api.ts
89
- var DASHBOARD_BASE = "https://dashboard.apiblaze.com";
90
- var PUBLIC_API_BASE = "https://api.apiblaze.com";
91
- async function createProxyAnonymous(body) {
94
+ var api_exports = {};
95
+ __export(api_exports, {
96
+ agentCall: () => agentCall,
97
+ checkProxyName: () => checkProxyName,
98
+ claimProxy: () => claimProxy,
99
+ claimTeam: () => claimTeam,
100
+ createProxy: () => createProxy,
101
+ createProxyAnonymous: () => createProxyAnonymous,
102
+ deleteDevTunnel: () => deleteDevTunnel,
103
+ getLocalhostTargets: () => getLocalhostTargets,
104
+ getProjects: () => getProjects,
105
+ getTeams: () => getTeams,
106
+ putDevTunnel: () => putDevTunnel,
107
+ sidecarApproveAnonymous: () => sidecarApproveAnonymous,
108
+ sidecarInitAnonymous: () => sidecarInitAnonymous
109
+ });
110
+ async function createProxyAnonymous(body, anonSession) {
92
111
  const res = await fetch(`${PUBLIC_API_BASE}/proxy`, {
93
112
  method: "POST",
94
- headers: { "Content-Type": "application/json" },
113
+ headers: { "Content-Type": "application/json", ...anonSession ? { "X-Abz-Anon-Session": anonSession } : {} },
95
114
  body: JSON.stringify(body)
96
115
  });
97
116
  if (!res.ok) {
@@ -105,9 +124,9 @@ async function createProxyAnonymous(body) {
105
124
  }
106
125
  return res.json();
107
126
  }
108
- async function apiFetch(path4, options = {}) {
127
+ async function apiFetch(path6, options = {}) {
109
128
  const token = getAccessToken();
110
- const url = `${DASHBOARD_BASE}${path4}`;
129
+ const url = `${DASHBOARD_BASE}${path6}`;
111
130
  const res = await fetch(url, {
112
131
  ...options,
113
132
  headers: {
@@ -121,8 +140,9 @@ async function apiFetch(path4, options = {}) {
121
140
  let parsed;
122
141
  try {
123
142
  parsed = await res.json();
143
+ const pick2 = (v) => typeof v === "string" ? v : v && typeof v === "object" ? pick2(v.message) ?? pick2(v.error) : void 0;
124
144
  const b = parsed;
125
- message = b.message ?? b.error ?? message;
145
+ message = pick2(b.message) ?? pick2(b.error) ?? pick2(b.details) ?? message;
126
146
  } catch {
127
147
  }
128
148
  throw new ApiError(res.status, message, parsed);
@@ -132,12 +152,12 @@ async function apiFetch(path4, options = {}) {
132
152
  }
133
153
  return res.json();
134
154
  }
135
- async function agentCall(path4, method, body) {
155
+ async function agentCall(path6, method, body) {
136
156
  const token = getAccessToken();
137
157
  const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
138
158
  method: "POST",
139
159
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
140
- body: JSON.stringify({ path: path4, method, body })
160
+ body: JSON.stringify({ path: path6, method, body })
141
161
  });
142
162
  let data = null;
143
163
  try {
@@ -177,6 +197,32 @@ async function claimProxy(claimCode, teamId) {
177
197
  body: JSON.stringify({ claimCode, team_id: teamId })
178
198
  });
179
199
  }
200
+ async function claimTeam(code) {
201
+ return apiFetch("/api/cli/claim-team", {
202
+ method: "POST",
203
+ body: JSON.stringify({ code })
204
+ });
205
+ }
206
+ async function sidecarInitAnonymous(anonSession) {
207
+ const res = await fetch(`${PUBLIC_API_BASE}/sidecar/init`, {
208
+ method: "POST",
209
+ headers: { "content-type": "application/json", ...anonSession ? { "X-Abz-Anon-Session": anonSession } : {} },
210
+ body: "{}"
211
+ });
212
+ const data = await res.json().catch(() => ({}));
213
+ if (!res.ok) throw new ApiError(res.status, data?.error ?? "init_failed", data);
214
+ return data;
215
+ }
216
+ async function sidecarApproveAnonymous(invokeKey, origin) {
217
+ const res = await fetch(`${PUBLIC_API_BASE}/sidecar/approve`, {
218
+ method: "POST",
219
+ headers: { "content-type": "application/json", "x-abz-sidecar-key": invokeKey },
220
+ body: JSON.stringify({ origin })
221
+ });
222
+ const data = await res.json().catch(() => ({}));
223
+ if (!res.ok) throw new ApiError(res.status, data?.detail ?? data?.error ?? "approve_failed", data);
224
+ return data;
225
+ }
180
226
  async function putDevTunnel(payload) {
181
227
  return apiFetch("/api/cli/dev-tunnel", {
182
228
  method: "PUT",
@@ -189,8 +235,81 @@ async function deleteDevTunnel(restore) {
189
235
  body: JSON.stringify({ restore })
190
236
  });
191
237
  }
238
+ var DASHBOARD_BASE, PUBLIC_API_BASE;
239
+ var init_api = __esm({
240
+ "src/lib/api.ts"() {
241
+ "use strict";
242
+ init_auth();
243
+ init_types();
244
+ DASHBOARD_BASE = "https://dashboard.apiblaze.com";
245
+ PUBLIC_API_BASE = "https://api.apiblaze.com";
246
+ }
247
+ });
248
+
249
+ // src/lib/anon-session.ts
250
+ var anon_session_exports = {};
251
+ __export(anon_session_exports, {
252
+ clearAnonSession: () => clearAnonSession,
253
+ loadAnonSession: () => loadAnonSession,
254
+ saveAnonSession: () => saveAnonSession
255
+ });
256
+ function loadAnonSession() {
257
+ try {
258
+ const s = JSON.parse(fs3.readFileSync(SESSION_PATH, "utf8"));
259
+ return s?.anon_session ? s : null;
260
+ } catch {
261
+ return null;
262
+ }
263
+ }
264
+ function saveAnonSession(anon_session, claim_code) {
265
+ try {
266
+ fs3.mkdirSync(DIR, { recursive: true });
267
+ const prev = loadAnonSession();
268
+ const merged = {
269
+ anon_session,
270
+ claim_code: claim_code ?? prev?.claim_code,
271
+ created_at: prev?.created_at ?? (/* @__PURE__ */ new Date()).toISOString()
272
+ };
273
+ fs3.writeFileSync(SESSION_PATH, JSON.stringify(merged, null, 2));
274
+ } catch {
275
+ }
276
+ }
277
+ function clearAnonSession() {
278
+ try {
279
+ fs3.unlinkSync(SESSION_PATH);
280
+ } catch {
281
+ }
282
+ }
283
+ var fs3, path2, os2, DIR, SESSION_PATH;
284
+ var init_anon_session = __esm({
285
+ "src/lib/anon-session.ts"() {
286
+ "use strict";
287
+ fs3 = __toESM(require("fs"));
288
+ path2 = __toESM(require("path"));
289
+ os2 = __toESM(require("os"));
290
+ DIR = path2.join(os2.homedir(), ".apiblaze");
291
+ SESSION_PATH = path2.join(DIR, "anon.json");
292
+ }
293
+ });
294
+
295
+ // src/index.ts
296
+ var import_commander = require("commander");
297
+ var import_chalk30 = __toESM(require("chalk"));
298
+
299
+ // package.json
300
+ var version = "0.7.2";
301
+
302
+ // src/index.ts
303
+ init_types();
304
+
305
+ // src/commands/login.ts
306
+ var import_chalk2 = __toESM(require("chalk"));
307
+ var import_ora = __toESM(require("ora"));
308
+ init_auth();
192
309
 
193
310
  // src/lib/team.ts
311
+ var import_chalk = __toESM(require("chalk"));
312
+ init_api();
194
313
  async function resolveLinkedTeam(opts) {
195
314
  let teams;
196
315
  try {
@@ -326,6 +445,8 @@ var import_fs = __toESM(require("fs"));
326
445
  var import_chalk4 = __toESM(require("chalk"));
327
446
  var import_ora2 = __toESM(require("ora"));
328
447
  var import_inquirer = __toESM(require("inquirer"));
448
+ init_auth();
449
+ init_api();
329
450
 
330
451
  // src/lib/traffic.ts
331
452
  var import_chalk3 = __toESM(require("chalk"));
@@ -420,11 +541,11 @@ function decodeJwt(token) {
420
541
  return null;
421
542
  }
422
543
  }
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);
544
+ function maskPath(path6) {
545
+ const q = path6.indexOf("?");
546
+ if (q < 0) return path6;
547
+ const base = path6.slice(0, q);
548
+ const query = path6.slice(q + 1);
428
549
  const masked = query.split("&").map((pair) => {
429
550
  const eq = pair.indexOf("=");
430
551
  if (eq < 0) return pair;
@@ -1018,6 +1139,8 @@ Tunneling ${selectedTargets.length} project(s) to localhost:${options.port}
1018
1139
  // src/commands/projects.ts
1019
1140
  var import_chalk5 = __toESM(require("chalk"));
1020
1141
  var import_ora3 = __toESM(require("ora"));
1142
+ init_auth();
1143
+ init_api();
1021
1144
  async function runProjects() {
1022
1145
  const creds = loadCredentials();
1023
1146
  if (!creds) {
@@ -1077,6 +1200,8 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
1077
1200
  var import_fs2 = __toESM(require("fs"));
1078
1201
  var import_chalk6 = __toESM(require("chalk"));
1079
1202
  var import_ora4 = __toESM(require("ora"));
1203
+ init_auth();
1204
+ init_api();
1080
1205
  function normalizeName(raw) {
1081
1206
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
1082
1207
  }
@@ -1345,15 +1470,19 @@ async function runAnonymousCreate(opts) {
1345
1470
  if (opts.displayName) body.display_name = opts.displayName;
1346
1471
  if (opts.apiversion) body.api_version = opts.apiversion;
1347
1472
  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;
1473
+ const { loadAnonSession: loadAnonSession2, saveAnonSession: saveAnonSession2, clearAnonSession: clearAnonSession2 } = await Promise.resolve().then(() => (init_anon_session(), anon_session_exports));
1474
+ if (opts.newSession) clearAnonSession2();
1475
+ const prior = loadAnonSession2();
1476
+ const spinner = !opts.json ? (0, import_ora4.default)(prior ? "Creating proxy (adding to your anonymous session)..." : "Creating proxy...").start() : null;
1349
1477
  let result;
1350
1478
  try {
1351
- result = await createProxyAnonymous(body);
1479
+ result = await createProxyAnonymous(body, prior?.anon_session);
1352
1480
  spinner?.succeed(import_chalk6.default.green("Proxy created!"));
1353
1481
  } catch (err) {
1354
1482
  spinner?.fail("Failed to create proxy.");
1355
1483
  throw err;
1356
1484
  }
1485
+ if (result.anon_session) saveAnonSession2(result.anon_session, result.team_claim_code);
1357
1486
  const version2 = result.api_version || "1.0.0";
1358
1487
  const keys = result.api_keys ?? {};
1359
1488
  const apiKey = result.apiKey ?? keys.prod ?? Object.values(keys)[0];
@@ -1380,17 +1509,26 @@ async function runAnonymousCreate(opts) {
1380
1509
  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
1510
  }
1382
1511
  if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
1383
- if (result.claim_url) {
1512
+ if (result.team_claim_code) {
1513
+ console.log();
1514
+ console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous \u2014 claim within 30 days or it expires.")} All your anonymous proxies`);
1515
+ console.log(` on this machine share ONE team. To keep them all:`);
1516
+ console.log(` ${import_chalk6.default.cyan("apiblaze login")} then ${import_chalk6.default.cyan("apiblaze claim")} ${import_chalk6.default.dim("(no code needed here)")}`);
1517
+ console.log(import_chalk6.default.dim(` From another machine: apiblaze claim ${result.team_claim_code}`));
1518
+ } else if (result.claim_url) {
1384
1519
  console.log();
1385
1520
  console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
1386
1521
  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
1522
  }
1389
1523
  console.log();
1390
1524
  }
1391
1525
 
1392
1526
  // src/commands/claim.ts
1393
1527
  var import_chalk7 = __toESM(require("chalk"));
1528
+ init_auth();
1529
+ init_anon_session();
1530
+ init_api();
1531
+ init_types();
1394
1532
  function fail2(message) {
1395
1533
  console.error(import_chalk7.default.red(`Error: ${message}`));
1396
1534
  process.exit(1);
@@ -1401,6 +1539,14 @@ async function runClaim(claimCodeArg, opts) {
1401
1539
  fail2("Not logged in. Run `apiblaze login` first, then claim.");
1402
1540
  }
1403
1541
  let claimCode = (claimCodeArg ?? "").trim();
1542
+ let fromLocalSession = false;
1543
+ if (!claimCode) {
1544
+ const sess = loadAnonSession();
1545
+ if (sess?.claim_code) {
1546
+ claimCode = sess.claim_code;
1547
+ fromLocalSession = true;
1548
+ }
1549
+ }
1404
1550
  if (!claimCode && process.stdin.isTTY) {
1405
1551
  const { default: inquirer2 } = await import("inquirer");
1406
1552
  const { code } = await inquirer2.prompt([{
@@ -1411,7 +1557,30 @@ async function runClaim(claimCodeArg, opts) {
1411
1557
  claimCode = (code ?? "").trim();
1412
1558
  }
1413
1559
  if (!claimCode) {
1414
- fail2("A claim code is required: `apiblaze claim <code>`.");
1560
+ fail2("Nothing to claim on this machine. Run `apiblaze claim <code>` with a code, or `apiblaze create`/`init` first.");
1561
+ }
1562
+ try {
1563
+ const t = await claimTeam(claimCode);
1564
+ if (fromLocalSession) clearAnonSession();
1565
+ if (opts.json) {
1566
+ console.log(JSON.stringify(t, null, 2));
1567
+ return;
1568
+ }
1569
+ console.log(import_chalk7.default.green(`
1570
+ \u2714 Claimed the whole team into your account.`));
1571
+ console.log(` Team: ${import_chalk7.default.bold(t.team_id)}`);
1572
+ console.log(` Its proxies + keys are now yours. Switch to it with ${import_chalk7.default.cyan(`apiblaze team ${t.team_id}`)}.`);
1573
+ console.log(`
1574
+ Manage it at ${import_chalk7.default.cyan("https://dashboard.apiblaze.com/dashboard")}`);
1575
+ return;
1576
+ } catch (err) {
1577
+ if (err instanceof ApiError && err.status !== 404) {
1578
+ const code = typeof err.body === "object" && err.body ? err.body.error : void 0;
1579
+ if (code === "already_claimed") fail2("That claim code has already been used.");
1580
+ if (code === "expired") fail2("That claim code has expired. Create a fresh setup with `apiblaze init`.");
1581
+ if (code === "not_claimable") fail2("That team can no longer be claimed (it already belongs to an account).");
1582
+ fail2(`Could not claim: ${err.message}`);
1583
+ }
1415
1584
  }
1416
1585
  const teams = await getTeams().catch(() => []);
1417
1586
  if (teams.length === 0) {
@@ -1443,7 +1612,13 @@ async function runClaim(claimCodeArg, opts) {
1443
1612
  fail2(`Specify a team: \`apiblaze claim ${claimCode} --team <name|id>\`. Available: ${teams.map((t) => t.name).join(", ")}.`);
1444
1613
  }
1445
1614
  const teamName = teams.find((t) => t.teamId === teamId)?.name ?? teamId;
1446
- const result = await claimProxy(claimCode, teamId);
1615
+ let result;
1616
+ try {
1617
+ result = await claimProxy(claimCode, teamId);
1618
+ } catch (err) {
1619
+ if (err instanceof ApiError) fail2(`That claim code isn't valid \u2014 it may be mistyped, already claimed, or expired.`);
1620
+ throw err;
1621
+ }
1447
1622
  if (opts.json) {
1448
1623
  console.log(JSON.stringify(result, null, 2));
1449
1624
  return;
@@ -1461,32 +1636,33 @@ async function runClaim(claimCodeArg, opts) {
1461
1636
 
1462
1637
  // src/commands/logout.ts
1463
1638
  var import_chalk8 = __toESM(require("chalk"));
1639
+ init_auth();
1464
1640
 
1465
1641
  // src/lib/consumer-auth.ts
1466
- var fs4 = __toESM(require("fs"));
1467
- var os2 = __toESM(require("os"));
1468
- var path2 = __toESM(require("path"));
1642
+ var fs5 = __toESM(require("fs"));
1643
+ var os3 = __toESM(require("os"));
1644
+ var path3 = __toESM(require("path"));
1469
1645
  var crypto = __toESM(require("crypto"));
1470
1646
  var import_child_process = require("child_process");
1471
1647
  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");
1648
+ var APIBLAZE_DIR2 = path3.join(os3.homedir(), ".apiblaze");
1649
+ var CONSUMER_PATH = path3.join(APIBLAZE_DIR2, "consumer.json");
1474
1650
  var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
1475
1651
  function loadConsumer() {
1476
1652
  try {
1477
- return JSON.parse(fs4.readFileSync(CONSUMER_PATH, "utf-8"));
1653
+ return JSON.parse(fs5.readFileSync(CONSUMER_PATH, "utf-8"));
1478
1654
  } catch {
1479
1655
  return null;
1480
1656
  }
1481
1657
  }
1482
1658
  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);
1659
+ fs5.mkdirSync(APIBLAZE_DIR2, { recursive: true });
1660
+ fs5.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
1661
+ fs5.chmodSync(CONSUMER_PATH, 384);
1486
1662
  }
1487
1663
  function clearConsumer() {
1488
1664
  try {
1489
- fs4.unlinkSync(CONSUMER_PATH);
1665
+ fs5.unlinkSync(CONSUMER_PATH);
1490
1666
  return true;
1491
1667
  } catch {
1492
1668
  return false;
@@ -1628,6 +1804,7 @@ async function runLogout(opts = {}) {
1628
1804
 
1629
1805
  // src/commands/whoami.ts
1630
1806
  var import_chalk9 = __toESM(require("chalk"));
1807
+ init_auth();
1631
1808
  async function runWhoami(opts = {}) {
1632
1809
  const creds = loadCredentials();
1633
1810
  const consumer2 = loadConsumer();
@@ -1668,6 +1845,8 @@ async function runWhoami(opts = {}) {
1668
1845
 
1669
1846
  // src/commands/team.ts
1670
1847
  var import_chalk10 = __toESM(require("chalk"));
1848
+ init_auth();
1849
+ init_api();
1671
1850
  function fail3(message) {
1672
1851
  console.error(import_chalk10.default.red(`Error: ${message}`));
1673
1852
  process.exit(1);
@@ -1712,10 +1891,13 @@ async function runTeam(arg) {
1712
1891
 
1713
1892
  // src/commands/authz.ts
1714
1893
  var import_chalk12 = __toESM(require("chalk"));
1894
+ init_auth();
1895
+ init_api();
1715
1896
 
1716
1897
  // src/lib/agent-chat.ts
1717
1898
  var import_readline = __toESM(require("readline"));
1718
1899
  var import_chalk11 = __toESM(require("chalk"));
1900
+ init_api();
1719
1901
  async function runAgentChatRepl(opts) {
1720
1902
  const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
1721
1903
  console.log("\n" + import_chalk11.default.cyan.bold(title));
@@ -1885,6 +2067,8 @@ async function runAuthz(projectArg, apiVersionArg) {
1885
2067
 
1886
2068
  // src/commands/openapi.ts
1887
2069
  var import_chalk13 = __toESM(require("chalk"));
2070
+ init_auth();
2071
+ init_api();
1888
2072
  async function runOpenapi(projectArg, apiVersionArg) {
1889
2073
  const creds = loadCredentials();
1890
2074
  if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
@@ -1949,6 +2133,8 @@ async function runOpenapi(projectArg, apiVersionArg) {
1949
2133
 
1950
2134
  // src/commands/mcp.ts
1951
2135
  var import_chalk14 = __toESM(require("chalk"));
2136
+ init_auth();
2137
+ init_api();
1952
2138
  async function runMcp(projectArg, apiVersionArg, opts) {
1953
2139
  const creds = loadCredentials();
1954
2140
  if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
@@ -1994,6 +2180,7 @@ var import_ora5 = __toESM(require("ora"));
1994
2180
 
1995
2181
  // src/lib/admin.ts
1996
2182
  var import_chalk16 = __toESM(require("chalk"));
2183
+ init_auth();
1997
2184
 
1998
2185
  // src/lib/trace.ts
1999
2186
  var import_chalk15 = __toESM(require("chalk"));
@@ -2036,6 +2223,7 @@ function renderTrace() {
2036
2223
  }
2037
2224
 
2038
2225
  // src/lib/admin.ts
2226
+ init_types();
2039
2227
  var DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2040
2228
  async function admin(call) {
2041
2229
  const token = getAccessToken();
@@ -2068,6 +2256,8 @@ function maybePrintBilling(data) {
2068
2256
 
2069
2257
  // src/lib/resolve.ts
2070
2258
  var import_chalk17 = __toESM(require("chalk"));
2259
+ init_auth();
2260
+ init_api();
2071
2261
  function requireAuth() {
2072
2262
  const creds = loadCredentials();
2073
2263
  if (!creds) {
@@ -2175,6 +2365,7 @@ async function runDelete(project, version2, opts) {
2175
2365
  // src/commands/export.ts
2176
2366
  var import_chalk19 = __toESM(require("chalk"));
2177
2367
  var import_fs3 = require("fs");
2368
+ init_auth();
2178
2369
  var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2179
2370
  async function runExport(projectArg, versionArg, opts) {
2180
2371
  const token = getAccessToken();
@@ -2581,7 +2772,7 @@ async function runKeyRevoke(keyId, opts) {
2581
2772
  }
2582
2773
 
2583
2774
  // src/commands/spec.ts
2584
- var fs5 = __toESM(require("fs"));
2775
+ var fs6 = __toESM(require("fs"));
2585
2776
  var import_chalk24 = __toESM(require("chalk"));
2586
2777
  var import_ora10 = __toESM(require("ora"));
2587
2778
  async function runSpecGet(project, opts) {
@@ -2601,7 +2792,7 @@ async function runSpecSet(project, opts) {
2601
2792
  }
2602
2793
  let specContent;
2603
2794
  try {
2604
- specContent = fs5.readFileSync(opts.file, "utf-8");
2795
+ specContent = fs6.readFileSync(opts.file, "utf-8");
2605
2796
  } catch {
2606
2797
  console.error(import_chalk24.default.red(`Cannot read file: ${opts.file}`));
2607
2798
  process.exit(1);
@@ -2627,9 +2818,11 @@ async function runSpecSet(project, opts) {
2627
2818
  // src/commands/agent.ts
2628
2819
  var import_chalk26 = __toESM(require("chalk"));
2629
2820
  var import_ora11 = __toESM(require("ora"));
2821
+ init_auth();
2630
2822
 
2631
2823
  // src/lib/tools.ts
2632
2824
  var import_chalk25 = __toESM(require("chalk"));
2825
+ init_api();
2633
2826
  async function proj(teamId, name, version2) {
2634
2827
  return resolveProject(teamId, name, version2);
2635
2828
  }
@@ -2779,6 +2972,7 @@ function findTool(name) {
2779
2972
  }
2780
2973
 
2781
2974
  // src/commands/agent.ts
2975
+ init_types();
2782
2976
  var DASHBOARD_BASE5 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
2783
2977
  var MAX_TOOL_STEPS = 6;
2784
2978
  async function callAgent(messages, teamId) {
@@ -3040,44 +3234,59 @@ async function runConsumerApikeys(opts) {
3040
3234
  // src/commands/sidecar.ts
3041
3235
  var import_chalk28 = __toESM(require("chalk"));
3042
3236
  var import_ora13 = __toESM(require("ora"));
3043
- var fs6 = __toESM(require("fs"));
3044
- var path3 = __toESM(require("path"));
3237
+ var fs7 = __toESM(require("fs"));
3238
+ var path4 = __toESM(require("path"));
3239
+ init_auth();
3045
3240
  function detectNextProject(root) {
3046
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs6.existsSync(path3.join(root, f)));
3241
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs7.existsSync(path4.join(root, f)));
3047
3242
  let hasDep = false;
3048
3243
  try {
3049
- const pkg = JSON.parse(fs6.readFileSync(path3.join(root, "package.json"), "utf8"));
3244
+ const pkg = JSON.parse(fs7.readFileSync(path4.join(root, "package.json"), "utf8"));
3050
3245
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
3051
3246
  } catch {
3052
3247
  }
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"));
3248
+ const appDir = fs7.existsSync(path4.join(root, "app")) || fs7.existsSync(path4.join(root, "src", "app"));
3249
+ const pagesDir = fs7.existsSync(path4.join(root, "pages")) || fs7.existsSync(path4.join(root, "src", "pages"));
3055
3250
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
3056
3251
  }
3057
3252
  function upsertEnvLocal(root, token) {
3058
- const p = path3.join(root, ".env.local");
3253
+ const p = path4.join(root, ".env.local");
3059
3254
  let existing = "";
3060
3255
  try {
3061
- existing = fs6.readFileSync(p, "utf8");
3256
+ existing = fs7.readFileSync(p, "utf8");
3062
3257
  } catch {
3063
3258
  }
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}
3259
+ const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
3260
+ let next = existing.replace(/^APIBLAZE_TOKEN=.*\n?/m, "");
3261
+ 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}
3262
+ `;
3263
+ if (!/^APIBLAZE_SIDECAR=/m.test(next)) {
3264
+ next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR=on
3066
3265
  `;
3067
- fs6.writeFileSync(p, next);
3266
+ }
3267
+ fs7.writeFileSync(p, next);
3068
3268
  return had ? "rotated" : "created";
3069
3269
  }
3270
+ function readEnvKey(root) {
3271
+ try {
3272
+ const s = fs7.readFileSync(path4.join(root, ".env.local"), "utf8");
3273
+ const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
3274
+ return m ? m[1].trim() : null;
3275
+ } catch {
3276
+ return null;
3277
+ }
3278
+ }
3070
3279
  function ensureGitignored(root) {
3071
- const p = path3.join(root, ".gitignore");
3280
+ const p = path4.join(root, ".gitignore");
3072
3281
  let c = "";
3073
3282
  try {
3074
- c = fs6.readFileSync(p, "utf8");
3283
+ c = fs7.readFileSync(p, "utf8");
3075
3284
  } catch {
3076
3285
  }
3077
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs6.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
3286
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs7.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
3078
3287
  }
3079
3288
  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));
3289
+ const existing = ["instrumentation.ts", "instrumentation.js", path4.join("src", "instrumentation.ts")].map((c) => path4.join(root, c)).find((f) => fs7.existsSync(f));
3081
3290
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
3082
3291
 
3083
3292
  export function register() {
@@ -3085,18 +3294,18 @@ export function register() {
3085
3294
  }
3086
3295
  `;
3087
3296
  if (!existing) {
3088
- fs6.writeFileSync(path3.join(root, "instrumentation.ts"), body);
3297
+ fs7.writeFileSync(path4.join(root, "instrumentation.ts"), body);
3089
3298
  return "created";
3090
3299
  }
3091
- const cur = fs6.readFileSync(existing, "utf8");
3300
+ const cur = fs7.readFileSync(existing, "utf8");
3092
3301
  if (cur.includes("apiblaze/sidecar")) return "present";
3093
3302
  if (/export\s+function\s+register\s*\(/.test(cur)) {
3094
- fs6.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3303
+ fs7.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3095
3304
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
3096
3305
  apiblaze();`));
3097
3306
  return "patched";
3098
3307
  }
3099
- fs6.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3308
+ fs7.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
3100
3309
  ${cur}
3101
3310
  // call apiblaze() inside your register() export.
3102
3311
  `);
@@ -3120,7 +3329,7 @@ export default async function Page() {
3120
3329
  return (
3121
3330
  <main style={{ fontFamily: "ui-monospace, monospace", padding: 24, lineHeight: 1.6 }}>
3122
3331
  <h1>APIblaze sidecar inspector</h1>
3123
- <p>Fetched httpbingo through the interceptor. If httpbingo.org is <b>not yet approved</b>, this went
3332
+ <p>Fetched httpbingo through the sidecar. If httpbingo.org is <b>not yet approved</b>, this went
3124
3333
  <b> direct</b> and now appears as a candidate in your dashboard \u2014 approve it, wait ~5 min, reload,
3125
3334
  and it will route through APIblaze (the echo below will show it arrived via your proxy).</p>
3126
3335
  <pre>{JSON.stringify(r, null, 2)}</pre>
@@ -3132,51 +3341,100 @@ export default async function Page() {
3132
3341
  function generateInspector(root, router) {
3133
3342
  try {
3134
3343
  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);
3344
+ const dir2 = fs7.existsSync(path4.join(root, "src", "pages")) ? path4.join(root, "src", "pages") : path4.join(root, "pages");
3345
+ const f2 = path4.join(dir2, "abz-inspector.tsx");
3346
+ fs7.writeFileSync(f2, INSPECTOR_PAGE);
3347
+ return path4.relative(root, f2);
3348
+ }
3349
+ const base = fs7.existsSync(path4.join(root, "src", "app")) ? path4.join(root, "src", "app") : path4.join(root, "app");
3350
+ const dir = path4.join(base, "abz-inspector");
3351
+ fs7.mkdirSync(dir, { recursive: true });
3352
+ const f = path4.join(dir, "page.tsx");
3353
+ fs7.writeFileSync(f, INSPECTOR_PAGE);
3354
+ return path4.relative(root, f);
3146
3355
  } catch {
3147
3356
  return null;
3148
3357
  }
3149
3358
  }
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();
3359
+ async function runAnonymousInit(root, router, opts) {
3360
+ const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
3361
+ const { loadAnonSession: loadAnonSession2, saveAnonSession: saveAnonSession2, clearAnonSession: clearAnonSession2 } = await Promise.resolve().then(() => (init_anon_session(), anon_session_exports));
3362
+ if (opts.newSession) clearAnonSession2();
3363
+ const prior = loadAnonSession2();
3364
+ const spinner = (0, import_ora13.default)(prior ? "Adding to your anonymous session..." : "Setting up a sidecar (no login needed)...").start();
3365
+ let out;
3366
+ try {
3367
+ out = await sidecarInitAnonymous2(prior?.anon_session);
3368
+ spinner.succeed("Sidecar ready (anonymous trial).");
3369
+ } catch (err) {
3370
+ spinner.fail("Setup failed.");
3371
+ throw err;
3155
3372
  }
3373
+ if (out.anon_session) saveAnonSession2(out.anon_session, out.claim_code);
3374
+ const envState = upsertEnvLocal(root, out.token);
3375
+ ensureGitignored(root);
3376
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3377
+ console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
3378
+ let inspectorPath = null;
3379
+ if (!opts.noInspector) {
3380
+ inspectorPath = generateInspector(root, router);
3381
+ if (inspectorPath) console.log(` ${import_chalk28.default.green("\u2713")} inspector at ${inspectorPath}`);
3382
+ }
3383
+ console.log("");
3384
+ console.log(import_chalk28.default.bold("Done (no account needed). What happens next:"));
3385
+ console.log(` 1. ${import_chalk28.default.cyan("npm install apiblaze")} then ${import_chalk28.default.cyan("npm run dev")} and use your app.`);
3386
+ console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
3387
+ console.log(` ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (no login needed)`);
3388
+ console.log("");
3389
+ console.log(import_chalk28.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
3390
+ console.log(` ${import_chalk28.default.cyan("apiblaze login")} then ${import_chalk28.default.cyan("apiblaze claim")} ${import_chalk28.default.dim("(no code needed here)")}`);
3391
+ console.log(import_chalk28.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
3392
+ }
3393
+ async function runSidecar(opts) {
3394
+ const root = path4.resolve(opts.dir ?? process.cwd());
3156
3395
  const detected = detectNextProject(root);
3157
3396
  if (!detected.found) {
3158
3397
  console.log(import_chalk28.default.yellow(`No Next.js project detected in ${root}.`));
3159
3398
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
3160
3399
  return;
3161
3400
  }
3401
+ if (!loadCredentials() && !readEnvKey(root)) {
3402
+ await runAnonymousInit(root, detected.router, opts);
3403
+ return;
3404
+ }
3405
+ if (!loadCredentials()) {
3406
+ upsertEnvLocal(root, readEnvKey(root));
3407
+ ensureGitignored(root);
3408
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
3409
+ console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
3410
+ console.log(import_chalk28.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
3411
+ return;
3412
+ }
3162
3413
  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;
3414
+ const existingKey = readEnvKey(root);
3415
+ const switchingTeam = !!opts.team;
3416
+ const mustMint = !existingKey || opts.rotate || switchingTeam;
3417
+ let token = existingKey ?? "";
3418
+ if (mustMint) {
3419
+ 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();
3420
+ try {
3421
+ const out = await admin({
3422
+ method: "POST",
3423
+ path: `/teams/${encodeURIComponent(teamId)}/sidecar/setup`,
3424
+ summary: `Set up sidecar for team ${teamName ?? teamId}`
3425
+ });
3426
+ token = out.token;
3427
+ spinner.succeed(existingKey ? `Sidecar re-established on team ${teamName ?? teamId}.` : "Sidecar ready.");
3428
+ } catch (err) {
3429
+ spinner.fail("Setup failed.");
3430
+ throw err;
3431
+ }
3432
+ } else {
3433
+ 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
3434
  }
3177
3435
  const envState = upsertEnvLocal(root, token);
3178
3436
  ensureGitignored(root);
3179
- console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_TOKEN) \u2014 gitignored`);
3437
+ console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3180
3438
  const wireState = wireInstrumentation(root);
3181
3439
  console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireState}`);
3182
3440
  let inspectorPath = null;
@@ -3191,15 +3449,33 @@ async function runSidecar(opts) {
3191
3449
  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
3450
  console.log(` 4. Approve the ones to route: ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (or in the dashboard)`);
3193
3451
  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)`);
3452
+ 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)`);
3453
+ 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>\`.`));
3454
+ console.log("");
3455
+ console.log(import_chalk28.default.dim(" Manage: apiblaze origins (list/approve/deny/remove)"));
3456
+ console.log(import_chalk28.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
3457
+ console.log(import_chalk28.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
3195
3458
  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."));
3459
+ 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
3460
  console.log(import_chalk28.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
3198
3461
  }
3199
3462
 
3200
3463
  // src/commands/origins.ts
3201
3464
  var import_chalk29 = __toESM(require("chalk"));
3202
3465
  var import_ora14 = __toESM(require("ora"));
3466
+ var fs8 = __toESM(require("fs"));
3467
+ var path5 = __toESM(require("path"));
3468
+ init_auth();
3469
+ init_api();
3470
+ function readEnvKey2() {
3471
+ try {
3472
+ const s = fs8.readFileSync(path5.join(process.cwd(), ".env.local"), "utf8");
3473
+ const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
3474
+ return m ? m[1].trim() : null;
3475
+ } catch {
3476
+ return null;
3477
+ }
3478
+ }
3203
3479
  async function runOriginsList(opts) {
3204
3480
  const { teamId, teamName } = await resolveTeam(opts.team);
3205
3481
  const out = await admin({
@@ -3230,6 +3506,22 @@ Candidates \u2014 going direct, not yet approved (${candidates.length})`));
3230
3506
  }
3231
3507
  }
3232
3508
  async function runOriginsApprove(origin, opts) {
3509
+ if (!loadCredentials()) {
3510
+ const key = readEnvKey2();
3511
+ if (!key) {
3512
+ console.error(import_chalk29.default.red("Not logged in and no .env.local APIBLAZE_API_KEY found. Run `apiblaze init` first."));
3513
+ process.exit(1);
3514
+ }
3515
+ const spinner2 = (0, import_ora14.default)(`Approving ${origin} (anonymous)...`).start();
3516
+ try {
3517
+ const out = await sidecarApproveAnonymous(key, origin.replace(/^https?:\/\//, ""));
3518
+ spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
3519
+ } catch (err) {
3520
+ spinner2.fail("Approve failed.");
3521
+ throw err;
3522
+ }
3523
+ return;
3524
+ }
3233
3525
  const { teamId } = await resolveTeam(opts.team);
3234
3526
  const spinner = (0, import_ora14.default)(`Approving ${origin}...`).start();
3235
3527
  try {
@@ -3257,6 +3549,17 @@ async function runOriginsDeny(origin, opts) {
3257
3549
  throw err;
3258
3550
  }
3259
3551
  }
3552
+ async function runOriginsRemove(origin, opts) {
3553
+ const { teamId } = await resolveTeam(opts.team);
3554
+ const spinner = (0, import_ora14.default)(`Removing the proxy for ${origin}...`).start();
3555
+ try {
3556
+ await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
3557
+ spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
3558
+ } catch (err) {
3559
+ spinner.fail("Remove failed.");
3560
+ throw err;
3561
+ }
3562
+ }
3260
3563
 
3261
3564
  // src/index.ts
3262
3565
  var program = new import_commander.Command();
@@ -3285,7 +3588,7 @@ program.command("login").description("Authenticate with APIblaze").action(async
3285
3588
  process.exit(1);
3286
3589
  }
3287
3590
  });
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) => {
3591
+ 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
3592
  try {
3290
3593
  await runCreate(opts);
3291
3594
  } catch (err) {
@@ -3297,10 +3600,11 @@ var agent = program.command("agent").description("Chat with an assistant that bu
3297
3600
  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
3601
  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
3602
  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 })));
3603
+ 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
3604
  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
3605
  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
3606
  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)));
3607
+ 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
3608
  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
3609
  try {
3306
3610
  const resolved = parseInt(port ?? opts.port, 10);
@@ -1,5 +1,5 @@
1
1
  /**
2
- * apiblaze/sidecar — the egress interceptor (observe → approve model).
2
+ * apiblaze/sidecar — the egress sidecar (observe → approve model).
3
3
  * Spec: specs/sidecar/apiblaze-sidecar-spec.md §3.
4
4
  *
5
5
  * // instrumentation.ts
@@ -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;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * apiblaze/sidecar — the egress interceptor (observe → approve model).
2
+ * apiblaze/sidecar — the egress sidecar (observe → approve model).
3
3
  * Spec: specs/sidecar/apiblaze-sidecar-spec.md §3.
4
4
  *
5
5
  * // instrumentation.ts
@@ -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 interceptor 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;
@@ -121,42 +127,55 @@ function register(opts) {
121
127
  if (include.length > 0 && !include.some((g) => hostMatchesGlob(host, g))) return false;
122
128
  return true;
123
129
  };
130
+ const urlOf = (input) => {
131
+ if (typeof input === "string") return input;
132
+ if (input instanceof URL) return input.href;
133
+ if (input && typeof input === "object" && typeof input.url === "string") return input.url;
134
+ return null;
135
+ };
124
136
  const wrapped = async (input, init) => {
125
137
  try {
126
- const req = new Request(input, init);
127
- const url = new URL(req.url);
128
- if (shouldConsider(url)) {
129
- const origin = canonicalOrigin(url.origin);
130
- if (origin) {
131
- const base = routes[origin];
132
- if (base) {
138
+ const urlStr = urlOf(input);
139
+ if (urlStr) {
140
+ const url = new URL(urlStr);
141
+ if (shouldConsider(url)) {
142
+ const origin = canonicalOrigin(url.origin);
143
+ if (origin) {
144
+ const base = routes[origin];
145
+ if (base) {
146
+ if (!seen.has(origin)) {
147
+ seen.add(origin);
148
+ if (!opts?.quiet) console.log(`[apiblaze/sidecar] routed \u2192 ${origin}`);
149
+ }
150
+ const isReq = input instanceof Request;
151
+ const headers = new Headers(isReq ? input.headers : void 0);
152
+ if (init?.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v));
153
+ const appKey = headers.get("x-api-key");
154
+ if (appKey) headers.set("x-abz-fwd-x-api-key", appKey);
155
+ headers.set("x-api-key", token);
156
+ const target = `${base}${url.pathname}${url.search}`;
157
+ const body = init?.body ?? (isReq ? input.body : void 0);
158
+ const nextInit = {
159
+ method: init?.method ?? (isReq ? input.method : "GET"),
160
+ headers,
161
+ body,
162
+ signal: init?.signal ?? (isReq ? input.signal : void 0),
163
+ redirect: init?.redirect ?? (isReq ? input.redirect : void 0),
164
+ credentials: init?.credentials ?? (isReq ? input.credentials : void 0)
165
+ };
166
+ if (body) nextInit.duplex = "half";
167
+ return original(target, nextInit);
168
+ }
133
169
  if (!seen.has(origin)) {
134
170
  seen.add(origin);
135
- if (!opts?.quiet) console.log(`[apiblaze/sidecar] routed \u2192 ${origin}`);
171
+ if (!opts?.quiet) console.log(`[apiblaze/sidecar] direct (not approved) \u2192 ${origin} \xB7 approve to route it`);
172
+ reportObserved(origin);
136
173
  }
137
- const headers = new Headers(req.headers);
138
- const appKey = headers.get("x-api-key");
139
- if (appKey) headers.set("x-abz-fwd-x-api-key", appKey);
140
- headers.set("x-api-key", token);
141
- const target = `${base}${url.pathname}${url.search}`;
142
- return original(target, {
143
- method: req.method,
144
- headers,
145
- body: req.body,
146
- redirect: "manual",
147
- duplex: req.body ? "half" : void 0
148
- // streaming request bodies
149
- });
150
- }
151
- if (!seen.has(origin)) {
152
- seen.add(origin);
153
- if (!opts?.quiet) console.log(`[apiblaze/sidecar] direct (not approved) \u2192 ${origin} \xB7 approve at your dashboard to route it`);
154
- reportObserved(origin);
155
174
  }
156
175
  }
157
176
  }
158
177
  } catch (err) {
159
- if (!opts?.quiet) console.warn("[apiblaze/sidecar] passthrough after interceptor error:", err?.message);
178
+ if (!opts?.quiet) console.warn("[apiblaze/sidecar] passthrough after sidecar error:", err?.message);
160
179
  }
161
180
  return original(input, init);
162
181
  };
@@ -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 interceptor 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;
@@ -95,42 +101,55 @@ function register(opts) {
95
101
  if (include.length > 0 && !include.some((g) => hostMatchesGlob(host, g))) return false;
96
102
  return true;
97
103
  };
104
+ const urlOf = (input) => {
105
+ if (typeof input === "string") return input;
106
+ if (input instanceof URL) return input.href;
107
+ if (input && typeof input === "object" && typeof input.url === "string") return input.url;
108
+ return null;
109
+ };
98
110
  const wrapped = async (input, init) => {
99
111
  try {
100
- const req = new Request(input, init);
101
- const url = new URL(req.url);
102
- if (shouldConsider(url)) {
103
- const origin = canonicalOrigin(url.origin);
104
- if (origin) {
105
- const base = routes[origin];
106
- if (base) {
112
+ const urlStr = urlOf(input);
113
+ if (urlStr) {
114
+ const url = new URL(urlStr);
115
+ if (shouldConsider(url)) {
116
+ const origin = canonicalOrigin(url.origin);
117
+ if (origin) {
118
+ const base = routes[origin];
119
+ if (base) {
120
+ if (!seen.has(origin)) {
121
+ seen.add(origin);
122
+ if (!opts?.quiet) console.log(`[apiblaze/sidecar] routed \u2192 ${origin}`);
123
+ }
124
+ const isReq = input instanceof Request;
125
+ const headers = new Headers(isReq ? input.headers : void 0);
126
+ if (init?.headers) new Headers(init.headers).forEach((v, k) => headers.set(k, v));
127
+ const appKey = headers.get("x-api-key");
128
+ if (appKey) headers.set("x-abz-fwd-x-api-key", appKey);
129
+ headers.set("x-api-key", token);
130
+ const target = `${base}${url.pathname}${url.search}`;
131
+ const body = init?.body ?? (isReq ? input.body : void 0);
132
+ const nextInit = {
133
+ method: init?.method ?? (isReq ? input.method : "GET"),
134
+ headers,
135
+ body,
136
+ signal: init?.signal ?? (isReq ? input.signal : void 0),
137
+ redirect: init?.redirect ?? (isReq ? input.redirect : void 0),
138
+ credentials: init?.credentials ?? (isReq ? input.credentials : void 0)
139
+ };
140
+ if (body) nextInit.duplex = "half";
141
+ return original(target, nextInit);
142
+ }
107
143
  if (!seen.has(origin)) {
108
144
  seen.add(origin);
109
- if (!opts?.quiet) console.log(`[apiblaze/sidecar] routed \u2192 ${origin}`);
145
+ if (!opts?.quiet) console.log(`[apiblaze/sidecar] direct (not approved) \u2192 ${origin} \xB7 approve to route it`);
146
+ reportObserved(origin);
110
147
  }
111
- const headers = new Headers(req.headers);
112
- const appKey = headers.get("x-api-key");
113
- if (appKey) headers.set("x-abz-fwd-x-api-key", appKey);
114
- headers.set("x-api-key", token);
115
- const target = `${base}${url.pathname}${url.search}`;
116
- return original(target, {
117
- method: req.method,
118
- headers,
119
- body: req.body,
120
- redirect: "manual",
121
- duplex: req.body ? "half" : void 0
122
- // streaming request bodies
123
- });
124
- }
125
- if (!seen.has(origin)) {
126
- seen.add(origin);
127
- if (!opts?.quiet) console.log(`[apiblaze/sidecar] direct (not approved) \u2192 ${origin} \xB7 approve at your dashboard to route it`);
128
- reportObserved(origin);
129
148
  }
130
149
  }
131
150
  }
132
151
  } catch (err) {
133
- if (!opts?.quiet) console.warn("[apiblaze/sidecar] passthrough after interceptor error:", err?.message);
152
+ if (!opts?.quiet) console.warn("[apiblaze/sidecar] passthrough after sidecar error:", err?.message);
134
153
  }
135
154
  return original(input, init);
136
155
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.6.0",
3
+ "version": "0.7.2",
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",