apiblaze 0.20.9 → 0.20.11

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
@@ -137,8 +137,8 @@ async function createProxyAnonymous(body) {
137
137
  }
138
138
  return res.json();
139
139
  }
140
- async function apiFetch(path8, options = {}, auth) {
141
- const url = `${DASHBOARD_BASE}${path8}`;
140
+ async function apiFetch(path9, options = {}, auth) {
141
+ const url = `${DASHBOARD_BASE}${path9}`;
142
142
  const res = await fetch(url, {
143
143
  ...options,
144
144
  headers: {
@@ -164,12 +164,12 @@ async function apiFetch(path8, options = {}, auth) {
164
164
  }
165
165
  return res.json();
166
166
  }
167
- async function agentCall(path8, method, body) {
167
+ async function agentCall(path9, method, body) {
168
168
  const token = getAccessToken();
169
169
  const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
170
170
  method: "POST",
171
171
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
172
- body: JSON.stringify({ path: path8, method, body })
172
+ body: JSON.stringify({ path: path9, method, body })
173
173
  });
174
174
  let data = null;
175
175
  try {
@@ -472,6 +472,98 @@ var init_admin = __esm({
472
472
  }
473
473
  });
474
474
 
475
+ // src/lib/spec-or-target.ts
476
+ var spec_or_target_exports = {};
477
+ __export(spec_or_target_exports, {
478
+ classifyTargetInput: () => classifyTargetInput,
479
+ looksLikeSpecDoc: () => looksLikeSpecDoc
480
+ });
481
+ function looksLikeSpecDoc(text) {
482
+ const probe = (d) => !!d && typeof d === "object" && !Array.isArray(d) && (typeof d.openapi === "string" || typeof d.swagger === "string") && typeof d.paths === "object";
483
+ try {
484
+ if (probe(JSON.parse(text))) return true;
485
+ } catch {
486
+ }
487
+ try {
488
+ if (probe((0, import_yaml.parse)(text))) return true;
489
+ } catch {
490
+ }
491
+ return false;
492
+ }
493
+ function isHttpUrl(s) {
494
+ try {
495
+ const u = new URL(s);
496
+ return u.protocol === "http:" || u.protocol === "https:";
497
+ } catch {
498
+ return false;
499
+ }
500
+ }
501
+ function smellsLikeSpecUrl(u) {
502
+ try {
503
+ const { pathname } = new URL(u);
504
+ return /\.(ya?ml|json)$/i.test(pathname) || /openapi|swagger/i.test(pathname);
505
+ } catch {
506
+ return false;
507
+ }
508
+ }
509
+ async function fetchTextCapped(url, timeoutMs) {
510
+ const controller = new AbortController();
511
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
512
+ try {
513
+ const res = await fetch(url, {
514
+ signal: controller.signal,
515
+ headers: { accept: "application/yaml, application/json, text/yaml, text/plain;q=0.8, */*;q=0.5" }
516
+ });
517
+ if (!res.ok) return null;
518
+ const text = await res.text();
519
+ return text.length > MAX_SPEC_BYTES ? null : text;
520
+ } catch {
521
+ return null;
522
+ } finally {
523
+ clearTimeout(timer);
524
+ }
525
+ }
526
+ async function classifyTargetInput(input, failFn) {
527
+ const t = (input ?? "").trim();
528
+ if (!isHttpUrl(t)) {
529
+ let isFile = false;
530
+ try {
531
+ isFile = fs4.existsSync(t) && fs4.statSync(t).isFile();
532
+ } catch {
533
+ isFile = false;
534
+ }
535
+ if (isFile) {
536
+ const text2 = fs4.readFileSync(t, "utf-8");
537
+ if (looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
538
+ failFn(
539
+ `${t} exists but is not an OpenAPI/Swagger document (no openapi/swagger version field with paths).`,
540
+ "Pass a spec file, a spec URL, or a target server base URL."
541
+ );
542
+ }
543
+ return { kind: "server" };
544
+ }
545
+ if (smellsLikeSpecUrl(t)) {
546
+ const text2 = await fetchTextCapped(t, 2e4);
547
+ if (text2 && looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
548
+ failFn(
549
+ `${t} looks like an OpenAPI file but did not return a valid OpenAPI/Swagger document.`,
550
+ text2 === null ? "The URL could not be fetched (or is over 5MB). Check it in a browser." : "The response parsed, but has no openapi/swagger version field with paths."
551
+ );
552
+ }
553
+ const text = await fetchTextCapped(t, 6e3);
554
+ if (text && looksLikeSpecDoc(text)) return { kind: "spec", specText: text, source: t };
555
+ return { kind: "server" };
556
+ }
557
+ var fs4, import_yaml, MAX_SPEC_BYTES;
558
+ var init_spec_or_target = __esm({
559
+ "src/lib/spec-or-target.ts"() {
560
+ "use strict";
561
+ fs4 = __toESM(require("fs"));
562
+ import_yaml = require("yaml");
563
+ MAX_SPEC_BYTES = 5 * 1024 * 1024;
564
+ }
565
+ });
566
+
475
567
  // src/lib/resolve.ts
476
568
  function requireAuth() {
477
569
  const creds = loadCredentials();
@@ -929,10 +1021,10 @@ var init_tenant_pick = __esm({
929
1021
 
930
1022
  // src/index.ts
931
1023
  var import_commander = require("commander");
932
- var import_chalk52 = __toESM(require("chalk"));
1024
+ var import_chalk53 = __toESM(require("chalk"));
933
1025
 
934
1026
  // package.json
935
- var version = "0.20.9";
1027
+ var version = "0.20.11";
936
1028
 
937
1029
  // src/index.ts
938
1030
  init_types();
@@ -1198,11 +1290,11 @@ function decodeJwt(token) {
1198
1290
  return null;
1199
1291
  }
1200
1292
  }
1201
- function maskPath(path8) {
1202
- const q = path8.indexOf("?");
1203
- if (q < 0) return path8;
1204
- const base2 = path8.slice(0, q);
1205
- const query = path8.slice(q + 1);
1293
+ function maskPath(path9) {
1294
+ const q = path9.indexOf("?");
1295
+ if (q < 0) return path9;
1296
+ const base2 = path9.slice(0, q);
1297
+ const query = path9.slice(q + 1);
1206
1298
  const masked = query.split("&").map((pair) => {
1207
1299
  const eq = pair.indexOf("=");
1208
1300
  if (eq < 0) return pair;
@@ -2007,7 +2099,7 @@ function suggestTenantName(projectSlug, taken) {
2007
2099
  }
2008
2100
 
2009
2101
  // src/commands/create.ts
2010
- var import_yaml = require("yaml");
2102
+ var import_yaml2 = require("yaml");
2011
2103
  var import_chalk11 = __toESM(require("chalk"));
2012
2104
  var import_ora5 = __toESM(require("ora"));
2013
2105
  init_auth();
@@ -2015,7 +2107,7 @@ init_api();
2015
2107
  function normalizeName(raw) {
2016
2108
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2017
2109
  }
2018
- function isHttpUrl(s) {
2110
+ function isHttpUrl2(s) {
2019
2111
  try {
2020
2112
  const u = new URL((s || "").trim());
2021
2113
  return u.protocol === "http:" || u.protocol === "https:";
@@ -2025,7 +2117,7 @@ function isHttpUrl(s) {
2025
2117
  }
2026
2118
  async function loadOpenapiSource(ref) {
2027
2119
  let text;
2028
- if (isHttpUrl(ref)) {
2120
+ if (isHttpUrl2(ref)) {
2029
2121
  const res = await fetch(ref.trim(), {
2030
2122
  headers: { accept: "application/json, application/yaml, text/yaml, */*" }
2031
2123
  }).catch((err) => fail(`Could not fetch the OpenAPI spec at ${ref} \u2014 ${err.message}`));
@@ -2041,7 +2133,7 @@ async function loadOpenapiSource(ref) {
2041
2133
  if (!text.trim()) fail(`The OpenAPI spec is empty: ${ref}`);
2042
2134
  let parsed;
2043
2135
  try {
2044
- parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml.parse)(text);
2136
+ parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml2.parse)(text);
2045
2137
  } catch (err) {
2046
2138
  fail(`Could not parse the OpenAPI spec at ${ref} as JSON or YAML \u2014 ${err.message}`);
2047
2139
  }
@@ -2070,8 +2162,9 @@ function stripTenantFromPortal(devPortal) {
2070
2162
  return devPortal;
2071
2163
  }
2072
2164
  }
2073
- function fail(message) {
2165
+ function fail(message, hint) {
2074
2166
  console.error(import_chalk11.default.red(`Error: ${message}`));
2167
+ if (hint) console.error(import_chalk11.default.dim(hint));
2075
2168
  process.exit(1);
2076
2169
  }
2077
2170
  function buildTryItCurl(url, authType, apiKey) {
@@ -2094,14 +2187,84 @@ function printCurlExample(url, authType, apiKey, devPortal) {
2094
2187
  }
2095
2188
  }
2096
2189
  var VALID_AUTH = ["api_key", "none", "oauth"];
2190
+ function parseOauthFlag(raw) {
2191
+ if (raw === void 0 || raw === false) return null;
2192
+ if (raw === true || typeof raw === "string" && raw.trim() === "") {
2193
+ return {
2194
+ auth: "oauth",
2195
+ bodyPatch: { login: { providers: [{ type: "github", managed: true }] } },
2196
+ summary: "APIblaze-hosted GitHub sign-in (managed)"
2197
+ };
2198
+ }
2199
+ let parsed;
2200
+ try {
2201
+ parsed = JSON.parse(String(raw));
2202
+ } catch {
2203
+ fail(
2204
+ `--oauth value is not valid JSON: ${raw}`,
2205
+ `Shapes:
2206
+ --oauth APIblaze GitHub sign-in
2207
+ --oauth '{"iss":"https://login.acme.com/","aud":"acme-api","jwks":"https://login.acme.com/.well-known/jwks.json"}'
2208
+ --oauth '{"provider":"google","clientId":"\u2026","clientSecret":"\u2026"}'`
2209
+ );
2210
+ }
2211
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--oauth must be a JSON object (or bare).");
2212
+ if (typeof parsed.iss === "string") {
2213
+ const iss = parsed.iss.trim();
2214
+ const aud = typeof parsed.aud === "string" ? parsed.aud.trim() : "";
2215
+ const jwks = typeof parsed.jwks === "string" ? parsed.jwks.trim() : typeof parsed.jwks_url === "string" ? parsed.jwks_url.trim() : "";
2216
+ if (!iss || !aud || !jwks) fail("--oauth issuer shape needs all three of: iss, aud, jwks (a JWKS URL).");
2217
+ if (!isHttpUrl2(jwks)) fail("--oauth: jwks must be an http(s) JWKS URL.");
2218
+ return {
2219
+ auth: "oauth",
2220
+ bodyPatch: {
2221
+ requests_auth: {
2222
+ mode: "authenticate",
2223
+ methods: ["jwt"],
2224
+ jwt: { allowed_pairs: [{ iss, aud, jwks_url: jwks }] }
2225
+ }
2226
+ },
2227
+ summary: `your own hosted login \u2014 JWTs from ${iss} (aud ${aud})`
2228
+ };
2229
+ }
2230
+ if (typeof parsed.provider === "string") {
2231
+ const provider = parsed.provider.trim().toLowerCase();
2232
+ const PROVIDERS2 = ["github", "google", "microsoft", "facebook", "auth0"];
2233
+ if (!PROVIDERS2.includes(provider)) fail(`--oauth: unknown provider "${provider}". One of: ${PROVIDERS2.join(" \xB7 ")}.`);
2234
+ const clientId = typeof parsed.clientId === "string" ? parsed.clientId.trim() : typeof parsed.client_id === "string" ? parsed.client_id.trim() : "";
2235
+ const clientSecret = typeof parsed.clientSecret === "string" ? parsed.clientSecret.trim() : typeof parsed.client_secret === "string" ? parsed.client_secret.trim() : "";
2236
+ if (!clientId || !clientSecret) fail("--oauth provider shape needs clientId and clientSecret (from your OAuth app).");
2237
+ return {
2238
+ auth: "oauth",
2239
+ bodyPatch: {
2240
+ login: {
2241
+ providers: [{ type: provider, client_id: clientId, client_secret: clientSecret }]
2242
+ }
2243
+ },
2244
+ summary: `APIblaze-hosted login page with your own ${provider} app`
2245
+ };
2246
+ }
2247
+ fail('--oauth JSON must contain either "iss" (your JWT issuer) or "provider" (your OAuth app).');
2248
+ }
2097
2249
  async function runCreate(opts = {}) {
2250
+ if (opts.target && opts.openapi === void 0) {
2251
+ const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
2252
+ const c = await classifyTargetInput2(opts.target, fail);
2253
+ if (c.kind === "spec") {
2254
+ console.log(import_chalk11.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 creating FROM the spec.`));
2255
+ opts.openapi = opts.target;
2256
+ opts.target = void 0;
2257
+ }
2258
+ }
2098
2259
  const creds = loadCredentials();
2099
2260
  if (!creds) {
2100
2261
  await runAnonymousCreate(opts);
2101
2262
  return;
2102
2263
  }
2103
2264
  const interactive = !!process.stdin.isTTY && !opts.json;
2104
- const auth = (opts.auth ?? "api_key").toLowerCase();
2265
+ if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
2266
+ const oauthPlan = parseOauthFlag(opts.oauth);
2267
+ const auth = (oauthPlan ? oauthPlan.auth : opts.apikey ? "api_key" : opts.auth ?? "api_key").toLowerCase();
2105
2268
  if (!VALID_AUTH.includes(auth)) {
2106
2269
  fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
2107
2270
  }
@@ -2165,13 +2328,13 @@ async function runCreate(opts = {}) {
2165
2328
  }
2166
2329
  let openapiContent = null;
2167
2330
  if (opts.openapi !== void 0) {
2168
- if (opts.target !== void 0) fail("Provide only one of --target or --openapi.");
2331
+ if (opts.target !== void 0) fail("Provide only one source (the smart --target covers servers, spec files and spec URLs).");
2169
2332
  openapiContent = await loadOpenapiSource(opts.openapi);
2170
2333
  }
2171
2334
  let targetUrl = "";
2172
2335
  if (openapiContent) {
2173
2336
  } else if (opts.target !== void 0) {
2174
- if (!isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
2337
+ if (!isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
2175
2338
  targetUrl = opts.target.trim();
2176
2339
  } else if (interactive) {
2177
2340
  const { default: inquirer3 } = await import("inquirer");
@@ -2181,7 +2344,7 @@ async function runCreate(opts = {}) {
2181
2344
  name: "url",
2182
2345
  message: "Target URL to forward requests to (e.g. https://httpbin.org):"
2183
2346
  }]);
2184
- if (!isHttpUrl(url)) {
2347
+ if (!isHttpUrl2(url)) {
2185
2348
  console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
2186
2349
  continue;
2187
2350
  }
@@ -2189,11 +2352,11 @@ async function runCreate(opts = {}) {
2189
2352
  break;
2190
2353
  }
2191
2354
  } else {
2192
- fail("--target or --openapi is required in non-interactive mode.");
2355
+ fail("--target is required in non-interactive mode (a server base URL, a local OpenAPI file, or a remote OpenAPI URL).");
2193
2356
  }
2194
2357
  if (interactive && !opts.yes) {
2195
2358
  const { default: inquirer3 } = await import("inquirer");
2196
- console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
2359
+ console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : oauthPlan ? ` \u2014 ${oauthPlan.summary}` : ""}`);
2197
2360
  const { ok } = await inquirer3.prompt([{
2198
2361
  type: "confirm",
2199
2362
  name: "ok",
@@ -2218,6 +2381,7 @@ async function runCreate(opts = {}) {
2218
2381
  ...openapiContent ? { openapi: openapiContent } : { target_url: targetUrl },
2219
2382
  auth_type: auth,
2220
2383
  team_id: teamId,
2384
+ ...oauthPlan ? oauthPlan.bodyPatch : {},
2221
2385
  ...chosenTenant ? { tenant: chosenTenant } : {},
2222
2386
  ...opts.apiversion ? { api_version: opts.apiversion } : {}
2223
2387
  });
@@ -2298,7 +2462,7 @@ async function runAnonymousCreate(opts) {
2298
2462
  if (opts.openapi !== void 0) {
2299
2463
  body.openapi = await loadOpenapiSource(opts.openapi);
2300
2464
  }
2301
- if (opts.target && !isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
2465
+ if (opts.target && !isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
2302
2466
  let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
2303
2467
  const hasOtherSource = !!(body.openapi || body.github);
2304
2468
  if (!target && !hasOtherSource) {
@@ -2320,7 +2484,7 @@ async function runAnonymousCreate(opts) {
2320
2484
  name: "url",
2321
2485
  message: "Target URL to forward requests to (e.g. https://httpbin.org):"
2322
2486
  }]);
2323
- if (!isHttpUrl(url)) {
2487
+ if (!isHttpUrl2(url)) {
2324
2488
  console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
2325
2489
  continue;
2326
2490
  }
@@ -2344,7 +2508,15 @@ async function runAnonymousCreate(opts) {
2344
2508
  if (opts.product) body.product_slug = normalizeName(opts.product);
2345
2509
  if (opts.displayName) body.display_name = opts.displayName;
2346
2510
  if (opts.apiversion) body.api_version = opts.apiversion;
2347
- if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
2511
+ if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
2512
+ const oauthPlan = parseOauthFlag(opts.oauth);
2513
+ if (oauthPlan) {
2514
+ Object.assign(body, oauthPlan.bodyPatch);
2515
+ if (oauthPlan.auth !== "api_key" && !("requests_auth" in oauthPlan.bodyPatch)) body.auth_type = oauthPlan.auth;
2516
+ if (!opts.json) console.log(`${import_chalk11.default.cyan("\u2192")} Login: ${oauthPlan.summary}`);
2517
+ } else if (opts.auth && opts.auth !== "api_key" && !opts.apikey) {
2518
+ body.auth_type = opts.auth;
2519
+ }
2348
2520
  const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
2349
2521
  if (opts.newSession) clearAnonCred2();
2350
2522
  const cred = loadAnonCred2();
@@ -2487,7 +2659,7 @@ var import_chalk13 = __toESM(require("chalk"));
2487
2659
  init_auth();
2488
2660
 
2489
2661
  // src/lib/consumer-auth.ts
2490
- var fs5 = __toESM(require("fs"));
2662
+ var fs6 = __toESM(require("fs"));
2491
2663
  var os3 = __toESM(require("os"));
2492
2664
  var path3 = __toESM(require("path"));
2493
2665
  var crypto = __toESM(require("crypto"));
@@ -2498,19 +2670,19 @@ var CONSUMER_PATH = path3.join(APIBLAZE_DIR2, "consumer.json");
2498
2670
  var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
2499
2671
  function loadConsumer() {
2500
2672
  try {
2501
- return JSON.parse(fs5.readFileSync(CONSUMER_PATH, "utf-8"));
2673
+ return JSON.parse(fs6.readFileSync(CONSUMER_PATH, "utf-8"));
2502
2674
  } catch {
2503
2675
  return null;
2504
2676
  }
2505
2677
  }
2506
2678
  function saveConsumer(creds) {
2507
- fs5.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2508
- fs5.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2509
- fs5.chmodSync(CONSUMER_PATH, 384);
2679
+ fs6.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2680
+ fs6.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2681
+ fs6.chmodSync(CONSUMER_PATH, 384);
2510
2682
  }
2511
2683
  function clearConsumer() {
2512
2684
  try {
2513
- fs5.unlinkSync(CONSUMER_PATH);
2685
+ fs6.unlinkSync(CONSUMER_PATH);
2514
2686
  return true;
2515
2687
  } catch {
2516
2688
  return false;
@@ -2655,12 +2827,12 @@ async function runLogout(opts = {}) {
2655
2827
 
2656
2828
  // src/commands/flush.ts
2657
2829
  var import_chalk14 = __toESM(require("chalk"));
2658
- var fs6 = __toESM(require("fs"));
2830
+ var fs7 = __toESM(require("fs"));
2659
2831
  var path4 = __toESM(require("path"));
2660
2832
  init_auth();
2661
2833
  async function runFlush(opts) {
2662
2834
  const dir = getApiblazeDir();
2663
- const entries2 = fs6.existsSync(dir) ? fs6.readdirSync(dir) : [];
2835
+ const entries2 = fs7.existsSync(dir) ? fs7.readdirSync(dir) : [];
2664
2836
  if (!entries2.length) {
2665
2837
  console.log(import_chalk14.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
2666
2838
  return;
@@ -2684,7 +2856,7 @@ async function runFlush(opts) {
2684
2856
  let removed = 0;
2685
2857
  for (const e of entries2) {
2686
2858
  try {
2687
- fs6.rmSync(path4.join(dir, e), { recursive: true, force: true });
2859
+ fs7.rmSync(path4.join(dir, e), { recursive: true, force: true });
2688
2860
  removed++;
2689
2861
  } catch (err) {
2690
2862
  console.log(import_chalk14.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
@@ -3357,8 +3529,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
3357
3529
  function normalizeName2(raw) {
3358
3530
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
3359
3531
  }
3360
- async function publicFetch(path8) {
3361
- const res = await fetch(`${RECIPES_BASE}/api/recipes${path8}`, {
3532
+ async function publicFetch(path9) {
3533
+ const res = await fetch(`${RECIPES_BASE}/api/recipes${path9}`, {
3362
3534
  headers: { accept: "application/json" }
3363
3535
  }).catch((err) => {
3364
3536
  throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
@@ -3411,11 +3583,11 @@ async function fetchRecipeFile(ref, caller) {
3411
3583
  Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
3412
3584
  );
3413
3585
  }
3414
- const path8 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
3586
+ const path9 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
3415
3587
  try {
3416
3588
  return await producer(caller, {
3417
3589
  method: "GET",
3418
- path: path8,
3590
+ path: path9,
3419
3591
  summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
3420
3592
  });
3421
3593
  } catch (err) {
@@ -4567,9 +4739,9 @@ function printEnvelopeViolations(violations) {
4567
4739
  console.log("bring-your-own-key case. Change the rules above, then publish again.");
4568
4740
  console.log();
4569
4741
  }
4570
- async function askPromotion(path8) {
4742
+ async function askPromotion(path9) {
4571
4743
  const { default: inquirer3 } = await import("inquirer");
4572
- const suggestedId = path8.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
4744
+ const suggestedId = path9.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
4573
4745
  const answers = await inquirer3.prompt([
4574
4746
  { type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
4575
4747
  { type: "input", name: "example", message: " example:" },
@@ -4580,7 +4752,7 @@ async function askPromotion(path8) {
4580
4752
  const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
4581
4753
  console.log(import_chalk27.default.dim(` \u2192 replaced with {{${id}}}`));
4582
4754
  return {
4583
- path: path8,
4755
+ path: path9,
4584
4756
  id,
4585
4757
  prompt: String(answers.prompt).trim(),
4586
4758
  ...answers.example ? { example: String(answers.example).trim() } : {},
@@ -6310,7 +6482,7 @@ async function providersMenu(cBase, clientLabel) {
6310
6482
  }
6311
6483
 
6312
6484
  // src/commands/spec.ts
6313
- var fs7 = __toESM(require("fs"));
6485
+ var fs8 = __toESM(require("fs"));
6314
6486
  var import_chalk37 = __toESM(require("chalk"));
6315
6487
  var import_ora16 = __toESM(require("ora"));
6316
6488
  init_admin();
@@ -6333,7 +6505,7 @@ async function runSpecSet(project, opts) {
6333
6505
  }
6334
6506
  let specContent;
6335
6507
  try {
6336
- specContent = fs7.readFileSync(opts.file, "utf-8");
6508
+ specContent = fs8.readFileSync(opts.file, "utf-8");
6337
6509
  } catch {
6338
6510
  console.error(import_chalk37.default.red(`Cannot read file: ${opts.file}`));
6339
6511
  process.exit(1);
@@ -6862,6 +7034,15 @@ var SETTINGS = [
6862
7034
  read: (cfg) => cfg.chat_sponsorship?.max_chars_per_message ?? 2e3,
6863
7035
  toPatch: (v) => ({ chat_sponsorship: { max_chars_per_message: v } })
6864
7036
  },
7037
+ {
7038
+ key: "chat_prompt_addendum",
7039
+ label: "AI chat: steering notes",
7040
+ group: "Portal & MCP",
7041
+ type: "string",
7042
+ desc: `Notes the chat assistant follows on this API \u2014 tone, suggestions, house rules ("always suggest the daily special"). Injected beneath the assistant's safety rules, so it can steer style but never override them. Max 1000 chars; empty clears`,
7043
+ read: (cfg) => cfg.chat_prompt_addendum ?? "",
7044
+ toPatch: (v) => ({ chat_prompt_addendum: typeof v === "string" && v.trim() ? v.trim() : null })
7045
+ },
6865
7046
  // ── Advanced (specs/missing.md): all optional, default-off; null clears ────
6866
7047
  {
6867
7048
  key: "ip_access.mode",
@@ -7621,11 +7802,11 @@ async function resolveTransport(opts) {
7621
7802
  const token = getAccessToken();
7622
7803
  return {
7623
7804
  tenant: tenant2,
7624
- call: async (path8, method = "GET", body) => {
7805
+ call: async (path9, method = "GET", body) => {
7625
7806
  const res = await fetch(`${DASHBOARD_BASE6}/api/cli/iam`, {
7626
7807
  method: "POST",
7627
7808
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
7628
- body: JSON.stringify({ tenant: tenant2, path: path8, method, body })
7809
+ body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
7629
7810
  });
7630
7811
  const data = await res.json().catch(() => ({}));
7631
7812
  if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
@@ -7643,8 +7824,8 @@ async function resolveTransport(opts) {
7643
7824
  return {
7644
7825
  tenant: fresh.tenant,
7645
7826
  selfEmail: fresh.email,
7646
- call: async (path8, method = "GET", body) => {
7647
- const res = await fetch(`${IAM_BASE}${path8}`, {
7827
+ call: async (path9, method = "GET", body) => {
7828
+ const res = await fetch(`${IAM_BASE}${path9}`, {
7648
7829
  method,
7649
7830
  headers: {
7650
7831
  "Content-Type": "application/json",
@@ -7996,12 +8177,12 @@ init_admin();
7996
8177
  init_resolve();
7997
8178
  init_types();
7998
8179
  var DASHBOARD_BASE7 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
7999
- async function iamCall(tenant2, path8, method = "GET", body) {
8180
+ async function iamCall(tenant2, path9, method = "GET", body) {
8000
8181
  const token = getAccessToken();
8001
8182
  const res = await fetch(`${DASHBOARD_BASE7}/api/cli/iam`, {
8002
8183
  method: "POST",
8003
8184
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
8004
- body: JSON.stringify({ tenant: tenant2, path: path8, method, body })
8185
+ body: JSON.stringify({ tenant: tenant2, path: path9, method, body })
8005
8186
  });
8006
8187
  const data = await res.json().catch(() => ({}));
8007
8188
  if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
@@ -8088,19 +8269,19 @@ async function runPreapprove(who, opts) {
8088
8269
  }
8089
8270
 
8090
8271
  // src/commands/apichat.ts
8091
- var fs9 = __toESM(require("fs"));
8092
- var path6 = __toESM(require("path"));
8272
+ var fs11 = __toESM(require("fs"));
8273
+ var path7 = __toESM(require("path"));
8093
8274
  var crypto2 = __toESM(require("crypto"));
8094
- var import_chalk46 = __toESM(require("chalk"));
8275
+ var import_chalk47 = __toESM(require("chalk"));
8095
8276
  var import_ora23 = __toESM(require("ora"));
8096
- var import_yaml2 = require("yaml");
8277
+ var import_yaml3 = require("yaml");
8097
8278
  init_auth();
8098
8279
  init_anon_cred();
8099
8280
  init_api();
8100
8281
  init_admin();
8101
8282
 
8102
8283
  // src/commands/llm.ts
8103
- var fs8 = __toESM(require("fs"));
8284
+ var fs9 = __toESM(require("fs"));
8104
8285
  var path5 = __toESM(require("path"));
8105
8286
  var import_chalk45 = __toESM(require("chalk"));
8106
8287
  var import_inquirer2 = __toESM(require("inquirer"));
@@ -8116,15 +8297,15 @@ function detectProvider(key) {
8116
8297
  }
8117
8298
  function loadLlmConfig() {
8118
8299
  try {
8119
- return JSON.parse(fs8.readFileSync(LLM_PATH, "utf-8"));
8300
+ return JSON.parse(fs9.readFileSync(LLM_PATH, "utf-8"));
8120
8301
  } catch {
8121
8302
  return null;
8122
8303
  }
8123
8304
  }
8124
8305
  function saveLlmConfig(cfg) {
8125
- fs8.mkdirSync(getApiblazeDir(), { recursive: true });
8126
- fs8.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
8127
- fs8.chmodSync(LLM_PATH, 384);
8306
+ fs9.mkdirSync(getApiblazeDir(), { recursive: true });
8307
+ fs9.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
8308
+ fs9.chmodSync(LLM_PATH, 384);
8128
8309
  }
8129
8310
  async function runLlmSetKey(keyArg, opts) {
8130
8311
  let key = keyArg?.trim();
@@ -8163,7 +8344,7 @@ async function runLlmShow() {
8163
8344
  }
8164
8345
  async function runLlmClearKey() {
8165
8346
  try {
8166
- fs8.unlinkSync(LLM_PATH);
8347
+ fs9.unlinkSync(LLM_PATH);
8167
8348
  console.log(`${import_chalk45.default.green("\u2713")} Removed local LLM key.`);
8168
8349
  } catch {
8169
8350
  console.log(import_chalk45.default.gray("No LLM key was set."));
@@ -8172,17 +8353,140 @@ async function runLlmClearKey() {
8172
8353
 
8173
8354
  // src/commands/apichat.ts
8174
8355
  init_trace();
8356
+
8357
+ // src/lib/external-mcp.ts
8358
+ var fs10 = __toESM(require("fs"));
8359
+ var os4 = __toESM(require("os"));
8360
+ var path6 = __toESM(require("path"));
8361
+ var import_child_process2 = require("child_process");
8362
+ var import_chalk46 = __toESM(require("chalk"));
8363
+ var run = (cmd, args, opts = {}) => (0, import_child_process2.spawnSync)(cmd, args, {
8364
+ encoding: "utf-8",
8365
+ stdio: opts.inherit ? ["ignore", "inherit", "inherit"] : ["ignore", "pipe", "pipe"],
8366
+ timeout: opts.inherit ? void 0 : 15e3,
8367
+ shell: process.platform === "win32"
8368
+ // .cmd shims on Windows
8369
+ });
8370
+ function detectExternalClis() {
8371
+ const found = [];
8372
+ for (const [kind, label3] of [["claude", "Claude CLI"], ["codex", "Codex CLI"]]) {
8373
+ try {
8374
+ const r = run(kind, ["--version"]);
8375
+ if (r.status === 0) found.push({ kind, label: label3, version: (r.stdout || "").trim().split("\n")[0] || void 0 });
8376
+ } catch {
8377
+ }
8378
+ }
8379
+ return found;
8380
+ }
8381
+ function claudeInstallArgs(spec2) {
8382
+ const args = ["mcp", "add", "--transport", "http", spec2.name, spec2.url];
8383
+ if (spec2.apiKey) args.push("--header", `X-API-Key: ${spec2.apiKey}`);
8384
+ return args;
8385
+ }
8386
+ function installIntoClaude(spec2) {
8387
+ run("claude", ["mcp", "remove", spec2.name]);
8388
+ const r = run("claude", claudeInstallArgs(spec2));
8389
+ if (r.status === 0) return { ok: true };
8390
+ return { ok: false, error: (r.stderr || r.stdout || `exit ${r.status}`).trim().slice(0, 400) };
8391
+ }
8392
+ function claudeOneShot(spec2, prompt) {
8393
+ const argv = ["claude", "-p", prompt, "--allowedTools", `mcp__${spec2.name}__*`];
8394
+ const r = run(argv[0], argv.slice(1), { inherit: true });
8395
+ return { argv, status: r.status };
8396
+ }
8397
+ function codexConfigPath() {
8398
+ return path6.join(process.env.CODEX_HOME || path6.join(os4.homedir(), ".codex"), "config.toml");
8399
+ }
8400
+ var tomlStr = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
8401
+ function codexServerBlock(spec2) {
8402
+ const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
8403
+ if (spec2.apiKey) lines.push(`http_headers = { "X-API-Key" = ${tomlStr(spec2.apiKey)} }`);
8404
+ return lines.join("\n") + "\n";
8405
+ }
8406
+ function installIntoCodex(spec2) {
8407
+ const file = codexConfigPath();
8408
+ try {
8409
+ fs10.mkdirSync(path6.dirname(file), { recursive: true });
8410
+ let text = "";
8411
+ try {
8412
+ text = fs10.readFileSync(file, "utf-8");
8413
+ } catch {
8414
+ }
8415
+ const esc = spec2.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8416
+ const section = new RegExp(`(^|\\n)\\[mcp_servers\\.(?:"${esc}"|${esc})\\][^\\[]*`, "g");
8417
+ const cleaned = text.replace(section, "$1");
8418
+ const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
8419
+ fs10.writeFileSync(file, cleaned + sep + codexServerBlock(spec2), "utf-8");
8420
+ return { ok: true, path: file };
8421
+ } catch (err) {
8422
+ return { ok: false, error: err instanceof Error ? err.message : String(err), path: file };
8423
+ }
8424
+ }
8425
+ function codexOneShot(_spec, prompt) {
8426
+ const argv = ["codex", "exec", prompt];
8427
+ const r = run(argv[0], argv.slice(1), { inherit: true });
8428
+ return { argv, status: r.status };
8429
+ }
8430
+ var shellQuote = (s) => `"${s.replace(/(["\\$`])/g, "\\$1")}"`;
8431
+ function renderCommand(argv) {
8432
+ return argv.map((a, i) => i === 0 || /^[A-Za-z0-9_@%+=:,./-]+$/.test(a) ? a : shellQuote(a)).join(" ");
8433
+ }
8434
+ function installAndDemo(cli, spec2, question, log = console.log) {
8435
+ if (cli.kind === "claude") {
8436
+ log(`
8437
+ ${import_chalk46.default.dim("$")} ${renderCommand(["claude", ...claudeInstallArgs(spec2)])}`);
8438
+ const r = installIntoClaude(spec2);
8439
+ if (!r.ok) {
8440
+ log(import_chalk46.default.red(` Install failed: ${r.error}`));
8441
+ return false;
8442
+ }
8443
+ log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to Claude CLI (local scope \u2014 this directory).`);
8444
+ } else {
8445
+ const r = installIntoCodex(spec2);
8446
+ if (!r.ok) {
8447
+ log(import_chalk46.default.red(` Could not write ${r.path}: ${r.error}`));
8448
+ return false;
8449
+ }
8450
+ log(` ${import_chalk46.default.green("\u2714")} MCP ${import_chalk46.default.bold(spec2.name)} added to ${r.path}.`);
8451
+ }
8452
+ if (!spec2.apiKey) {
8453
+ log(import_chalk46.default.dim(` This proxy authenticates by login: the first call from ${cli.label} will open its sign-in.`));
8454
+ log(`
8455
+ ${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
8456
+ return true;
8457
+ }
8458
+ const oneShot = cli.kind === "claude" ? claudeOneShot : codexOneShot;
8459
+ const abilities = `What are the tool abilities of the MCP server "${spec2.name}"? List them briefly.`;
8460
+ log(`
8461
+ ${import_chalk46.default.dim("Checking what the MCP exposes\u2026")}`);
8462
+ log(` ${import_chalk46.default.dim("$")} ${renderCommand(cli.kind === "claude" ? ["claude", "-p", abilities, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", abilities])}
8463
+ `);
8464
+ oneShot(spec2, abilities);
8465
+ if (question) {
8466
+ log(`
8467
+ ${import_chalk46.default.dim("Your question, through " + cli.label + ":")}`);
8468
+ const shown = cli.kind === "claude" ? ["claude", "-p", question, "--allowedTools", `mcp__${spec2.name}__*`] : ["codex", "exec", question];
8469
+ log(` ${import_chalk46.default.dim("$")} ${renderCommand(shown)}
8470
+ `);
8471
+ oneShot(spec2, question);
8472
+ }
8473
+ log(`
8474
+ ${import_chalk46.default.bold(`Your ${cli.label} is now able to talk to the ${spec2.projectLabel} API.`)}`);
8475
+ return true;
8476
+ }
8477
+
8478
+ // src/commands/apichat.ts
8175
8479
  init_types();
8176
8480
  function fail4(message, hint) {
8177
- console.error(import_chalk46.default.red(`
8481
+ console.error(import_chalk47.default.red(`
8178
8482
  Error: ${message}`));
8179
- if (hint) console.error(import_chalk46.default.dim(hint));
8483
+ if (hint) console.error(import_chalk47.default.dim(hint));
8180
8484
  process.exit(1);
8181
8485
  }
8182
8486
  function normalizeName3(raw) {
8183
8487
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
8184
8488
  }
8185
- function isHttpUrl2(s) {
8489
+ function isHttpUrl3(s) {
8186
8490
  try {
8187
8491
  const u = new URL((s || "").trim());
8188
8492
  return u.protocol === "http:" || u.protocol === "https:";
@@ -8196,7 +8500,7 @@ function parseSpec(text) {
8196
8500
  parsed = JSON.parse(text);
8197
8501
  } catch {
8198
8502
  try {
8199
- parsed = (0, import_yaml2.parse)(text);
8503
+ parsed = (0, import_yaml3.parse)(text);
8200
8504
  } catch {
8201
8505
  fail4("Could not parse the spec as JSON or YAML.");
8202
8506
  }
@@ -8207,7 +8511,7 @@ function parseSpec(text) {
8207
8511
  }
8208
8512
  return parsed;
8209
8513
  }
8210
- var GENERATOR_HINT = "Provide one with --openapispec <file|url>, or build one from real traffic:\n apiblaze create --target <url> then apiblaze agent openapi <project>";
8514
+ var GENERATOR_HINT = "Pass --target <server-url | spec-file | spec-url>, or build a spec from real traffic:\n apiblaze create --target <url> then apiblaze agent openapi <project>";
8211
8515
  async function fetchText(url) {
8212
8516
  try {
8213
8517
  const res = await fetch(url, { headers: { accept: "application/json, application/yaml, text/yaml, */*" } });
@@ -8233,32 +8537,32 @@ async function discoverSpec(target) {
8233
8537
  }
8234
8538
  async function loadSpec(opts) {
8235
8539
  if (opts.openapispec) {
8236
- if (isHttpUrl2(opts.openapispec)) {
8540
+ if (isHttpUrl3(opts.openapispec)) {
8237
8541
  const text2 = await fetchText(opts.openapispec);
8238
8542
  if (!text2) fail4(`Could not fetch the spec at ${opts.openapispec}.`);
8239
8543
  return { spec: parseSpec(text2), sourceUrl: opts.openapispec };
8240
8544
  }
8241
8545
  let text;
8242
8546
  try {
8243
- text = fs9.readFileSync(opts.openapispec, "utf-8");
8547
+ text = fs11.readFileSync(opts.openapispec, "utf-8");
8244
8548
  } catch {
8245
8549
  fail4(`Cannot read spec file: ${opts.openapispec}`);
8246
8550
  }
8247
8551
  return { spec: parseSpec(text) };
8248
8552
  }
8249
8553
  if (opts.target) {
8250
- if (!isHttpUrl2(opts.target)) fail4("--target must be a valid http(s) URL.");
8554
+ if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
8251
8555
  const found = await discoverSpec(opts.target);
8252
8556
  if (!found) {
8253
8557
  fail4(`No OpenAPI spec found at ${opts.target} (tried /openapi.json, /openapi.yaml, /swagger.json).`, GENERATOR_HINT);
8254
8558
  }
8255
8559
  return { spec: found.spec, sourceUrl: found.sourceUrl };
8256
8560
  }
8257
- fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
8561
+ fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
8258
8562
  }
8259
8563
  function resolveTarget(spec2, opts, sourceUrl) {
8260
8564
  if (opts.target) {
8261
- if (!isHttpUrl2(opts.target)) fail4("--target must be a valid http(s) URL.");
8565
+ if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
8262
8566
  return opts.target.trim();
8263
8567
  }
8264
8568
  const servers = Array.isArray(spec2.servers) ? spec2.servers : [];
@@ -8268,7 +8572,7 @@ function resolveTarget(spec2, opts, sourceUrl) {
8268
8572
  }
8269
8573
  try {
8270
8574
  const resolved = sourceUrl ? new URL(raw, sourceUrl).toString() : raw;
8271
- if (!isHttpUrl2(resolved)) {
8575
+ if (!isHttpUrl3(resolved)) {
8272
8576
  fail4(`servers[0].url ("${raw}") is not an absolute URL and could not be resolved.`, "Re-run with --target <upstream base URL>.");
8273
8577
  }
8274
8578
  return resolved;
@@ -8324,7 +8628,7 @@ async function resolveTargetAuth(spec2, opts) {
8324
8628
  "Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
8325
8629
  );
8326
8630
  }
8327
- if (sawOAuth) console.log(import_chalk46.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
8631
+ if (sawOAuth) console.log(import_chalk47.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
8328
8632
  return null;
8329
8633
  }
8330
8634
  if (candidates.length === 1 && !noneAllowed) return candidates[0];
@@ -8378,9 +8682,10 @@ async function captureTargetSecret(auth, opts) {
8378
8682
  return secret;
8379
8683
  }
8380
8684
  async function dataPlaneAuth(p) {
8685
+ const idHeader = p.endUserId ? { "X-End-User-Id": p.endUserId } : {};
8381
8686
  if (!p.consumerAuth) {
8382
- if (!p.dpKey) throw new Error("No API key for this proxy.");
8383
- return { "X-API-Key": p.dpKey };
8687
+ if (!p.dpKey) return idHeader;
8688
+ return { "X-API-Key": p.dpKey, ...idHeader };
8384
8689
  }
8385
8690
  const stored = loadConsumer();
8386
8691
  if (!stored) {
@@ -8388,7 +8693,7 @@ async function dataPlaneAuth(p) {
8388
8693
  }
8389
8694
  const fresh = await validConsumerToken(stored) ?? stored;
8390
8695
  if (fresh.accessToken !== stored.accessToken) saveConsumer(fresh);
8391
- return { Authorization: `Bearer ${fresh.accessToken}` };
8696
+ return { Authorization: `Bearer ${fresh.accessToken}`, ...idHeader };
8392
8697
  }
8393
8698
  async function ensureConsumerLogin(teamId, tenant2, version2) {
8394
8699
  const resource = `https://${tenant2}.portal.apiblaze.com/${version2}`;
@@ -8397,7 +8702,7 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
8397
8702
  const fresh = await validConsumerToken(existing);
8398
8703
  if (fresh) {
8399
8704
  if (fresh.accessToken !== existing.accessToken) saveConsumer({ ...fresh, resource });
8400
- console.log(import_chalk46.default.dim(` Using your consumer session on ${import_chalk46.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
8705
+ console.log(import_chalk47.default.dim(` Using your consumer session on ${import_chalk47.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
8401
8706
  return fresh;
8402
8707
  }
8403
8708
  }
@@ -8416,13 +8721,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
8416
8721
  );
8417
8722
  }
8418
8723
  const clientId = pick2.client_id ?? pick2.clientId;
8419
- console.log(`${import_chalk46.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk46.default.bold(tenant2)}...`);
8724
+ console.log(`${import_chalk47.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk47.default.bold(tenant2)}...`);
8420
8725
  const result = await deviceLogin(clientId, "openid email profile offline_access", ({ verificationUri, userCode }) => {
8421
8726
  console.log(`
8422
- Open: ${import_chalk46.default.underline(verificationUri)}`);
8423
- console.log(` Code: ${import_chalk46.default.bold(userCode)}
8727
+ Open: ${import_chalk47.default.underline(verificationUri)}`);
8728
+ console.log(` Code: ${import_chalk47.default.bold(userCode)}
8424
8729
  `);
8425
- console.log(import_chalk46.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
8730
+ console.log(import_chalk47.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
8426
8731
  }, resource);
8427
8732
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
8428
8733
  const creds = {
@@ -8438,16 +8743,16 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
8438
8743
  resource
8439
8744
  };
8440
8745
  saveConsumer(creds);
8441
- console.log(` ${import_chalk46.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk46.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
8746
+ console.log(` ${import_chalk47.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk47.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
8442
8747
  return creds;
8443
8748
  }
8444
- async function cpPost(anon, path8, body, summary) {
8749
+ async function cpPost(anon, path9, body, summary) {
8445
8750
  if (anon) {
8446
8751
  const cred = loadAnonCred();
8447
8752
  if (!cred) throw new Error("Anonymous workspace credential missing.");
8448
- return cpFetch(cred.cp_key, path8, { method: "POST", body: JSON.stringify(body) });
8753
+ return cpFetch(cred.cp_key, path9, { method: "POST", body: JSON.stringify(body) });
8449
8754
  }
8450
- return admin({ method: "POST", path: path8, body, summary });
8755
+ return admin({ method: "POST", path: path9, body, summary });
8451
8756
  }
8452
8757
  async function provision(spec2, target, opts) {
8453
8758
  const loggedIn = !!loadCredentials();
@@ -8467,7 +8772,7 @@ async function provision(spec2, target, opts) {
8467
8772
  let name = opts.name ? base2 : `${base2}${salt()}`;
8468
8773
  const access = anon ? "open" : opts.access === "open" ? "open" : "invite";
8469
8774
  if (anon && opts.access === "invite") {
8470
- console.log(import_chalk46.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
8775
+ console.log(import_chalk47.default.dim(" Note: --access invite needs an account to pre-approve people. Staying open for this anonymous proxy \u2014 run `apiblaze login`, then `apiblaze apichat --access invite`."));
8471
8776
  }
8472
8777
  const DUAL_AUTH = {
8473
8778
  mode: "authenticate",
@@ -8566,7 +8871,7 @@ async function provision(spec2, target, opts) {
8566
8871
  try {
8567
8872
  await addPreapprovalRule(tenant2, email);
8568
8873
  } catch {
8569
- console.log(import_chalk46.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
8874
+ console.log(import_chalk47.default.dim(` (Could not auto-approve your email for sign-in \u2014 add it later: apiblaze preapprove ${email})`));
8570
8875
  }
8571
8876
  }
8572
8877
  }
@@ -8610,7 +8915,7 @@ async function uploadSpec(p, specText, opts) {
8610
8915
  throw err;
8611
8916
  }
8612
8917
  if (out && out.reused === true) {
8613
- console.log(import_chalk46.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
8918
+ console.log(import_chalk47.default.dim(" Spec unchanged since the last provision \u2014 reusing the existing configuration."));
8614
8919
  } else if (out && out.changed === true && out.previous_spec_hash) {
8615
8920
  const interactive = !!process.stdin.isTTY && !opts.yes;
8616
8921
  if (interactive) {
@@ -8618,7 +8923,7 @@ async function uploadSpec(p, specText, opts) {
8618
8923
  const { go } = await inquirer3.prompt([
8619
8924
  { type: "confirm", name: "go", message: "The spec changed since the last provision \u2014 re-publish the MCP catalogue?", default: true }
8620
8925
  ]);
8621
- if (!go) console.log(import_chalk46.default.dim(" Keeping the existing MCP catalogue."));
8926
+ if (!go) console.log(import_chalk47.default.dim(" Keeping the existing MCP catalogue."));
8622
8927
  }
8623
8928
  }
8624
8929
  }
@@ -8648,7 +8953,6 @@ async function publishMcp(p, spec2) {
8648
8953
  return null;
8649
8954
  }
8650
8955
  }
8651
- var CLIENT_ROUND_CAP = 12;
8652
8956
  function chatUrl(p) {
8653
8957
  return `https://${p.mcpHost}/${p.version}/${p.environment}/runtime-chat`;
8654
8958
  }
@@ -8656,91 +8960,66 @@ function maskKey(k) {
8656
8960
  return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
8657
8961
  }
8658
8962
  var revealAuth = false;
8659
- function renderToolEvents(events, cred) {
8660
- for (const e of events ?? []) {
8661
- const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
8662
- const mark = ok ? import_chalk46.default.green("\u2713") : import_chalk46.default.red("\u2717");
8663
- console.log(` ${mark} ${import_chalk46.default.cyan(e.name)} ${import_chalk46.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
8664
- if (isVerbose() && e.method && e.url) {
8665
- console.log(import_chalk46.default.dim(` curl -sS -X ${e.method} '${e.url}'${cred ? " \\" : ""}`));
8666
- if (cred) {
8667
- const shown = revealAuth ? cred.value : maskKey(cred.value);
8668
- const keyLine = ` -H '${cred.header}: ${shown}'`;
8669
- const hint = revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth will reveal this");
8670
- console.log(import_chalk46.default.dim(keyLine) + hint);
8671
- }
8672
- }
8673
- }
8674
- }
8675
- function renderToolResults(delta, events) {
8676
- const anyFailed = (events ?? []).some((e) => e.status === "error");
8677
- if (!anyFailed && !isVerbose()) return;
8678
- const cap = anyFailed ? 24 : 12;
8679
- for (const m of delta ?? []) {
8680
- if (!m || m.role !== "tool" || typeof m.content !== "string") continue;
8681
- const body = m.content.trim();
8682
- if (!body) continue;
8683
- const pretty = (() => {
8684
- try {
8685
- return JSON.stringify(JSON.parse(body), null, 2);
8686
- } catch {
8687
- return body;
8688
- }
8689
- })();
8690
- const lines = pretty.split("\n");
8691
- console.log(import_chalk46.default.dim(" response:"));
8692
- for (const line of lines.slice(0, cap)) {
8693
- console.log(import_chalk46.default.dim(` ${line}`));
8694
- }
8695
- if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
8696
- }
8697
- }
8698
- function billingLine(billing) {
8699
- if (!billing || typeof billing.cents !== "number") return null;
8700
- if (typeof billing.free_turns_remaining === "number") return null;
8701
- const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
8702
- let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk46.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
8703
- if (typeof billing.credits_remaining === "number") {
8704
- line += import_chalk46.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
8963
+ function billingLine(info) {
8964
+ if (!info || typeof info.charged_cents !== "number" || info.charged_cents <= 0) return null;
8965
+ if (typeof info.free_turns_remaining === "number") return null;
8966
+ const cents = info.charged_cents;
8967
+ const usd = (cents / 100).toFixed(Math.abs(cents - Math.round(cents)) < 1e-9 ? 2 : 4);
8968
+ let line = import_chalk47.default.magenta(` \u{1F4B3} $${usd}`);
8969
+ if (typeof info.credits_remaining === "number") {
8970
+ line += import_chalk47.default.dim(` \xB7 balance $${(info.credits_remaining / 100).toFixed(2)}`);
8705
8971
  }
8706
8972
  return line;
8707
8973
  }
8708
- function freeBudgetWarning(billing, anon) {
8709
- if (!anon || !billing) return null;
8710
- if (typeof billing.free_turns_remaining === "number") {
8711
- const left2 = billing.free_turns_remaining;
8712
- if (left2 <= 0) return import_chalk46.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
8713
- return import_chalk46.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
8974
+ function freeBudgetWarning(info, anon) {
8975
+ if (!anon || !info) return null;
8976
+ if (typeof info.free_turns_remaining === "number") {
8977
+ const left2 = info.free_turns_remaining;
8978
+ if (left2 <= 0) return import_chalk47.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
8979
+ return import_chalk47.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
8714
8980
  }
8715
- if (typeof billing.free_remaining_cents !== "number") return null;
8716
- const perTurn = Math.max(billing.cents || 0, 0.02);
8717
- const left = Math.floor(billing.free_remaining_cents / perTurn);
8981
+ if (typeof info.free_remaining_cents !== "number") return null;
8982
+ const perTurn = Math.max(info.charged_cents || 0, 0.02);
8983
+ const left = Math.floor(info.free_remaining_cents / perTurn);
8718
8984
  if (left > 8) return null;
8719
- if (left <= 0) return import_chalk46.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
8720
- return import_chalk46.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
8721
- }
8722
- function printAssistant(delta) {
8723
- for (let i = delta.length - 1; i >= 0; i--) {
8724
- const m = delta[i];
8725
- if (m && m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
8726
- console.log("\n" + import_chalk46.default.green("assistant \u203A ") + m.content + "\n");
8727
- return;
8985
+ if (left <= 0) return import_chalk47.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
8986
+ return import_chalk47.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
8987
+ }
8988
+ async function readSse(body, onEvent) {
8989
+ const reader = body.getReader();
8990
+ const decoder = new TextDecoder();
8991
+ let buf = "";
8992
+ for (; ; ) {
8993
+ const { done, value } = await reader.read();
8994
+ if (done) break;
8995
+ buf += decoder.decode(value, { stream: true });
8996
+ for (; ; ) {
8997
+ const at = buf.indexOf("\n\n");
8998
+ if (at === -1) break;
8999
+ const rawEvent = buf.slice(0, at);
9000
+ buf = buf.slice(at + 2);
9001
+ const data = rawEvent.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
9002
+ if (!data || data === "[DONE]") continue;
9003
+ try {
9004
+ onEvent(JSON.parse(data));
9005
+ } catch {
9006
+ }
8728
9007
  }
8729
9008
  }
8730
9009
  }
8731
9010
  async function replTurn(p, messages, userText) {
8732
- messages.push({ role: "user", content: userText });
9011
+ messages.push({ id: crypto2.randomUUID(), role: "user", parts: [{ type: "text", text: userText }] });
8733
9012
  const llm2 = loadLlmConfig();
8734
9013
  const turnId = crypto2.randomUUID();
8735
- for (let round = 0; round < CLIENT_ROUND_CAP; round++) {
8736
- const spinner = (0, import_ora23.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
8737
- const body = {
8738
- turn_id: turnId,
8739
- messages,
8740
- environment: p.environment,
8741
- ...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
8742
- };
8743
- let res;
9014
+ const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
9015
+ const body = {
9016
+ turn_id: turnId,
9017
+ messages,
9018
+ environment: p.environment,
9019
+ ...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
9020
+ };
9021
+ let res = null;
9022
+ for (let attempt = 0; attempt < 3; attempt++) {
8744
9023
  try {
8745
9024
  res = await fetch(chatUrl(p), {
8746
9025
  method: "POST",
@@ -8749,93 +9028,286 @@ async function replTurn(p, messages, userText) {
8749
9028
  });
8750
9029
  } catch (err) {
8751
9030
  spinner.fail("Network error.");
8752
- console.log(import_chalk46.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
9031
+ console.log(import_chalk47.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
8753
9032
  return;
8754
9033
  }
9034
+ if ((res.headers.get("content-type") ?? "").includes("text/event-stream") && res.ok && res.body) break;
9035
+ spinner.stop();
8755
9036
  let data = null;
8756
9037
  try {
8757
9038
  data = await res.json();
8758
9039
  } catch {
8759
9040
  }
8760
- spinner.stop();
8761
- if (data && data.upsell) {
8762
- renderUpsell(p, data.upsell);
9041
+ const errObj = data && typeof data.error === "object" ? data.error : null;
9042
+ const code = errObj && errObj.code || null;
9043
+ const msg = String(errObj && errObj.message || data && (data.error || data.message) || "");
9044
+ const tty = !!process.stdin.isTTY;
9045
+ if (res.status === 404 && /project not found/i.test(msg)) {
9046
+ const flipped = p.mcpHost.includes(".tryabz.run") ? p.mcpHost.replace(".tryabz.run", ".abz.run") : p.mcpHost.replace(".abz.run", ".tryabz.run");
9047
+ if (flipped !== p.mcpHost && attempt === 0) {
9048
+ p.mcpHost = flipped;
9049
+ p.proxyUrl = p.proxyUrl?.includes("tryabz.run") ? p.proxyUrl.replace("tryabz.run", "abz.run") : p.proxyUrl?.replace("abz.run", "tryabz.run");
9050
+ p.anon = flipped.includes(".tryabz.run");
9051
+ spinner.start("retrying on the " + (p.anon ? "trial" : "claimed") + " plane\u2026");
9052
+ continue;
9053
+ }
9054
+ console.log(import_chalk47.default.red(` No proxy named ${p.projectId} was found (tried both abz.run and tryabz.run).`));
9055
+ return;
9056
+ }
9057
+ if (code === "identity_required" || /identif/i.test(msg) && !code) {
9058
+ if (!p.endUserId && tty) {
9059
+ console.log(import_chalk47.default.yellow(" This API requires every call to say WHO is calling."));
9060
+ const { default: inquirer3 } = await import("inquirer");
9061
+ const { id } = await inquirer3.prompt([{ type: "input", name: "id", message: "Your end-user id (usually your email):" }]);
9062
+ if (typeof id === "string" && id.trim()) {
9063
+ p.endUserId = id.trim();
9064
+ persistAuthState(p);
9065
+ spinner.start("retrying\u2026");
9066
+ continue;
9067
+ }
9068
+ }
9069
+ console.log(import_chalk47.default.red(" This API requires an identified caller."));
9070
+ console.log(import_chalk47.default.dim(" Re-run with --xenduserid <your id> (usually your email)."));
8763
9071
  return;
8764
9072
  }
8765
- if (res.status === 402 || res.status === 403) {
8766
- renderUpsell(p, { reason: "PAUSED", message: data && data.error || "This turn is not available on your current plan." });
9073
+ if (code === "user_not_preapproved") {
9074
+ console.log(import_chalk47.default.yellow(` ${msg || "You are not pre-approved on this API."}`));
9075
+ if (p.endUserId) console.log(import_chalk47.default.dim(` Identity sent: ${p.endUserId}`));
9076
+ const accessUrl = errObj && errObj.request_access_url;
9077
+ if (accessUrl) console.log(` Request access: ${import_chalk47.default.bold(String(accessUrl))}`);
9078
+ console.log(import_chalk47.default.dim(" Or ask the producer to pre-approve you: `apiblaze preapprove <your-email> --tenant <tenant>`."));
8767
9079
  return;
8768
9080
  }
8769
- if (res.status === 401) {
8770
- const reason = data && (data.error || data.message) || null;
8771
- const usingKey = !p.consumerAuth;
8772
- console.log(import_chalk46.default.red(` The proxy rejected the request (401)${reason ? `: ${reason}` : "."}`));
8773
- console.log(
8774
- import_chalk46.default.dim(
8775
- usingKey ? " Sent an API key. If the key was revoked, re-run `apiblaze apichat` to re-provision." : " Sent your consumer OAuth token. Re-run `apiblaze apichat` to sign in again."
8776
- )
8777
- );
9081
+ if (code === "user_frozen") {
9082
+ console.log(import_chalk47.default.red(` ${msg || "Your access to this API has been frozen by the producer."}`));
8778
9083
  return;
8779
9084
  }
8780
- if (!res.ok || !data) {
8781
- const err = data && data.error || `HTTP ${res.status}`;
8782
- console.log(import_chalk46.default.red(` Chat error: ${err}`));
9085
+ const oauthWanted = /oauth token required|authorization: bearer/i.test(msg);
9086
+ const keyWanted = /api key required|x-api-key/i.test(msg);
9087
+ if (oauthWanted && !p.consumerAuth) {
9088
+ if (p.teamId && p.tenant && loadCredentials()) {
9089
+ console.log(import_chalk47.default.dim(" This proxy signs consumers in with OAuth \u2014 starting the login\u2026"));
9090
+ try {
9091
+ await ensureConsumerLogin(p.teamId, p.tenant, p.version);
9092
+ p.consumerAuth = true;
9093
+ p.dpKey = void 0;
9094
+ persistAuthState(p);
9095
+ spinner.start("retrying\u2026");
9096
+ continue;
9097
+ } catch (err) {
9098
+ console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
9099
+ return;
9100
+ }
9101
+ }
9102
+ console.log(import_chalk47.default.red(" This proxy signs consumers in with OAuth (a login), not an API key."));
9103
+ console.log(import_chalk47.default.dim(" Sign in with: `apiblaze consumer login --tenant <tenant> --client <app-client-id>`, then re-run apichat."));
8783
9104
  return;
8784
9105
  }
8785
- if (Array.isArray(data.delta)) {
8786
- renderToolEvents(
8787
- data.tool_events,
8788
- p.consumerAuth ? (() => {
8789
- const t = loadConsumer()?.accessToken;
8790
- return t ? { header: "Authorization", value: `Bearer ${t}` } : void 0;
8791
- })() : p.dpKey ? { header: "X-API-Key", value: p.dpKey } : void 0
8792
- );
8793
- renderToolResults(data.delta, data.tool_events);
8794
- for (const m of data.delta) messages.push(m);
8795
- printAssistant(data.delta);
9106
+ if (keyWanted) {
9107
+ if (tty) {
9108
+ console.log(import_chalk47.default.yellow(` ${msg || "This API requires an API key."}`));
9109
+ const { default: inquirer3 } = await import("inquirer");
9110
+ const { key } = await inquirer3.prompt([{ type: "password", name: "key", mask: "*", message: "API key for this proxy:" }]);
9111
+ if (typeof key === "string" && key.trim()) {
9112
+ p.dpKey = key.trim();
9113
+ p.consumerAuth = false;
9114
+ persistAuthState(p);
9115
+ spinner.start("retrying\u2026");
9116
+ continue;
9117
+ }
9118
+ }
9119
+ console.log(import_chalk47.default.red(` ${msg || "This API requires an API key."}`));
9120
+ console.log(import_chalk47.default.dim(" Re-run with --apikey <key> (mint one from the producer's site or dev portal)."));
9121
+ return;
8796
9122
  }
8797
- const bl = billingLine(data.billing);
8798
- if (bl) console.log(bl);
8799
- const warn = freeBudgetWarning(data.billing, p.anon);
8800
- if (warn) console.log(warn);
8801
- if (!data.continue) return;
9123
+ if (res && (res.status === 402 || res.status === 403)) {
9124
+ renderUpsell(p, { reason: "PAUSED", message: msg || "This turn is not available on your current plan." });
9125
+ return;
9126
+ }
9127
+ if (res && res.status === 401) {
9128
+ console.log(import_chalk47.default.red(` The proxy rejected the request (401)${msg ? `: ${msg}` : "."}`));
9129
+ console.log(import_chalk47.default.dim(p.consumerAuth ? " Sent your consumer OAuth token. Run `apiblaze consumer login` again." : " Sent an API key. Pass a different one with --apikey <key>."));
9130
+ return;
9131
+ }
9132
+ console.log(import_chalk47.default.red(` Chat error: ${msg || (res ? `HTTP ${res.status}` : "request failed")}`));
9133
+ return;
8802
9134
  }
8803
- console.log(import_chalk46.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
8804
- }
8805
- function renderUpsell(p, upsell) {
9135
+ if (!res || !res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream")) {
9136
+ spinner.stop();
9137
+ console.log(import_chalk47.default.red(" Chat error: could not authenticate to this proxy after several attempts."));
9138
+ return;
9139
+ }
9140
+ let spinnerLive = true;
9141
+ const stopSpinner = () => {
9142
+ if (spinnerLive) {
9143
+ spinner.stop();
9144
+ spinnerLive = false;
9145
+ }
9146
+ };
9147
+ const parts = [];
9148
+ let openTextIdx = -1;
9149
+ let assistantOpen = false;
9150
+ const toolMeta = /* @__PURE__ */ new Map();
9151
+ let upsell = null;
9152
+ let turnInfo = null;
9153
+ let errorText = null;
9154
+ let messageId = `msg_${Date.now()}`;
9155
+ const credHint = () => {
9156
+ if (!isVerbose()) return null;
9157
+ if (p.consumerAuth) {
9158
+ const t = loadConsumer()?.accessToken;
9159
+ return t ? `Authorization: Bearer ${revealAuth ? t : maskKey(t)}` : null;
9160
+ }
9161
+ return p.dpKey ? `X-API-Key: ${revealAuth ? p.dpKey : maskKey(p.dpKey)}` : null;
9162
+ };
9163
+ try {
9164
+ await readSse(res.body, (ev) => {
9165
+ switch (ev?.type) {
9166
+ case "start":
9167
+ if (typeof ev.messageId === "string") messageId = ev.messageId;
9168
+ break;
9169
+ case "tool-input-start": {
9170
+ stopSpinner();
9171
+ if (assistantOpen) {
9172
+ process.stdout.write("\n");
9173
+ assistantOpen = false;
9174
+ }
9175
+ const name = String(ev.toolName ?? "tool");
9176
+ parts.push({ type: `tool-${name}`, toolCallId: String(ev.toolCallId ?? ""), state: "input-streaming" });
9177
+ toolMeta.set(String(ev.toolCallId ?? ""), { name, startedAt: Date.now(), partIdx: parts.length - 1 });
9178
+ console.log(` ${import_chalk47.default.cyan("\u2699")} ${import_chalk47.default.cyan(name)}${import_chalk47.default.dim("\u2026")}`);
9179
+ break;
9180
+ }
9181
+ case "tool-input-available": {
9182
+ const id = String(ev.toolCallId ?? "");
9183
+ const meta = toolMeta.get(id);
9184
+ const input = ev.input && typeof ev.input === "object" ? ev.input : {};
9185
+ if (meta) {
9186
+ meta.input = input;
9187
+ Object.assign(parts[meta.partIdx], { state: "input-available", input });
9188
+ }
9189
+ if (isVerbose()) {
9190
+ console.log(import_chalk47.default.dim(` args ${JSON.stringify(input)}`));
9191
+ const hint = credHint();
9192
+ if (hint) console.log(import_chalk47.default.dim(` auth ${hint}`) + (revealAuth ? "" : import_chalk47.default.yellow(" \u2190 /showauth reveals")));
9193
+ }
9194
+ break;
9195
+ }
9196
+ case "tool-output-available":
9197
+ case "tool-output-error": {
9198
+ stopSpinner();
9199
+ const id = String(ev.toolCallId ?? "");
9200
+ const meta = toolMeta.get(id);
9201
+ const ok = ev.type === "tool-output-available";
9202
+ const ms = meta ? Date.now() - meta.startedAt : void 0;
9203
+ const mark = ok ? import_chalk47.default.green("\u2713") : import_chalk47.default.red("\u2717");
9204
+ console.log(` ${mark} ${import_chalk47.default.cyan(meta?.name ?? "tool")} ${import_chalk47.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
9205
+ const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
9206
+ if (meta) {
9207
+ Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
9208
+ }
9209
+ if (!ok || isVerbose()) {
9210
+ const pretty = (() => {
9211
+ try {
9212
+ return JSON.stringify(JSON.parse(detail), null, 2);
9213
+ } catch {
9214
+ return detail;
9215
+ }
9216
+ })();
9217
+ const lines = pretty.split("\n");
9218
+ const cap = ok ? 12 : 24;
9219
+ console.log(import_chalk47.default.dim(" response:"));
9220
+ for (const line of lines.slice(0, cap)) console.log(import_chalk47.default.dim(` ${line}`));
9221
+ if (lines.length > cap) console.log(import_chalk47.default.dim(` \u2026${lines.length - cap} more lines`));
9222
+ }
9223
+ break;
9224
+ }
9225
+ case "text-start":
9226
+ stopSpinner();
9227
+ parts.push({ type: "text", text: "" });
9228
+ openTextIdx = parts.length - 1;
9229
+ if (!assistantOpen) {
9230
+ process.stdout.write("\n" + import_chalk47.default.green("assistant \u203A "));
9231
+ assistantOpen = true;
9232
+ }
9233
+ break;
9234
+ case "text-delta": {
9235
+ const delta = String(ev.delta ?? "");
9236
+ if (openTextIdx >= 0) parts[openTextIdx].text = String(parts[openTextIdx].text ?? "") + delta;
9237
+ process.stdout.write(delta);
9238
+ break;
9239
+ }
9240
+ case "text-end":
9241
+ openTextIdx = -1;
9242
+ break;
9243
+ case "data-apiblaze-upsell": {
9244
+ const d = ev.data ?? {};
9245
+ upsell = { reason: String(d.reason ?? ""), message: String(d.message ?? "") };
9246
+ break;
9247
+ }
9248
+ case "data-apiblaze-turn":
9249
+ turnInfo = ev.data ?? {};
9250
+ break;
9251
+ case "error":
9252
+ stopSpinner();
9253
+ errorText = String(ev.errorText ?? "Something went wrong.");
9254
+ break;
9255
+ default:
9256
+ break;
9257
+ }
9258
+ });
9259
+ } catch (err) {
9260
+ stopSpinner();
9261
+ console.log(import_chalk47.default.red(` Stream error: ${err instanceof Error ? err.message : String(err)}`));
9262
+ }
9263
+ stopSpinner();
9264
+ if (assistantOpen) process.stdout.write("\n\n");
9265
+ if (parts.length) messages.push({ id: messageId, role: "assistant", parts });
9266
+ if (errorText) console.log(import_chalk47.default.red(` ${errorText}`));
9267
+ if (upsell) {
9268
+ renderUpsell(p, upsell, { messageAlreadyShown: true });
9269
+ }
9270
+ const bl = billingLine(turnInfo);
9271
+ if (bl) console.log(bl);
9272
+ const warn = freeBudgetWarning(turnInfo, p.anon);
9273
+ if (warn) console.log(warn);
9274
+ }
9275
+ function renderUpsell(p, upsell, opts = {}) {
8806
9276
  const loggedIn = !!loadCredentials();
8807
9277
  if (upsell.reason === "CAPPED" && !loggedIn) {
8808
- console.log("\n" + import_chalk46.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
8809
- console.log(import_chalk46.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
9278
+ console.log("\n" + import_chalk47.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
9279
+ console.log(import_chalk47.default.dim(" (or `/login` right here \u2014 your chat is preserved \u2014 or `apiblaze llm set-key` for your own model key.)"));
8810
9280
  console.log();
8811
9281
  return;
8812
9282
  }
8813
- console.log("\n" + import_chalk46.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
9283
+ if (!opts.messageAlreadyShown) {
9284
+ console.log("\n" + import_chalk47.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
9285
+ }
8814
9286
  if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "QUOTA" || upsell.reason === "PROXY_CAP" || upsell.reason === "PAUSED" || upsell.reason === "BYO_REQUIRED") {
8815
9287
  if (!loggedIn) {
8816
- console.log(import_chalk46.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
9288
+ console.log(import_chalk47.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
8817
9289
  } else {
8818
- console.log(import_chalk46.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
9290
+ console.log(import_chalk47.default.dim(" Options: top up your wallet, or `apiblaze llm set-key` to bring your own model key (bypasses platform limits)."));
8819
9291
  }
8820
9292
  } else if (upsell.reason === "INFLIGHT") {
8821
- console.log(import_chalk46.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
9293
+ console.log(import_chalk47.default.dim(" Another turn is still in flight \u2014 wait a moment and try again."));
8822
9294
  }
8823
9295
  console.log();
8824
9296
  }
8825
- var apichatsPath = () => path6.join(getApiblazeDir(), "apichats.json");
9297
+ var apichatsPath = () => path7.join(getApiblazeDir(), "apichats.json");
8826
9298
  function loadApichats() {
8827
9299
  try {
8828
- const list = JSON.parse(fs9.readFileSync(apichatsPath(), "utf-8"));
9300
+ const list = JSON.parse(fs11.readFileSync(apichatsPath(), "utf-8"));
8829
9301
  return Array.isArray(list) ? list : [];
8830
9302
  } catch {
8831
9303
  return [];
8832
9304
  }
8833
9305
  }
8834
9306
  function writeApichats(list) {
8835
- fs9.mkdirSync(getApiblazeDir(), { recursive: true });
8836
- fs9.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
9307
+ fs11.mkdirSync(getApiblazeDir(), { recursive: true });
9308
+ fs11.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
8837
9309
  try {
8838
- fs9.chmodSync(apichatsPath(), 384);
9310
+ fs11.chmodSync(apichatsPath(), 384);
8839
9311
  } catch {
8840
9312
  }
8841
9313
  }
@@ -8849,6 +9321,16 @@ function upsertApichat(entry) {
8849
9321
  else list.unshift(entry);
8850
9322
  writeApichats(list.slice(0, 30));
8851
9323
  }
9324
+ function persistAuthState(p) {
9325
+ const list = loadApichats();
9326
+ const i = list.findIndex((a) => a.projectId === p.projectId && a.version === p.version);
9327
+ if (i < 0) return;
9328
+ list[i].dpKey = p.dpKey;
9329
+ list[i].consumerAuth = p.consumerAuth;
9330
+ list[i].endUserId = p.endUserId;
9331
+ list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9332
+ writeApichats(list);
9333
+ }
8852
9334
  function saveTranscript(p, messages) {
8853
9335
  const list = loadApichats();
8854
9336
  const i = list.findIndex((a) => apichatKey(a) === apichatKey(p));
@@ -8898,6 +9380,57 @@ async function fetchProxyDoor(teamId, projectId, apiVersion) {
8898
9380
  return { methods: null };
8899
9381
  }
8900
9382
  }
9383
+ async function openDirectProject(projectId, opts) {
9384
+ const saved = loadApichats().find((a) => a.projectId === projectId);
9385
+ const version2 = opts.apiversion || saved?.version || "1.0.0";
9386
+ const environment = opts.environment || saved?.environment || "prod";
9387
+ const anon = saved?.anon ?? !loadCredentials();
9388
+ const mcpHost = saved?.mcpHost || `${projectId}.mcp.${anon ? "tryabz" : "abz"}.run`;
9389
+ const p = {
9390
+ projectId,
9391
+ version: version2,
9392
+ environment,
9393
+ dpKey: opts.apikey ?? saved?.dpKey,
9394
+ consumerAuth: opts.apikey ? false : saved?.consumerAuth,
9395
+ mcpHost,
9396
+ proxyUrl: `https://${projectId}.${anon ? "tryabz" : "abz"}.run/${version2}/${environment}`,
9397
+ anon,
9398
+ access: "invite",
9399
+ tenant: saved?.tenant,
9400
+ teamId: saved?.teamId,
9401
+ endUserId: opts.xenduserid ?? saved?.endUserId
9402
+ };
9403
+ if (!p.dpKey && !p.consumerAuth) {
9404
+ if (process.stdin.isTTY) {
9405
+ const { default: inquirer3 } = await import("inquirer");
9406
+ const { key } = await inquirer3.prompt([{
9407
+ type: "password",
9408
+ name: "key",
9409
+ mask: "*",
9410
+ message: `API key for ${projectId} (leave empty if it uses a login):`
9411
+ }]);
9412
+ if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
9413
+ else p.consumerAuth = true;
9414
+ }
9415
+ }
9416
+ console.log(` ${import_chalk47.default.dim("Proxy:")} ${import_chalk47.default.bold(p.proxyUrl)}`);
9417
+ if (p.endUserId) console.log(` ${import_chalk47.default.dim("Acting as:")} ${import_chalk47.default.bold(p.endUserId)}`);
9418
+ upsertApichat({
9419
+ name: projectId,
9420
+ target: p.proxyUrl,
9421
+ projectId,
9422
+ version: version2,
9423
+ environment,
9424
+ mcpHost,
9425
+ teamId: p.teamId,
9426
+ tenant: p.tenant,
9427
+ dpKey: p.dpKey,
9428
+ consumerAuth: p.consumerAuth,
9429
+ anon,
9430
+ endUserId: p.endUserId
9431
+ });
9432
+ return { p, messages: saved?.messages?.filter((m) => Array.isArray(m.parts)) ?? [] };
9433
+ }
8901
9434
  async function openServerProxy(project) {
8902
9435
  const version2 = project.apiVersion || "1.0.0";
8903
9436
  const environment = "prod";
@@ -8911,9 +9444,9 @@ async function openServerProxy(project) {
8911
9444
  let consumerAuth = false;
8912
9445
  if (acceptsApiKey) {
8913
9446
  if (!dpKey) {
8914
- console.log(import_chalk46.default.dim(` Minting an API key for tenant ${import_chalk46.default.bold(tenant2)} to query project ${import_chalk46.default.bold(project.projectName)}\u2026`));
9447
+ console.log(import_chalk47.default.dim(` Minting an API key for tenant ${import_chalk47.default.bold(tenant2)} to query project ${import_chalk47.default.bold(project.projectName)}\u2026`));
8915
9448
  dpKey = await mintDurableProxyKey(project.teamId, tenant2);
8916
- console.log(` ${import_chalk46.default.green("\u2714")} API key: ${import_chalk46.default.dim(maskKey(dpKey))}`);
9449
+ console.log(` ${import_chalk47.default.green("\u2714")} API key: ${import_chalk47.default.dim(maskKey(dpKey))}`);
8917
9450
  }
8918
9451
  } else {
8919
9452
  consumerAuth = true;
@@ -8942,9 +9475,9 @@ async function openServerProxy(project) {
8942
9475
  const spec2 = raw && (raw.spec ?? raw);
8943
9476
  spinner.stop();
8944
9477
  if (!spec2 || !(spec2.paths || spec2.openapi)) {
8945
- console.log(import_chalk46.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
9478
+ console.log(import_chalk47.default.yellow(" This proxy has no OpenAPI spec yet \u2014 chat will have no tools. Build one with `apiblaze agent openapi`."));
8946
9479
  } else {
8947
- console.log(import_chalk46.default.dim(" Using the proxy's existing MCP catalogue (`apiblaze mcp` to rebuild it)."));
9480
+ console.log(import_chalk47.default.dim(" Using the proxy's existing MCP catalogue (`apiblaze mcp` to rebuild it)."));
8948
9481
  }
8949
9482
  } catch (err) {
8950
9483
  spinner.fail("Could not open the proxy.");
@@ -8976,15 +9509,15 @@ function discoverLocalSpecs() {
8976
9509
  const found = [];
8977
9510
  for (const n of known) {
8978
9511
  try {
8979
- if (fs9.statSync(path6.join(cwd, n)).isFile()) found.push(n);
9512
+ if (fs11.statSync(path7.join(cwd, n)).isFile()) found.push(n);
8980
9513
  } catch {
8981
9514
  }
8982
9515
  }
8983
9516
  try {
8984
- const files = fs9.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
9517
+ const files = fs11.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
8985
9518
  for (const f of files.slice(0, 60)) {
8986
9519
  try {
8987
- const head = fs9.readFileSync(path6.join(cwd, f), "utf-8").slice(0, 4e3);
9520
+ const head = fs11.readFileSync(path7.join(cwd, f), "utf-8").slice(0, 4e3);
8988
9521
  if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
8989
9522
  found.push(f);
8990
9523
  }
@@ -9000,7 +9533,7 @@ async function noArgsMenu(opts) {
9000
9533
  const me = loadCredentials()?.apiblazeUserId;
9001
9534
  const saved = loadApichats().filter((a) => a.anon ? true : a.ownerUserId !== void 0 && a.ownerUserId === me);
9002
9535
  const choices = saved.map((a) => ({
9003
- name: `Chat with ${import_chalk46.default.bold(a.name)} ${import_chalk46.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
9536
+ name: `Chat with ${import_chalk47.default.bold(a.name)} ${import_chalk47.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
9004
9537
  value: { type: "existing", a }
9005
9538
  }));
9006
9539
  const creds = loadCredentials();
@@ -9010,14 +9543,14 @@ async function noArgsMenu(opts) {
9010
9543
  const proxies = (await getProjects(creds.teamId)).filter((pr) => !savedIds.has(pr.projectId));
9011
9544
  for (const pr of proxies) {
9012
9545
  choices.push({
9013
- name: `Chat with ${import_chalk46.default.bold(pr.projectName)} ${import_chalk46.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
9546
+ name: `Chat with ${import_chalk47.default.bold(pr.projectName)} ${import_chalk47.default.dim(`(v${pr.apiVersion}) \xB7 your proxy`)}`,
9014
9547
  value: { type: "server", project: pr }
9015
9548
  });
9016
9549
  }
9017
9550
  } catch {
9018
9551
  }
9019
9552
  }
9020
- choices.push({ name: import_chalk46.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
9553
+ choices.push({ name: import_chalk47.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
9021
9554
  const { pick: pick2 } = await inquirer3.prompt([
9022
9555
  { type: "list", name: "pick", message: "What would you like to do?", choices }
9023
9556
  ]);
@@ -9119,37 +9652,37 @@ async function noArgsMenu(opts) {
9119
9652
  }
9120
9653
  async function runRepl(p, initialMessages) {
9121
9654
  const { default: inquirer3 } = await import("inquirer");
9122
- const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
9123
- console.log("\n" + import_chalk46.default.cyan.bold("Chat with your API") + import_chalk46.default.dim(` \xB7 ${p.mcpHost}`));
9124
- if (messages.length) console.log(import_chalk46.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
9655
+ const messages = (initialMessages ?? []).filter((m) => Array.isArray(m.parts));
9656
+ console.log("\n" + import_chalk47.default.cyan.bold("Chat with your API") + import_chalk47.default.dim(` \xB7 ${p.mcpHost}`));
9657
+ if (messages.length) console.log(import_chalk47.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
9125
9658
  const llm2 = loadLlmConfig();
9126
9659
  console.log(
9127
- import_chalk46.default.dim(
9660
+ import_chalk47.default.dim(
9128
9661
  llm2 ? `Using your local ${llm2.provider} key for the model. Type a question, or /exit. /login /claim manage your workspace.` : "Ask a question in plain English. /exit to quit \xB7 /login for more free chats \xB7 /claim to keep this workspace \xB7 `apiblaze llm set-key` for BYO models."
9129
9662
  )
9130
9663
  );
9131
9664
  for (; ; ) {
9132
- const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk46.default.green("you \u203A") }]);
9665
+ const { input } = await inquirer3.prompt([{ type: "input", name: "input", message: import_chalk47.default.green("you \u203A") }]);
9133
9666
  const text = (input ?? "").trim();
9134
9667
  if (!text) continue;
9135
9668
  if (["/exit", "/quit", "exit", "quit", ":q"].includes(text.toLowerCase())) break;
9136
9669
  if (text === "/login") {
9137
9670
  try {
9138
9671
  await runLogin();
9139
- console.log(import_chalk46.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
9672
+ console.log(import_chalk47.default.dim(" Logged in \u2014 history preserved. Keep chatting."));
9140
9673
  } catch (err) {
9141
- console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
9674
+ console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
9142
9675
  }
9143
9676
  continue;
9144
9677
  }
9145
9678
  if (text === "/claim") {
9146
9679
  const justLoggedIn = !loadCredentials();
9147
9680
  if (justLoggedIn) {
9148
- console.log(import_chalk46.default.dim(" Logging in to claim your workspace\u2026"));
9681
+ console.log(import_chalk47.default.dim(" Logging in to claim your workspace\u2026"));
9149
9682
  try {
9150
9683
  await runLogin();
9151
9684
  } catch (err) {
9152
- console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
9685
+ console.log(import_chalk47.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
9153
9686
  continue;
9154
9687
  }
9155
9688
  if (!loadCredentials()) continue;
@@ -9160,43 +9693,123 @@ async function runRepl(p, initialMessages) {
9160
9693
  p.mcpHost = p.mcpHost.replace(".mcp.tryabz.run", ".mcp.abz.run");
9161
9694
  p.anon = false;
9162
9695
  claimApichat(p, loadCredentials()?.apiblazeUserId);
9163
- console.log(import_chalk46.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
9696
+ console.log(import_chalk47.default.dim(` Workspace claimed \u2014 chat now routes on ${p.mcpHost}. History preserved.`));
9164
9697
  }
9165
9698
  } catch (err) {
9166
- console.log(import_chalk46.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
9699
+ console.log(import_chalk47.default.red(` Claim failed: ${err instanceof Error ? err.message : String(err)}`));
9167
9700
  }
9168
9701
  continue;
9169
9702
  }
9170
9703
  if (text === "/showauth") {
9171
9704
  revealAuth = !revealAuth;
9172
- console.log(import_chalk46.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
9705
+ console.log(import_chalk47.default.dim(revealAuth ? " The real API key will be shown in the next call's curl. /showauth again to re-mask." : " API key re-masked."));
9173
9706
  continue;
9174
9707
  }
9175
9708
  if (text.startsWith("/")) {
9176
- console.log(import_chalk46.default.dim(" Commands: /login /claim /showauth /exit"));
9709
+ console.log(import_chalk47.default.dim(" Commands: /login /claim /showauth /exit"));
9177
9710
  continue;
9178
9711
  }
9179
9712
  await replTurn(p, messages, text);
9180
9713
  saveTranscript(p, messages);
9181
9714
  }
9182
- console.log(import_chalk46.default.dim("\nBye."));
9715
+ console.log(import_chalk47.default.dim("\nBye."));
9716
+ }
9717
+ function rememberCliOffer(projectId, cli, state) {
9718
+ const list = loadApichats();
9719
+ const i = list.findIndex((a) => a.projectId === projectId);
9720
+ if (i < 0) return;
9721
+ list[i].cliOffer = { ...list[i].cliOffer ?? {}, [cli]: state };
9722
+ list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9723
+ writeApichats(list);
9724
+ }
9725
+ async function maybeInstallExternalCli(p, opts) {
9726
+ const spec2 = {
9727
+ name: p.projectId,
9728
+ url: `https://${p.mcpHost}/${p.version}/${p.environment}`,
9729
+ // consumerAuth means the door is a login — install bare, the CLI signs in.
9730
+ apiKey: p.consumerAuth ? void 0 : p.dpKey,
9731
+ projectLabel: p.projectId
9732
+ };
9733
+ const clis = detectExternalClis();
9734
+ if (opts.installMcp) {
9735
+ const want = opts.installMcp.toLowerCase();
9736
+ if (want !== "claude" && want !== "codex") fail4(`--install-mcp takes "claude" or "codex", not "${opts.installMcp}".`);
9737
+ const cli = clis.find((c) => c.kind === want);
9738
+ if (!cli) fail4(
9739
+ `${want === "claude" ? "Claude" : "Codex"} CLI not found on this machine.`,
9740
+ want === "claude" ? "Install it: npm install -g @anthropic-ai/claude-code" : "Install it: npm install -g @openai/codex"
9741
+ );
9742
+ const question2 = opts.prompt ?? (process.stdin.isTTY ? await askApiQuestion() : void 0);
9743
+ const ran2 = installAndDemo(cli, spec2, question2);
9744
+ if (ran2) rememberCliOffer(p.projectId, cli.kind, "installed");
9745
+ return ran2;
9746
+ }
9747
+ if (!process.stdin.isTTY || clis.length === 0) return false;
9748
+ const offer = loadApichats().find((a) => a.projectId === p.projectId)?.cliOffer ?? {};
9749
+ const fresh = clis.filter((c) => !offer[c.kind]);
9750
+ if (fresh.length === 0) return false;
9751
+ const { default: inquirer3 } = await import("inquirer");
9752
+ const names = fresh.map((c) => c.label).join(" and ");
9753
+ const { pick: pick2 } = await inquirer3.prompt([{
9754
+ type: "list",
9755
+ name: "pick",
9756
+ message: `I see ${names} ${fresh.length > 1 ? "are" : "is"} installed on this computer. Add the MCP for this proxy so you can chat with your API from there directly?`,
9757
+ choices: [
9758
+ ...fresh.map((c) => ({ name: `Yes \u2014 add it to ${c.label}`, value: c })),
9759
+ { name: "No \u2014 chat here instead", value: "no" }
9760
+ ]
9761
+ }]);
9762
+ if (pick2 === "no") {
9763
+ for (const c of fresh) rememberCliOffer(p.projectId, c.kind, "declined");
9764
+ return false;
9765
+ }
9766
+ const question = opts.prompt ?? await askApiQuestion();
9767
+ const ran = installAndDemo(pick2, spec2, question);
9768
+ if (ran) rememberCliOffer(p.projectId, pick2.kind, "installed");
9769
+ return ran;
9770
+ }
9771
+ async function askApiQuestion() {
9772
+ const { default: inquirer3 } = await import("inquirer");
9773
+ const { q } = await inquirer3.prompt([{
9774
+ type: "input",
9775
+ name: "q",
9776
+ message: "What question do you have for this API?"
9777
+ }]);
9778
+ const t = (q ?? "").trim();
9779
+ return t || void 0;
9183
9780
  }
9184
9781
  async function runApichat(opts) {
9185
9782
  setVerbose(opts.verbose !== false);
9186
- console.log(import_chalk46.default.bold("\napichat \u2014 turn any API into a chat\n"));
9783
+ console.log(import_chalk47.default.bold("\napichat \u2014 turn any API into a chat\n"));
9784
+ if (opts.target && !opts.openapispec) {
9785
+ const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
9786
+ const c = await classifyTargetInput2(opts.target, fail4);
9787
+ if (c.kind === "spec") {
9788
+ console.log(import_chalk47.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 using it as the spec.`));
9789
+ opts.openapispec = opts.target;
9790
+ opts.target = void 0;
9791
+ }
9792
+ }
9793
+ if (opts.project) {
9794
+ const opened = await openDirectProject(opts.project, opts);
9795
+ if (await maybeInstallExternalCli(opened.p, opts)) return;
9796
+ await runRepl(opened.p, opened.messages);
9797
+ return;
9798
+ }
9187
9799
  if (!opts.openapispec && !opts.target) {
9188
9800
  if (!process.stdin.isTTY) {
9189
- fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
9801
+ fail4("No spec source. Pass --target <server-url | openapi-file | openapi-url>.", GENERATOR_HINT);
9190
9802
  }
9191
9803
  const resumed = await noArgsMenu(opts);
9192
9804
  if (resumed) {
9805
+ if (await maybeInstallExternalCli(resumed.p, opts)) return;
9193
9806
  await runRepl(resumed.p, resumed.messages);
9194
9807
  return;
9195
9808
  }
9196
9809
  }
9197
9810
  const { spec: spec2, sourceUrl } = await loadSpec(opts);
9198
9811
  const target = resolveTarget(spec2, opts, sourceUrl);
9199
- console.log(` ${import_chalk46.default.dim("Target:")} ${import_chalk46.default.bold(target)}`);
9812
+ console.log(` ${import_chalk47.default.dim("Target:")} ${import_chalk47.default.bold(target)}`);
9200
9813
  const auth = await resolveTargetAuth(spec2, opts);
9201
9814
  if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
9202
9815
  fail4(
@@ -9205,7 +9818,7 @@ async function runApichat(opts) {
9205
9818
  );
9206
9819
  }
9207
9820
  const p = await provision(spec2, target, opts);
9208
- console.log(` ${import_chalk46.default.dim("Proxy: ")} ${import_chalk46.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
9821
+ console.log(` ${import_chalk47.default.dim("Proxy: ")} ${import_chalk47.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
9209
9822
  upsertApichat({
9210
9823
  name: p.projectId,
9211
9824
  target,
@@ -9225,30 +9838,31 @@ async function runApichat(opts) {
9225
9838
  const secret = await captureTargetSecret(auth, opts);
9226
9839
  if (secret) await writeTargetAuth(p, auth, secret);
9227
9840
  } else {
9228
- console.log(import_chalk46.default.dim(" Target auth: none required."));
9841
+ console.log(import_chalk47.default.dim(" Target auth: none required."));
9229
9842
  }
9230
9843
  const specText = JSON.stringify(spec2);
9231
9844
  await uploadSpec(p, specText, opts);
9232
9845
  const mcpUrl = await publishMcp(p, spec2);
9233
9846
  console.log();
9234
- if (p.proxyUrl) console.log(` ${import_chalk46.default.green("\u2713")} proxy ${import_chalk46.default.bold(p.proxyUrl)}`);
9847
+ if (p.proxyUrl) console.log(` ${import_chalk47.default.green("\u2713")} proxy ${import_chalk47.default.bold(p.proxyUrl)}`);
9235
9848
  if (mcpUrl) {
9236
- console.log(` ${import_chalk46.default.green("\u2713")} mcp ${import_chalk46.default.bold(mcpUrl)}`);
9849
+ console.log(` ${import_chalk47.default.green("\u2713")} mcp ${import_chalk47.default.bold(mcpUrl)}`);
9237
9850
  if (p.access === "invite") {
9238
- console.log(import_chalk46.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
9239
- console.log(import_chalk46.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
9851
+ console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: invite \u2014 only you + emails you pre-approve"));
9852
+ console.log(import_chalk47.default.dim(` Let others in: apiblaze preapprove someone@company.com${p.tenant ? ` --tenant ${p.tenant}` : ""} (or re-run with --access open)`));
9240
9853
  } else {
9241
- console.log(import_chalk46.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
9854
+ console.log(import_chalk47.default.dim(" Claude/ChatGPT-connectable (GitHub sign-in) \xB7 access: open \u2014 anyone who signs in can call this API"));
9242
9855
  }
9243
9856
  }
9244
9857
  if (p.anon) {
9245
- console.log(import_chalk46.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
9858
+ console.log(import_chalk47.default.dim("\n Anonymous workspace \u2014 /claim inside the chat to log in and keep it beyond 30 days."));
9246
9859
  }
9860
+ if (await maybeInstallExternalCli(p, opts)) return;
9247
9861
  await runRepl(p);
9248
9862
  }
9249
9863
 
9250
9864
  // src/commands/consumer.ts
9251
- var import_chalk47 = __toESM(require("chalk"));
9865
+ var import_chalk48 = __toESM(require("chalk"));
9252
9866
  var import_ora24 = __toESM(require("ora"));
9253
9867
  init_admin();
9254
9868
  init_resolve();
@@ -9270,7 +9884,7 @@ async function consumerFetch(creds, suffix, init) {
9270
9884
  function requireConsumer2() {
9271
9885
  const c = loadConsumer();
9272
9886
  if (!c) {
9273
- console.error(import_chalk47.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
9887
+ console.error(import_chalk48.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
9274
9888
  process.exit(1);
9275
9889
  }
9276
9890
  return c;
@@ -9281,7 +9895,7 @@ async function runConsumerLogin(opts) {
9281
9895
  let clientId = opts.client;
9282
9896
  if (clientId) {
9283
9897
  if (!tenant2) {
9284
- console.error(import_chalk47.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
9898
+ console.error(import_chalk48.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
9285
9899
  process.exit(1);
9286
9900
  }
9287
9901
  } else {
@@ -9299,18 +9913,18 @@ async function runConsumerLogin(opts) {
9299
9913
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
9300
9914
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
9301
9915
  if (!pick2) {
9302
- console.error(import_chalk47.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
9916
+ console.error(import_chalk48.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
9303
9917
  process.exit(1);
9304
9918
  }
9305
9919
  clientId = pick2.client_id ?? pick2.clientId;
9306
9920
  }
9307
- console.log(`${import_chalk47.default.cyan("\u2192")} Logging in to ${import_chalk47.default.bold(tenant2)} as a consumer...`);
9921
+ console.log(`${import_chalk48.default.cyan("\u2192")} Logging in to ${import_chalk48.default.bold(tenant2)} as a consumer...`);
9308
9922
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
9309
9923
  console.log(`
9310
- Open: ${import_chalk47.default.underline(verificationUri)}`);
9311
- console.log(` Code: ${import_chalk47.default.bold(userCode)}
9924
+ Open: ${import_chalk48.default.underline(verificationUri)}`);
9925
+ console.log(` Code: ${import_chalk48.default.bold(userCode)}
9312
9926
  `);
9313
- console.log(import_chalk47.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
9927
+ console.log(import_chalk48.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
9314
9928
  });
9315
9929
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
9316
9930
  const creds = {
@@ -9325,7 +9939,7 @@ async function runConsumerLogin(opts) {
9325
9939
  obtainedAt: Date.now()
9326
9940
  };
9327
9941
  saveConsumer(creds);
9328
- console.log(import_chalk47.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
9942
+ console.log(import_chalk48.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
9329
9943
  }
9330
9944
  async function runConsumerTokens(opts) {
9331
9945
  const creds = requireConsumer2();
@@ -9338,18 +9952,18 @@ async function runConsumerTokens(opts) {
9338
9952
  console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
9339
9953
  return;
9340
9954
  }
9341
- console.log(`${import_chalk47.default.cyan("Consumer")} ${import_chalk47.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk47.default.bold(fresh.tenant)}
9955
+ console.log(`${import_chalk48.default.cyan("Consumer")} ${import_chalk48.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk48.default.bold(fresh.tenant)}
9342
9956
  `);
9343
- console.log(`${import_chalk47.default.bold("access_token")} ${import_chalk47.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
9957
+ console.log(`${import_chalk48.default.bold("access_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
9344
9958
  ${fresh.accessToken}
9345
9959
  `);
9346
- if (fresh.idToken) console.log(`${import_chalk47.default.bold("id_token")} ${import_chalk47.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
9960
+ if (fresh.idToken) console.log(`${import_chalk48.default.bold("id_token")} ${import_chalk48.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
9347
9961
  ${fresh.idToken}
9348
9962
  `);
9349
- if (fresh.refreshToken) console.log(`${import_chalk47.default.bold("refresh_token")}
9963
+ if (fresh.refreshToken) console.log(`${import_chalk48.default.bold("refresh_token")}
9350
9964
  ${fresh.refreshToken}
9351
9965
  `);
9352
- console.log(import_chalk47.default.dim("These are your own tokens \u2014 keep them secret."));
9966
+ console.log(import_chalk48.default.dim("These are your own tokens \u2014 keep them secret."));
9353
9967
  }
9354
9968
  async function runConsumerApikeys(opts) {
9355
9969
  const creds = requireConsumer2();
@@ -9359,8 +9973,8 @@ async function runConsumerApikeys(opts) {
9359
9973
  const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
9360
9974
  spinner.stop();
9361
9975
  if (list.status >= 400) {
9362
- console.error(import_chalk47.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
9363
- if (list.status === 401) console.error(import_chalk47.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
9976
+ console.error(import_chalk48.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
9977
+ if (list.status === 401) console.error(import_chalk48.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
9364
9978
  process.exit(1);
9365
9979
  }
9366
9980
  const keys = list.data?.keys ?? [];
@@ -9368,16 +9982,16 @@ async function runConsumerApikeys(opts) {
9368
9982
  if (opts.json) {
9369
9983
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
9370
9984
  } else if (!keys.length) {
9371
- console.log(import_chalk47.default.yellow("No API keys yet."));
9985
+ console.log(import_chalk48.default.yellow("No API keys yet."));
9372
9986
  } else {
9373
9987
  for (const k of keys) {
9374
9988
  const clear = revealMap[k.environment]?.key;
9375
- const shown = clear ? import_chalk47.default.green(clear) : import_chalk47.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
9376
- const exp = k.expires_at ? import_chalk47.default.dim(`exp ${k.expires_at}`) : import_chalk47.default.dim("no expiry");
9377
- console.log(` ${import_chalk47.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk47.default.dim(k.description ?? "")}`);
9989
+ const shown = clear ? import_chalk48.default.green(clear) : import_chalk48.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
9990
+ const exp = k.expires_at ? import_chalk48.default.dim(`exp ${k.expires_at}`) : import_chalk48.default.dim("no expiry");
9991
+ console.log(` ${import_chalk48.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk48.default.dim(k.description ?? "")}`);
9378
9992
  }
9379
9993
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
9380
- console.log(import_chalk47.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
9994
+ console.log(import_chalk48.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
9381
9995
  }
9382
9996
  }
9383
9997
  if (opts.json) return;
@@ -9399,35 +10013,35 @@ async function runConsumerApikeys(opts) {
9399
10013
  }
9400
10014
  s2.succeed("Key created.");
9401
10015
  const key = created.data?.key ?? created.data?.fullKey;
9402
- if (key) console.log(` ${import_chalk47.default.green(key)} ${import_chalk47.default.dim("(shown once \u2014 store it now)")}`);
9403
- else console.log(import_chalk47.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
10016
+ if (key) console.log(` ${import_chalk48.default.green(key)} ${import_chalk48.default.dim("(shown once \u2014 store it now)")}`);
10017
+ else console.log(import_chalk48.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
9404
10018
  }
9405
10019
 
9406
10020
  // src/commands/sidecar.ts
9407
- var import_chalk48 = __toESM(require("chalk"));
10021
+ var import_chalk49 = __toESM(require("chalk"));
9408
10022
  var import_ora25 = __toESM(require("ora"));
9409
- var fs10 = __toESM(require("fs"));
9410
- var path7 = __toESM(require("path"));
10023
+ var fs12 = __toESM(require("fs"));
10024
+ var path8 = __toESM(require("path"));
9411
10025
  init_admin();
9412
10026
  init_resolve();
9413
10027
  init_auth();
9414
10028
  function detectNextProject(root) {
9415
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs10.existsSync(path7.join(root, f)));
10029
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs12.existsSync(path8.join(root, f)));
9416
10030
  let hasDep = false;
9417
10031
  try {
9418
- const pkg = JSON.parse(fs10.readFileSync(path7.join(root, "package.json"), "utf8"));
10032
+ const pkg = JSON.parse(fs12.readFileSync(path8.join(root, "package.json"), "utf8"));
9419
10033
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
9420
10034
  } catch {
9421
10035
  }
9422
- const appDir = fs10.existsSync(path7.join(root, "app")) || fs10.existsSync(path7.join(root, "src", "app"));
9423
- const pagesDir = fs10.existsSync(path7.join(root, "pages")) || fs10.existsSync(path7.join(root, "src", "pages"));
10036
+ const appDir = fs12.existsSync(path8.join(root, "app")) || fs12.existsSync(path8.join(root, "src", "app"));
10037
+ const pagesDir = fs12.existsSync(path8.join(root, "pages")) || fs12.existsSync(path8.join(root, "src", "pages"));
9424
10038
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
9425
10039
  }
9426
10040
  function upsertEnvLocal(root, token) {
9427
- const p = path7.join(root, ".env.local");
10041
+ const p = path8.join(root, ".env.local");
9428
10042
  let existing = "";
9429
10043
  try {
9430
- existing = fs10.readFileSync(p, "utf8");
10044
+ existing = fs12.readFileSync(p, "utf8");
9431
10045
  } catch {
9432
10046
  }
9433
10047
  const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
@@ -9442,15 +10056,15 @@ function upsertEnvLocal(root, token) {
9442
10056
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
9443
10057
  `;
9444
10058
  }
9445
- fs10.writeFileSync(p, next);
10059
+ fs12.writeFileSync(p, next);
9446
10060
  return had ? "rotated" : "created";
9447
10061
  }
9448
10062
  function installSidecarPackage(root) {
9449
- if (fs10.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
9450
- console.log(` ${import_chalk48.default.green("\u2713")} apiblaze package already installed`);
10063
+ if (fs12.existsSync(path8.join(root, "node_modules", "apiblaze", "package.json"))) {
10064
+ console.log(` ${import_chalk49.default.green("\u2713")} apiblaze package already installed`);
9451
10065
  return;
9452
10066
  }
9453
- const has = (f) => fs10.existsSync(path7.join(root, f));
10067
+ const has = (f) => fs12.existsSync(path8.join(root, f));
9454
10068
  const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
9455
10069
  const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
9456
10070
  try {
@@ -9458,12 +10072,12 @@ function installSidecarPackage(root) {
9458
10072
  execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
9459
10073
  spinner.succeed("Installed apiblaze (the sidecar runtime).");
9460
10074
  } catch {
9461
- spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk48.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk48.default.cyan("npm run dev")}.`);
10075
+ spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk49.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk49.default.cyan("npm run dev")}.`);
9462
10076
  }
9463
10077
  }
9464
10078
  function readEnvKey(root) {
9465
10079
  try {
9466
- const s = fs10.readFileSync(path7.join(root, ".env.local"), "utf8");
10080
+ const s = fs12.readFileSync(path8.join(root, ".env.local"), "utf8");
9467
10081
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
9468
10082
  return m ? m[1].trim() : null;
9469
10083
  } catch {
@@ -9471,16 +10085,16 @@ function readEnvKey(root) {
9471
10085
  }
9472
10086
  }
9473
10087
  function ensureGitignored(root) {
9474
- const p = path7.join(root, ".gitignore");
10088
+ const p = path8.join(root, ".gitignore");
9475
10089
  let c = "";
9476
10090
  try {
9477
- c = fs10.readFileSync(p, "utf8");
10091
+ c = fs12.readFileSync(p, "utf8");
9478
10092
  } catch {
9479
10093
  }
9480
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs10.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
10094
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs12.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
9481
10095
  }
9482
10096
  function wireInstrumentation(root) {
9483
- const existing = ["instrumentation.ts", "instrumentation.js", path7.join("src", "instrumentation.ts")].map((c) => path7.join(root, c)).find((f) => fs10.existsSync(f));
10097
+ const existing = ["instrumentation.ts", "instrumentation.js", path8.join("src", "instrumentation.ts")].map((c) => path8.join(root, c)).find((f) => fs12.existsSync(f));
9484
10098
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
9485
10099
 
9486
10100
  export function register() {
@@ -9488,18 +10102,18 @@ export function register() {
9488
10102
  }
9489
10103
  `;
9490
10104
  if (!existing) {
9491
- fs10.writeFileSync(path7.join(root, "instrumentation.ts"), body);
10105
+ fs12.writeFileSync(path8.join(root, "instrumentation.ts"), body);
9492
10106
  return "created";
9493
10107
  }
9494
- const cur = fs10.readFileSync(existing, "utf8");
10108
+ const cur = fs12.readFileSync(existing, "utf8");
9495
10109
  if (cur.includes("apiblaze/sidecar")) return "present";
9496
10110
  if (/export\s+function\s+register\s*\(/.test(cur)) {
9497
- fs10.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
10111
+ fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9498
10112
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
9499
10113
  apiblaze();`));
9500
10114
  return "patched";
9501
10115
  }
9502
- fs10.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
10116
+ fs12.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9503
10117
  ${cur}
9504
10118
  // call apiblaze() inside your register() export.
9505
10119
  `);
@@ -9578,17 +10192,17 @@ export default async function Page() {
9578
10192
  function generateInspector(root, router) {
9579
10193
  try {
9580
10194
  if (router === "pages") {
9581
- const dir2 = fs10.existsSync(path7.join(root, "src", "pages")) ? path7.join(root, "src", "pages") : path7.join(root, "pages");
9582
- const f2 = path7.join(dir2, "abz-inspector.tsx");
9583
- fs10.writeFileSync(f2, INSPECTOR_PAGE);
9584
- return path7.relative(root, f2);
9585
- }
9586
- const base2 = fs10.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
9587
- const dir = path7.join(base2, "abz-inspector");
9588
- fs10.mkdirSync(dir, { recursive: true });
9589
- const f = path7.join(dir, "page.tsx");
9590
- fs10.writeFileSync(f, INSPECTOR_PAGE);
9591
- return path7.relative(root, f);
10195
+ const dir2 = fs12.existsSync(path8.join(root, "src", "pages")) ? path8.join(root, "src", "pages") : path8.join(root, "pages");
10196
+ const f2 = path8.join(dir2, "abz-inspector.tsx");
10197
+ fs12.writeFileSync(f2, INSPECTOR_PAGE);
10198
+ return path8.relative(root, f2);
10199
+ }
10200
+ const base2 = fs12.existsSync(path8.join(root, "src", "app")) ? path8.join(root, "src", "app") : path8.join(root, "app");
10201
+ const dir = path8.join(base2, "abz-inspector");
10202
+ fs12.mkdirSync(dir, { recursive: true });
10203
+ const f = path8.join(dir, "page.tsx");
10204
+ fs12.writeFileSync(f, INSPECTOR_PAGE);
10205
+ return path8.relative(root, f);
9592
10206
  } catch {
9593
10207
  return null;
9594
10208
  }
@@ -9609,29 +10223,29 @@ async function runAnonymousInit(root, router, opts) {
9609
10223
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
9610
10224
  const envState = upsertEnvLocal(root, out.token);
9611
10225
  ensureGitignored(root);
9612
- console.log(` ${import_chalk48.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
9613
- console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
10226
+ console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
10227
+ console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
9614
10228
  installSidecarPackage(root);
9615
10229
  let inspectorPath = null;
9616
10230
  if (!opts.noInspector) {
9617
10231
  inspectorPath = generateInspector(root, router);
9618
- if (inspectorPath) console.log(` ${import_chalk48.default.green("\u2713")} inspector at ${inspectorPath}`);
10232
+ if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
9619
10233
  }
9620
10234
  console.log("");
9621
- console.log(import_chalk48.default.bold("Done (no account needed). What happens next:"));
9622
- console.log(` 1. ${import_chalk48.default.cyan("npm run dev")} and use your app.`);
10235
+ console.log(import_chalk49.default.bold("Done (no account needed). What happens next:"));
10236
+ console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app.`);
9623
10237
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
9624
- console.log(` ${import_chalk48.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
10238
+ console.log(` ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
9625
10239
  console.log("");
9626
- console.log(import_chalk48.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
9627
- console.log(` ${import_chalk48.default.cyan("apiblaze login")} then ${import_chalk48.default.cyan("apiblaze claim")} ${import_chalk48.default.dim("(no code needed here)")}`);
9628
- console.log(import_chalk48.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
10240
+ console.log(import_chalk49.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
10241
+ console.log(` ${import_chalk49.default.cyan("apiblaze login")} then ${import_chalk49.default.cyan("apiblaze claim")} ${import_chalk49.default.dim("(no code needed here)")}`);
10242
+ console.log(import_chalk49.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
9629
10243
  }
9630
10244
  async function runSidecar(opts) {
9631
- const root = path7.resolve(opts.dir ?? process.cwd());
10245
+ const root = path8.resolve(opts.dir ?? process.cwd());
9632
10246
  const detected = detectNextProject(root);
9633
10247
  if (!detected.found) {
9634
- console.log(import_chalk48.default.yellow(`No Next.js project detected in ${root}.`));
10248
+ console.log(import_chalk49.default.yellow(`No Next.js project detected in ${root}.`));
9635
10249
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
9636
10250
  return;
9637
10251
  }
@@ -9642,10 +10256,10 @@ async function runSidecar(opts) {
9642
10256
  if (!loadCredentials()) {
9643
10257
  upsertEnvLocal(root, readEnvKey(root));
9644
10258
  ensureGitignored(root);
9645
- console.log(` ${import_chalk48.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
9646
- console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
10259
+ console.log(` ${import_chalk49.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
10260
+ console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
9647
10261
  installSidecarPackage(root);
9648
- console.log(import_chalk48.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
10262
+ console.log(import_chalk49.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
9649
10263
  return;
9650
10264
  }
9651
10265
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -9668,38 +10282,38 @@ async function runSidecar(opts) {
9668
10282
  throw err;
9669
10283
  }
9670
10284
  } else {
9671
- console.log(import_chalk48.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
10285
+ console.log(import_chalk49.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
9672
10286
  }
9673
10287
  const envState = upsertEnvLocal(root, token);
9674
10288
  ensureGitignored(root);
9675
- console.log(` ${import_chalk48.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
10289
+ console.log(` ${import_chalk49.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
9676
10290
  const wireState = wireInstrumentation(root);
9677
- console.log(` ${import_chalk48.default.green("\u2713")} instrumentation.ts ${wireState}`);
10291
+ console.log(` ${import_chalk49.default.green("\u2713")} instrumentation.ts ${wireState}`);
9678
10292
  installSidecarPackage(root);
9679
10293
  let inspectorPath = null;
9680
10294
  if (!opts.noInspector) {
9681
10295
  inspectorPath = generateInspector(root, detected.router);
9682
- if (inspectorPath) console.log(` ${import_chalk48.default.green("\u2713")} inspector at ${inspectorPath}`);
10296
+ if (inspectorPath) console.log(` ${import_chalk49.default.green("\u2713")} inspector at ${inspectorPath}`);
9683
10297
  }
9684
10298
  console.log("");
9685
- console.log(import_chalk48.default.bold("Done. What happens next:"));
9686
- console.log(` 1. ${import_chalk48.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
9687
- console.log(` 2. The origins your app calls appear as ${import_chalk48.default.bold("candidates")} \u2014 list them: ${import_chalk48.default.cyan("apiblaze sidecar")}`);
9688
- console.log(` 3. Approve the ones to route: ${import_chalk48.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
10299
+ console.log(import_chalk49.default.bold("Done. What happens next:"));
10300
+ console.log(` 1. ${import_chalk49.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
10301
+ console.log(` 2. The origins your app calls appear as ${import_chalk49.default.bold("candidates")} \u2014 list them: ${import_chalk49.default.cyan("apiblaze sidecar")}`);
10302
+ console.log(` 3. Approve the ones to route: ${import_chalk49.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
9689
10303
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
9690
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk48.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path7.dirname(inspectorPath)} before shipping)`);
9691
- if (switchingTeam) console.log(import_chalk48.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
10304
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk49.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path8.dirname(inspectorPath)} before shipping)`);
10305
+ if (switchingTeam) console.log(import_chalk49.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
9692
10306
  console.log("");
9693
- console.log(import_chalk48.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
9694
- console.log(import_chalk48.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
9695
- console.log(import_chalk48.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
10307
+ console.log(import_chalk49.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
10308
+ console.log(import_chalk49.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
10309
+ console.log(import_chalk49.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
9696
10310
  console.log("");
9697
- console.log(import_chalk48.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
9698
- console.log(import_chalk48.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
10311
+ console.log(import_chalk49.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
10312
+ console.log(import_chalk49.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
9699
10313
  }
9700
10314
 
9701
10315
  // src/commands/origins.ts
9702
- var import_chalk49 = __toESM(require("chalk"));
10316
+ var import_chalk50 = __toESM(require("chalk"));
9703
10317
  var import_ora26 = __toESM(require("ora"));
9704
10318
  init_admin();
9705
10319
  init_resolve();
@@ -9710,7 +10324,7 @@ async function runOriginsList(opts) {
9710
10324
  if (!loadCredentials()) {
9711
10325
  const cred = loadAnonCred();
9712
10326
  if (!cred) {
9713
- console.log(import_chalk49.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
10327
+ console.log(import_chalk50.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
9714
10328
  return;
9715
10329
  }
9716
10330
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -9728,27 +10342,27 @@ async function runOriginsList(opts) {
9728
10342
  }
9729
10343
  const routed = out.routed ?? [];
9730
10344
  const candidates = out.candidates ?? [];
9731
- console.log(import_chalk49.default.bold(`
10345
+ console.log(import_chalk50.default.bold(`
9732
10346
  Routed through APIblaze (${routed.length})`));
9733
- if (!routed.length) console.log(import_chalk49.default.dim(" none yet"));
9734
- for (const r of routed) console.log(` ${import_chalk49.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk49.default.dim(`\u2192 ${r.project_id}`)}`);
9735
- console.log(import_chalk49.default.bold(`
10347
+ if (!routed.length) console.log(import_chalk50.default.dim(" none yet"));
10348
+ for (const r of routed) console.log(` ${import_chalk50.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk50.default.dim(`\u2192 ${r.project_id}`)}`);
10349
+ console.log(import_chalk50.default.bold(`
9736
10350
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
9737
- if (!candidates.length) console.log(import_chalk49.default.dim(" none \u2014 run your app to discover the origins it calls"));
10351
+ if (!candidates.length) console.log(import_chalk50.default.dim(" none \u2014 run your app to discover the origins it calls"));
9738
10352
  for (const c of candidates) {
9739
- console.log(` ${import_chalk49.default.yellow("\u25CB")} ${c.origin} ${import_chalk49.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
10353
+ console.log(` ${import_chalk50.default.yellow("\u25CB")} ${c.origin} ${import_chalk50.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
9740
10354
  }
9741
10355
  if (candidates.length) {
9742
- console.log(import_chalk49.default.dim(`
10356
+ console.log(import_chalk50.default.dim(`
9743
10357
  Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
9744
- console.log(import_chalk49.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
10358
+ console.log(import_chalk50.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
9745
10359
  }
9746
10360
  }
9747
10361
  async function runOriginsApprove(origin, opts) {
9748
10362
  if (!loadCredentials()) {
9749
10363
  const cred = loadAnonCred();
9750
10364
  if (!cred) {
9751
- console.error(import_chalk49.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
10365
+ console.error(import_chalk50.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
9752
10366
  process.exit(1);
9753
10367
  }
9754
10368
  const spinner2 = (0, import_ora26.default)(`Approving ${origin} (anonymous)...`).start();
@@ -9801,13 +10415,13 @@ async function runOriginsRemove(origin, opts) {
9801
10415
  }
9802
10416
 
9803
10417
  // src/commands/op.ts
9804
- var import_chalk51 = __toESM(require("chalk"));
10418
+ var import_chalk52 = __toESM(require("chalk"));
9805
10419
  init_auth();
9806
10420
  init_trace();
9807
10421
  init_types();
9808
10422
 
9809
10423
  // src/commands/op-billing.ts
9810
- var import_chalk50 = __toESM(require("chalk"));
10424
+ var import_chalk51 = __toESM(require("chalk"));
9811
10425
  init_admin();
9812
10426
  var SANDBOX = {
9813
10427
  teamId: "team_1782844865835_zujrf",
@@ -9866,7 +10480,7 @@ async function rowsForRays(project, version2, tenant2, rays) {
9866
10480
  }
9867
10481
  function printDoors(data) {
9868
10482
  const checks = [];
9869
- console.log(import_chalk50.default.bold("\n Doors \u2014 is every way in metered?\n"));
10483
+ console.log(import_chalk51.default.bold("\n Doors \u2014 is every way in metered?\n"));
9870
10484
  const doors = data?.doors ?? [];
9871
10485
  const metered = doors.filter((d) => d.verdict === "metered");
9872
10486
  const allowed = doors.filter((d) => d.verdict === "allowed-free");
@@ -9875,29 +10489,29 @@ function printDoors(data) {
9875
10489
  const errs = data?.errors ?? [];
9876
10490
  const routeAuditBroke = errs.some((e) => e.startsWith("zone "));
9877
10491
  const devAuditBroke = errs.some((e) => e.startsWith("workers.dev audit"));
9878
- console.log(import_chalk50.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
9879
- for (const d of metered) console.log(import_chalk50.default.green(` \u2713 ${d.pattern}`));
9880
- console.log(import_chalk50.default.dim(`
10492
+ console.log(import_chalk51.default.dim(` ${metered.length} route(s) behind main-proxy (metered)`));
10493
+ for (const d of metered) console.log(import_chalk51.default.green(` \u2713 ${d.pattern}`));
10494
+ console.log(import_chalk51.default.dim(`
9881
10495
  ${allowed.length} route(s) free ON PURPOSE`));
9882
10496
  for (const d of allowed) {
9883
- console.log(import_chalk50.default.cyan(` \u2022 ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9884
- console.log(import_chalk50.default.dim(` ${d.why}`));
10497
+ console.log(import_chalk51.default.cyan(` \u2022 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
10498
+ console.log(import_chalk51.default.dim(` ${d.why}`));
9885
10499
  }
9886
10500
  if (known.length) {
9887
- console.log(import_chalk50.default.yellow(`
10501
+ console.log(import_chalk51.default.yellow(`
9888
10502
  ${known.length} route(s) KNOWN OPEN \u2014 unmetered, not yet closed`));
9889
10503
  for (const d of known) {
9890
- console.log(import_chalk50.default.yellow(` ! ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9891
- console.log(import_chalk50.default.dim(` ${d.why}`));
10504
+ console.log(import_chalk51.default.yellow(` ! ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
10505
+ console.log(import_chalk51.default.dim(` ${d.why}`));
9892
10506
  }
9893
10507
  checks.push({ name: "no known-open doors", status: "KNOWN", detail: `${known.length} unmetered route(s) still open \u2014 see above` });
9894
10508
  }
9895
10509
  if (stray.length) {
9896
- console.log(import_chalk50.default.red(`
10510
+ console.log(import_chalk51.default.red(`
9897
10511
  ${stray.length} STRAY route(s) \u2014 not main-proxy, not on the allowlist`));
9898
10512
  for (const d of stray) {
9899
- console.log(import_chalk50.default.red(` \u2717 ${d.pattern}`) + import_chalk50.default.dim(` \u2192 ${d.script}`));
9900
- console.log(import_chalk50.default.dim(` ${d.why}`));
10513
+ console.log(import_chalk51.default.red(` \u2717 ${d.pattern}`) + import_chalk51.default.dim(` \u2192 ${d.script}`));
10514
+ console.log(import_chalk51.default.dim(` ${d.why}`));
9901
10515
  }
9902
10516
  checks.push({ name: "no stray routes", status: "FAIL", detail: `${stray.length}: ${stray.map((s) => s.pattern).join(", ")}` });
9903
10517
  } else if (routeAuditBroke) {
@@ -9908,22 +10522,22 @@ function printDoors(data) {
9908
10522
  const wd = data?.workers_dev ?? {};
9909
10523
  const open = wd.enabled ?? [];
9910
10524
  if (open.length) {
9911
- console.log(import_chalk50.default.red(`
10525
+ console.log(import_chalk51.default.red(`
9912
10526
  ${open.length} of ${wd.total} worker(s) reachable on *.workers.dev`));
9913
- for (const s of open) console.log(import_chalk50.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk50.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
9914
- console.log(import_chalk50.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
10527
+ for (const s of open) console.log(import_chalk51.default.red(` \u2717 ${s.script}.workers.dev`) + import_chalk51.default.dim(` (enabled=${s.enabled} previews=${s.previews})`));
10528
+ console.log(import_chalk51.default.dim(" A workers.dev hostname bypasses every CF route, WAF rule and the credit gate."));
9915
10529
  checks.push({ name: "no workers.dev doors", status: "FAIL", detail: `${open.length} script(s) publicly reachable: ${open.map((s) => s.script).join(", ")}` });
9916
10530
  } else if (devAuditBroke || !wd.total) {
9917
10531
  checks.push({ name: "no workers.dev doors", status: "SKIP", detail: "script enumeration failed \u2014 NOT a pass, no subdomain was ever read. Needs a CF token with Workers Scripts:Read." });
9918
10532
  } else {
9919
- console.log(import_chalk50.default.green(`
10533
+ console.log(import_chalk51.default.green(`
9920
10534
  \u2713 0 of ${wd.total} workers reachable on *.workers.dev`));
9921
10535
  checks.push({ name: "no workers.dev doors", status: "PASS", detail: `all ${wd.total} scripts have workers.dev + previews disabled` });
9922
10536
  }
9923
- if (data?.how_to_fix) console.log(import_chalk50.default.yellow(`
9924
- ${data.reason}`) + import_chalk50.default.dim(`
10537
+ if (data?.how_to_fix) console.log(import_chalk51.default.yellow(`
10538
+ ${data.reason}`) + import_chalk51.default.dim(`
9925
10539
  ${data.how_to_fix}`));
9926
- else for (const e of errs) console.log(import_chalk50.default.red(`
10540
+ else for (const e of errs) console.log(import_chalk51.default.red(`
9927
10541
  audit error: ${e}`));
9928
10542
  if (errs.length) {
9929
10543
  checks.push({ name: "audit completeness", status: "FAIL", detail: `${errs.length} part(s) of the audit could not run \u2014 coverage is INCOMPLETE, and the checks they would have covered are SKIP above` });
@@ -9933,9 +10547,9 @@ function printDoors(data) {
9933
10547
  async function runMeter(readLedger, opts) {
9934
10548
  const checks = [];
9935
10549
  const N = Math.max(1, Math.min(10, opts.count ?? 3));
9936
- console.log(import_chalk50.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
10550
+ console.log(import_chalk51.default.bold("\n Meter \u2014 is 1 request charged exactly 1 request?\n"));
9937
10551
  const snap = await readLedger();
9938
- console.log(import_chalk50.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
10552
+ console.log(import_chalk51.default.dim(` wallet ${snap.billing_account_id} \xB7 band count ${snap.band_count} \xB7 next request ${snap.next_request_cents}\xA2
9939
10553
  `));
9940
10554
  const url = `https://${dpHost}/${SANDBOX.version}/${SANDBOX.environment}/`;
9941
10555
  const headers = opts.key ? { "X-API-Key": opts.key } : {};
@@ -10017,22 +10631,22 @@ async function runMeter(readLedger, opts) {
10017
10631
  return checks;
10018
10632
  }
10019
10633
  function printChecks(checks) {
10020
- console.log(import_chalk50.default.bold("\n Results\n"));
10021
- const mark = { PASS: import_chalk50.default.green(" PASS"), FAIL: import_chalk50.default.red(" FAIL"), SKIP: import_chalk50.default.dim(" SKIP"), KNOWN: import_chalk50.default.yellow(" KNOWN") };
10634
+ console.log(import_chalk51.default.bold("\n Results\n"));
10635
+ const mark = { PASS: import_chalk51.default.green(" PASS"), FAIL: import_chalk51.default.red(" FAIL"), SKIP: import_chalk51.default.dim(" SKIP"), KNOWN: import_chalk51.default.yellow(" KNOWN") };
10022
10636
  for (const ch of checks) {
10023
- console.log(` ${mark[ch.status]} ${import_chalk50.default.bold(ch.name)}`);
10024
- console.log(import_chalk50.default.dim(` ${ch.detail}`));
10637
+ console.log(` ${mark[ch.status]} ${import_chalk51.default.bold(ch.name)}`);
10638
+ console.log(import_chalk51.default.dim(` ${ch.detail}`));
10025
10639
  }
10026
10640
  const fails = checks.filter((c) => c.status === "FAIL").length;
10027
10641
  const skips = checks.filter((c) => c.status === "SKIP").length;
10028
10642
  const known = checks.filter((c) => c.status === "KNOWN").length;
10029
10643
  console.log("");
10030
10644
  const passes = checks.filter((c) => c.status === "PASS").length;
10031
- if (fails) console.log(import_chalk50.default.red(` ${fails} check(s) FAILED.`));
10032
- else if (passes) console.log(import_chalk50.default.green(` ${passes} check(s) passed, 0 failed.`));
10033
- else console.log(import_chalk50.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
10034
- if (known) console.log(import_chalk50.default.yellow(` ${known} known-open issue(s) still outstanding.`));
10035
- if (skips) console.log(import_chalk50.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
10645
+ if (fails) console.log(import_chalk51.default.red(` ${fails} check(s) FAILED.`));
10646
+ else if (passes) console.log(import_chalk51.default.green(` ${passes} check(s) passed, 0 failed.`));
10647
+ else console.log(import_chalk51.default.yellow(" NOTHING WAS VERIFIED \u2014 every check was skipped."));
10648
+ if (known) console.log(import_chalk51.default.yellow(` ${known} known-open issue(s) still outstanding.`));
10649
+ if (skips) console.log(import_chalk51.default.dim(` ${skips} check(s) NOT RUN (see SKIP above) \u2014 those invariants are unverified.`));
10036
10650
  console.log("");
10037
10651
  }
10038
10652
 
@@ -10079,93 +10693,93 @@ var OP_COMMANDS = [
10079
10693
  function renderOpCommands() {
10080
10694
  const width = Math.max(...OP_COMMANDS.map((c) => c.cmd.length)) + 10;
10081
10695
  const lines = OP_COMMANDS.map((c) => {
10082
- const left = ` ${import_chalk51.default.cyan(`apiblaze ${c.cmd}`)}`;
10696
+ const left = ` ${import_chalk52.default.cyan(`apiblaze ${c.cmd}`)}`;
10083
10697
  const pad = " ".repeat(Math.max(1, width - c.cmd.length));
10084
- return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk51.default.dim(`(${c.extra})`) : ""}`;
10698
+ return `${left}${pad}${c.blurb}${c.extra ? " " + import_chalk52.default.dim(`(${c.extra})`) : ""}`;
10085
10699
  });
10086
10700
  return [
10087
- import_chalk51.default.bold("Operator commands"),
10701
+ import_chalk52.default.bold("Operator commands"),
10088
10702
  ...lines,
10089
10703
  "",
10090
- import_chalk51.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
10091
- import_chalk51.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
10092
- import_chalk51.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
10704
+ import_chalk52.default.dim(" Operators only. The gate is server-side (dashboard /api/cli/op checks the"),
10705
+ import_chalk52.default.dim(" signed-in email, admin-api re-checks with operatorGate) \u2014 a patched CLI just"),
10706
+ import_chalk52.default.dim(" gets 403s. Every op call is read-only except `op sweep`."),
10093
10707
  "",
10094
- import_chalk51.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
10095
- import_chalk51.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
10096
- import_chalk51.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
10708
+ import_chalk52.default.dim(" Not a CLI command: to prune all non-CP data run scripts/nuke-but-cp.sh --apply --sweep"),
10709
+ import_chalk52.default.dim(" in the repo. Operator dashboards (dlq, thresholds, throttling, pricing, billing,"),
10710
+ import_chalk52.default.dim(" agent-spend, teams, tests, leak-detection, lifecycle) live at /operator/* in the app.")
10097
10711
  ].join("\n");
10098
10712
  }
10099
10713
  function printResidue(report, applied) {
10100
10714
  const up = report?.upstash ?? {};
10101
10715
  const fga = report?.fga ?? {};
10102
10716
  const ghosts = report?.ghosts ?? {};
10103
- console.log(import_chalk51.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
10104
- console.log(import_chalk51.default.bold("\n Upstash"));
10717
+ console.log(import_chalk52.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
10718
+ console.log(import_chalk52.default.bold("\n Upstash"));
10105
10719
  const orphans = up.orphans ?? [];
10106
- if (orphans.length === 0) console.log(import_chalk51.default.green(" no orphaned keys"));
10107
- for (const o of orphans) console.log(` ${import_chalk51.default.yellow(o.key)} ${import_chalk51.default.dim(`\u2014 ${o.reason}`)}`);
10108
- console.log(import_chalk51.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
10720
+ if (orphans.length === 0) console.log(import_chalk52.default.green(" no orphaned keys"));
10721
+ for (const o of orphans) console.log(` ${import_chalk52.default.yellow(o.key)} ${import_chalk52.default.dim(`\u2014 ${o.reason}`)}`);
10722
+ console.log(import_chalk52.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
10109
10723
  if (up.anon_wallet_detail) {
10110
10724
  const d = up.anon_wallet_detail;
10111
- console.log(import_chalk51.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
10725
+ console.log(import_chalk52.default.dim(` anon wallets: ${d.count} ($${(d.total_cents / 100).toFixed(2)}), ${d.no_ttl} with NO TTL${d.no_ttl ? " \u26A0" : " (all self-expire)"}`));
10112
10726
  }
10113
10727
  if (up.keyspace_census) {
10114
10728
  const census = Object.entries(up.keyspace_census).map(([k, v]) => `${k}=${v}`).join(" \xB7 ");
10115
- console.log(import_chalk51.default.dim(` keyspace: ${census}`));
10729
+ console.log(import_chalk52.default.dim(` keyspace: ${census}`));
10116
10730
  }
10117
- if (up.unknown?.length) console.log(import_chalk51.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
10118
- if (applied) console.log(` ${import_chalk51.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
10119
- for (const e of up.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
10120
- console.log(import_chalk51.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
10731
+ if (up.unknown?.length) console.log(import_chalk52.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
10732
+ if (applied) console.log(` ${import_chalk52.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
10733
+ for (const e of up.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
10734
+ console.log(import_chalk52.default.bold("\n OpenFGA / Neon \u2014 orphan stores"));
10121
10735
  if (applied) {
10122
10736
  const swept = fga?.swept ?? [];
10123
- if (swept.length === 0) console.log(import_chalk51.default.green(" no orphaned stores"));
10737
+ if (swept.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
10124
10738
  for (const s of swept) {
10125
10739
  console.log(
10126
- ` ${import_chalk51.default.yellow(s.store_id)} ${import_chalk51.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
10740
+ ` ${import_chalk52.default.yellow(s.store_id)} ${import_chalk52.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
10127
10741
  );
10128
10742
  }
10129
- if (fga?.remaining) console.log(import_chalk51.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
10743
+ if (fga?.remaining) console.log(import_chalk52.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
10130
10744
  const st = fga?.side_tables;
10131
- if (st) console.log(import_chalk51.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
10745
+ if (st) console.log(import_chalk52.default.dim(` Neon side-tables purged: ${st.soft_deleted_stores} store records, ${st.orphan_models} models, ${st.orphan_changelog} changelog rows${st.error ? ` (${st.error})` : ""}`));
10132
10746
  } else {
10133
10747
  const fgaOrphans = fga?.orphans ?? [];
10134
- if (fgaOrphans.length === 0) console.log(import_chalk51.default.green(" no orphaned stores"));
10748
+ if (fgaOrphans.length === 0) console.log(import_chalk52.default.green(" no orphaned stores"));
10135
10749
  for (const s of fgaOrphans) {
10136
10750
  const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
10137
- console.log(` ${import_chalk51.default.yellow(s.store_id)} ${import_chalk51.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
10751
+ console.log(` ${import_chalk52.default.yellow(s.store_id)} ${import_chalk52.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
10138
10752
  }
10139
- console.log(import_chalk51.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
10753
+ console.log(import_chalk52.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
10140
10754
  const st = fga?.side_tables;
10141
- if (st) console.log(import_chalk51.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
10755
+ if (st) console.log(import_chalk52.default.dim(` Neon side-table residue: ${st.soft_deleted_stores} soft-deleted store records, ${st.orphan_models} orphan models, ${st.orphan_changelog} orphan changelog rows`));
10142
10756
  }
10143
- for (const e of fga?.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
10144
- console.log(import_chalk51.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
10757
+ for (const e of fga?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
10758
+ console.log(import_chalk52.default.bold("\n OpenFGA \u2014 ghost tuples in surviving stores"));
10145
10759
  if (applied) {
10146
- if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk51.default.green(" no ghost tuples"));
10147
- else console.log(` ${import_chalk51.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk51.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
10760
+ if ((ghosts?.ghost_count ?? 0) === 0) console.log(import_chalk52.default.green(" no ghost tuples"));
10761
+ else console.log(` ${import_chalk52.default.bold(String(ghosts.deleted ?? 0))} ghost tuple(s) deleted ${import_chalk52.default.dim(`(of ${ghosts.ghost_count} found, ${ghosts.scanned_tuples} scanned across ${ghosts.live_stores} live stores)`)}`);
10148
10762
  } else {
10149
10763
  const n = ghosts?.ghost_count ?? 0;
10150
- if (n === 0) console.log(import_chalk51.default.green(` no ghost tuples ${import_chalk51.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
10764
+ if (n === 0) console.log(import_chalk52.default.green(` no ghost tuples ${import_chalk52.default.dim(`(${ghosts.scanned_tuples ?? 0} scanned across ${ghosts.live_stores ?? 0} live stores)`)}`));
10151
10765
  else {
10152
- console.log(import_chalk51.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
10766
+ console.log(import_chalk52.default.yellow(` ${n} ghost tuple(s) referencing entities absent from D1:`));
10153
10767
  for (const g of (ghosts.ghosts ?? []).slice(0, 20)) {
10154
- console.log(import_chalk51.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
10768
+ console.log(import_chalk52.default.dim(` ${g.object_type}:${g.object_id} ${g.relation} ${g._user}`));
10155
10769
  }
10156
- if (n > 20) console.log(import_chalk51.default.dim(` \u2026 and ${n - 20} more`));
10770
+ if (n > 20) console.log(import_chalk52.default.dim(` \u2026 and ${n - 20} more`));
10157
10771
  }
10158
10772
  }
10159
- for (const e of ghosts?.errors ?? []) console.log(import_chalk51.default.red(` error: ${e}`));
10773
+ for (const e of ghosts?.errors ?? []) console.log(import_chalk52.default.red(` error: ${e}`));
10160
10774
  console.log();
10161
10775
  }
10162
10776
  async function runOp(sub, opts = {}, view) {
10163
10777
  if (!loadCredentials()) {
10164
- console.log(import_chalk51.default.dim("Not logged in. Run `apiblaze login`."));
10778
+ console.log(import_chalk52.default.dim("Not logged in. Run `apiblaze login`."));
10165
10779
  return;
10166
10780
  }
10167
10781
  if (!isOperatorLogin()) {
10168
- console.log(import_chalk51.default.dim("`apiblaze op` is only available to platform operators."));
10782
+ console.log(import_chalk52.default.dim("`apiblaze op` is only available to platform operators."));
10169
10783
  return;
10170
10784
  }
10171
10785
  switch (sub) {
@@ -10191,17 +10805,17 @@ async function runOp(sub, opts = {}, view) {
10191
10805
  const nSide = (st.soft_deleted_stores ?? 0) + (st.orphan_models ?? 0) + (st.orphan_changelog ?? 0);
10192
10806
  printResidue(report, false);
10193
10807
  if (nUp + nFga + nGhost + nSide === 0) {
10194
- console.log(import_chalk51.default.green("Nothing to sweep."));
10808
+ console.log(import_chalk52.default.green("Nothing to sweep."));
10195
10809
  return;
10196
10810
  }
10197
10811
  if (!opts.yes) {
10198
10812
  const readline3 = await import("readline/promises");
10199
10813
  const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
10200
10814
  const answer = await rl.question(
10201
- import_chalk51.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
10815
+ import_chalk52.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s) + ${nGhost} ghost tuple(s) + ${nSide} Neon side-table row(s)? Type 'sweep' to confirm: `)
10202
10816
  );
10203
10817
  rl.close();
10204
- if (answer.trim() !== "sweep") return void console.log(import_chalk51.default.dim("Aborted."));
10818
+ if (answer.trim() !== "sweep") return void console.log(import_chalk52.default.dim("Aborted."));
10205
10819
  }
10206
10820
  const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
10207
10821
  if (opts.json) return void console.log(JSON.stringify(result, null, 2));
@@ -10211,25 +10825,25 @@ async function runOp(sub, opts = {}, view) {
10211
10825
  case "mark": {
10212
10826
  const label3 = (view ?? "").trim();
10213
10827
  if (!label3) {
10214
- console.log(import_chalk51.default.red("Give the change a name:") + import_chalk51.default.cyan(' apiblaze op mark "cached tenant count"'));
10828
+ console.log(import_chalk52.default.red("Give the change a name:") + import_chalk52.default.cyan(' apiblaze op mark "cached tenant count"'));
10215
10829
  return;
10216
10830
  }
10217
10831
  const res = await opCall({ method: "POST", path: "/operator/latency/mark", body: { label: label3 }, summary: "record change marker" });
10218
10832
  const ts = new Date(res?.marker?.ts ?? Date.now()).toISOString();
10219
- console.log(import_chalk51.default.green(`
10220
- Marked: `) + import_chalk51.default.bold(label3));
10221
- console.log(import_chalk51.default.dim(` ${ts}`));
10222
- console.log(import_chalk51.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk51.default.cyan("apiblaze op latency compare") + "\n");
10833
+ console.log(import_chalk52.default.green(`
10834
+ Marked: `) + import_chalk52.default.bold(label3));
10835
+ console.log(import_chalk52.default.dim(` ${ts}`));
10836
+ console.log(import_chalk52.default.dim(` Once traffic has run on both sides, compare with: `) + import_chalk52.default.cyan("apiblaze op latency compare") + "\n");
10223
10837
  return;
10224
10838
  }
10225
10839
  case "credits": {
10226
10840
  const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
10227
10841
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10228
10842
  const accounts = data?.accounts ?? [];
10229
- if (accounts.length === 0) return void console.log(import_chalk51.default.dim("No credit wallets."));
10843
+ if (accounts.length === 0) return void console.log(import_chalk52.default.dim("No credit wallets."));
10230
10844
  for (const a of accounts) {
10231
10845
  const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
10232
- console.log(` ${import_chalk51.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk51.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk51.default.dim(" \u2014 anon") : ""}`);
10846
+ console.log(` ${import_chalk52.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk52.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk52.default.dim(" \u2014 anon") : ""}`);
10233
10847
  }
10234
10848
  return;
10235
10849
  }
@@ -10240,7 +10854,7 @@ async function runOp(sub, opts = {}, view) {
10240
10854
  case "billing": {
10241
10855
  const which = (view ?? "").trim().toLowerCase();
10242
10856
  if (which && which !== "doors" && which !== "meter") {
10243
- return void console.log(import_chalk51.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
10857
+ return void console.log(import_chalk52.default.red(`Unknown: apiblaze op billing ${which}. Use 'doors', 'meter', or neither for both.`));
10244
10858
  }
10245
10859
  const checks = [];
10246
10860
  let doorsData = null;
@@ -10301,29 +10915,29 @@ async function runOp(sub, opts = {}, view) {
10301
10915
  const data = await opCall({ method: "GET", path: `/operator/latency/grades${q}`, summary: "latency grades" });
10302
10916
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10303
10917
  const t = data.thresholds_ms;
10304
- console.log(import_chalk51.default.bold("\nHow good was apiblaze itself?") + import_chalk51.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
10305
- console.log(import_chalk51.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
10918
+ console.log(import_chalk52.default.bold("\nHow good was apiblaze itself?") + import_chalk52.default.dim(" (our overhead only \u2014 a slow customer API never counts against us)"));
10919
+ console.log(import_chalk52.default.dim(` excellent <${t.excellent.replace("<", "")}ms \xB7 okay ${t.okay}ms \xB7 bad ${t.bad}ms \xB7 terrible ${t.terrible.replace(">=", "")}ms+
10306
10920
  `));
10307
- console.log(import_chalk51.default.dim(" date reqs excellent okay bad terrible"));
10921
+ console.log(import_chalk52.default.dim(" date reqs excellent okay bad terrible"));
10308
10922
  for (const d of data.days ?? []) {
10309
10923
  const p = d.pct;
10310
- const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk51.default.dim(`${String(v).padStart(5)}%`);
10924
+ const cell = (v, colour) => v > 0 ? colour(`${String(v).padStart(5)}%`) : import_chalk52.default.dim(`${String(v).padStart(5)}%`);
10311
10925
  console.log(
10312
- ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk51.default.green)} ${cell(p.okay, import_chalk51.default.cyan)} ${cell(p.bad, import_chalk51.default.yellow)} ${cell(p.terrible, import_chalk51.default.red)}`
10926
+ ` ${d.date} ${String(d.total).padStart(5)} ${cell(p.excellent, import_chalk52.default.green)} ${cell(p.okay, import_chalk52.default.cyan)} ${cell(p.bad, import_chalk52.default.yellow)} ${cell(p.terrible, import_chalk52.default.red)}`
10313
10927
  );
10314
10928
  }
10315
10929
  const cul = data.culprits ?? [];
10316
10930
  if (cul.length) {
10317
- console.log(import_chalk51.default.bold("\n Who caused the bad and terrible ones\n"));
10318
- console.log(import_chalk51.default.dim(" bad terrible feature \u2192 dependency"));
10931
+ console.log(import_chalk52.default.bold("\n Who caused the bad and terrible ones\n"));
10932
+ console.log(import_chalk52.default.dim(" bad terrible feature \u2192 dependency"));
10319
10933
  for (const r of cul.slice(0, 12)) {
10320
10934
  if (!r.bad && !r.terrible) continue;
10321
10935
  console.log(
10322
- ` ${String(r.bad).padStart(6)} ${import_chalk51.default.red(String(r.terrible).padStart(8))} ${import_chalk51.default.yellow(r.feature)} ${import_chalk51.default.dim("\u2192")} ${import_chalk51.default.cyan(r.dep)}`
10936
+ ` ${String(r.bad).padStart(6)} ${import_chalk52.default.red(String(r.terrible).padStart(8))} ${import_chalk52.default.yellow(r.feature)} ${import_chalk52.default.dim("\u2192")} ${import_chalk52.default.cyan(r.dep)}`
10323
10937
  );
10324
10938
  }
10325
10939
  }
10326
- if (data.caveat) console.log(import_chalk51.default.dim(`
10940
+ if (data.caveat) console.log(import_chalk52.default.dim(`
10327
10941
  \u26A0 ${data.caveat}
10328
10942
  `));
10329
10943
  return;
@@ -10332,24 +10946,24 @@ async function runOp(sub, opts = {}, view) {
10332
10946
  const data = await opCall({ method: "GET", path: `/operator/latency/compare${q}`, summary: "latency before/after" });
10333
10947
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10334
10948
  const b = data.before, a = data.after, d = data.delta;
10335
- console.log(import_chalk51.default.bold(`
10336
- Before vs after: `) + import_chalk51.default.cyan(data.marker.label));
10337
- console.log(import_chalk51.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
10949
+ console.log(import_chalk52.default.bold(`
10950
+ Before vs after: `) + import_chalk52.default.cyan(data.marker.label));
10951
+ console.log(import_chalk52.default.dim(` marked ${new Date(data.marker.ts).toISOString()} \xB7 ${data.window_hours}h either side
10338
10952
  `));
10339
10953
  const row = (name, before, after, delta) => {
10340
10954
  const arrow = delta === 0 ? "=" : delta < 0 ? "\u2193" : "\u2191";
10341
10955
  const txt = `${String(before).padStart(6)}ms \u2192${String(after).padStart(7)}ms ${arrow}${Math.abs(delta)}ms`;
10342
- console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk51.default.green(txt) : import_chalk51.default.red(txt) : import_chalk51.default.dim(txt)}`);
10956
+ console.log(` ${name.padEnd(22)}${data.trustworthy ? delta <= 0 ? import_chalk52.default.green(txt) : import_chalk52.default.red(txt) : import_chalk52.default.dim(txt)}`);
10343
10957
  };
10344
- console.log(import_chalk51.default.dim(" metric before after change"));
10958
+ console.log(import_chalk52.default.dim(" metric before after change"));
10345
10959
  row("total p50", b.total_p50, a.total_p50, d.total_p50);
10346
10960
  row("total p95", b.total_p95, a.total_p95, d.total_p95);
10347
10961
  row("apiblaze overhead p50", b.gw_p50, a.gw_p50, d.gw_p50);
10348
10962
  row("apiblaze overhead p95", b.gw_p95, a.gw_p95, d.gw_p95);
10349
- console.log(import_chalk51.default.dim(`
10963
+ console.log(import_chalk52.default.dim(`
10350
10964
  requests: ${b.requests} before \xB7 ${a.requests} after`));
10351
10965
  for (const w of data.warnings ?? []) {
10352
- console.log((data.trustworthy ? import_chalk51.default.dim : import_chalk51.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
10966
+ console.log((data.trustworthy ? import_chalk52.default.dim : import_chalk52.default.yellow)(` ${data.trustworthy ? "\xB7" : "\u26A0"} ${w}`));
10353
10967
  }
10354
10968
  console.log("");
10355
10969
  return;
@@ -10358,19 +10972,19 @@ Before vs after: `) + import_chalk51.default.cyan(data.marker.label));
10358
10972
  const data = await opCall({ method: "GET", path: `/operator/latency/slow${q}`, summary: "slowest requests" });
10359
10973
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10360
10974
  const rows2 = data?.rows ?? [];
10361
- if (!rows2.length) return void console.log(import_chalk51.default.dim("No requests over the threshold in that window."));
10362
- console.log(import_chalk51.default.bold(`
10975
+ if (!rows2.length) return void console.log(import_chalk52.default.dim("No requests over the threshold in that window."));
10976
+ console.log(import_chalk52.default.bold(`
10363
10977
  Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10364
10978
  `));
10365
- console.log(import_chalk51.default.dim(" total ours theirs blame request id"));
10979
+ console.log(import_chalk52.default.dim(" total ours theirs blame request id"));
10366
10980
  for (const r of rows2.slice(0, 30)) {
10367
10981
  console.log(
10368
- ` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk51.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk51.default.dim(r.request_id || "")}`
10982
+ ` ${String(Math.round(r.duration_ms)).padStart(6)} ${String(Math.round(r.gateway_ms)).padStart(5)} ${String(Math.round(r.upstream_ttfb_ms)).padStart(6)} ${import_chalk52.default.yellow(`${r.slow_gw_feature || "-"}\u2192${r.slow_dep || "-"}`.padEnd(20))} ${import_chalk52.default.dim(r.request_id || "")}`
10369
10983
  );
10370
10984
  }
10371
- console.log(import_chalk51.default.dim(`
10985
+ console.log(import_chalk52.default.dim(`
10372
10986
  The last column is the request id (Cloudflare calls it a "cf-ray"). Look one up with`));
10373
- console.log(import_chalk51.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
10987
+ console.log(import_chalk52.default.dim(` \`apiblaze logs\` for that request's exact per-feature breakdown \u2014 unsampled, unlike the table above.
10374
10988
  `));
10375
10989
  return;
10376
10990
  }
@@ -10378,17 +10992,18 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10378
10992
  const data = await opCall({ method: "GET", path: `/operator/latency/llm${q}`, summary: "llm latency" });
10379
10993
  if (opts.json) return void console.log(JSON.stringify(data, null, 2));
10380
10994
  const rows2 = data?.rows ?? [];
10381
- if (!rows2.length) return void console.log(import_chalk51.default.dim("No LLM traffic in that window."));
10382
- console.log(import_chalk51.default.bold(`
10995
+ if (!rows2.length) return void console.log(import_chalk52.default.dim("No LLM traffic in that window."));
10996
+ console.log(import_chalk52.default.bold(`
10383
10997
  LLM timing \u2014 last ${data.window_hours}h
10384
10998
  `));
10385
- console.log(import_chalk51.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
10999
+ console.log(import_chalk52.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
10386
11000
  for (const r of rows2) {
11001
+ const n = r.turns ?? r.requests ?? 0;
10387
11002
  console.log(
10388
- ` ${String(Math.round(r.requests)).padStart(8)} ${String(Math.round(r.gen_p95_ms)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95)).padStart(6)}/${String(Math.round(r.output_tokens_p95)).padEnd(6)} ${r.model || "-"}`
11003
+ ` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
10389
11004
  );
10390
11005
  }
10391
- console.log(import_chalk51.default.dim(`
11006
+ console.log(import_chalk52.default.dim(`
10392
11007
  ${data.note}
10393
11008
  `));
10394
11009
  return;
@@ -10399,26 +11014,26 @@ LLM timing \u2014 last ${data.window_hours}h
10399
11014
  ]);
10400
11015
  if (opts.json) return void console.log(JSON.stringify({ blame, summary }, null, 2));
10401
11016
  const ov = summary?.apiblaze_overhead_ms ?? {};
10402
- console.log(import_chalk51.default.bold(`
11017
+ console.log(import_chalk52.default.bold(`
10403
11018
  Latency \u2014 last ${summary?.window_hours ?? "?"}h, ${Number(summary?.requests ?? 0).toLocaleString()} requests
10404
11019
  `));
10405
- console.log(` ${import_chalk51.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk51.default.dim("\u2190 ours")}`);
10406
- console.log(` ${import_chalk51.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk51.default.dim("\u2190 theirs")}`);
10407
- console.log(import_chalk51.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
11020
+ console.log(` ${import_chalk52.default.bold("apiblaze overhead")} p50 ${String(ov.p50 ?? 0).padStart(5)}ms p95 ${String(ov.p95 ?? 0).padStart(6)}ms p99 ${String(ov.p99 ?? 0).padStart(6)}ms ${import_chalk52.default.dim("\u2190 ours")}`);
11021
+ console.log(` ${import_chalk52.default.bold("upstream ttfb ")} ${" ".repeat(24)}p95 ${String(summary?.upstream_ttfb_p95_ms ?? 0).padStart(6)}ms ${import_chalk52.default.dim("\u2190 theirs")}`);
11022
+ console.log(import_chalk52.default.dim(` (per-request percentiles \u2014 never subtract one from the other)
10408
11023
  `));
10409
11024
  const rows = blame?.blame ?? [];
10410
- if (!rows.length) return void console.log(import_chalk51.default.dim("No latency rows in that window."));
10411
- console.log(import_chalk51.default.bold(" Which feature ate the time, and what inside it\n"));
10412
- console.log(import_chalk51.default.dim(" share p95 feature \u2192 dependency"));
11025
+ if (!rows.length) return void console.log(import_chalk52.default.dim("No latency rows in that window."));
11026
+ console.log(import_chalk52.default.bold(" Which feature ate the time, and what inside it\n"));
11027
+ console.log(import_chalk52.default.dim(" share p95 feature \u2192 dependency"));
10413
11028
  for (const r of rows.slice(0, 15)) {
10414
11029
  const share = `${(r.share * 100).toFixed(1)}%`;
10415
- console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk51.default.yellow(r.feature)} ${import_chalk51.default.dim("\u2192")} ${import_chalk51.default.cyan(r.dep)}`);
11030
+ console.log(` ${share.padStart(6)} ${String(r.p95_ms).padStart(6)}ms ${import_chalk52.default.yellow(r.feature)} ${import_chalk52.default.dim("\u2192")} ${import_chalk52.default.cyan(r.dep)}`);
10416
11031
  }
10417
11032
  console.log("");
10418
11033
  return;
10419
11034
  }
10420
11035
  default:
10421
- console.log(import_chalk51.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
11036
+ console.log(import_chalk52.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
10422
11037
  }
10423
11038
  }
10424
11039
 
@@ -10451,7 +11066,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
10451
11066
  process.exit(1);
10452
11067
  }
10453
11068
  });
10454
- 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("--openapi <file|url>", "Create FROM an OpenAPI spec instead of --target \u2014 a local file or a URL (e.g. https://pokeapi.co/openapi.yaml). Routes, API version and environments (one per `servers` entry) all come from the spec").option("--openapispec <file|url>", "Alias for --openapi").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("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").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("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").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) => {
11069
+ 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|file>", "What to proxy \u2014 pass ANY of: a target server base URL (https://httpbin.org), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml). Spec inputs are detected automatically; routes, API version and environments then come from the spec").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).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("--apikey", "Protect with API keys and print the bootstrap keys (the default, made explicit). Consumers send X-API-Key.").option("--oauth [config]", `Login door. Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0).`).option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").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("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").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) => {
10455
11070
  try {
10456
11071
  await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
10457
11072
  } catch (err) {
@@ -10464,7 +11079,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
10464
11079
  program.command("rule").description("Author an object-level access rule in plain English, in one shot (billed per turn)").argument("<rule>", 'The rule in plain English, e.g. "users see only their own rows"').argument("<project>", "Project name or id").option("--enforce", "Turn enforcement on immediately (default: shadow-publish only)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((rule, project, opts) => runRule(rule, project, opts)));
10465
11080
  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)));
10466
11081
  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)));
10467
- program.command("apichat").description("Turn any API into a chat: point at an OpenAPI spec and start asking questions (no login needed)").option("--openapi <file|url>", "OpenAPI/Swagger spec to build from (JSON or YAML; file path or URL)").option("--openapispec <file|url>", "Alias for --openapi").option("--target <url>", "Upstream base URL (overrides servers[0].url; enables spec discovery when --openapi is omitted)").option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").action(action((opts) => runApichat({ ...opts, openapispec: opts.openapispec ?? opts.openapi })));
11082
+ program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--target <url|file>", "What to chat with \u2014 pass ANY of: a target server base URL (spec auto-discovered at /openapi.json etc.), a local OpenAPI file (./openapi.yaml), or a remote OpenAPI URL (https://acme.com/openapi.yaml)").addOption(new import_commander.Option("--openapi <file|url>", "Deprecated alias \u2014 --target now detects spec files/URLs itself").hideHelp()).addOption(new import_commander.Option("--openapispec <file|url>", "Deprecated alias for --openapi").hideHelp()).option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").option("-p, --prompt <question>", "One-shot question piped through the external agent CLI after the MCP install (used with --install-mcp or the install offer)").option("--install-mcp <cli>", "Install this proxy's MCP into an external agent CLI without asking: claude | codex. Also re-offers after an earlier decline.").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
10468
11083
  var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
10469
11084
  llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
10470
11085
  llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));
@@ -10481,7 +11096,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
10481
11096
  try {
10482
11097
  const resolved = parseInt(port ?? opts.port, 10);
10483
11098
  if (Number.isNaN(resolved)) {
10484
- console.error(import_chalk52.default.red(`Invalid port: ${port ?? opts.port}`));
11099
+ console.error(import_chalk53.default.red(`Invalid port: ${port ?? opts.port}`));
10485
11100
  process.exit(1);
10486
11101
  }
10487
11102
  await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
@@ -10613,7 +11228,7 @@ function groupedCommandHelp() {
10613
11228
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
10614
11229
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
10615
11230
  }).filter(Boolean).join("\n");
10616
- return `${import_chalk52.default.bold(g.title)}
11231
+ return `${import_chalk53.default.bold(g.title)}
10617
11232
  ${rows}`;
10618
11233
  }).join("\n\n");
10619
11234
  }
@@ -10651,14 +11266,14 @@ async function recoverStaleTeam() {
10651
11266
  const { resolveLinkedTeam: resolveLinkedTeam2 } = await Promise.resolve().then(() => (init_team(), team_exports));
10652
11267
  const linked = await resolveLinkedTeam2({ preferredId: creds.teamId, interactive: !!process.stdin.isTTY });
10653
11268
  if (!linked) {
10654
- console.error(import_chalk52.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
11269
+ console.error(import_chalk53.default.yellow("Your account has no teams anymore (deleted?). Run `apiblaze login` or `apiblaze create` to get a workspace."));
10655
11270
  return;
10656
11271
  }
10657
11272
  if (linked.teamId === creds.teamId) return;
10658
11273
  const next = { ...creds, teamId: linked.teamId, teamName: linked.teamName };
10659
11274
  delete next.activeTenant;
10660
11275
  saveCredentials(next);
10661
- console.error(import_chalk52.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk52.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
11276
+ console.error(import_chalk53.default.yellow(`Your previous team no longer exists \u2014 relinked to ${import_chalk53.default.bold(linked.teamName ?? linked.teamId)}. Re-run your command.`));
10662
11277
  } catch {
10663
11278
  }
10664
11279
  }
@@ -10666,16 +11281,16 @@ async function printError(err) {
10666
11281
  if (err instanceof ApiError) {
10667
11282
  const data = err.body;
10668
11283
  const extra = [data?.body?.reason, data?.body?.details, data?.details, data?.body?.error].find((x) => typeof x === "string" && x && x !== err.message);
10669
- console.error(import_chalk52.default.red(`
11284
+ console.error(import_chalk53.default.red(`
10670
11285
  API error (${err.status}): ${err.message}${extra ? ` \u2014 ${extra}` : ""}`));
10671
11286
  if (err.status === 403 || err.status === 404) {
10672
11287
  await recoverStaleTeam();
10673
11288
  }
10674
11289
  } else if (err instanceof Error) {
10675
- console.error(import_chalk52.default.red(`
11290
+ console.error(import_chalk53.default.red(`
10676
11291
  Error: ${err.message}`));
10677
11292
  } else {
10678
- console.error(import_chalk52.default.red("\nUnknown error"));
11293
+ console.error(import_chalk53.default.red("\nUnknown error"));
10679
11294
  }
10680
11295
  }
10681
11296
  program.parse(process.argv);