apiblaze 0.21.27 → 0.21.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +864 -393
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var __toESM = (mod, isNodeMode, target2) => (target2 = mod != null ? __create(__
34
34
  var version;
35
35
  var init_package = __esm({
36
36
  "package.json"() {
37
- version = "0.21.27";
37
+ version = "0.21.28";
38
38
  }
39
39
  });
40
40
 
@@ -384,8 +384,8 @@ function dualAuth(opts = {}) {
384
384
  function bothDoorsBody(opts = {}) {
385
385
  return { auth_type: "api_key", requests_auth: dualAuth(opts), login: GITHUB_LOGIN };
386
386
  }
387
- async function apiFetch(path14, options = {}, auth) {
388
- const url = `${DASHBOARD_BASE2}${path14}`;
387
+ async function apiFetch(path16, options = {}, auth) {
388
+ const url = `${DASHBOARD_BASE2}${path16}`;
389
389
  const res = await fetch(url, {
390
390
  ...options,
391
391
  headers: {
@@ -411,12 +411,12 @@ async function apiFetch(path14, options = {}, auth) {
411
411
  }
412
412
  return res.json();
413
413
  }
414
- async function agentCall(path14, method, body) {
414
+ async function agentCall(path16, method, body) {
415
415
  const token = getAccessToken();
416
416
  const res = await fetch(`${DASHBOARD_BASE2}/api/cli/agents`, {
417
417
  method: "POST",
418
418
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
419
- body: JSON.stringify({ path: path14, method, body })
419
+ body: JSON.stringify({ path: path16, method, body })
420
420
  });
421
421
  let data = null;
422
422
  try {
@@ -896,11 +896,11 @@ function decodeJwt(token) {
896
896
  return null;
897
897
  }
898
898
  }
899
- function maskPath(path14) {
900
- const q = path14.indexOf("?");
901
- if (q < 0) return path14;
902
- const base2 = path14.slice(0, q);
903
- const query = path14.slice(q + 1);
899
+ function maskPath(path16) {
900
+ const q = path16.indexOf("?");
901
+ if (q < 0) return path16;
902
+ const base2 = path16.slice(0, q);
903
+ const query = path16.slice(q + 1);
904
904
  const masked = query.split("&").map((pair) => {
905
905
  const eq = pair.indexOf("=");
906
906
  if (eq < 0) return pair;
@@ -1197,8 +1197,8 @@ function pageQuery(cursor, limit = PAGE_LIMIT) {
1197
1197
  if (cursor) qs.set("cursor", cursor);
1198
1198
  return qs.toString();
1199
1199
  }
1200
- function withPage(path14, cursor, limit = PAGE_LIMIT) {
1201
- return `${path14}${path14.includes("?") ? "&" : "?"}${pageQuery(cursor, limit)}`;
1200
+ function withPage(path16, cursor, limit = PAGE_LIMIT) {
1201
+ return `${path16}${path16.includes("?") ? "&" : "?"}${pageQuery(cursor, limit)}`;
1202
1202
  }
1203
1203
  var PAGE_LIMIT, MAX_HOPS;
1204
1204
  var init_paging = __esm({
@@ -1885,13 +1885,53 @@ var init_tunnel_client = __esm({
1885
1885
  }
1886
1886
  });
1887
1887
 
1888
+ // src/lib/env-local.ts
1889
+ function saveEnvLocal(dir, vars) {
1890
+ const file2 = import_path3.default.join(dir, ".env.local");
1891
+ try {
1892
+ const existed = import_fs2.default.existsSync(file2);
1893
+ const lines = existed ? import_fs2.default.readFileSync(file2, "utf8").split("\n") : [];
1894
+ const left = new Map(Object.entries(vars));
1895
+ const out = lines.map((line) => {
1896
+ const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=/.exec(line);
1897
+ if (m && left.has(m[1])) {
1898
+ const v = left.get(m[1]);
1899
+ left.delete(m[1]);
1900
+ return `${m[1]}=${v}`;
1901
+ }
1902
+ return line;
1903
+ });
1904
+ while (out.length && out[out.length - 1] === "") out.pop();
1905
+ for (const [k, v] of left) out.push(`${k}=${v}`);
1906
+ import_fs2.default.writeFileSync(file2, `${out.join("\n")}
1907
+ `, existed ? void 0 : { mode: 384 });
1908
+ return { ok: true, gitignoreWarning: !gitIgnores(dir) };
1909
+ } catch {
1910
+ return { ok: false, gitignoreWarning: false };
1911
+ }
1912
+ }
1913
+ function gitIgnores(dir) {
1914
+ const gi = import_path3.default.join(dir, ".gitignore");
1915
+ if (!import_fs2.default.existsSync(gi)) return !import_fs2.default.existsSync(import_path3.default.join(dir, ".git"));
1916
+ const pats = import_fs2.default.readFileSync(gi, "utf8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
1917
+ return pats.some((p) => [".env.local", "/.env.local", ".env*", "/.env*", ".env*.local", "*.local", ".env.*"].includes(p));
1918
+ }
1919
+ var import_fs2, import_path3;
1920
+ var init_env_local = __esm({
1921
+ "src/lib/env-local.ts"() {
1922
+ "use strict";
1923
+ import_fs2 = __toESM(require("fs"));
1924
+ import_path3 = __toESM(require("path"));
1925
+ }
1926
+ });
1927
+
1888
1928
  // src/lib/preapproval.ts
1889
- async function iamCall(tenant2, path14, method = "GET", body) {
1929
+ async function iamCall(tenant2, path16, method = "GET", body) {
1890
1930
  const token = getAccessToken();
1891
1931
  const res = await fetch(`${DASHBOARD_BASE5}/api/cli/iam`, {
1892
1932
  method: "POST",
1893
1933
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
1894
- body: JSON.stringify({ tenant: tenant2, path: path14, method, body })
1934
+ body: JSON.stringify({ tenant: tenant2, path: path16, method, body })
1895
1935
  });
1896
1936
  const data = await res.json().catch(() => ({}));
1897
1937
  if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
@@ -1962,15 +2002,15 @@ function detectProvider(key) {
1962
2002
  }
1963
2003
  function loadLlmConfig() {
1964
2004
  try {
1965
- return JSON.parse(fs4.readFileSync(LLM_PATH, "utf-8"));
2005
+ return JSON.parse(fs5.readFileSync(LLM_PATH, "utf-8"));
1966
2006
  } catch {
1967
2007
  return null;
1968
2008
  }
1969
2009
  }
1970
2010
  function saveLlmConfig(cfg) {
1971
- fs4.mkdirSync(getApiblazeDir(), { recursive: true });
1972
- fs4.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
1973
- fs4.chmodSync(LLM_PATH, 384);
2011
+ fs5.mkdirSync(getApiblazeDir(), { recursive: true });
2012
+ fs5.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
2013
+ fs5.chmodSync(LLM_PATH, 384);
1974
2014
  }
1975
2015
  async function runLlmSetKey(keyArg, opts) {
1976
2016
  let key = keyArg?.trim();
@@ -2009,23 +2049,23 @@ async function runLlmShow() {
2009
2049
  }
2010
2050
  async function runLlmClearKey() {
2011
2051
  try {
2012
- fs4.unlinkSync(LLM_PATH);
2052
+ fs5.unlinkSync(LLM_PATH);
2013
2053
  console.log(`${import_chalk10.default.green("\u2713")} Removed local LLM key.`);
2014
2054
  } catch {
2015
2055
  console.log(import_chalk10.default.gray("No LLM key was set."));
2016
2056
  }
2017
2057
  }
2018
- var fs4, path5, import_chalk10, import_inquirer, LLM_PATH;
2058
+ var fs5, path6, import_chalk10, import_inquirer, LLM_PATH;
2019
2059
  var init_llm = __esm({
2020
2060
  "src/commands/llm.ts"() {
2021
2061
  "use strict";
2022
- fs4 = __toESM(require("fs"));
2023
- path5 = __toESM(require("path"));
2062
+ fs5 = __toESM(require("fs"));
2063
+ path6 = __toESM(require("path"));
2024
2064
  import_chalk10 = __toESM(require("chalk"));
2025
2065
  import_inquirer = __toESM(require("inquirer"));
2026
2066
  init_auth();
2027
2067
  init_traffic();
2028
- LLM_PATH = path5.join(getApiblazeDir(), "llm.json");
2068
+ LLM_PATH = path6.join(getApiblazeDir(), "llm.json");
2029
2069
  }
2030
2070
  });
2031
2071
 
@@ -2216,7 +2256,7 @@ function claudeInteractive(spec2, prompt) {
2216
2256
  return { argv, status: r.status };
2217
2257
  }
2218
2258
  function codexConfigPath() {
2219
- return path6.join(process.env.CODEX_HOME || path6.join(os4.homedir(), ".codex"), "config.toml");
2259
+ return path7.join(process.env.CODEX_HOME || path7.join(os4.homedir(), ".codex"), "config.toml");
2220
2260
  }
2221
2261
  function codexServerBlock(spec2) {
2222
2262
  const lines = [`[mcp_servers.${tomlStr(spec2.name)}]`, `url = ${tomlStr(spec2.url)}`];
@@ -2248,11 +2288,11 @@ function codexSectionRanges(lines, name) {
2248
2288
  function installIntoCodex(spec2) {
2249
2289
  const file2 = codexConfigPath();
2250
2290
  try {
2251
- fs5.mkdirSync(path6.dirname(file2), { recursive: true });
2291
+ fs6.mkdirSync(path7.dirname(file2), { recursive: true });
2252
2292
  let text = "";
2253
2293
  let existed = true;
2254
2294
  try {
2255
- text = fs5.readFileSync(file2, "utf-8");
2295
+ text = fs6.readFileSync(file2, "utf-8");
2256
2296
  } catch {
2257
2297
  existed = false;
2258
2298
  }
@@ -2275,11 +2315,11 @@ function installIntoCodex(spec2) {
2275
2315
  if (cleaned.length && !cleaned.endsWith("\n")) cleaned += "\n";
2276
2316
  if (cleaned.length && !cleaned.endsWith("\n\n")) cleaned += "\n";
2277
2317
  const tmp = `${file2}.tmp-${process.pid}`;
2278
- fs5.writeFileSync(tmp, cleaned + codexServerBlock(spec2), { encoding: "utf-8", mode: 384 });
2279
- fs5.renameSync(tmp, file2);
2318
+ fs6.writeFileSync(tmp, cleaned + codexServerBlock(spec2), { encoding: "utf-8", mode: 384 });
2319
+ fs6.renameSync(tmp, file2);
2280
2320
  if (!existed) {
2281
2321
  try {
2282
- fs5.chmodSync(file2, 384);
2322
+ fs6.chmodSync(file2, 384);
2283
2323
  } catch {
2284
2324
  }
2285
2325
  }
@@ -2290,7 +2330,7 @@ function installIntoCodex(spec2) {
2290
2330
  }
2291
2331
  function codexSiblingApiblazeServers(keepName) {
2292
2332
  try {
2293
- const text = fs5.readFileSync(codexConfigPath(), "utf-8");
2333
+ const text = fs6.readFileSync(codexConfigPath(), "utf-8");
2294
2334
  const lines = text.split("\n");
2295
2335
  const out = [];
2296
2336
  const header = /^\[mcp_servers\.(?:"([^"]+)"|([^\]]+))\]\s*(#.*)?$/;
@@ -2333,7 +2373,7 @@ async function mcpEndpointIsDead(url) {
2333
2373
  }
2334
2374
  function codexServerUrl(name) {
2335
2375
  try {
2336
- const lines = fs5.readFileSync(codexConfigPath(), "utf-8").split("\n");
2376
+ const lines = fs6.readFileSync(codexConfigPath(), "utf-8").split("\n");
2337
2377
  const ranges = codexSectionRanges(lines, name);
2338
2378
  if (!ranges.length) return null;
2339
2379
  const body = lines.slice(ranges[0].start, ranges[0].end).join("\n");
@@ -2361,13 +2401,13 @@ function removeClaudeServer(name) {
2361
2401
  function removeCodexServer(name) {
2362
2402
  try {
2363
2403
  const file2 = codexConfigPath();
2364
- const lines = fs5.readFileSync(file2, "utf-8").split("\n");
2404
+ const lines = fs6.readFileSync(file2, "utf-8").split("\n");
2365
2405
  const ranges = codexSectionRanges(lines, name);
2366
2406
  if (!ranges.length) return false;
2367
2407
  for (const r of [...ranges].reverse()) lines.splice(r.start, r.end - r.start);
2368
2408
  const tmp = `${file2}.tmp-${process.pid}`;
2369
- fs5.writeFileSync(tmp, lines.join("\n"), { encoding: "utf-8", mode: 384 });
2370
- fs5.renameSync(tmp, file2);
2409
+ fs6.writeFileSync(tmp, lines.join("\n"), { encoding: "utf-8", mode: 384 });
2410
+ fs6.renameSync(tmp, file2);
2371
2411
  return true;
2372
2412
  } catch {
2373
2413
  return false;
@@ -2388,7 +2428,7 @@ function directedPrompt(spec2, question) {
2388
2428
  return `Use the "${spec2.name}" MCP server (it exposes this API's tools) to answer: ${question}${ctx}`;
2389
2429
  }
2390
2430
  function agentInstructionFile(cli, dir = process.cwd()) {
2391
- return path6.join(dir, cli === "claude" ? "CLAUDE.md" : "AGENTS.md");
2431
+ return path7.join(dir, cli === "claude" ? "CLAUDE.md" : "AGENTS.md");
2392
2432
  }
2393
2433
  function stripMarkedBlock(text, name) {
2394
2434
  const m = marker(name);
@@ -2422,12 +2462,12 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
2422
2462
  ].join("\n");
2423
2463
  let text = "";
2424
2464
  try {
2425
- text = fs5.readFileSync(file2, "utf-8");
2465
+ text = fs6.readFileSync(file2, "utf-8");
2426
2466
  } catch {
2427
2467
  }
2428
2468
  const cleaned = stripMarkedBlock(text, spec2.name);
2429
2469
  const sep = cleaned.length && !cleaned.endsWith("\n\n") ? cleaned.endsWith("\n") ? "\n" : "\n\n" : "";
2430
- fs5.writeFileSync(file2, cleaned + sep + block, "utf-8");
2470
+ fs6.writeFileSync(file2, cleaned + sep + block, "utf-8");
2431
2471
  return file2;
2432
2472
  } catch {
2433
2473
  return null;
@@ -2436,10 +2476,10 @@ function writeAgentInstruction(cli, spec2, dir = process.cwd()) {
2436
2476
  function removeAgentInstruction(cli, name, dir = process.cwd()) {
2437
2477
  try {
2438
2478
  const file2 = agentInstructionFile(cli, dir);
2439
- const text = fs5.readFileSync(file2, "utf-8");
2479
+ const text = fs6.readFileSync(file2, "utf-8");
2440
2480
  const cleaned = stripMarkedBlock(text, name);
2441
2481
  if (cleaned === text) return false;
2442
- fs5.writeFileSync(file2, cleaned.replace(/\n{3,}/g, "\n\n"), "utf-8");
2482
+ fs6.writeFileSync(file2, cleaned.replace(/\n{3,}/g, "\n\n"), "utf-8");
2443
2483
  return true;
2444
2484
  } catch {
2445
2485
  return false;
@@ -2526,14 +2566,14 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
2526
2566
  const question = await getQuestion();
2527
2567
  if (question) {
2528
2568
  const handoff = !!process.stdin.isTTY;
2529
- const ask2 = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
2569
+ const ask3 = handoff ? cli.kind === "claude" ? claudeInteractive : codexInteractive : oneShot;
2530
2570
  log4(`
2531
2571
  ${import_chalk12.default.dim(handoff ? `Opening ${cli.label} with your question \u2014 you'll stay in that session:` : `Your question, through ${cli.label}:`)}`);
2532
2572
  const directed = directedPrompt(spec2, question);
2533
2573
  const shown = cli.kind === "claude" ? handoff ? ["claude", directed, "--allowedTools", `mcp__${spec2.name}__*`, "--append-system-prompt", "\u2026"] : ["claude", "-p", directed, "--allowedTools", `mcp__${spec2.name}__*`] : handoff ? ["codex", directed] : ["codex", "exec", "--skip-git-repo-check", directed];
2534
2574
  log4(` ${import_chalk12.default.dim("$")} ${renderCommand(shown)}
2535
2575
  `);
2536
- const ans = ask2(spec2, directedPrompt(spec2, question));
2576
+ const ans = ask3(spec2, directedPrompt(spec2, question));
2537
2577
  if (ans.status !== 0) log4(import_chalk12.default.yellow(`
2538
2578
  ${cli.label} exited with ${ans.status ?? "no status"} \u2014 the MCP stays installed.`));
2539
2579
  }
@@ -2553,13 +2593,13 @@ async function installAndDemo(cli, spec2, getQuestion, log4 = console.log) {
2553
2593
  log4(import_chalk12.default.red(` ${import_chalk12.default.bold(off)}`));
2554
2594
  return true;
2555
2595
  }
2556
- var fs5, os4, path6, import_child_process, import_chalk12, useShell, winQuote, run, detected, mcpLoginSupport, OURS, mask, tomlStr, shellQuote, marker;
2596
+ var fs6, os4, path7, import_child_process, import_chalk12, useShell, winQuote, run, detected, mcpLoginSupport, OURS, mask, tomlStr, shellQuote, marker;
2557
2597
  var init_external_mcp = __esm({
2558
2598
  "src/lib/external-mcp.ts"() {
2559
2599
  "use strict";
2560
- fs5 = __toESM(require("fs"));
2600
+ fs6 = __toESM(require("fs"));
2561
2601
  os4 = __toESM(require("os"));
2562
- path6 = __toESM(require("path"));
2602
+ path7 = __toESM(require("path"));
2563
2603
  import_child_process = require("child_process");
2564
2604
  import_chalk12 = __toESM(require("chalk"));
2565
2605
  useShell = process.platform === "win32";
@@ -2835,19 +2875,19 @@ var init_problem = __esm({
2835
2875
  // src/lib/consumer-auth.ts
2836
2876
  function loadConsumer() {
2837
2877
  try {
2838
- return JSON.parse(fs6.readFileSync(CONSUMER_PATH, "utf-8"));
2878
+ return JSON.parse(fs7.readFileSync(CONSUMER_PATH, "utf-8"));
2839
2879
  } catch {
2840
2880
  return null;
2841
2881
  }
2842
2882
  }
2843
2883
  function saveConsumer(creds) {
2844
- fs6.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2845
- fs6.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2846
- fs6.chmodSync(CONSUMER_PATH, 384);
2884
+ fs7.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2885
+ fs7.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2886
+ fs7.chmodSync(CONSUMER_PATH, 384);
2847
2887
  }
2848
2888
  function clearConsumer() {
2849
2889
  try {
2850
- fs6.unlinkSync(CONSUMER_PATH);
2890
+ fs7.unlinkSync(CONSUMER_PATH);
2851
2891
  return true;
2852
2892
  } catch {
2853
2893
  return false;
@@ -2949,18 +2989,18 @@ async function validConsumerToken(creds) {
2949
2989
  if (Date.now() < creds.expiresAt - 3e4) return creds;
2950
2990
  return refreshConsumer(creds);
2951
2991
  }
2952
- var fs6, os5, path7, crypto, import_child_process2, AUTH_BASE, APIBLAZE_DIR2, CONSUMER_PATH, DEVICE_GRANT;
2992
+ var fs7, os5, path8, crypto, import_child_process2, AUTH_BASE, APIBLAZE_DIR2, CONSUMER_PATH, DEVICE_GRANT;
2953
2993
  var init_consumer_auth = __esm({
2954
2994
  "src/lib/consumer-auth.ts"() {
2955
2995
  "use strict";
2956
- fs6 = __toESM(require("fs"));
2996
+ fs7 = __toESM(require("fs"));
2957
2997
  os5 = __toESM(require("os"));
2958
- path7 = __toESM(require("path"));
2998
+ path8 = __toESM(require("path"));
2959
2999
  crypto = __toESM(require("crypto"));
2960
3000
  import_child_process2 = require("child_process");
2961
3001
  AUTH_BASE = process.env.APIBLAZE_AUTH_BASE || "https://auth.apiblaze.com";
2962
- APIBLAZE_DIR2 = path7.join(os5.homedir(), ".apiblaze");
2963
- CONSUMER_PATH = path7.join(APIBLAZE_DIR2, "consumer.json");
3002
+ APIBLAZE_DIR2 = path8.join(os5.homedir(), ".apiblaze");
3003
+ CONSUMER_PATH = path8.join(APIBLAZE_DIR2, "consumer.json");
2964
3004
  DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
2965
3005
  }
2966
3006
  });
@@ -3021,12 +3061,12 @@ async function classifyTargetInput(input, failFn) {
3021
3061
  if (!isHttpUrl(t)) {
3022
3062
  let isFile = false;
3023
3063
  try {
3024
- isFile = fs7.existsSync(t) && fs7.statSync(t).isFile();
3064
+ isFile = fs8.existsSync(t) && fs8.statSync(t).isFile();
3025
3065
  } catch {
3026
3066
  isFile = false;
3027
3067
  }
3028
3068
  if (isFile) {
3029
- const text2 = fs7.readFileSync(t, "utf-8");
3069
+ const text2 = fs8.readFileSync(t, "utf-8");
3030
3070
  if (looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
3031
3071
  failFn(
3032
3072
  `${t} exists but is not an OpenAPI/Swagger document (no openapi/swagger version field with paths).`,
@@ -3047,11 +3087,11 @@ async function classifyTargetInput(input, failFn) {
3047
3087
  if (text && looksLikeSpecDoc(text)) return { kind: "spec", specText: text, source: t };
3048
3088
  return { kind: "server" };
3049
3089
  }
3050
- var fs7, import_yaml2, MAX_SPEC_BYTES;
3090
+ var fs8, import_yaml2, MAX_SPEC_BYTES;
3051
3091
  var init_spec_or_target = __esm({
3052
3092
  "src/lib/spec-or-target.ts"() {
3053
3093
  "use strict";
3054
- fs7 = __toESM(require("fs"));
3094
+ fs8 = __toESM(require("fs"));
3055
3095
  import_yaml2 = require("yaml");
3056
3096
  MAX_SPEC_BYTES = 5 * 1024 * 1024;
3057
3097
  }
@@ -3124,7 +3164,7 @@ async function loadSpec(opts) {
3124
3164
  }
3125
3165
  let text;
3126
3166
  try {
3127
- text = fs8.readFileSync(opts.openapispec, "utf-8");
3167
+ text = fs9.readFileSync(opts.openapispec, "utf-8");
3128
3168
  } catch {
3129
3169
  fail3(`Cannot read spec file: ${opts.openapispec}`);
3130
3170
  }
@@ -3326,13 +3366,13 @@ async function ensureConsumerLogin(teamId, tenant2, version2) {
3326
3366
  console.log(` ${import_chalk15.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk15.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
3327
3367
  return creds;
3328
3368
  }
3329
- async function cpPost(anon, path14, body, summary) {
3369
+ async function cpPost(anon, path16, body, summary) {
3330
3370
  if (anon) {
3331
3371
  const cred = loadAnonCred();
3332
3372
  if (!cred) throw new Error("Anonymous workspace credential missing.");
3333
- return cpFetch(cred.cp_key, path14, { method: "POST", body: JSON.stringify(body) });
3373
+ return cpFetch(cred.cp_key, path16, { method: "POST", body: JSON.stringify(body) });
3334
3374
  }
3335
- return admin({ method: "POST", path: path14, body, summary });
3375
+ return admin({ method: "POST", path: path16, body, summary });
3336
3376
  }
3337
3377
  async function provision(spec2, target2, opts) {
3338
3378
  const loggedIn = !!loadCredentials();
@@ -3882,17 +3922,17 @@ function renderUpsell(p, upsell, opts = {}) {
3882
3922
  }
3883
3923
  function loadApichats() {
3884
3924
  try {
3885
- const list = JSON.parse(fs8.readFileSync(apichatsPath(), "utf-8"));
3925
+ const list = JSON.parse(fs9.readFileSync(apichatsPath(), "utf-8"));
3886
3926
  return Array.isArray(list) ? list : [];
3887
3927
  } catch {
3888
3928
  return [];
3889
3929
  }
3890
3930
  }
3891
3931
  function writeApichats(list) {
3892
- fs8.mkdirSync(getApiblazeDir(), { recursive: true });
3893
- fs8.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
3932
+ fs9.mkdirSync(getApiblazeDir(), { recursive: true });
3933
+ fs9.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
3894
3934
  try {
3895
- fs8.chmodSync(apichatsPath(), 384);
3935
+ fs9.chmodSync(apichatsPath(), 384);
3896
3936
  } catch {
3897
3937
  }
3898
3938
  }
@@ -4108,15 +4148,15 @@ function discoverLocalSpecs() {
4108
4148
  const found = [];
4109
4149
  for (const n of known) {
4110
4150
  try {
4111
- if (fs8.statSync(path8.join(cwd, n)).isFile()) found.push(n);
4151
+ if (fs9.statSync(path9.join(cwd, n)).isFile()) found.push(n);
4112
4152
  } catch {
4113
4153
  }
4114
4154
  }
4115
4155
  try {
4116
- const files = fs8.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
4156
+ const files = fs9.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
4117
4157
  for (const f of files.slice(0, 60)) {
4118
4158
  try {
4119
- const head = fs8.readFileSync(path8.join(cwd, f), "utf-8").slice(0, 4e3);
4159
+ const head = fs9.readFileSync(path9.join(cwd, f), "utf-8").slice(0, 4e3);
4120
4160
  if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
4121
4161
  found.push(f);
4122
4162
  }
@@ -4763,12 +4803,12 @@ async function runApichat(opts) {
4763
4803
  if (mcpUrl2 && await maybeInstallExternalCli(p, opts)) return;
4764
4804
  await startChat(p, [], opts);
4765
4805
  }
4766
- var fs8, path8, crypto2, import_child_process3, import_chalk15, import_ora2, import_yaml3, GENERATOR_HINT, revealAuth, apichatsPath;
4806
+ var fs9, path9, crypto2, import_child_process3, import_chalk15, import_ora2, import_yaml3, GENERATOR_HINT, revealAuth, apichatsPath;
4767
4807
  var init_apichat = __esm({
4768
4808
  "src/commands/apichat.ts"() {
4769
4809
  "use strict";
4770
- fs8 = __toESM(require("fs"));
4771
- path8 = __toESM(require("path"));
4810
+ fs9 = __toESM(require("fs"));
4811
+ path9 = __toESM(require("path"));
4772
4812
  crypto2 = __toESM(require("crypto"));
4773
4813
  import_child_process3 = require("child_process");
4774
4814
  import_chalk15 = __toESM(require("chalk"));
@@ -4792,7 +4832,7 @@ var init_apichat = __esm({
4792
4832
  init_consumer_auth();
4793
4833
  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>";
4794
4834
  revealAuth = false;
4795
- apichatsPath = () => path8.join(getApiblazeDir(), "apichats.json");
4835
+ apichatsPath = () => path9.join(getApiblazeDir(), "apichats.json");
4796
4836
  }
4797
4837
  });
4798
4838
 
@@ -5247,7 +5287,7 @@ async function loadOpenapiSource(ref) {
5247
5287
  text = await res.text();
5248
5288
  } else {
5249
5289
  try {
5250
- text = import_fs2.default.readFileSync(ref, "utf8");
5290
+ text = import_fs3.default.readFileSync(ref, "utf8");
5251
5291
  } catch {
5252
5292
  fail4(`Cannot read the OpenAPI spec "${ref}" \u2014 it is not a readable file, and not an http(s) URL.`);
5253
5293
  }
@@ -5760,7 +5800,18 @@ async function printDevOutcome(o, port) {
5760
5800
  console.log(BRAND === "mcpblaze" ? mcpLine : restLine);
5761
5801
  console.log(BRAND === "mcpblaze" ? restLine : mcpLine);
5762
5802
  if (o.auth.doors.includes("api_key") && o.apiKey) {
5763
- console.log(` ${import_chalk19.default.dim("Key ")} ${import_chalk19.default.bold.green(o.apiKey)} ${import_chalk19.default.dim("shown once \xB7 your server sends it as X-API-Key, naming the user in X-End-User-Id")}`);
5803
+ const saved = process.stdout.isTTY ? null : saveEnvLocal(process.cwd(), {
5804
+ APIBLAZE_URL: rest,
5805
+ APIBLAZE_MCP_URL: agents,
5806
+ APIBLAZE_SERVER_KEY: o.apiKey,
5807
+ ...o.tenant ? { APIBLAZE_TENANT: o.tenant } : {}
5808
+ });
5809
+ if (saved?.ok) {
5810
+ console.log(` ${import_chalk19.default.dim("Key ")} saved to .env.local as APIBLAZE_SERVER_KEY, never printed ${import_chalk19.default.dim("your server sends it as X-API-Key, naming the user in X-End-User-Id")}`);
5811
+ if (saved.gitignoreWarning) console.log(import_chalk19.default.yellow(" .env.local is not in .gitignore: add it before you commit."));
5812
+ } else {
5813
+ console.log(` ${import_chalk19.default.dim("Key ")} ${import_chalk19.default.bold.green(o.apiKey)} ${import_chalk19.default.dim("shown once \xB7 your server sends it as X-API-Key, naming the user in X-End-User-Id")}`);
5814
+ }
5764
5815
  }
5765
5816
  console.log(` ${import_chalk19.default.dim("Included")} ${["API keys", "sign-in", lockedPhrase, "rate limits", adminBit].filter(Boolean).join(import_chalk19.default.dim(" \xB7 "))}`);
5766
5817
  if (isVerbose() && !o.adminEmail) console.log(import_chalk19.default.dim(` No admin yet: npx ${B.cli}@latest admins add you@example.com --tenant ${o.tenant ?? "<tenant>"}`));
@@ -6136,7 +6187,7 @@ Create an ${B.thing}`));
6136
6187
  if (opts.config) {
6137
6188
  let raw = "";
6138
6189
  try {
6139
- raw = import_fs2.default.readFileSync(opts.config, "utf8");
6190
+ raw = import_fs3.default.readFileSync(opts.config, "utf8");
6140
6191
  } catch {
6141
6192
  fail4(`Cannot read --config file: ${opts.config}`);
6142
6193
  }
@@ -6303,11 +6354,12 @@ Create an ${B.thing}`));
6303
6354
  claim: { claimCode: result.claim_code || cred?.claim_code, claimUrl: result.claim_url }
6304
6355
  });
6305
6356
  }
6306
- var import_fs2, import_yaml4, import_chalk19, import_ora6, VALID_AUTH, BOTH_DOORS, MANAGED_GITHUB, API_KEY, OPEN, PROVIDERS, ISSUER_PROVIDERS, SECRET_REQUIRED_TYPES, OAUTH_SHAPES, EMAIL_RE, AGENT_NAME;
6357
+ var import_fs3, import_yaml4, import_chalk19, import_ora6, VALID_AUTH, BOTH_DOORS, MANAGED_GITHUB, API_KEY, OPEN, PROVIDERS, ISSUER_PROVIDERS, SECRET_REQUIRED_TYPES, OAUTH_SHAPES, EMAIL_RE, AGENT_NAME;
6307
6358
  var init_create = __esm({
6308
6359
  "src/commands/create.ts"() {
6309
6360
  "use strict";
6310
- import_fs2 = __toESM(require("fs"));
6361
+ import_fs3 = __toESM(require("fs"));
6362
+ init_env_local();
6311
6363
  init_tenant_choice();
6312
6364
  init_random_name();
6313
6365
  init_apichat();
@@ -6678,24 +6730,24 @@ ready.then((c) => {
6678
6730
 
6679
6731
  // src/lib/log-view.ts
6680
6732
  function statusColor(status2) {
6681
- if (status2 < 300) return import_chalk25.default.green;
6682
- if (status2 < 400) return import_chalk25.default.blue;
6683
- if (status2 < 500) return import_chalk25.default.yellow;
6684
- return import_chalk25.default.red;
6733
+ if (status2 < 300) return import_chalk22.default.green;
6734
+ if (status2 < 400) return import_chalk22.default.blue;
6735
+ if (status2 < 500) return import_chalk22.default.yellow;
6736
+ return import_chalk22.default.red;
6685
6737
  }
6686
6738
  function methodColor(method) {
6687
6739
  switch ((method || "").toUpperCase()) {
6688
6740
  case "GET":
6689
- return import_chalk25.default.green;
6741
+ return import_chalk22.default.green;
6690
6742
  case "POST":
6691
- return import_chalk25.default.blue;
6743
+ return import_chalk22.default.blue;
6692
6744
  case "PUT":
6693
6745
  case "PATCH":
6694
- return import_chalk25.default.yellow;
6746
+ return import_chalk22.default.yellow;
6695
6747
  case "DELETE":
6696
- return import_chalk25.default.red;
6748
+ return import_chalk22.default.red;
6697
6749
  default:
6698
- return import_chalk25.default.white;
6750
+ return import_chalk22.default.white;
6699
6751
  }
6700
6752
  }
6701
6753
  function clock(ts2) {
@@ -6706,12 +6758,12 @@ function routeCell(route) {
6706
6758
  return (r.length > ROUTE_W ? `\u2026${r.slice(-(ROUTE_W - 1))}` : r).padEnd(ROUTE_W);
6707
6759
  }
6708
6760
  function formatRow(r) {
6709
- const time = import_chalk25.default.dim(clock(r.ts));
6761
+ const time = import_chalk22.default.dim(clock(r.ts));
6710
6762
  const method = methodColor(r.method)(String(r.method || "\u2014").padEnd(6));
6711
6763
  const route = routeCell(String(r.route || "\u2014"));
6712
6764
  const status2 = statusColor(r.status)(String(r.status ?? "\u2014").padStart(3));
6713
- const ms2 = import_chalk25.default.dim(`${r.duration_ms}ms`.padStart(8));
6714
- const step = r.error_step ? import_chalk25.default.dim(` ${r.error_step}`) : "";
6765
+ const ms2 = import_chalk22.default.dim(`${r.duration_ms}ms`.padStart(8));
6766
+ const step = r.error_step ? import_chalk22.default.dim(` ${r.error_step}`) : "";
6715
6767
  return `${time} ${method} ${route} ${status2} ${ms2}${step}`;
6716
6768
  }
6717
6769
  function printRow(r, json) {
@@ -6765,13 +6817,13 @@ async function runTailSession(opts) {
6765
6817
  if (!held.length && !heldRowsDropped && !heldNotesDropped) return;
6766
6818
  const rows = held.filter((h) => h.kind === "row").length;
6767
6819
  const total = rows + heldRowsDropped;
6768
- if (total) console.log(import_chalk25.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
6820
+ if (total) console.log(import_chalk22.default.dim(`\u2500\u2500\u2500 ${total} request${total === 1 ? "" : "s"} arrived while paused \u2500\u2500\u2500`));
6769
6821
  for (const h of held) {
6770
6822
  if (h.kind === "row") printRow(h.row, opts.json);
6771
6823
  else console.error(h.msg);
6772
6824
  }
6773
6825
  if (heldRowsDropped || heldNotesDropped) {
6774
- console.log(import_chalk25.default.dim(`\u2500\u2500\u2500 ${heldRowsDropped} older one${heldRowsDropped === 1 ? "" : "s"} not shown (pause buffer full) \u2014 browse them with \`logs list\` \u2500\u2500\u2500`));
6826
+ console.log(import_chalk22.default.dim(`\u2500\u2500\u2500 ${heldRowsDropped} older one${heldRowsDropped === 1 ? "" : "s"} not shown (pause buffer full) \u2014 browse them with \`logs list\` \u2500\u2500\u2500`));
6775
6827
  }
6776
6828
  held.length = 0;
6777
6829
  heldRowsDropped = 0;
@@ -6785,7 +6837,7 @@ async function runTailSession(opts) {
6785
6837
  activeSocket?.close();
6786
6838
  } catch {
6787
6839
  }
6788
- if (!opts.json) console.log(import_chalk25.default.dim("\nStopped."));
6840
+ if (!opts.json) console.log(import_chalk22.default.dim("\nStopped."));
6789
6841
  process.exit(0);
6790
6842
  }
6791
6843
  function onKeypress(_s, key) {
@@ -6813,17 +6865,17 @@ async function runTailSession(opts) {
6813
6865
  async function openInspector() {
6814
6866
  if (paused) return;
6815
6867
  if (recent.length === 0) {
6816
- console.log(import_chalk25.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
6868
+ console.log(import_chalk22.default.dim(" nothing captured yet \u2014 waiting for a request\u2026"));
6817
6869
  return;
6818
6870
  }
6819
6871
  paused = true;
6820
6872
  unbindKeys();
6821
6873
  try {
6822
6874
  const { default: inquirer3 } = await import("inquirer");
6823
- console.log(import_chalk25.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
6875
+ console.log(import_chalk22.default.dim("\n\u2500\u2500\u2500 paused \xB7 still capturing in the background \u2500\u2500\u2500"));
6824
6876
  for (; ; ) {
6825
6877
  const choices = recent.slice(-TAIL_INSPECT_CHOICES).reverse().map((r) => ({ name: formatRow(r), value: r.request_id }));
6826
- choices.push({ name: import_chalk25.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
6878
+ choices.push({ name: import_chalk22.default.dim("\u2039resume tail\u203A"), value: "__resume__" });
6827
6879
  const { picked } = await inquirer3.prompt([{
6828
6880
  type: "list",
6829
6881
  name: "picked",
@@ -6834,12 +6886,12 @@ async function runTailSession(opts) {
6834
6886
  if (picked === "__resume__") break;
6835
6887
  const row = recent.find((r) => r.request_id === picked);
6836
6888
  if (row) printDetail(row, opts.detail);
6837
- else console.log(import_chalk25.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
6889
+ else console.log(import_chalk22.default.dim(" that request has scrolled out of the tail buffer \u2014 find it with `logs list`.\n"));
6838
6890
  }
6839
6891
  } finally {
6840
6892
  paused = false;
6841
6893
  flushHeld();
6842
- console.log(import_chalk25.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
6894
+ console.log(import_chalk22.default.dim("\u2500\u2500\u2500 resumed \u2500\u2500\u2500"));
6843
6895
  bindKeys();
6844
6896
  }
6845
6897
  }
@@ -6849,12 +6901,12 @@ async function runTailSession(opts) {
6849
6901
  if (reconnects >= MAX_RECONNECTS) {
6850
6902
  paused = false;
6851
6903
  flushHeld();
6852
- console.error(import_chalk25.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
6904
+ console.error(import_chalk22.default.red(`Connection lost \u2014 giving up after repeated reconnects${lastFailure ? ` (last error: ${lastFailure})` : ""}.`));
6853
6905
  process.exit(1);
6854
6906
  }
6855
6907
  const delay = Math.min(1e3 * 2 ** reconnects, 15e3);
6856
6908
  reconnects++;
6857
- diag(import_chalk25.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
6909
+ diag(import_chalk22.default.dim(`disconnected${lastFailure ? ` (${lastFailure})` : ""} \u2014 reconnecting in ${Math.round(delay / 1e3)}s\u2026`));
6858
6910
  setTimeout(() => {
6859
6911
  if (!closed) void connect();
6860
6912
  }, delay);
@@ -6865,7 +6917,7 @@ async function runTailSession(opts) {
6865
6917
  url = await opts.mintTicket();
6866
6918
  } catch (err) {
6867
6919
  if (!everConnected) {
6868
- console.error(import_chalk25.default.red(`Could not start tail: ${err.message}`));
6920
+ console.error(import_chalk22.default.red(`Could not start tail: ${err.message}`));
6869
6921
  process.exit(1);
6870
6922
  }
6871
6923
  lastFailure = `ticket mint failed: ${err.message}`;
@@ -6878,7 +6930,7 @@ async function runTailSession(opts) {
6878
6930
  } catch (err) {
6879
6931
  lastFailure = `bad tail URL: ${err.message}`;
6880
6932
  if (!everConnected) {
6881
- console.error(import_chalk25.default.red(`Could not start tail: ${lastFailure}`));
6933
+ console.error(import_chalk22.default.red(`Could not start tail: ${lastFailure}`));
6882
6934
  process.exit(1);
6883
6935
  }
6884
6936
  scheduleReconnect();
@@ -6893,9 +6945,9 @@ async function runTailSession(opts) {
6893
6945
  lastInbound = Date.now();
6894
6946
  lastFailure = "";
6895
6947
  if (isReconnect) {
6896
- diag(import_chalk25.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
6948
+ diag(import_chalk22.default.dim("reconnected \u2014 requests during the gap were not replayed (browse them with `logs list`)."));
6897
6949
  } else {
6898
- diag(import_chalk25.default.dim(interactive2 ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
6950
+ diag(import_chalk22.default.dim(interactive2 ? "[Enter] inspect a request \xB7 [Ctrl+C] stop\n" : "Ctrl+C to stop."));
6899
6951
  }
6900
6952
  bindKeys();
6901
6953
  pingTimer = setInterval(() => {
@@ -6922,7 +6974,7 @@ async function runTailSession(opts) {
6922
6974
  return;
6923
6975
  }
6924
6976
  if (frame?.type === "ceiling") {
6925
- diag(import_chalk25.default.yellow(
6977
+ diag(import_chalk22.default.yellow(
6926
6978
  `\u26A0 volume ceiling \u2014 ${Number(frame.dropped_this_hour) || 0} rows dropped this hour; errors keep streaming.`
6927
6979
  ));
6928
6980
  return;
@@ -6998,10 +7050,10 @@ async function fetchPage(fetchWindow3, params, notices) {
6998
7050
  return { rows, nextCursor: res.next_cursor ?? void 0 };
6999
7051
  }
7000
7052
  if (hop >= MAX_EMPTY_HOPS) {
7001
- console.error(import_chalk25.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
7053
+ console.error(import_chalk22.default.yellow("\u26A0 stopped after scanning a long empty span \u2014 older rows may still exist."));
7002
7054
  return { rows: [], nextCursor: res.next_cursor, fuseTripped: true };
7003
7055
  }
7004
- if (hop === 0) console.error(import_chalk25.default.dim("searching older history\u2026"));
7056
+ if (hop === 0) console.error(import_chalk22.default.dim("searching older history\u2026"));
7005
7057
  cursor = res.next_cursor;
7006
7058
  }
7007
7059
  }
@@ -7039,18 +7091,18 @@ async function runPlainList(opts, notices) {
7039
7091
  }
7040
7092
  }
7041
7093
  if (rows.length === 0) {
7042
- console.log(import_chalk25.default.dim(fuseTripped ? "Nothing found in the scanned span \u2014 older requests may still exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7094
+ console.log(import_chalk22.default.dim(fuseTripped ? "Nothing found in the scanned span \u2014 older requests may still exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7043
7095
  return;
7044
7096
  }
7045
7097
  const now = Date.now();
7046
7098
  for (const d of groupIntoDays(rows)) {
7047
- console.log(import_chalk25.default.bold(dayLabel(d.dayMs, now)));
7099
+ console.log(import_chalk22.default.bold(dayLabel(d.dayMs, now)));
7048
7100
  for (const r of d.rows) console.log(` ${formatRow(r)}`);
7049
7101
  console.log("");
7050
7102
  }
7051
7103
  const errs = rows.filter((r) => r.status >= 400).length;
7052
7104
  const tail = fuseTripped ? " \xB7 stopped early (long empty span) \u2014 older requests may still exist" : more ? " \xB7 showing the most recent \u2014 `--json` streams the rest" : "";
7053
- console.log(import_chalk25.default.dim(
7105
+ console.log(import_chalk22.default.dim(
7054
7106
  `${rows.length} request${rows.length === 1 ? "" : "s"} \xB7 ${errs} error${errs === 1 ? "" : "s"}${tail}`
7055
7107
  ));
7056
7108
  }
@@ -7069,7 +7121,7 @@ async function runHistoryBrowser(opts) {
7069
7121
  for (const r of page.rows) console.log(JSON.stringify(r));
7070
7122
  any = any || page.rows.length > 0;
7071
7123
  if (page.fuseTripped || !page.nextCursor) {
7072
- if (!any && !page.fuseTripped) console.error(import_chalk25.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7124
+ if (!any && !page.fuseTripped) console.error(import_chalk22.default.dim(opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7073
7125
  return;
7074
7126
  }
7075
7127
  cursor2 = page.nextCursor;
@@ -7090,7 +7142,7 @@ async function runHistoryBrowser(opts) {
7090
7142
  scanRows = first.rows;
7091
7143
  scanCursor = first.nextCursor;
7092
7144
  if (scanRows.length === 0) {
7093
- console.log(import_chalk25.default.dim(first.fuseTripped ? "Nothing found in the scanned span \u2014 older rows may exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7145
+ console.log(import_chalk22.default.dim(first.fuseTripped ? "Nothing found in the scanned span \u2014 older rows may exist." : opts.errorsOnly ? "No errors logged yet." : "No requests logged yet."));
7094
7146
  return;
7095
7147
  }
7096
7148
  }
@@ -7101,10 +7153,10 @@ async function runHistoryBrowser(opts) {
7101
7153
  const now2 = Date.now();
7102
7154
  const oldestKey = days[days.length - 1]?.key;
7103
7155
  const choices = days.map((d) => ({
7104
- name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk25.default.red(` ${d.errors} errors`) : ""}`,
7156
+ name: `${dayLabel(d.dayMs, now2).padEnd(12)} ${String(d.rows.length).padStart(4)}${(scanCursor !== void 0 || scanFloorUnknown) && d.key === oldestKey ? "+" : ""} requests${d.errors ? import_chalk22.default.red(` ${d.errors} errors`) : ""}`,
7105
7157
  value: d.key
7106
7158
  }));
7107
- if (scanCursor) choices.push({ name: import_chalk25.default.dim("\u2026 older days"), value: "__older__" });
7159
+ if (scanCursor) choices.push({ name: import_chalk22.default.dim("\u2026 older days"), value: "__older__" });
7108
7160
  const { chosenDay } = await inquirer3.prompt([{
7109
7161
  type: "list",
7110
7162
  name: "chosenDay",
@@ -7124,7 +7176,7 @@ async function runHistoryBrowser(opts) {
7124
7176
  scanCursor = void 0;
7125
7177
  } else {
7126
7178
  scanCursor = more.nextCursor;
7127
- if (more.rows.length === 0 && !scanCursor) console.log(import_chalk25.default.dim("That was everything \u2014 no older requests."));
7179
+ if (more.rows.length === 0 && !scanCursor) console.log(import_chalk22.default.dim("That was everything \u2014 no older requests."));
7128
7180
  }
7129
7181
  continue;
7130
7182
  }
@@ -7146,11 +7198,11 @@ async function runHistoryBrowser(opts) {
7146
7198
  cursor
7147
7199
  }, notices);
7148
7200
  if (page.rows.length === 0 && pageRows.length === 0) {
7149
- console.log(import_chalk25.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
7201
+ console.log(import_chalk22.default.dim(page.fuseTripped ? "Stopped scanning this day (long empty span) \u2014 older rows in it may exist." : "No requests for this day."));
7150
7202
  return;
7151
7203
  }
7152
7204
  if (page.rows.length === 0) {
7153
- console.log(import_chalk25.default.dim(page.fuseTripped ? "Stopped scanning further into this day (long empty span) \u2014 older rows in it may exist." : "That was everything for this day."));
7205
+ console.log(import_chalk22.default.dim(page.fuseTripped ? "Stopped scanning further into this day (long empty span) \u2014 older rows in it may exist." : "That was everything for this day."));
7154
7206
  moreCursor = void 0;
7155
7207
  } else {
7156
7208
  pageRows = page.rows;
@@ -7161,8 +7213,8 @@ async function runHistoryBrowser(opts) {
7161
7213
  name: formatRow(r),
7162
7214
  value: r.request_id
7163
7215
  }));
7164
- if (moreCursor) choices.push({ name: import_chalk25.default.dim("\u2026 load more"), value: "__more__" });
7165
- choices.push({ name: import_chalk25.default.dim("quit"), value: "__quit__" });
7216
+ if (moreCursor) choices.push({ name: import_chalk22.default.dim("\u2026 load more"), value: "__more__" });
7217
+ choices.push({ name: import_chalk22.default.dim("quit"), value: "__quit__" });
7166
7218
  for (; ; ) {
7167
7219
  const { picked } = await inquirer3.prompt([{
7168
7220
  type: "list",
@@ -7197,19 +7249,19 @@ function wrapCells(items, width) {
7197
7249
  ${PANE_INDENT}`);
7198
7250
  }
7199
7251
  function renderHeaders(h) {
7200
- if (!h) return import_chalk25.default.dim("\u2014");
7252
+ if (!h) return import_chalk22.default.dim("\u2014");
7201
7253
  const width = process.stdout.columns || 100;
7202
7254
  if (h.values) {
7203
7255
  const e = Object.entries(h.values);
7204
- if (!e.length) return import_chalk25.default.dim("none");
7256
+ if (!e.length) return import_chalk22.default.dim("none");
7205
7257
  const rank = (k) => {
7206
7258
  const i = HEADER_PRIORITY.indexOf(k.toLowerCase());
7207
7259
  return i === -1 ? NOISE_HEADER.test(k) ? 999 : 500 : i;
7208
7260
  };
7209
- return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk25.default.dim(k + ":")} ${v}`).join(`
7261
+ return e.sort((a, b) => rank(a[0]) - rank(b[0])).map(([k, v]) => `${import_chalk22.default.dim(k + ":")} ${v}`).join(`
7210
7262
  ${PANE_INDENT}`);
7211
7263
  }
7212
- if (!h.names?.length) return import_chalk25.default.dim("none");
7264
+ if (!h.names?.length) return import_chalk22.default.dim("none");
7213
7265
  const names = [...h.names];
7214
7266
  const noise = names.filter((n) => NOISE_HEADER.test(n));
7215
7267
  const shown = names.filter((n) => !NOISE_HEADER.test(n)).sort((a, b) => {
@@ -7217,77 +7269,77 @@ ${PANE_INDENT}`);
7217
7269
  const ib = HEADER_PRIORITY.indexOf(b.toLowerCase());
7218
7270
  return (ia === -1 ? 500 : ia) - (ib === -1 ? 500 : ib) || a.localeCompare(b);
7219
7271
  });
7220
- const body = shown.length ? wrapCells(shown, width) : import_chalk25.default.dim("(all forwarding headers)");
7272
+ const body = shown.length ? wrapCells(shown, width) : import_chalk22.default.dim("(all forwarding headers)");
7221
7273
  const tail = noise.length ? `
7222
- ${PANE_INDENT}${import_chalk25.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
7223
- return `${body}${tail} ${import_chalk25.default.dim("(names only)")}`;
7274
+ ${PANE_INDENT}${import_chalk22.default.dim(`+ ${noise.length} forwarding/client-hint header${noise.length === 1 ? "" : "s"} (cf-*, sec-*)`)}` : "";
7275
+ return `${body}${tail} ${import_chalk22.default.dim("(names only)")}`;
7224
7276
  }
7225
7277
  function renderBody(b) {
7226
- if (!b) return import_chalk25.default.dim("\u2014");
7278
+ if (!b) return import_chalk22.default.dim("\u2014");
7227
7279
  const meta = [b.content_type, b.size != null ? `${b.size}B` : null, b.truncated ? "truncated" : null].filter(Boolean).join(" \xB7 ");
7228
- if (b.not_captured) return import_chalk25.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
7280
+ if (b.not_captured) return import_chalk22.default.dim(`not captured (${b.not_captured})${meta ? " \xB7 " + meta : ""}`);
7229
7281
  const isShape = b.value === void 0 && b.shape !== void 0;
7230
7282
  const content = b.value !== void 0 ? b.value : b.shape;
7231
7283
  if (content === null || content === void 0 || content === "" || content === "empty") {
7232
- return import_chalk25.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
7284
+ return import_chalk22.default.dim(`empty${meta ? " \xB7 " + meta : ""}`);
7233
7285
  }
7234
7286
  const text = typeof content === "string" ? content : JSON.stringify(content, null, 2);
7235
- const head = isShape ? import_chalk25.default.dim("shape only (values not captured)\n ") : "";
7287
+ const head = isShape ? import_chalk22.default.dim("shape only (values not captured)\n ") : "";
7236
7288
  return head + text.split("\n").join("\n ");
7237
7289
  }
7238
7290
  function pane(title, tone, view, empty) {
7239
7291
  console.log(` ${tone(title)}`);
7240
7292
  if (empty !== void 0) {
7241
- console.log(` ${import_chalk25.default.dim(empty)}`);
7293
+ console.log(` ${import_chalk22.default.dim(empty)}`);
7242
7294
  return;
7243
7295
  }
7244
7296
  const v = view ?? {};
7245
- if (v.url) console.log(` ${import_chalk25.default.dim("target")} ${v.url}`);
7297
+ if (v.url) console.log(` ${import_chalk22.default.dim("target")} ${v.url}`);
7246
7298
  if (v.status != null) {
7247
- console.log(` ${import_chalk25.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk25.default.dim(" " + v.status_text) : ""}`);
7299
+ console.log(` ${import_chalk22.default.dim("status")} ${statusColor(v.status)(String(v.status))}${v.status_text ? import_chalk22.default.dim(" " + v.status_text) : ""}`);
7248
7300
  }
7249
- if (v.raw_path) console.log(` ${import_chalk25.default.dim("path")} ${v.raw_path}`);
7301
+ if (v.raw_path) console.log(` ${import_chalk22.default.dim("path")} ${v.raw_path}`);
7250
7302
  if (v.query && Object.keys(v.query).length) {
7251
- console.log(` ${import_chalk25.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
7303
+ console.log(` ${import_chalk22.default.dim("query")} ${Object.entries(v.query).map(([k, q]) => `${k}=${q}`).join(" ")}`);
7252
7304
  }
7253
- if (v.jwt !== void 0) console.log(` ${import_chalk25.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
7254
- console.log(` ${import_chalk25.default.dim("headers")} ${renderHeaders(v.headers)}`);
7255
- console.log(` ${import_chalk25.default.dim("body")} ${renderBody(v.body)}`);
7305
+ if (v.jwt !== void 0) console.log(` ${import_chalk22.default.dim("jwt")} ${JSON.stringify(v.jwt)}`);
7306
+ console.log(` ${import_chalk22.default.dim("headers")} ${renderHeaders(v.headers)}`);
7307
+ console.log(` ${import_chalk22.default.dim("body")} ${renderBody(v.body)}`);
7256
7308
  }
7257
7309
  function printDetail(r, mode = "producer") {
7258
7310
  const v = r.views;
7259
7311
  console.log("");
7260
- console.log(import_chalk25.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk25.default.dim(` ${r.duration_ms}ms`));
7312
+ console.log(import_chalk22.default.bold(`${r.method} ${r.route}`) + " " + statusColor(r.status)(String(r.status)) + import_chalk22.default.dim(` ${r.duration_ms}ms`));
7261
7313
  const cost = typeof r.cost_cents === "number" && r.cost_cents > 0 ? ` \xB7 ${r.cost_cents}\xA2` : "";
7262
- console.log(import_chalk25.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
7314
+ console.log(import_chalk22.default.dim(` request_id ${r.request_id}${r.environment ? ` \xB7 env ${r.environment}` : ""}${r.consumer_id ? ` \xB7 consumer ${r.consumer_id}` : ""}${cost}`));
7263
7315
  console.log("");
7264
7316
  if (!v) {
7265
- console.log(import_chalk25.default.dim(" No view detail captured for this request.\n"));
7317
+ console.log(import_chalk22.default.dim(" No view detail captured for this request.\n"));
7266
7318
  return;
7267
7319
  }
7268
- pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk25.default.cyan, v.request);
7320
+ pane("Request (before) \u2014 client \u2192 APIblaze", import_chalk22.default.cyan, v.request);
7269
7321
  if (mode === "producer") {
7270
7322
  pane(
7271
7323
  "Request (after) \u2014 APIblaze \u2192 upstream",
7272
- import_chalk25.default.cyan,
7324
+ import_chalk22.default.cyan,
7273
7325
  v.forwarded_request,
7274
7326
  v.forwarded_request ? void 0 : "Request did not reach an upstream."
7275
7327
  );
7276
7328
  pane(
7277
7329
  "Response (before) \u2014 upstream \u2192 APIblaze",
7278
- import_chalk25.default.magenta,
7330
+ import_chalk22.default.magenta,
7279
7331
  v.upstream_response,
7280
7332
  v.upstream_response ? void 0 : "No upstream response."
7281
7333
  );
7282
7334
  }
7283
- pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk25.default.magenta, v.response);
7335
+ pane("Response (after) \u2014 APIblaze \u2192 client", import_chalk22.default.magenta, v.response);
7284
7336
  console.log("");
7285
7337
  }
7286
- var import_chalk25, readline, ROUTE_W, WindowNotices, MAX_RECONNECTS, PING_MS, LIVENESS_MS, STABLE_MS, SEEN_CAP, TAIL_INSPECT_BUFFER, TAIL_INSPECT_CHOICES, SCAN_LIMIT, PAGE_LIMIT2, JSON_PAGE_LIMIT, MAX_EMPTY_HOPS, epipeGuarded, PLAIN_MAX_ROWS, NOISE_HEADER, HEADER_PRIORITY, PANE_INDENT;
7338
+ var import_chalk22, readline, ROUTE_W, WindowNotices, MAX_RECONNECTS, PING_MS, LIVENESS_MS, STABLE_MS, SEEN_CAP, TAIL_INSPECT_BUFFER, TAIL_INSPECT_CHOICES, SCAN_LIMIT, PAGE_LIMIT2, JSON_PAGE_LIMIT, MAX_EMPTY_HOPS, epipeGuarded, PLAIN_MAX_ROWS, NOISE_HEADER, HEADER_PRIORITY, PANE_INDENT;
7287
7339
  var init_log_view = __esm({
7288
7340
  "src/lib/log-view.ts"() {
7289
7341
  "use strict";
7290
- import_chalk25 = __toESM(require("chalk"));
7342
+ import_chalk22 = __toESM(require("chalk"));
7291
7343
  readline = __toESM(require("readline"));
7292
7344
  ROUTE_W = 34;
7293
7345
  WindowNotices = class {
@@ -7299,15 +7351,15 @@ var init_log_view = __esm({
7299
7351
  note(res) {
7300
7352
  if (res.history_unavailable && !this.saidUnavailable) {
7301
7353
  this.saidUnavailable = true;
7302
- console.error(import_chalk25.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
7354
+ console.error(import_chalk22.default.yellow("\u26A0 log history is unreachable right now \u2014 showing the live buffer only, older requests exist but cannot be read."));
7303
7355
  }
7304
7356
  if ((res.history_markers?.length ?? 0) > 0 && !this.saidGap) {
7305
7357
  this.saidGap = true;
7306
- console.error(import_chalk25.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
7358
+ console.error(import_chalk22.default.yellow("\u26A0 this range includes a volume-ceiling gap \u2014 some rows were dropped at intake and are gone; counts are incomplete."));
7307
7359
  }
7308
7360
  if ((res.dropped_this_hour ?? 0) > 0 && !this.saidDropped) {
7309
7361
  this.saidDropped = true;
7310
- console.error(import_chalk25.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
7362
+ console.error(import_chalk22.default.yellow(`\u26A0 volume ceiling \u2014 ${res.dropped_this_hour} rows dropped this hour; errors keep logging.`));
7311
7363
  }
7312
7364
  }
7313
7365
  };
@@ -7394,7 +7446,7 @@ async function resolve(projectArg, versionArg, opts) {
7394
7446
  return { projectId, projectName: match.projectName, version: version2, tenant: tenant2 };
7395
7447
  }
7396
7448
  function otherPlaneHint() {
7397
- if (loadConsumer()) console.log(import_chalk26.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
7449
+ if (loadConsumer()) console.log(import_chalk23.default.dim(" \u2039consumer view: apiblaze consumer logs\u203A"));
7398
7450
  }
7399
7451
  function fetchWindow(projectId, version2, tenant2) {
7400
7452
  return (params) => {
@@ -7413,8 +7465,8 @@ function fetchWindow(projectId, version2, tenant2) {
7413
7465
  async function runLogsTail(projectArg, versionArg, opts) {
7414
7466
  const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
7415
7467
  if (!opts.json) {
7416
- console.log(import_chalk26.default.bold("API Producer logs") + import_chalk26.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
7417
- console.log(import_chalk26.default.dim("every caller's requests, with cost and upstream detail"));
7468
+ console.log(import_chalk23.default.bold("API Producer logs") + import_chalk23.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
7469
+ console.log(import_chalk23.default.dim("every caller's requests, with cost and upstream detail"));
7418
7470
  otherPlaneHint();
7419
7471
  }
7420
7472
  await runTailSession({
@@ -7435,8 +7487,8 @@ async function runLogsTail(projectArg, versionArg, opts) {
7435
7487
  async function runLogsList(projectArg, versionArg, opts) {
7436
7488
  const { projectId, projectName, version: version2, tenant: tenant2 } = await resolve(projectArg, versionArg, opts);
7437
7489
  if (!opts.json) {
7438
- console.log(import_chalk26.default.bold("API Producer logs") + import_chalk26.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
7439
- console.log(import_chalk26.default.dim("every caller's requests, with cost and upstream detail"));
7490
+ console.log(import_chalk23.default.bold("API Producer logs") + import_chalk23.default.dim(` \xB7 ${projectName}@${version2} \xB7 tenant ${tenant2}${opts.errorsOnly ? " \xB7 errors only" : ""}`));
7491
+ console.log(import_chalk23.default.dim("every caller's requests, with cost and upstream detail"));
7440
7492
  otherPlaneHint();
7441
7493
  console.log("");
7442
7494
  }
@@ -7448,11 +7500,11 @@ async function runLogsList(projectArg, versionArg, opts) {
7448
7500
  detail: "producer"
7449
7501
  });
7450
7502
  }
7451
- var import_chalk26, activeCaller, viaCaller;
7503
+ var import_chalk23, activeCaller, viaCaller;
7452
7504
  var init_logs = __esm({
7453
7505
  "src/commands/logs.ts"() {
7454
7506
  "use strict";
7455
- import_chalk26 = __toESM(require("chalk"));
7507
+ import_chalk23 = __toESM(require("chalk"));
7456
7508
  init_caller();
7457
7509
  init_auth();
7458
7510
  init_consumer_auth();
@@ -7482,7 +7534,7 @@ function demoName() {
7482
7534
  }
7483
7535
  function portFree(port) {
7484
7536
  return new Promise((resolve4) => {
7485
- const s = import_net.default.createServer();
7537
+ const s = import_net2.default.createServer();
7486
7538
  s.once("error", () => resolve4(false));
7487
7539
  s.once("listening", () => s.close(() => resolve4(true)));
7488
7540
  s.listen(port, "127.0.0.1");
@@ -7494,15 +7546,15 @@ async function freePort(from, avoid = []) {
7494
7546
  }
7495
7547
  function readRegistry() {
7496
7548
  try {
7497
- return JSON.parse(import_fs5.default.readFileSync(registryFile(), "utf8"));
7549
+ return JSON.parse(import_fs6.default.readFileSync(registryFile(), "utf8"));
7498
7550
  } catch {
7499
7551
  return [];
7500
7552
  }
7501
7553
  }
7502
7554
  function writeRegistry(rows) {
7503
7555
  try {
7504
- import_fs5.default.mkdirSync(import_path4.default.dirname(registryFile()), { recursive: true });
7505
- import_fs5.default.writeFileSync(registryFile(), JSON.stringify(rows));
7556
+ import_fs6.default.mkdirSync(import_path5.default.dirname(registryFile()), { recursive: true });
7557
+ import_fs6.default.writeFileSync(registryFile(), JSON.stringify(rows));
7506
7558
  } catch {
7507
7559
  }
7508
7560
  }
@@ -7511,7 +7563,7 @@ function registerDemo(dir, pid = 0) {
7511
7563
  }
7512
7564
  async function waitPorts(ports, ms2 = 1e4) {
7513
7565
  const listening = (port) => new Promise((resolve4) => {
7514
- const sock = import_net.default.connect({ port, host: "127.0.0.1" });
7566
+ const sock = import_net2.default.connect({ port, host: "127.0.0.1" });
7515
7567
  sock.once("connect", () => {
7516
7568
  sock.destroy();
7517
7569
  resolve4(true);
@@ -7546,8 +7598,8 @@ async function stopTunnels() {
7546
7598
  return stopped;
7547
7599
  }
7548
7600
  function appCtl(dir, arg) {
7549
- if (!import_fs5.default.existsSync(import_path4.default.join(dir, "start.js"))) return false;
7550
- const r = (0, import_child_process4.spawnSync)(process.execPath, [import_path4.default.join(dir, "start.js"), arg], { cwd: dir, stdio: "ignore", timeout: 15e3 });
7601
+ if (!import_fs6.default.existsSync(import_path5.default.join(dir, "start.js"))) return false;
7602
+ const r = (0, import_child_process4.spawnSync)(process.execPath, [import_path5.default.join(dir, "start.js"), arg], { cwd: dir, stdio: "ignore", timeout: 15e3 });
7551
7603
  return r.status === 0;
7552
7604
  }
7553
7605
  async function stopDemos() {
@@ -7558,47 +7610,47 @@ async function stopDemos() {
7558
7610
  return n;
7559
7611
  }
7560
7612
  function writeApp(dir) {
7561
- import_fs5.default.mkdirSync(dir, { recursive: true });
7562
- for (const entry of import_fs5.default.readdirSync(dir)) import_fs5.default.rmSync(import_path4.default.join(dir, entry), { recursive: true, force: true });
7613
+ import_fs6.default.mkdirSync(dir, { recursive: true });
7614
+ for (const entry of import_fs6.default.readdirSync(dir)) import_fs6.default.rmSync(import_path5.default.join(dir, entry), { recursive: true, force: true });
7563
7615
  for (const [rel, text] of Object.entries(DEMO_FILES)) {
7564
- const f = import_path4.default.join(dir, rel);
7565
- import_fs5.default.mkdirSync(import_path4.default.dirname(f), { recursive: true });
7566
- import_fs5.default.writeFileSync(f, text);
7616
+ const f = import_path5.default.join(dir, rel);
7617
+ import_fs6.default.mkdirSync(import_path5.default.dirname(f), { recursive: true });
7618
+ import_fs6.default.writeFileSync(f, text);
7567
7619
  }
7568
- import_fs5.default.writeFileSync(import_path4.default.join(dir, "package.json"), JSON.stringify({
7620
+ import_fs6.default.writeFileSync(import_path5.default.join(dir, "package.json"), JSON.stringify({
7569
7621
  name: "nino-pizza-demo",
7570
7622
  private: true,
7571
7623
  scripts: { start: "node start.js --background", stop: "node start.js --stop", restart: "node start.js --restart", dev: "node start.js" }
7572
7624
  }, null, 2) + "\n");
7573
- import_fs5.default.writeFileSync(import_path4.default.join(dir, ".gitignore"), ".env.local\n.demo/\n");
7574
- for (const [from, to] of [[import_path4.default.join(__dirname, "server", "index.js"), import_path4.default.join(dir, "lib", "apiblaze-server.js")], [import_path4.default.join(__dirname, "browser", "widgets.js"), import_path4.default.join(dir, "public", "apiblaze-widgets.js")]]) {
7575
- if (!import_fs5.default.existsSync(from)) continue;
7576
- import_fs5.default.mkdirSync(import_path4.default.dirname(to), { recursive: true });
7577
- import_fs5.default.copyFileSync(from, to);
7625
+ import_fs6.default.writeFileSync(import_path5.default.join(dir, ".gitignore"), ".env.local\n.demo/\n");
7626
+ for (const [from, to] of [[import_path5.default.join(__dirname, "server", "index.js"), import_path5.default.join(dir, "lib", "apiblaze-server.js")], [import_path5.default.join(__dirname, "browser", "widgets.js"), import_path5.default.join(dir, "public", "apiblaze-widgets.js")]]) {
7627
+ if (!import_fs6.default.existsSync(from)) continue;
7628
+ import_fs6.default.mkdirSync(import_path5.default.dirname(to), { recursive: true });
7629
+ import_fs6.default.copyFileSync(from, to);
7578
7630
  }
7579
7631
  }
7580
7632
  function saveState(dir, s) {
7581
- const d = import_path4.default.join(dir, STATE_DIR);
7582
- import_fs5.default.mkdirSync(d, { recursive: true });
7583
- import_fs5.default.writeFileSync(import_path4.default.join(d, "state.json"), JSON.stringify(s, null, 2), { mode: 384 });
7633
+ const d = import_path5.default.join(dir, STATE_DIR);
7634
+ import_fs6.default.mkdirSync(d, { recursive: true });
7635
+ import_fs6.default.writeFileSync(import_path5.default.join(d, "state.json"), JSON.stringify(s, null, 2), { mode: 384 });
7584
7636
  }
7585
7637
  function loadState(dir) {
7586
7638
  try {
7587
- return JSON.parse(import_fs5.default.readFileSync(import_path4.default.join(dir, STATE_DIR, "state.json"), "utf8"));
7639
+ return JSON.parse(import_fs6.default.readFileSync(import_path5.default.join(dir, STATE_DIR, "state.json"), "utf8"));
7588
7640
  } catch {
7589
7641
  return null;
7590
7642
  }
7591
7643
  }
7592
7644
  function writeEnv(dir, v) {
7593
7645
  const lines = Object.entries(v).map(([k, val]) => `${k}=${val}`);
7594
- import_fs5.default.writeFileSync(import_path4.default.join(dir, ".env.local"), `# Written by ${LATEST} demo. Server-side only: never commit or share.
7646
+ import_fs6.default.writeFileSync(import_path5.default.join(dir, ".env.local"), `# Written by ${LATEST} demo. Server-side only: never commit or share.
7595
7647
  ${lines.join("\n")}
7596
7648
  `, { mode: 384 });
7597
7649
  }
7598
7650
  function readEnvFile(file2) {
7599
7651
  const out = {};
7600
7652
  try {
7601
- for (const line of import_fs5.default.readFileSync(file2, "utf8").split("\n")) {
7653
+ for (const line of import_fs6.default.readFileSync(file2, "utf8").split("\n")) {
7602
7654
  const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
7603
7655
  if (m) out[m[1]] = m[2];
7604
7656
  }
@@ -7608,20 +7660,20 @@ function readEnvFile(file2) {
7608
7660
  }
7609
7661
  function demoDir() {
7610
7662
  const here = process.cwd();
7611
- if (import_fs5.default.existsSync(import_path4.default.join(here, STATE_DIR, "state.json"))) return here;
7612
- return import_path4.default.resolve(here, DIR_NAME);
7663
+ if (import_fs6.default.existsSync(import_path5.default.join(here, STATE_DIR, "state.json"))) return here;
7664
+ return import_path5.default.resolve(here, DIR_NAME);
7613
7665
  }
7614
7666
  function adoptWorkspace(cpKey, teamId, claimCode) {
7615
7667
  const prev = loadAnonCred();
7616
7668
  if (prev?.cp_key && prev.team_id && prev.team_id !== teamId) {
7617
7669
  try {
7618
- import_fs5.default.writeFileSync(import_path4.default.join(getApiblazeDir(), `anon.${prev.team_id}.json`), JSON.stringify(prev, null, 2), { mode: 384 });
7670
+ import_fs6.default.writeFileSync(import_path5.default.join(getApiblazeDir(), `anon.${prev.team_id}.json`), JSON.stringify(prev, null, 2), { mode: 384 });
7619
7671
  } catch {
7620
7672
  }
7621
7673
  }
7622
7674
  saveAnonCred(cpKey, teamId, claimCode);
7623
7675
  }
7624
- async function createProxy2(name, backendPort, openapi, tenantName) {
7676
+ async function createProxy2(name, backendPort2, openapi, tenantName) {
7625
7677
  const auth = resolveAuth({});
7626
7678
  const proposed = await proposeFromSpec(openapi).catch(() => null);
7627
7679
  const families = proposed?.status === 200 && Array.isArray(proposed.data?.families) ? proposed.data.families.filter((f) => !f.skipped_reason).map((f) => f.key) : [];
@@ -7631,7 +7683,7 @@ async function createProxy2(name, backendPort, openapi, tenantName) {
7631
7683
  auth_type: auth.auth,
7632
7684
  ...auth.bodyPatch,
7633
7685
  openapi,
7634
- environments: { dev: { target: `http://localhost:${backendPort}`, description: `the ${B.cli} demo on this machine` } },
7686
+ environments: { dev: { target: `http://localhost:${backendPort2}`, description: `the ${B.cli} demo on this machine` } },
7635
7687
  // Two customers of the same app, each with its own key: the proxy's own tenant is Nino
7636
7688
  // Pizza; the server adds Gino Pizza and returns its key.
7637
7689
  tenant: tenantName,
@@ -7674,12 +7726,12 @@ async function waitThrough(apiUrl, serverKey) {
7674
7726
  async function confirmStep(opts, question) {
7675
7727
  if (opts.yes || !interactive()) return true;
7676
7728
  console.log();
7677
- return ask(`${import_chalk27.default.bold("?")} ${question} ${import_chalk27.default.dim("(Y/n)")} `);
7729
+ return ask(`${import_chalk24.default.bold("?")} ${question} ${import_chalk24.default.dim("(Y/n)")} `);
7678
7730
  }
7679
7731
  async function busy(text, fn, minMs = 0) {
7680
7732
  if (!process.stdout.isTTY) return fn();
7681
7733
  const t = Date.now();
7682
- const spinner = (0, import_ora10.default)({ text: import_chalk27.default.dim(text), indent: 3, discardStdin: false }).start();
7734
+ const spinner = (0, import_ora9.default)({ text: import_chalk24.default.dim(text), indent: 3, discardStdin: false }).start();
7683
7735
  try {
7684
7736
  const out = await fn();
7685
7737
  const left = minMs - (Date.now() - t);
@@ -7707,12 +7759,12 @@ function openBrowser3(url) {
7707
7759
  }
7708
7760
  async function offerBrowserAndLogs(dir, opts) {
7709
7761
  if (opts.open === false || opts.yes || !interactive()) return;
7710
- const env = readEnvFile(import_path4.default.join(dir, ".env.local"));
7762
+ const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
7711
7763
  const url = `http://localhost:${env.APP_PORT}`;
7712
7764
  console.log();
7713
7765
  if (await ask(` Open ${url} in your browser? (Y/n) `)) openBrowser3(url);
7714
7766
  if (await ask(` Show the requests reaching ${B.product} here, live? (Y/n) `)) {
7715
- console.log(import_chalk27.default.dim(` $ ${logsCommand(env)}`));
7767
+ console.log(import_chalk24.default.dim(` $ ${logsCommand(env)}`));
7716
7768
  const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
7717
7769
  await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
7718
7770
  }
@@ -7742,13 +7794,13 @@ function routesOf(openapi) {
7742
7794
  return out;
7743
7795
  }
7744
7796
  async function runDemo(opts = {}) {
7745
- if (opts.serve) return serveTunnel(import_path4.default.resolve(opts.serve));
7797
+ if (opts.serve) return serveTunnel(import_path5.default.resolve(opts.serve));
7746
7798
  try {
7747
7799
  await startDemo(opts);
7748
7800
  } catch (err) {
7749
7801
  const e = err instanceof DemoError ? err : new DemoError(`The demo could not start (${err.message}).`, `Run again: ${LATEST} demo`);
7750
7802
  await stopDemos().catch(() => 0);
7751
- console.log(`${import_chalk27.default.red("\u2717")} ${e.message}`);
7803
+ console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
7752
7804
  console.log(` ${e.fix}`);
7753
7805
  process.exit(1);
7754
7806
  }
@@ -7769,25 +7821,25 @@ function printTable(rows) {
7769
7821
  if (line) out.push(line);
7770
7822
  return out;
7771
7823
  };
7772
- const head = (l, r) => console.log(` ${import_chalk27.default.dim(l.padEnd(leftW))} ${import_chalk27.default.dim(r)}`);
7824
+ const head = (l, r) => console.log(` ${import_chalk24.default.dim(l.padEnd(leftW))} ${import_chalk24.default.dim(r)}`);
7773
7825
  head(`What ${B.product} set up`, "The command that does the same");
7774
7826
  head("\u2500".repeat(leftW), "\u2500".repeat(Math.min(rightW, 60)));
7775
7827
  for (const r of rows) {
7776
- const left = `${import_chalk27.default.green("\u2713")} ${import_chalk27.default.bold(r.label.padEnd(17))} ${r.what}`;
7828
+ const left = `${import_chalk24.default.green("\u2713")} ${import_chalk24.default.bold(r.label.padEnd(17))} ${r.what}`;
7777
7829
  const leftPlain = `\u2713 ${r.label.padEnd(17)} ${r.what}`;
7778
7830
  const right = r.cmds.length ? r.cmds.flatMap(wrap2) : ["\u2014"];
7779
- console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${import_chalk27.default.cyan(right[0])}`);
7780
- for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${import_chalk27.default.cyan(extra)}`);
7831
+ console.log(` ${left}${" ".repeat(Math.max(0, leftW - leftPlain.length))} ${import_chalk24.default.cyan(right[0])}`);
7832
+ for (const extra of right.slice(1)) console.log(` ${" ".repeat(leftW)} ${import_chalk24.default.cyan(extra)}`);
7781
7833
  }
7782
7834
  }
7783
7835
  async function startDemo(opts) {
7784
7836
  const t0 = Date.now();
7785
7837
  const here = process.cwd();
7786
- const dir = import_fs5.default.existsSync(import_path4.default.join(here, STATE_DIR, "state.json")) ? here : import_path4.default.resolve(here, DIR_NAME);
7787
- const rel = import_path4.default.relative(here, dir) || ".";
7838
+ const dir = import_fs6.default.existsSync(import_path5.default.join(here, STATE_DIR, "state.json")) ? here : import_path5.default.resolve(here, DIR_NAME);
7839
+ const rel = import_path5.default.relative(here, dir) || ".";
7788
7840
  const cdDir = rel === "." ? "" : `cd ${rel} && `;
7789
- const shell = (c, note = "") => console.log(` ${import_chalk27.default.dim("$")} ${import_chalk27.default.cyan(c)}${note ? ` ${import_chalk27.default.dim(note)}` : ""}`);
7790
- const ok = (text, note = "") => console.log(` ${import_chalk27.default.green("\u2713")} ${text}${note ? ` ${import_chalk27.default.dim(note)}` : ""}`);
7841
+ const shell = (c, note = "") => console.log(` ${import_chalk24.default.dim("$")} ${import_chalk24.default.cyan(c)}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
7842
+ const ok = (text, note = "") => console.log(` ${import_chalk24.default.green("\u2713")} ${text}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
7791
7843
  const stopHere = (lines) => {
7792
7844
  console.log();
7793
7845
  for (const l of lines) console.log(` ${l}`);
@@ -7795,42 +7847,42 @@ async function startDemo(opts) {
7795
7847
  await stopDemos();
7796
7848
  writeApp(dir);
7797
7849
  const appPort = await freePort(3e3);
7798
- const backendPort = await freePort(3001, [appPort]);
7799
- const directPort = await freePort(3002, [appPort, backendPort]);
7850
+ const backendPort2 = await freePort(3001, [appPort]);
7851
+ const directPort = await freePort(3002, [appPort, backendPort2]);
7800
7852
  const name = demoName();
7801
7853
  const ninoTenant = `ninopizza${import_crypto.default.randomBytes(2).toString("hex")}`;
7802
7854
  const backendSecret = import_crypto.default.randomBytes(18).toString("base64url");
7803
7855
  const openapi = DEMO_FILES["openapi.yaml"];
7804
7856
  const L = (c) => `${LATEST} ${c}`;
7805
- writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT: backendPort, DIRECT_PORT: directPort });
7857
+ writeEnv(dir, { BACKEND_SECRET: backendSecret, DEMO_NINO_TENANT: ninoTenant, APP_PORT: appPort, BACKEND_PORT: backendPort2, DIRECT_PORT: directPort });
7806
7858
  console.log();
7807
- console.log(import_chalk27.default.bold("1. The app: ResiResi, a table booking app for Tenants (Nino and Gino Pizzas) to allow end-users to book tables in their restaurants") + import_chalk27.default.dim(` (no ${B.product} yet)`));
7859
+ console.log(import_chalk24.default.bold("1. The app: ResiResi, a table booking app for Tenants (Nino and Gino Pizzas) to allow end-users to book tables in their restaurants") + import_chalk24.default.dim(` (no ${B.product} yet)`));
7808
7860
  console.log(` Written to ${rel}. Its routes, from openapi.yaml:`);
7809
- for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk27.default.dim(r.summary)}`);
7810
- console.log(import_chalk27.default.dim(" No login, no API keys, no permission checks in its code. It only checks one shared backend secret (in .env.local)."));
7861
+ for (const r of routesOf(openapi)) console.log(` ${r.method.padEnd(7)}${r.path.padEnd(21)}${import_chalk24.default.dim(r.summary)}`);
7862
+ console.log(import_chalk24.default.dim(" No login, no API keys, no permission checks in its code. It only checks one shared backend secret (in .env.local)."));
7811
7863
  shell(`${cdDir}npm run start`, "npm run stop \xB7 npm run restart");
7812
7864
  if (!appCtl(dir, "--background")) throw new DemoError("The ResiResi app could not start.", `Run again: ${LATEST} demo`);
7813
7865
  registerDemo(dir);
7814
- const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort, directPort]), 1800);
7815
- if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path4.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`);
7816
- ok(`${import_chalk27.default.bold("Reservation backend")}: localhost:${backendPort}`, "unprotected");
7817
- ok(`${import_chalk27.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk27.default.bold(`http://localhost:${directPort}`)}`);
7866
+ const appUp = await busy("Starting the reservation backend and its front-end\u2026", () => waitPorts([backendPort2, directPort]), 1800);
7867
+ if (!appUp) throw new DemoError(`The ResiResi app did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Run again: ${LATEST} demo`);
7868
+ ok(`${import_chalk24.default.bold("Reservation backend")}: localhost:${backendPort2}`, "unprotected");
7869
+ ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, going directly to the reservation backend: ${import_chalk24.default.bold(`http://localhost:${directPort}`)}`);
7818
7870
  console.log(" Book a table directly, the way the unprotected front-end does:");
7819
- console.log(import_chalk27.default.cyan(` curl -X POST http://localhost:${backendPort}/reservations \\`));
7820
- console.log(import_chalk27.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path4.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
7821
- console.log(import_chalk27.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
7822
- console.log(import_chalk27.default.cyan(` -H "content-type: application/json" -d '{"time":"19:30","guests":2}'`));
7871
+ console.log(import_chalk24.default.cyan(` curl -X POST http://localhost:${backendPort2}/reservations \\`));
7872
+ console.log(import_chalk24.default.cyan(` -H "x-target-api-key: $(grep BACKEND_SECRET ${import_path5.default.join(rel, ".env.local")} | cut -d= -f2)" \\`));
7873
+ console.log(import_chalk24.default.cyan(` -H "x-abz-user-id: ana" -H "x-abz-tenant-id: ${ninoTenant}" \\`));
7874
+ console.log(import_chalk24.default.cyan(` -H "content-type: application/json" -d '{"time":"19:30","guests":2}'`));
7823
7875
  if (!await confirmStep(opts, `Ok to put an ${B.product} proxy and MCP in front of the reservation backend?`)) {
7824
7876
  return stopHere([
7825
- `Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk27.default.dim(`(${cdDir}npm run stop stops it)`)}`,
7877
+ `Stopped before ${B.product}. ResiResi keeps running on this laptop: http://localhost:${directPort} ${import_chalk24.default.dim(`(${cdDir}npm run stop stops it)`)}`,
7826
7878
  `Run the whole demo again any time: ${LATEST} demo`
7827
7879
  ]);
7828
7880
  }
7829
7881
  console.log();
7830
- console.log(import_chalk27.default.bold(`2. ${B.product}, in front of it`));
7882
+ console.log(import_chalk24.default.bold(`2. ${B.product}, in front of it`));
7831
7883
  const tCreate = Date.now();
7832
7884
  const setup = await busy(`Creating the proxy, its tenants, admins, rules and limits\u2026`, async () => {
7833
- const r = await createProxy2(name, backendPort, openapi, ninoTenant);
7885
+ const r = await createProxy2(name, backendPort2, openapi, ninoTenant);
7834
7886
  const cpKey2 = r.cp_key;
7835
7887
  const teamId2 = r.team_id;
7836
7888
  const keys = r.api_keys ?? {};
@@ -7884,13 +7936,13 @@ async function startDemo(opts) {
7884
7936
  BACKEND_SECRET: backendSecret,
7885
7937
  DEMO_NINO_TENANT: ninoTenant,
7886
7938
  APP_PORT: appPort,
7887
- BACKEND_PORT: backendPort,
7939
+ BACKEND_PORT: backendPort2,
7888
7940
  DIRECT_PORT: directPort
7889
7941
  });
7890
- const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort, apiUrl, claimCode: setup.claimCode };
7942
+ const state = { name: project, version: version2, tenant: tenant2, teamId, cpKey, appPort, backendPort: backendPort2, apiUrl, claimCode: setup.claimCode };
7891
7943
  saveState(dir, state);
7892
7944
  printTable([
7893
- { label: "Proxy", what: `${project} (${createSecs} s, no account)`, cmds: [L(`dev --port ${backendPort} --openapi ./openapi.yaml --name ${name} --tenant ${ninoTenant} --auto`)] },
7945
+ { label: "Proxy", what: `${project} (${createSecs} s, no account)`, cmds: [L(`dev --port ${backendPort2} --openapi ./openapi.yaml --name ${name} --tenant ${ninoTenant} --auto`)] },
7894
7946
  {
7895
7947
  label: "Customers/Tenants",
7896
7948
  what: gino ? "Nino Pizza and Gino Pizza, own API keys" : "Nino Pizza, its own API key",
@@ -7907,20 +7959,20 @@ async function startDemo(opts) {
7907
7959
  { label: "Backend secret", what: "added to every call APIblaze forwards", cmds: [L(`transform ${project} set-header x-target-api-key --value-env BACKEND_SECRET --secret`)] },
7908
7960
  { label: "Keys", what: "saved to .env.local, never printed", cmds: [] }
7909
7961
  ]);
7910
- if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${backendPort}?`)) {
7962
+ if (!await confirmStep(opts, `Ok to create a tunnel allowing ${project} to forward requests to localhost:${backendPort2}?`)) {
7911
7963
  return stopHere([
7912
7964
  `Stopped before the tunnel. The proxy exists but cannot reach this laptop yet.`,
7913
7965
  `Open the tunnel any time: ${LATEST} demo start`
7914
7966
  ]);
7915
7967
  }
7916
7968
  console.log();
7917
- console.log(import_chalk27.default.bold("3. Live on the internet"));
7918
- shell(`${LATEST} dev --port ${backendPort} --project ${project} --auto`, "the tunnel, in the background");
7919
- await busy(`Opening the tunnel to localhost:${backendPort} and checking it with a real call\u2026`, async () => {
7969
+ console.log(import_chalk24.default.bold("3. Live on the internet"));
7970
+ shell(`${LATEST} dev --port ${backendPort2} --project ${project} --auto`, "the tunnel, in the background");
7971
+ await busy(`Opening the tunnel to localhost:${backendPort2} and checking it with a real call\u2026`, async () => {
7920
7972
  await spawnTunnel(dir, state);
7921
7973
  await waitThrough(apiUrl, serverKey);
7922
7974
  }, 1200);
7923
- ok(`${import_chalk27.default.bold("Tunnel")}: the public address reaches localhost:${backendPort} on this laptop`, `checked with a real call \xB7 ${Math.round((Date.now() - t0) / 1e3)} s so far`);
7975
+ ok(`${import_chalk24.default.bold("Tunnel")}: the public address reaches localhost:${backendPort2} on this laptop`, `checked with a real call \xB7 ${Math.round((Date.now() - t0) / 1e3)} s so far`);
7924
7976
  printAddresses(dir);
7925
7977
  if (!await confirmStep(opts, "Ok to launch the protected website, which will use the proxy to reach the backend?")) {
7926
7978
  return stopHere([
@@ -7929,29 +7981,29 @@ async function startDemo(opts) {
7929
7981
  ]);
7930
7982
  }
7931
7983
  console.log();
7932
- console.log(import_chalk27.default.bold(`4. The protected website, through ${B.product}`));
7984
+ console.log(import_chalk24.default.bold(`4. The protected website, through ${B.product}`));
7933
7985
  shell(`${cdDir}npm run restart`, "picks up the keys in .env.local");
7934
7986
  const siteUp = await busy("Starting the protected website\u2026", async () => {
7935
7987
  appCtl(dir, "--stop");
7936
7988
  appCtl(dir, "--background");
7937
- return waitPorts([appPort, directPort, backendPort]);
7989
+ return waitPorts([appPort, directPort, backendPort2]);
7938
7990
  }, 1500);
7939
- if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path4.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`);
7940
- ok(`${import_chalk27.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk27.default.bold(`http://localhost:${appPort}`)}`);
7941
- ok(`${import_chalk27.default.bold("Unprotected reservation front-end")}, still going directly to the backend: http://localhost:${directPort}`, "the switch at the top of the page goes between them");
7991
+ if (!siteUp) throw new DemoError(`The protected website did not start (see ${import_path5.default.join(rel, STATE_DIR, "app.log")}).`, `Try: ${cdDir}npm run restart`);
7992
+ ok(`${import_chalk24.default.bold("Protected reservation website")}, going through the ${B.product} proxy ${apiUrl}: ${import_chalk24.default.bold(`http://localhost:${appPort}`)}`);
7993
+ ok(`${import_chalk24.default.bold("Unprotected reservation front-end")}, still going directly to the backend: http://localhost:${directPort}`, "the switch at the top of the page goes between them");
7942
7994
  console.log();
7943
- console.log(import_chalk27.default.dim(` Logs: ${logsCommand(readEnvFile(import_path4.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
7995
+ console.log(import_chalk24.default.dim(` Logs: ${logsCommand(readEnvFile(import_path5.default.join(dir, ".env.local")))} \xB7 Stop the tunnel: ${LATEST} demo stop \xB7 Your own app: ${LATEST} dev --port <port>`));
7944
7996
  await offerBrowserAndLogs(dir, opts);
7945
7997
  }
7946
7998
  function printAddresses(dir) {
7947
- const env = readEnvFile(import_path4.default.join(dir, ".env.local"));
7948
- const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk27.default.dim(note)}` : ""}`);
7999
+ const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
8000
+ const row = (label3, value, note = "") => console.log(` ${label3.padEnd(11)} ${value}${note ? ` ${import_chalk24.default.dim(note)}` : ""}`);
7949
8001
  row("Public API", env.APIBLAZE_URL ?? "", `${B.product} tunnel \u2192 localhost:${env.BACKEND_PORT}`);
7950
8002
  row("MCP", env.APIBLAZE_MCP_URL ?? "", "for Claude, ChatGPT, Cursor (Nino Pizza)");
7951
8003
  if (env.APIBLAZE_PORTAL_URL) row("Dev portal", env.APIBLAZE_PORTAL_URL, "sign in, get a key, try the API");
7952
8004
  }
7953
8005
  async function spawnTunnel(dir, state) {
7954
- const log4 = import_fs5.default.openSync(import_path4.default.join(dir, STATE_DIR, "tunnel.log"), "a");
8006
+ const log4 = import_fs6.default.openSync(import_path5.default.join(dir, STATE_DIR, "tunnel.log"), "a");
7955
8007
  const child = (0, import_child_process4.spawn)(process.execPath, [process.argv[1], "demo", "--serve", dir], { cwd: dir, detached: true, stdio: ["ignore", log4, log4] });
7956
8008
  child.unref();
7957
8009
  saveState(dir, { ...state, pid: child.pid, ready: false, error: void 0 });
@@ -7962,9 +8014,9 @@ async function runDemoStart(opts = {}) {
7962
8014
  const t0 = Date.now();
7963
8015
  const dir = demoDir();
7964
8016
  const state = loadState(dir);
7965
- const env = readEnvFile(import_path4.default.join(dir, ".env.local"));
8017
+ const env = readEnvFile(import_path5.default.join(dir, ".env.local"));
7966
8018
  if (!state || !env.APIBLAZE_SERVER_KEY) {
7967
- console.log(`${import_chalk27.default.red("\u2717")} There is no demo here to start.`);
8019
+ console.log(`${import_chalk24.default.red("\u2717")} There is no demo here to start.`);
7968
8020
  console.log(` Make one: ${LATEST} demo`);
7969
8021
  process.exit(1);
7970
8022
  }
@@ -7973,30 +8025,30 @@ async function runDemoStart(opts = {}) {
7973
8025
  await spawnTunnel(dir, state);
7974
8026
  } catch (err) {
7975
8027
  const e = err instanceof DemoError ? err : new DemoError(`The tunnel could not start (${err.message}).`, `Start over: ${LATEST} demo`);
7976
- console.log(`${import_chalk27.default.red("\u2717")} ${e.message}`);
8028
+ console.log(`${import_chalk24.default.red("\u2717")} ${e.message}`);
7977
8029
  console.log(` ${e.fix}`);
7978
8030
  process.exit(1);
7979
8031
  }
7980
- console.log(`${import_chalk27.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk27.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
8032
+ console.log(`${import_chalk24.default.green("\u2713")} Tunnel open: the public address reaches localhost:${env.BACKEND_PORT} again ${import_chalk24.default.dim(`(${Math.round((Date.now() - t0) / 1e3)} s)`)}`);
7981
8033
  printAddresses(dir);
7982
8034
  const appUp = await fetch(`http://127.0.0.1:${env.APP_PORT}/api/config`).then(() => true).catch(() => false);
7983
- if (!appUp) console.log(import_chalk27.default.yellow(` ResiResi is not running: cd ${import_path4.default.relative(process.cwd(), dir) || "."} && npm run start`));
8035
+ if (!appUp) console.log(import_chalk24.default.yellow(` ResiResi is not running: cd ${import_path5.default.relative(process.cwd(), dir) || "."} && npm run start`));
7984
8036
  await offerBrowserAndLogs(dir, opts);
7985
8037
  }
7986
8038
  async function runDemoLogs() {
7987
- const env = readEnvFile(import_path4.default.join(demoDir(), ".env.local"));
8039
+ const env = readEnvFile(import_path5.default.join(demoDir(), ".env.local"));
7988
8040
  if (!env.APIBLAZE_NAME) {
7989
- console.log(`${import_chalk27.default.red("\u2717")} There is no demo here.`);
8041
+ console.log(`${import_chalk24.default.red("\u2717")} There is no demo here.`);
7990
8042
  console.log(` Make one: ${LATEST} demo`);
7991
8043
  process.exit(1);
7992
8044
  }
7993
- console.log(import_chalk27.default.dim(`$ ${logsCommand(env)}`));
8045
+ console.log(import_chalk24.default.dim(`$ ${logsCommand(env)}`));
7994
8046
  const { runLogsTail: runLogsTail2 } = await Promise.resolve().then(() => (init_logs(), logs_exports));
7995
8047
  await runLogsTail2(env.APIBLAZE_NAME, void 0, { tenant: env.APIBLAZE_TENANT });
7996
8048
  }
7997
8049
  async function runDemoStop() {
7998
8050
  const n = await stopTunnels();
7999
- console.log(n ? `${import_chalk27.default.green("\u2713")} Stopped the tunnel. ResiResi still runs on this laptop (npm run stop stops it).` : "No demo tunnel is running.");
8051
+ console.log(n ? `${import_chalk24.default.green("\u2713")} Stopped the tunnel. ResiResi still runs on this laptop (npm run stop stops it).` : "No demo tunnel is running.");
8000
8052
  }
8001
8053
  async function serveTunnel(dir) {
8002
8054
  const state = loadState(dir);
@@ -8054,17 +8106,17 @@ async function serveTunnel(dir) {
8054
8106
  await new Promise(() => {
8055
8107
  });
8056
8108
  }
8057
- var import_fs5, import_path4, import_net, import_child_process4, import_crypto, import_chalk27, import_ora10, DIR_NAME, STATE_DIR, LATEST, DEMO_RPS, DEMO_ADMIN_EMAIL, DEMO_GINO_ADMIN_EMAIL, RULE_SENTENCE, DemoError, ADJ, NOUN, pick2, registryFile, alive, interactive, logsCommand;
8109
+ var import_fs6, import_path5, import_net2, import_child_process4, import_crypto, import_chalk24, import_ora9, DIR_NAME, STATE_DIR, LATEST, DEMO_RPS, DEMO_ADMIN_EMAIL, DEMO_GINO_ADMIN_EMAIL, RULE_SENTENCE, DemoError, ADJ, NOUN, pick2, registryFile, alive, interactive, logsCommand;
8058
8110
  var init_demo = __esm({
8059
8111
  "src/commands/demo.ts"() {
8060
8112
  "use strict";
8061
- import_fs5 = __toESM(require("fs"));
8062
- import_path4 = __toESM(require("path"));
8063
- import_net = __toESM(require("net"));
8113
+ import_fs6 = __toESM(require("fs"));
8114
+ import_path5 = __toESM(require("path"));
8115
+ import_net2 = __toESM(require("net"));
8064
8116
  import_child_process4 = require("child_process");
8065
8117
  import_crypto = __toESM(require("crypto"));
8066
- import_chalk27 = __toESM(require("chalk"));
8067
- import_ora10 = __toESM(require("ora"));
8118
+ import_chalk24 = __toESM(require("chalk"));
8119
+ import_ora9 = __toESM(require("ora"));
8068
8120
  init_api();
8069
8121
  init_tunnel_client();
8070
8122
  init_auth();
@@ -8088,7 +8140,7 @@ var init_demo = __esm({
8088
8140
  ADJ = ["brave", "calm", "clever", "eager", "happy", "jolly", "lucky", "merry", "noble", "quick", "sunny", "swift", "witty", "zesty", "bright", "gentle"];
8089
8141
  NOUN = ["otter", "falcon", "panda", "heron", "koala", "lemur", "puffin", "robin", "tiger", "walrus", "wombat", "badger", "comet", "maple", "river", "finch"];
8090
8142
  pick2 = (a) => a[import_crypto.default.randomInt(a.length)];
8091
- registryFile = () => import_path4.default.join(getApiblazeDir(), "demo-runs.json");
8143
+ registryFile = () => import_path5.default.join(getApiblazeDir(), "demo-runs.json");
8092
8144
  alive = (pid) => {
8093
8145
  try {
8094
8146
  process.kill(pid, 0);
@@ -8353,7 +8405,7 @@ function runCli(args, cwd, home, timeoutMs) {
8353
8405
  function readEnv(file2) {
8354
8406
  const out = {};
8355
8407
  try {
8356
- for (const line of import_fs8.default.readFileSync(file2, "utf8").split("\n")) {
8408
+ for (const line of import_fs10.default.readFileSync(file2, "utf8").split("\n")) {
8357
8409
  const m = /^([A-Z0-9_]+)=(.*)$/.exec(line.trim());
8358
8410
  if (m) out[m[1]] = m[2];
8359
8411
  }
@@ -8386,7 +8438,7 @@ async function journey(label3, work, home, checks) {
8386
8438
  `exit ${run2.code} in ${Math.round(run2.ms / 1e3)} s${leaked ? " \xB7 PRINTED A SECRET" : ""}${run2.code !== 0 ? ` \xB7 ${run2.out.trim().split("\n").slice(-2).join(" / ")}` : ""}`
8387
8439
  );
8388
8440
  if (run2.code !== 0) return null;
8389
- const env = readEnv(import_path5.default.join(work, `${B.cli}-demo`, ".env.local"));
8441
+ const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
8390
8442
  const api = env.APIBLAZE_URL;
8391
8443
  const nino = env.APIBLAZE_SERVER_KEY;
8392
8444
  const as = (user, key = nino) => ({ "x-api-key": key, "x-end-user-id": user, "content-type": "application/json" });
@@ -8451,11 +8503,11 @@ async function journey(label3, work, home, checks) {
8451
8503
  }
8452
8504
  async function runOpAnon(o = {}) {
8453
8505
  const t0 = Date.now();
8454
- const root = import_fs8.default.mkdtempSync(import_path5.default.join(import_os2.default.tmpdir(), `${B.cli}-op-anon-`));
8455
- const home = import_path5.default.join(root, "home");
8456
- const work = import_path5.default.join(root, "work");
8457
- import_fs8.default.mkdirSync(home);
8458
- import_fs8.default.mkdirSync(work);
8506
+ const root = import_fs10.default.mkdtempSync(import_path7.default.join(import_os2.default.tmpdir(), `${B.cli}-op-anon-`));
8507
+ const home = import_path7.default.join(root, "home");
8508
+ const work = import_path7.default.join(root, "work");
8509
+ import_fs10.default.mkdirSync(home);
8510
+ import_fs10.default.mkdirSync(work);
8459
8511
  const checks = [];
8460
8512
  const log4 = (s) => {
8461
8513
  if (!o.json) console.log(s);
@@ -8466,21 +8518,21 @@ async function runOpAnon(o = {}) {
8466
8518
  await journey("rerun", work, home, checks);
8467
8519
  } finally {
8468
8520
  const stop = await runCli(["demo", "stop"], work, home, 3e4);
8469
- const demoFolder = import_path5.default.join(work, `${B.cli}-demo`);
8470
- if (import_fs8.default.existsSync(import_path5.default.join(demoFolder, "start.js"))) {
8521
+ const demoFolder = import_path7.default.join(work, `${B.cli}-demo`);
8522
+ if (import_fs10.default.existsSync(import_path7.default.join(demoFolder, "start.js"))) {
8471
8523
  await new Promise((res) => {
8472
- const c = (0, import_child_process5.spawn)(process.execPath, [import_path5.default.join(demoFolder, "start.js"), "--stop"], { cwd: demoFolder, stdio: "ignore" });
8524
+ const c = (0, import_child_process5.spawn)(process.execPath, [import_path7.default.join(demoFolder, "start.js"), "--stop"], { cwd: demoFolder, stdio: "ignore" });
8473
8525
  c.on("close", () => res());
8474
8526
  });
8475
8527
  }
8476
- const env = readEnv(import_path5.default.join(work, `${B.cli}-demo`, ".env.local"));
8528
+ const env = readEnv(import_path7.default.join(work, `${B.cli}-demo`, ".env.local"));
8477
8529
  let portsFree = true;
8478
8530
  for (const p of [env.APP_PORT, env.BACKEND_PORT].filter(Boolean)) {
8479
8531
  const r = await http(`http://127.0.0.1:${p}/`, { timeoutMs: 2e3 });
8480
8532
  if (r.status !== 0) portsFree = false;
8481
8533
  }
8482
8534
  checks.push({ id: "stop", ok: stop.code === 0 && portsFree, detail: `exit ${stop.code}${portsFree ? "" : " \xB7 ports still answering"}` });
8483
- import_fs8.default.rmSync(root, { recursive: true, force: true });
8535
+ import_fs10.default.rmSync(root, { recursive: true, force: true });
8484
8536
  }
8485
8537
  for (const c of checks) log4(` ${c.skipped ? import_chalk70.default.yellow("\u2013") : c.ok ? import_chalk70.default.green("\u2713") : import_chalk70.default.red("\u2717")} ${c.id.padEnd(24)} ${import_chalk70.default.dim(c.detail)}`);
8486
8538
  const failed = checks.filter((c) => !c.ok).length;
@@ -8493,13 +8545,13 @@ async function runOpAnon(o = {}) {
8493
8545
  if (failed) process.exitCode = 1;
8494
8546
  return { ok: !failed, checks };
8495
8547
  }
8496
- var import_fs8, import_os2, import_path5, import_child_process5, import_chalk70, SECRET;
8548
+ var import_fs10, import_os2, import_path7, import_child_process5, import_chalk70, SECRET;
8497
8549
  var init_op_anon = __esm({
8498
8550
  "src/commands/op-anon.ts"() {
8499
8551
  "use strict";
8500
- import_fs8 = __toESM(require("fs"));
8552
+ import_fs10 = __toESM(require("fs"));
8501
8553
  import_os2 = __toESM(require("os"));
8502
- import_path5 = __toESM(require("path"));
8554
+ import_path7 = __toESM(require("path"));
8503
8555
  import_child_process5 = require("child_process");
8504
8556
  import_chalk70 = __toESM(require("chalk"));
8505
8557
  init_brand();
@@ -8508,7 +8560,7 @@ var init_op_anon = __esm({
8508
8560
  });
8509
8561
 
8510
8562
  // src/index.ts
8511
- var import_fs9 = __toESM(require("fs"));
8563
+ var import_fs11 = __toESM(require("fs"));
8512
8564
  var import_commander = require("commander");
8513
8565
  var import_chalk72 = __toESM(require("chalk"));
8514
8566
  init_package();
@@ -8517,7 +8569,7 @@ init_types();
8517
8569
  init_login();
8518
8570
 
8519
8571
  // src/commands/dev.ts
8520
- var import_fs3 = __toESM(require("fs"));
8572
+ var import_fs4 = __toESM(require("fs"));
8521
8573
  init_package();
8522
8574
  init_version_guard();
8523
8575
  var import_chalk20 = __toESM(require("chalk"));
@@ -8839,7 +8891,7 @@ async function runDev(options) {
8839
8891
  let recordSink;
8840
8892
  let captureStream;
8841
8893
  if (options.captureFile) {
8842
- captureStream = import_fs3.default.createWriteStream(options.captureFile, { flags: "a" });
8894
+ captureStream = import_fs4.default.createWriteStream(options.captureFile, { flags: "a" });
8843
8895
  recordSink = (r) => captureStream.write(JSON.stringify(r) + "\n");
8844
8896
  }
8845
8897
  let restore = [];
@@ -9026,12 +9078,199 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
9026
9078
  init_create();
9027
9079
 
9028
9080
  // src/commands/skill.ts
9029
- var import_fs4 = __toESM(require("fs"));
9030
- var import_path3 = __toESM(require("path"));
9031
- var import_chalk22 = __toESM(require("chalk"));
9081
+ var import_fs7 = __toESM(require("fs"));
9082
+ var import_path6 = __toESM(require("path"));
9083
+ var import_chalk25 = __toESM(require("chalk"));
9032
9084
  init_brand();
9085
+
9086
+ // src/lib/detect-backend.ts
9087
+ var import_fs5 = __toESM(require("fs"));
9088
+ var import_path4 = __toESM(require("path"));
9089
+ var import_net = __toESM(require("net"));
9090
+ var NODE_SERVERS = [
9091
+ ["express", "Express", 3e3],
9092
+ ["fastify", "Fastify", 3e3],
9093
+ ["koa", "Koa", 3e3],
9094
+ ["hono", "Hono", 3e3],
9095
+ ["@nestjs/core", "NestJS", 3e3],
9096
+ ["@hapi/hapi", "hapi", 3e3],
9097
+ ["restify", "Restify", 8080],
9098
+ ["elysia", "Elysia", 3e3],
9099
+ ["next", "Next.js", 3e3],
9100
+ ["nuxt", "Nuxt", 3e3],
9101
+ ["@remix-run/node", "Remix", 3e3]
9102
+ ];
9103
+ var NODE_FRONTENDS = [["vite", "Vite"], ["react-scripts", "Create React App"], ["@angular/core", "Angular"], ["svelte", "Svelte"]];
9104
+ var PY_SERVERS = [
9105
+ [/\bfastapi\b/i, "FastAPI", 8e3],
9106
+ [/\bdjango\b/i, "Django", 8e3],
9107
+ [/\bflask\b/i, "Flask", 5e3],
9108
+ [/\bstarlette\b/i, "Starlette", 8e3],
9109
+ [/\baiohttp\b/i, "aiohttp", 8080]
9110
+ ];
9111
+ var GO_SERVERS = [[/gin-gonic\/gin/, "Gin"], [/labstack\/echo/, "Echo"], [/go-chi\/chi/, "chi"], [/gofiber\/fiber/, "Fiber"]];
9112
+ var RUBY_SERVERS = [[/^\s*gem ['"]rails['"]/m, "Rails", 3e3], [/^\s*gem ['"]sinatra['"]/m, "Sinatra", 4567]];
9113
+ var SPEC_NAMES = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json"];
9114
+ var SPEC_DIRS = [".", "api", "spec", "specs", "docs", "openapi", "src"];
9115
+ var COMMON_PORTS = [3e3, 3001, 4e3, 5e3, 8e3, 8080, 8787];
9116
+ var SPEC_PATHS = ["/openapi.json", "/openapi.yaml", "/docs/openapi.json", "/swagger.json", "/api/openapi.json"];
9117
+ function readText(file2, max = 256 * 1024) {
9118
+ try {
9119
+ const st = import_fs5.default.statSync(file2);
9120
+ if (!st.isFile() || st.size > max) return null;
9121
+ return import_fs5.default.readFileSync(file2, "utf8");
9122
+ } catch {
9123
+ return null;
9124
+ }
9125
+ }
9126
+ function findSpecFile(dir) {
9127
+ for (const d of SPEC_DIRS) {
9128
+ for (const n of SPEC_NAMES) {
9129
+ const rel = d === "." ? n : `${d}/${n}`;
9130
+ const text = readText(import_path4.default.join(dir, rel));
9131
+ if (text && /\b(openapi|swagger)\s*["']?\s*:/.test(text)) {
9132
+ const m = /\b(?:localhost|127\.0\.0\.1):(\d{2,5})\b/.exec(text);
9133
+ return { file: `./${rel}`, ...m ? { port: Number(m[1]) } : {} };
9134
+ }
9135
+ }
9136
+ }
9137
+ return void 0;
9138
+ }
9139
+ function portFromScripts(scripts) {
9140
+ for (const s of Object.values(scripts)) {
9141
+ const m = /(?:--port[= ]|-p |PORT=)(\d{2,5})\b/.exec(s);
9142
+ if (m) return Number(m[1]);
9143
+ }
9144
+ return void 0;
9145
+ }
9146
+ function fromNode(dir, out) {
9147
+ const text = readText(import_path4.default.join(dir, "package.json"));
9148
+ if (!text) return;
9149
+ let pkg = {};
9150
+ try {
9151
+ pkg = JSON.parse(text);
9152
+ } catch {
9153
+ return;
9154
+ }
9155
+ const deps = { ...pkg.devDependencies ?? {}, ...pkg.dependencies ?? {} };
9156
+ const scripts = pkg.scripts ?? {};
9157
+ const server = NODE_SERVERS.find(([dep]) => dep in deps);
9158
+ if (server) {
9159
+ out.framework = server[1];
9160
+ out.source = "package.json";
9161
+ out.defaultPort = server[2];
9162
+ } else if (/\bnode\s+\S+\.(c|m)?js\b/.test(Object.values(scripts).join(" "))) {
9163
+ out.framework = "a Node server";
9164
+ out.source = "package.json";
9165
+ out.defaultPort = 3e3;
9166
+ } else {
9167
+ const fe = NODE_FRONTENDS.find(([dep]) => dep in deps);
9168
+ if (fe) out.frontendOnly = fe[1];
9169
+ return;
9170
+ }
9171
+ out.configuredPort = portFromScripts(scripts);
9172
+ out.start = scripts.dev ? "npm run dev" : scripts.start ? "npm start" : void 0;
9173
+ }
9174
+ function fromPython(dir, out) {
9175
+ for (const f of ["requirements.txt", "pyproject.toml", "Pipfile"]) {
9176
+ const text = readText(import_path4.default.join(dir, f));
9177
+ if (!text) continue;
9178
+ const hit = PY_SERVERS.find(([re]) => re.test(text));
9179
+ if (hit) {
9180
+ out.framework = hit[1];
9181
+ out.source = f;
9182
+ out.defaultPort = hit[2];
9183
+ return;
9184
+ }
9185
+ }
9186
+ }
9187
+ function fromOthers(dir, out) {
9188
+ const gomod = readText(import_path4.default.join(dir, "go.mod"));
9189
+ if (gomod) {
9190
+ const hit = GO_SERVERS.find(([re]) => re.test(gomod));
9191
+ out.framework = hit ? hit[1] : "a Go server";
9192
+ out.source = "go.mod";
9193
+ out.defaultPort = 8080;
9194
+ return;
9195
+ }
9196
+ const gemfile = readText(import_path4.default.join(dir, "Gemfile"));
9197
+ if (gemfile) {
9198
+ const hit = RUBY_SERVERS.find(([re]) => re.test(gemfile));
9199
+ if (hit) {
9200
+ out.framework = hit[1];
9201
+ out.source = "Gemfile";
9202
+ out.defaultPort = hit[2];
9203
+ return;
9204
+ }
9205
+ }
9206
+ for (const f of ["pom.xml", "build.gradle", "build.gradle.kts"]) {
9207
+ const text = readText(import_path4.default.join(dir, f));
9208
+ if (text && /spring-boot/.test(text)) {
9209
+ out.framework = "Spring Boot";
9210
+ out.source = f;
9211
+ out.defaultPort = 8080;
9212
+ return;
9213
+ }
9214
+ }
9215
+ }
9216
+ function portAnswers(port, timeoutMs = 300) {
9217
+ return new Promise((resolve4) => {
9218
+ const sock = import_net.default.connect({ port, host: "127.0.0.1" });
9219
+ const done = (v) => {
9220
+ sock.destroy();
9221
+ resolve4(v);
9222
+ };
9223
+ sock.setTimeout(timeoutMs, () => done(false));
9224
+ sock.once("connect", () => done(true));
9225
+ sock.once("error", () => done(false));
9226
+ });
9227
+ }
9228
+ async function liveSpec(port) {
9229
+ for (const p of SPEC_PATHS) {
9230
+ try {
9231
+ const r = await fetch(`http://localhost:${port}${p}`, { signal: AbortSignal.timeout(800) });
9232
+ if (!r.ok) continue;
9233
+ const text = (await r.text()).slice(0, 4096);
9234
+ if (/\b(openapi|swagger)\s*["']?\s*:/.test(text)) return `http://localhost:${port}${p}`;
9235
+ } catch {
9236
+ }
9237
+ }
9238
+ return void 0;
9239
+ }
9240
+ async function detectBackend(dir, opts = {}) {
9241
+ const out = {};
9242
+ try {
9243
+ if (import_fs5.default.existsSync(import_path4.default.join(dir, ".demo", "state.json"))) out.isDemo = true;
9244
+ fromNode(dir, out);
9245
+ if (!out.framework) fromPython(dir, out);
9246
+ if (!out.framework) fromOthers(dir, out);
9247
+ const spec2 = findSpecFile(dir);
9248
+ if (spec2) {
9249
+ out.specFile = spec2.file;
9250
+ if (spec2.port && !out.configuredPort) out.configuredPort = spec2.port;
9251
+ }
9252
+ } catch {
9253
+ }
9254
+ const preferred = [...new Set([out.configuredPort, out.defaultPort].filter((p) => !!p))];
9255
+ const ports = [.../* @__PURE__ */ new Set([...preferred, ...opts.ports ?? COMMON_PORTS])];
9256
+ const answering = await Promise.all(ports.map(async (p) => await portAnswers(p) ? p : 0));
9257
+ const up = answering.filter(Boolean);
9258
+ const mine = preferred.find((p) => up.includes(p));
9259
+ if (mine && (out.framework || out.specFile)) out.listening = mine;
9260
+ else if (!out.framework && !out.specFile && up.length) out.strayPorts = up;
9261
+ if (out.listening) out.specUrl = await liveSpec(out.listening);
9262
+ return out;
9263
+ }
9264
+ function hasBackend(f) {
9265
+ return !f.isDemo && !!(f.framework || f.specFile);
9266
+ }
9267
+ function backendPort(f) {
9268
+ return f.listening ?? f.configuredPort ?? f.defaultPort;
9269
+ }
9270
+
9271
+ // src/commands/skill.ts
9033
9272
  var SKILL_NAME = "ship-safely";
9034
- var SKILL_RELATIVE_PATH = import_path3.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
9273
+ var SKILL_RELATIVE_PATH = import_path6.default.join(".claude", "skills", SKILL_NAME, "SKILL.md");
9035
9274
  var CP_TOOLS = [
9036
9275
  "describe_server",
9037
9276
  "propose_rules",
@@ -9046,9 +9285,82 @@ var CP_TOOLS = [
9046
9285
  function withLatest(md) {
9047
9286
  return md.replace(/npx (apiblaze|mcpblaze)(?![@\w-])/g, "npx $1@latest");
9048
9287
  }
9049
- function heroMarkdown() {
9288
+ function pitchText() {
9289
+ const kind = BRAND === "mcpblaze" ? "MCP and API" : "API and MCP";
9290
+ return `${B.product} is a serverless ${kind} gateway. Put it in front of your backend and it's ready for traffic from people and AI agents. Callers sign in with API keys or OAuth, each can only do what their credentials allow, bots get throttled, and every customer gets their own space. Seconds, not weeks, even for an app on your laptop.`;
9291
+ }
9292
+ function appName(f) {
9293
+ const fw = f.framework ?? "an API";
9294
+ if (/^an? /.test(fw)) return fw[0].toUpperCase() + fw.slice(1);
9295
+ return `${/^[AEIOU]/i.test(fw) ? "An" : "A"} ${fw} app`;
9296
+ }
9297
+ function folderReport(f, opts = {}) {
9298
+ const P = B.product;
9299
+ const demoQuestion = "Want me to show you on a sample app? About 30 seconds, nothing to install.";
9300
+ if (f.isDemo) {
9301
+ return { lines: [`This is the ${P} demo folder (ResiResi). It is already behind ${P}; \`npx ${B.cli}@latest demo start\` reopens its tunnel.`], question: demoQuestion, ownApi: false };
9302
+ }
9303
+ if (!hasBackend(f)) {
9304
+ const extra = [
9305
+ f.frontendOnly ? ` It is a ${f.frontendOnly} frontend with no server of its own.` : "",
9306
+ // A person may know what that is; an agent would only be distracted by it.
9307
+ opts.forPerson && f.strayPorts?.length ? ` Something answers on localhost:${f.strayPorts[0]}, but nothing in this folder points at it.` : ""
9308
+ ].join("");
9309
+ return { lines: [`No backend here: no server framework in the project files and no OpenAPI spec.${extra}`], question: demoQuestion, ownApi: false };
9310
+ }
9311
+ const port = backendPort(f);
9312
+ const spec2 = f.specFile ? `Spec: ${f.specFile}.` : f.specUrl ? `It serves its spec at ${f.specUrl}.` : "No OpenAPI spec yet.";
9313
+ const from = f.source ? ` (${f.source})` : "";
9314
+ if (f.listening) {
9315
+ return {
9316
+ lines: [`${appName(f)}${from}, answering on localhost:${f.listening} right now. ${spec2}`],
9317
+ question: `Want me to put your API on port ${f.listening} behind ${P}? About 30 seconds, no sign-up.`,
9318
+ ownApi: true
9319
+ };
9320
+ }
9321
+ const starts = f.start ? ` It starts with \`${f.start}\`` : " Its start command is not in its files";
9322
+ const listens = port ? ` and listens on ${port}.` : ".";
9323
+ return {
9324
+ lines: [`${appName(f)}${from}, not running.${starts}${listens} ${spec2}`],
9325
+ question: `Want me to start your app and put it behind ${P}? About a minute, no sign-up.`,
9326
+ ownApi: true
9327
+ };
9328
+ }
9329
+ function heroMarkdown(found) {
9050
9330
  const P = B.product;
9051
9331
  const run2 = `npx ${B.cli}@latest`;
9332
+ const report = found ? folderReport(found) : null;
9333
+ const port = found ? backendPort(found) : void 0;
9334
+ const folder = report ? `## This folder, checked by the CLI just now: read no files to decide what to offer
9335
+
9336
+ ${report.lines.map((l) => `- ${l}`).join("\n")}
9337
+ ` : `## First, the folder
9338
+
9339
+ Run \`${run2} skills\` in the project folder and follow what it prints: it checks the folder
9340
+ and gives the exact question to ask.
9341
+ `;
9342
+ const question = report?.question ?? "<the question it prints>";
9343
+ const ownApi = report?.ownApi && found ? (() => {
9344
+ const specArg = found.specFile ? ` --openapi ${found.specFile}` : "";
9345
+ const steps = [`Say one line: "Putting your API behind ${P}, about 30 seconds."`];
9346
+ if (!found.listening) {
9347
+ steps.push(`Start it first as its own detached process${found.start ? `: \`nohup ${found.start} > app.log 2>&1 < /dev/null &\`` : ", with its usual start command"}. Wait until localhost:${port ?? "<port>"} answers.`);
9348
+ }
9349
+ if (!found.specFile && !found.specUrl) {
9350
+ steps.push('Write `./openapi.yaml` from its route files first: every path and method, and the id each POST returns. Say "Writing an OpenAPI spec from your code."');
9351
+ }
9352
+ const spec2 = found.specFile ? specArg : !found.specUrl ? " --openapi ./openapi.yaml" : "";
9353
+ steps.push(`Open the tunnel as its own detached process, never \`cmd &\` inside a shell call that ends:
9354
+ \`nohup ${run2} dev --port ${port ?? "<port>"}${spec2} --auto > ${B.cli}-dev.log 2>&1 < /dev/null &\`
9355
+ Then read ${B.cli}-dev.log until it says "is live on the internet" (about 20 seconds). It saves the key to .env.local itself.`);
9356
+ steps.push(`Tell them in plain words, with the two addresses it printed: their API is on the internet at the API address, and AI assistants can use it at the MCP address. API keys, sign-in, rules for who can change what, and rate limits are already on. Say "saved your key to .env.local". Leave the tunnel running.`);
9357
+ steps.push(`Ask one question: **"Want it ready for real customers: their own API keys, sign-in, and only-the-owner-can-change rules?"** If yes, run \`${run2} skills ship\` and follow it from "Adopt".`);
9358
+ return `
9359
+ ## Their API: they say yes to putting it behind ${P}
9360
+
9361
+ ${steps.map((t, i) => `${i + 1}. ${t}`).join("\n")}
9362
+ `;
9363
+ })() : "";
9052
9364
  return `---
9053
9365
  name: ${SKILL_NAME}
9054
9366
  description: Use when the user asks what ${P} (or ${B.cli}) does or can do for them; says yes to a demo or asks to see / try / demo it; wants to ship, share or expose an API; wants API keys, sign-in, per-user data, rate limits or quotas; wants AI agents to use their app safely; or says users can reach each other's records.
@@ -9056,17 +9368,16 @@ description: Use when the user asks what ${P} (or ${B.cli}) does or can do for t
9056
9368
 
9057
9369
  # ${P}
9058
9370
 
9059
- ## Explain: they ask what ${P} does
9060
-
9061
- Run nothing. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
9371
+ ${folder}
9372
+ ## Explain: they ask what ${P} does or can do for them
9062
9373
 
9063
- "${P} puts the app on your laptop on the internet in one command. It gets a public address,
9064
- plus an address AI assistants like me can use to work with it. API keys, sign-in and
9065
- who-can-change-what come built in, with no code. Want me to show you in about 30 seconds?"
9374
+ Run nothing else. Say exactly this and nothing else: no list of use cases, no commands, no options. Then stop:
9066
9375
 
9067
- Do not use the words proxy, MCP, tenant, OpenAPI or header unless the user does first.
9376
+ "${pitchText()} ${question}"
9068
9377
 
9069
- ## Demo: they say yes, or ask to see or try it
9378
+ Do not use the words proxy, tenant, OpenAPI or header unless the user does first.
9379
+ ${ownApi}
9380
+ ## Demo: they say yes to the sample app, or ask to see or try it
9070
9381
 
9071
9382
  1. Say one line: "Starting the demo, about 30 seconds."
9072
9383
  2. Run exactly this, in the foreground. It needs no login and asks nothing. It sets
@@ -9083,10 +9394,17 @@ Rules for the demo: keep each update to one line. Do not narrate steps, read the
9083
9394
  files, open logs, run other commands, or write code. Never show a key: the demo saves it
9084
9395
  in \`.env.local\` and never prints it.
9085
9396
 
9086
- ## Their own app: they say yes to that
9397
+ ## Their own app: they say yes after the demo
9087
9398
 
9088
9399
  Their app runs on this machine \u2192 it will use \`${run2} dev\`; it is deployed \u2192 \`${run2} create\`.
9089
- Run \`${run2} skills --ship\` and follow what it prints.
9400
+ Run \`${run2} skills ship\` and follow what it prints.
9401
+
9402
+ ## One thing at a time, later
9403
+
9404
+ When they ask about one thing, read only its page: \`${run2} skills keys\` (API keys) \xB7
9405
+ \`signin\` (OAuth sign-in and the developer portal) \xB7 \`rules\` (who can change what) \xB7
9406
+ \`limits\` (rate limits and quotas) \xB7 \`mcp\` (AI agents) \xB7 \`widgets\` (keys, users and chat on
9407
+ their site) \xB7 \`ship\` (the whole procedure for their own app).
9090
9408
  `;
9091
9409
  }
9092
9410
  function shipMarkdown() {
@@ -9299,8 +9617,11 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
9299
9617
  read the log. If it stops, re-run the SAME command: the same \`--name\` reattaches and
9300
9618
  creates nothing. Never pick a new name to get it back \u2014 that is a second proxy. **Deployed code** \u2192
9301
9619
  \`npx ${cli} create --target <url-or-spec> --name <name> --auto <door flags>\`. Both print the
9302
- URL, the MCP URL (the "agents:" line), what is locked, and the bootstrap key ONCE \u2014 that key
9303
- goes in the frontend-server's env, never a browser, a repo or an agent config. The tenant is
9620
+ URL, the MCP URL (the "agents:" line) and what is locked. Run by you (no terminal), \`dev\`
9621
+ saves the bootstrap key to \`.env.local\` as \`APIBLAZE_SERVER_KEY\` and never prints it;
9622
+ \`create\` prints it ONCE \u2014 save it there yourself. Either way say only "saved your key to
9623
+ .env.local": that key goes in the frontend-server's env, never a browser, a repo, an agent
9624
+ config or the chat. The tenant is
9304
9625
  not labelled: read it from the \`admins add \u2026 --tenant <tenant>\` hint (it is also the middle
9305
9626
  of the MCP hostname). Save \`APIBLAZE_URL\`, \`APIBLAZE_SERVER_KEY\`, \`APIBLAZE_TENANT\` in
9306
9627
  the frontend-server's env (\`.env.local\` for Next.js). Then prove it with three curls and
@@ -9335,6 +9656,8 @@ ONLY for what the scan could not find \u2014 one short list of questions, not an
9335
9656
  - the command to chat with their API: \`npx ${cli} apichat <name>\` (add
9336
9657
  \`--xenduserid ana\` when \xA75b is on).
9337
9658
  Then ask once: "Would you like me to add the self-serve API key widget to your site?"
9659
+ After that answer, offer once: "Want me to save this as a skill in your project, so I know
9660
+ ${P} next time?" Yes \u2192 \`npx ${cli} skills --install\`.
9338
9661
 
9339
9662
  ## Report like this
9340
9663
 
@@ -9410,36 +9733,184 @@ through one MCP: \`claude mcp add --transport http ${BRAND} ${B.controlPlaneMcp}
9410
9733
  ${CP_TOOLS.join(", ")} \u2014 the same steps, one tool each.
9411
9734
  `;
9412
9735
  }
9736
+ function sectionOf(md, heading) {
9737
+ const at = md.indexOf(`
9738
+ ${heading}`);
9739
+ if (at < 0) return "";
9740
+ const next = md.indexOf("\n## ", at + heading.length + 1);
9741
+ return md.slice(at + 1, next < 0 ? void 0 : next).trim();
9742
+ }
9743
+ var TOPICS = ["ship", "keys", "signin", "rules", "limits", "mcp", "widgets"];
9744
+ function topicMarkdown(topic) {
9745
+ const P = B.product;
9746
+ const cli = B.cli;
9747
+ const legacy = legacySkillMarkdown();
9748
+ const page = (title, body) => withLatest(`# ${title}
9749
+
9750
+ ${body.trim()}
9751
+ `);
9752
+ switch (topic) {
9753
+ case "ship":
9754
+ return shipMarkdown();
9755
+ case "keys":
9756
+ return page(`API keys with ${P}`, `
9757
+ Every ${B.thingShort} has a key door. Three kinds of key for a tenant (one of their customers), one flag each:
9758
+ - \`npx ${cli} apikeys mint --tenant <tenant> --backend\`: a server key. Their server sends it as
9759
+ \`X-API-Key\` and names the person in \`X-End-User-Id\`.
9760
+ - \`--client\`: a key that cannot name a user (a public client, a script).
9761
+ - \`--for <email>\`: bound to one person, like an agent or a CI job. Calls count as that person, and
9762
+ ownership rules trust it. It is a DIFFERENT identity from a bare \`X-End-User-Id\` string: rows
9763
+ created as "ana" through the header are not owned by ana@example.com's key.
9764
+
9765
+ Their customers get a key per tenant from the self-serve keys widget (\`npx ${cli} skills widgets\`):
9766
+ each customer mints their own, in their own tenant. The bootstrap key is only for their own server.
9767
+ Never show a key in the chat: save it to \`.env.local\` and say "saved your key to .env.local".`);
9768
+ case "signin":
9769
+ return page(`Sign-in (OAuth) with ${P}`, `
9770
+ ${sectionOf(legacy, "## Sign-in shapes")}
9771
+
9772
+ Each tenant has its own developer portal, where people sign in and get a key:
9773
+ \`https://<tenant>.portal.apiblaze.com/<version>\`. A tenant added later gets the same sign-in:
9774
+ \`npx ${cli} tenant attach <name> --tenant <tenant>\`.
9775
+
9776
+ Tenant admins, by email, even before that person ever signs in:
9777
+ \`npx ${cli} admins add <email> --tenant <tenant>\` \xB7 \`admins list --tenant <tenant>\` \xB7 \`admins remove <email> --tenant <tenant>\`.`);
9778
+ case "rules":
9779
+ return page("Who can change what", `
9780
+ - With a spec, \`dev\` and \`create\` lock every resource to the person who created it.
9781
+ - \`npx ${cli} rule <name>\` shows the rules. \`npx ${cli} rule <name> "<sentence>"\` adds one in plain
9782
+ words, for example "Only the person who made a reservation can change or cancel it". Both need
9783
+ a login (\`npx ${cli} login\`); an anonymous workspace keeps the creator locks \`dev\` applied.
9784
+ - \`npx ${cli} identified <name> require\`: every call must name a person (\`X-End-User-Id\` or a
9785
+ sign-in token). \`allow-anon\` undoes it.
9786
+ - \`npx ${cli} config <name> authorization.enforce_authorization false\` turns enforcement off
9787
+ without losing the rules.
9788
+ - The gateway locks single records. Lists are the backend's job: filter every collection GET by
9789
+ \`x-abz-tenant-id\` AND \`x-abz-user-id\`, since a user id is not unique across tenants.`);
9790
+ case "limits":
9791
+ return page("Rate limits and quotas", `
9792
+ Every new ${B.thingShort} has a rate limit of 10 requests/s per consumer (100 per 10 s) and a quota
9793
+ of 3,000 a day. Say so in those words (rate limit, quota).
9794
+ - \`npx ${cli} limits get <name>\` shows them.
9795
+ - \`npx ${cli} limits set <name> --rate <n> --quota <n> --period daily\`, plus \`--spend-cap <usd>\` for
9796
+ a monthly spend cap.
9797
+ - Rates are per consumer key and approximate per location. A rate above the self-serve ceiling is
9798
+ refused with the exact \`limits request-max\` command to run: relay it, do not retry.
9799
+ - It works logged out; an anonymous workspace is capped until claimed.`);
9800
+ case "mcp":
9801
+ return page(`AI agents (MCP) with ${P}`, `
9802
+ - Every ${B.thingShort} has an MCP address for AI agents, one per tenant: use the "MCP" line that
9803
+ \`dev\` or \`create\` printed. Its tools are the spec's operations.
9804
+ - Agents sign in with the tenant's sign-in (OAuth), or use a person-bound key:
9805
+ \`npx ${cli} apikeys mint --tenant <tenant> --for <email>\`. Never hand an agent a server key: it
9806
+ names no person, so protected routes refuse it.
9807
+ - Chat with the API from the terminal: \`npx ${cli} apichat <name>\` (add \`--xenduserid ana\` when calls
9808
+ must name a person). Connect an agent: \`npx ${cli} apichat <name> --install-mcp claude\`.
9809
+
9810
+ ${sectionOf(legacy, "## No shell? The control-plane MCP")}`);
9811
+ case "widgets":
9812
+ return page(`Keys, users and chat on their site`, sectionOf(legacy, "## Wiring a real site").replace(/^## Wiring a real site\n+/, ""));
9813
+ default:
9814
+ return null;
9815
+ }
9816
+ }
9817
+ function readByAgent(opts) {
9818
+ if (opts.agent) return true;
9819
+ if (process.env.CLAUDECODE || process.env.APIBLAZE_AGENT) return true;
9820
+ return !(process.stdin.isTTY && process.stdout.isTTY);
9821
+ }
9822
+ async function ask2(question) {
9823
+ const readline3 = await import("readline");
9824
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stdout });
9825
+ const answer = await new Promise((res) => rl.question(question, res));
9826
+ rl.close();
9827
+ return !/^n/i.test(answer.trim());
9828
+ }
9829
+ async function runForPerson(found) {
9830
+ const P = B.product;
9831
+ const run2 = `npx ${B.cli}@latest`;
9832
+ console.log();
9833
+ console.log(` ${import_chalk25.default.bold(pitchText().split(". ")[0] + ".")}`);
9834
+ console.log(` ${pitchText().split(". ").slice(1).join(". ")}`);
9835
+ console.log();
9836
+ console.log(import_chalk25.default.dim(` With an AI assistant? Tell it: "Tell me what ${B.cli} can do for me using ${run2} skills"`));
9837
+ console.log();
9838
+ const report = folderReport(found, { forPerson: true });
9839
+ for (const l of report.lines) console.log(` ${l.replace(/`/g, "")}`);
9840
+ const port = backendPort(found);
9841
+ if (report.ownApi && found.listening) {
9842
+ const args = ["dev", "--port", String(found.listening), ...found.specFile ? ["--openapi", found.specFile] : []];
9843
+ if (!await ask2(` ${import_chalk25.default.bold("?")} Put your API on port ${found.listening} behind ${P} now? ${import_chalk25.default.dim("(Y/n)")} `)) {
9844
+ console.log(` Any time: ${import_chalk25.default.cyan(`${run2} ${args.join(" ")}`)}`);
9845
+ return;
9846
+ }
9847
+ console.log(import_chalk25.default.dim(` $ ${run2} ${args.join(" ")}`));
9848
+ const { spawnSync: spawnSync4 } = await import("child_process");
9849
+ const r = spawnSync4(process.execPath, [process.argv[1], ...args], { stdio: "inherit" });
9850
+ process.exitCode = r.status ?? 0;
9851
+ return;
9852
+ }
9853
+ if (report.ownApi) {
9854
+ console.log(` Start it${found.start ? ` (${found.start})` : ""}, then put it on the internet:`);
9855
+ console.log(` ${import_chalk25.default.cyan(`${run2} dev --port ${port ?? "<port>"}${found.specFile ? ` --openapi ${found.specFile}` : ""}`)}`);
9856
+ return;
9857
+ }
9858
+ if (!await ask2(` ${import_chalk25.default.bold("?")} See it on a sample app first? ${import_chalk25.default.dim("(Y/n)")} `)) {
9859
+ console.log(` Your own app, when it runs: ${import_chalk25.default.cyan(`${run2} dev --port <port>`)}`);
9860
+ return;
9861
+ }
9862
+ const { runDemo: runDemo2 } = await Promise.resolve().then(() => (init_demo(), demo_exports));
9863
+ await runDemo2({});
9864
+ }
9413
9865
  async function runSkill(opts = {}) {
9414
- if (!opts.install) {
9415
- process.stdout.write(opts.ship ? shipMarkdown() : heroMarkdown());
9866
+ if (opts.install) return installSkill(opts);
9867
+ if (opts.static) {
9868
+ process.stdout.write(skillMarkdown());
9416
9869
  return;
9417
9870
  }
9871
+ const topic = opts.ship ? "ship" : opts.topic;
9872
+ if (topic) {
9873
+ const md = topicMarkdown(topic);
9874
+ if (!md) {
9875
+ console.error(`Unknown page "${topic}". Pages: ${TOPICS.join(" \xB7 ")}`);
9876
+ process.exit(1);
9877
+ }
9878
+ process.stdout.write(md);
9879
+ return;
9880
+ }
9881
+ const found = await detectBackend(process.cwd());
9882
+ if (readByAgent(opts)) {
9883
+ process.stdout.write(heroMarkdown(found));
9884
+ return;
9885
+ }
9886
+ await runForPerson(found);
9887
+ }
9888
+ function installSkill(opts) {
9418
9889
  const md = skillMarkdown();
9419
- const target2 = import_path3.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
9420
- if (import_fs4.default.existsSync(target2) && !opts.force) {
9421
- console.error(import_chalk22.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
9890
+ const target2 = import_path6.default.resolve(process.cwd(), SKILL_RELATIVE_PATH);
9891
+ if (import_fs7.default.existsSync(target2) && !opts.force) {
9892
+ console.error(import_chalk25.default.yellow(`${SKILL_RELATIVE_PATH} already exists \u2014 re-run with --force to overwrite it.`));
9422
9893
  process.exit(1);
9423
9894
  }
9424
- import_fs4.default.mkdirSync(import_path3.default.dirname(target2), { recursive: true });
9425
- import_fs4.default.writeFileSync(target2, md, "utf8");
9426
- console.log(`${import_chalk22.default.green("\u2713")} Installed the ${import_chalk22.default.bold(SKILL_NAME)} skill: ${import_chalk22.default.bold(target2)}`);
9427
- console.log(import_chalk22.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli} dev / create for you.`));
9895
+ import_fs7.default.mkdirSync(import_path6.default.dirname(target2), { recursive: true });
9896
+ import_fs7.default.writeFileSync(target2, md, "utf8");
9897
+ console.log(`${import_chalk25.default.green("\u2713")} Installed the ${import_chalk25.default.bold(SKILL_NAME)} skill: ${import_chalk25.default.bold(target2)}`);
9898
+ console.log(import_chalk25.default.dim(` Claude Code picks it up in this project. Say "ship this API" and it runs npx ${B.cli}@latest dev / create for you.`));
9428
9899
  }
9429
9900
 
9430
9901
  // src/commands/integration.ts
9431
- var import_chalk23 = __toESM(require("chalk"));
9432
- var import_ora9 = __toESM(require("ora"));
9902
+ var import_chalk26 = __toESM(require("chalk"));
9903
+ var import_ora10 = __toESM(require("ora"));
9433
9904
  init_caller();
9434
9905
  init_api();
9435
9906
  init_brand();
9436
9907
  function file(f, indent2 = " ") {
9437
- console.log(` ${import_chalk23.default.cyan(f.file)}`);
9908
+ console.log(` ${import_chalk26.default.cyan(f.file)}`);
9438
9909
  for (const line of f.code.split("\n")) console.log(`${indent2}${line}`);
9439
9910
  console.log();
9440
9911
  }
9441
9912
  function bullets(lines, indent2 = " ") {
9442
- for (const l of lines) console.log(`${indent2}${import_chalk23.default.dim("\u2022")} ${l}`);
9913
+ for (const l of lines) console.log(`${indent2}${import_chalk26.default.dim("\u2022")} ${l}`);
9443
9914
  }
9444
9915
  async function runIntegration(name, opts = {}) {
9445
9916
  const stackRaw = (opts.stack ?? "nextjs").trim().toLowerCase();
@@ -9450,7 +9921,7 @@ async function runIntegration(name, opts = {}) {
9450
9921
  const project = name.trim().toLowerCase();
9451
9922
  const caller = requireCaller();
9452
9923
  if (opts.team && caller.mode === "anon") throw new Error("--team is not available in an anonymous workspace.");
9453
- const spinner = opts.json ? null : (0, import_ora9.default)(`Rendering the ${stack} kit for ${project}\u2026`).start();
9924
+ const spinner = opts.json ? null : (0, import_ora10.default)(`Rendering the ${stack} kit for ${project}\u2026`).start();
9454
9925
  let kit;
9455
9926
  try {
9456
9927
  kit = await producer(caller, integrationKitCall(project, stack));
@@ -9464,44 +9935,44 @@ async function runIntegration(name, opts = {}) {
9464
9935
  return;
9465
9936
  }
9466
9937
  console.log();
9467
- console.log(import_chalk23.default.bold(`Integration kit \u2014 ${kit.project} (${kit.stack})`));
9468
- console.log(import_chalk23.default.dim(` Edit only the frontend and the frontend-server. Your backend changes in one way: it trusts the ${B.product} identity headers.`));
9938
+ console.log(import_chalk26.default.bold(`Integration kit \u2014 ${kit.project} (${kit.stack})`));
9939
+ console.log(import_chalk26.default.dim(` Edit only the frontend and the frontend-server. Your backend changes in one way: it trusts the ${B.product} identity headers.`));
9469
9940
  console.log();
9470
- console.log(` ${import_chalk23.default.dim("Proxy: ")} ${import_chalk23.default.bold(kit.urls.proxy)}`);
9471
- console.log(` ${import_chalk23.default.dim("MCP: ")} ${kit.urls.mcp.join(import_chalk23.default.dim(" \xB7 "))}`);
9472
- console.log(` ${import_chalk23.default.dim("Portal: ")} ${kit.urls.portal}`);
9473
- console.log(` ${import_chalk23.default.dim("Spec: ")} ${kit.urls.openapi}`);
9941
+ console.log(` ${import_chalk26.default.dim("Proxy: ")} ${import_chalk26.default.bold(kit.urls.proxy)}`);
9942
+ console.log(` ${import_chalk26.default.dim("MCP: ")} ${kit.urls.mcp.join(import_chalk26.default.dim(" \xB7 "))}`);
9943
+ console.log(` ${import_chalk26.default.dim("Portal: ")} ${kit.urls.portal}`);
9944
+ console.log(` ${import_chalk26.default.dim("Spec: ")} ${kit.urls.openapi}`);
9474
9945
  const w = kit["1_frontend_widgets"];
9475
9946
  console.log();
9476
- console.log(import_chalk23.default.bold("1. Frontend \u2014 the widgets (keys + chat)"));
9477
- console.log(import_chalk23.default.dim(` ${w.note}`));
9947
+ console.log(import_chalk26.default.bold("1. Frontend \u2014 the widgets (keys + chat)"));
9948
+ console.log(import_chalk26.default.dim(` ${w.note}`));
9478
9949
  console.log();
9479
9950
  file(w.frontend);
9480
9951
  for (const r of w.server_routes) file(r);
9481
9952
  const c = kit["2_frontend_server_call_through"];
9482
- console.log(import_chalk23.default.bold("2. Frontend-server \u2014 call the proxy with the server key and say who is calling"));
9953
+ console.log(import_chalk26.default.bold("2. Frontend-server \u2014 call the proxy with the server key and say who is calling"));
9483
9954
  console.log();
9484
9955
  file(c);
9485
9956
  const t = kit["3_backend_trust_contract"];
9486
- console.log(import_chalk23.default.bold("3. Backend \u2014 trust the identity headers, and nothing else"));
9487
- for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${import_chalk23.default.cyan(h)} \u2014 ${why}`);
9957
+ console.log(import_chalk26.default.bold("3. Backend \u2014 trust the identity headers, and nothing else"));
9958
+ for (const [h, why] of Object.entries(t.headers_you_receive)) console.log(` ${import_chalk26.default.cyan(h)} \u2014 ${why}`);
9488
9959
  console.log();
9489
- console.log(` ${import_chalk23.default.dim("How to verify:")}`);
9960
+ console.log(` ${import_chalk26.default.dim("How to verify:")}`);
9490
9961
  bullets(t.how_to_verify);
9491
9962
  console.log();
9492
- console.log(` ${import_chalk23.default.dim("Never:")}`);
9963
+ console.log(` ${import_chalk26.default.dim("Never:")}`);
9493
9964
  bullets(t.what_not_to_do);
9494
9965
  console.log();
9495
- console.log(import_chalk23.default.bold("4. Env vars (server only)"));
9496
- for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${import_chalk23.default.cyan(k)} ${import_chalk23.default.dim("=")} ${v}`);
9966
+ console.log(import_chalk26.default.bold("4. Env vars (server only)"));
9967
+ for (const [k, v] of Object.entries(kit["4_env_vars"])) console.log(` ${import_chalk26.default.cyan(k)} ${import_chalk26.default.dim("=")} ${v}`);
9497
9968
  const o = kit["5_what_stays_open"];
9498
9969
  console.log();
9499
- console.log(import_chalk23.default.bold("5. What stays open"));
9500
- console.log(` ${import_chalk23.default.dim("Protected by a rule:")} ${o.protected.length ? o.protected.join(", ") : import_chalk23.default.dim("nothing")}`);
9501
- console.log(` ${import_chalk23.default.dim("Open to any identified caller:")} ${o.open.length ? o.open.join(", ") : import_chalk23.default.dim("nothing")}`);
9502
- console.log(import_chalk23.default.dim(` ${o.note}`));
9970
+ console.log(import_chalk26.default.bold("5. What stays open"));
9971
+ console.log(` ${import_chalk26.default.dim("Protected by a rule:")} ${o.protected.length ? o.protected.join(", ") : import_chalk26.default.dim("nothing")}`);
9972
+ console.log(` ${import_chalk26.default.dim("Open to any identified caller:")} ${o.open.length ? o.open.join(", ") : import_chalk26.default.dim("nothing")}`);
9973
+ console.log(import_chalk26.default.dim(` ${o.note}`));
9503
9974
  console.log();
9504
- console.log(import_chalk23.default.dim(` Raw JSON: npx ${B.cli} integration ${kit.project} --stack ${kit.stack} --json`));
9975
+ console.log(import_chalk26.default.dim(` Raw JSON: npx ${B.cli} integration ${kit.project} --stack ${kit.stack} --json`));
9505
9976
  console.log();
9506
9977
  }
9507
9978
 
@@ -9509,14 +9980,14 @@ async function runIntegration(name, opts = {}) {
9509
9980
  init_claim();
9510
9981
 
9511
9982
  // src/commands/logout.ts
9512
- var import_chalk24 = __toESM(require("chalk"));
9983
+ var import_chalk27 = __toESM(require("chalk"));
9513
9984
  init_auth();
9514
9985
  init_consumer_auth();
9515
9986
  async function runLogout(opts = {}) {
9516
9987
  const creds = loadCredentials();
9517
9988
  const consumer2 = loadConsumer();
9518
9989
  if (!creds && !consumer2) {
9519
- console.log(import_chalk24.default.yellow("You were not logged in."));
9990
+ console.log(import_chalk27.default.yellow("You were not logged in."));
9520
9991
  return;
9521
9992
  }
9522
9993
  let target2;
@@ -9543,18 +10014,18 @@ async function runLogout(opts = {}) {
9543
10014
  }
9544
10015
  if (target2 === "producer" || target2 === "all") {
9545
10016
  clearCredentials();
9546
- console.log(import_chalk24.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
10017
+ console.log(import_chalk27.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
9547
10018
  }
9548
10019
  if (target2 === "consumer" || target2 === "all") {
9549
10020
  clearConsumer();
9550
- console.log(import_chalk24.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
10021
+ console.log(import_chalk27.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
9551
10022
  }
9552
10023
  }
9553
10024
 
9554
10025
  // src/commands/flush.ts
9555
10026
  var import_chalk28 = __toESM(require("chalk"));
9556
- var fs13 = __toESM(require("fs"));
9557
- var path11 = __toESM(require("path"));
10027
+ var fs15 = __toESM(require("fs"));
10028
+ var path13 = __toESM(require("path"));
9558
10029
  init_auth();
9559
10030
  init_external_mcp();
9560
10031
  async function runFlush(opts) {
@@ -9562,7 +10033,7 @@ async function runFlush(opts) {
9562
10033
  const demos = await stopDemos2().catch(() => 0);
9563
10034
  if (demos) console.log(import_chalk28.default.dim(` Stopped ${demos} running demo(s).`));
9564
10035
  const dir = getApiblazeDir();
9565
- const entries2 = fs13.existsSync(dir) ? fs13.readdirSync(dir) : [];
10036
+ const entries2 = fs15.existsSync(dir) ? fs15.readdirSync(dir) : [];
9566
10037
  if (!entries2.length) {
9567
10038
  console.log(import_chalk28.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
9568
10039
  return;
@@ -9575,7 +10046,7 @@ async function runFlush(opts) {
9575
10046
  const footprint = apiblazeMcpFootprint();
9576
10047
  const mdFiles = ["claude", "codex"].map((k) => ({ k, file: agentInstructionFile(k) })).filter(({ file: file2 }) => {
9577
10048
  try {
9578
- return fs13.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
10049
+ return fs15.readFileSync(file2, "utf-8").includes("<!-- apiblaze:mcp:");
9579
10050
  } catch {
9580
10051
  return false;
9581
10052
  }
@@ -9584,7 +10055,7 @@ async function runFlush(opts) {
9584
10055
  console.log(import_chalk28.default.dim(` \u2022 MCP servers installed in other tools: ${[...footprint.claude, ...footprint.codex].join(", ")}`));
9585
10056
  }
9586
10057
  if (mdFiles.length) {
9587
- console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path11.basename(m.file)).join(" and ")} (here)`));
10058
+ console.log(import_chalk28.default.dim(` \u2022 the "use this MCP" blocks in ${mdFiles.map((m) => path13.basename(m.file)).join(" and ")} (here)`));
9588
10059
  }
9589
10060
  console.log(import_chalk28.default.dim(" It does NOT delete server-side proxies/keys \u2014 those GC or are revoked separately."));
9590
10061
  if (!opts.yes) {
@@ -9600,7 +10071,7 @@ async function runFlush(opts) {
9600
10071
  let removed = 0;
9601
10072
  for (const e of entries2) {
9602
10073
  try {
9603
- fs13.rmSync(path11.join(dir, e), { recursive: true, force: true });
10074
+ fs15.rmSync(path13.join(dir, e), { recursive: true, force: true });
9604
10075
  removed++;
9605
10076
  } catch (err) {
9606
10077
  console.log(import_chalk28.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
@@ -9615,14 +10086,14 @@ async function runFlush(opts) {
9615
10086
  for (const { k, file: file2 } of mdFiles) {
9616
10087
  let text = "";
9617
10088
  try {
9618
- text = fs13.readFileSync(file2, "utf-8");
10089
+ text = fs15.readFileSync(file2, "utf-8");
9619
10090
  } catch {
9620
10091
  continue;
9621
10092
  }
9622
10093
  const names = [...text.matchAll(/<!-- apiblaze:mcp:([^\s>]+) -->/g)].map((m) => m[1]);
9623
10094
  let cleared = 0;
9624
10095
  for (const n of new Set(names)) if (removeAgentInstruction(k, n)) cleared++;
9625
- if (cleared) console.log(` ${import_chalk28.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk28.default.bold(path11.basename(file2))}`);
10096
+ if (cleared) console.log(` ${import_chalk28.default.dim("\u2022")} removed ${cleared} APIblaze block(s) from ${import_chalk28.default.bold(path13.basename(file2))}`);
9626
10097
  }
9627
10098
  console.log(`${import_chalk28.default.green("\u2714")} Flushed \u2014 removed ${removed} item(s). You're fully logged out and clean.`);
9628
10099
  console.log(import_chalk28.default.dim(" Next `apiblaze create` starts a brand-new workspace with nothing carried over."));
@@ -9941,11 +10412,11 @@ async function runAgentChatRepl(opts) {
9941
10412
  }
9942
10413
  }
9943
10414
  const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
9944
- const ask2 = (q) => new Promise((res) => rl.question(q, res));
10415
+ const ask3 = (q) => new Promise((res) => rl.question(q, res));
9945
10416
  await turn(seedPrompt);
9946
10417
  for (; ; ) {
9947
10418
  console.log(footer());
9948
- const line = (await ask2(import_chalk32.default.green("you \u203A "))).trim();
10419
+ const line = (await ask3(import_chalk32.default.green("you \u203A "))).trim();
9949
10420
  if (!line) continue;
9950
10421
  const lower = line.toLowerCase();
9951
10422
  if (line === "/exit" || line === "/quit" || lower === "exit" || lower === "quit" || lower === "q") {
@@ -10323,7 +10794,7 @@ async function runOpenapi(projectArg, apiVersionArg) {
10323
10794
  }
10324
10795
 
10325
10796
  // src/commands/mcp.ts
10326
- var import_fs6 = __toESM(require("fs"));
10797
+ var import_fs8 = __toESM(require("fs"));
10327
10798
  var import_chalk37 = __toESM(require("chalk"));
10328
10799
  init_brand();
10329
10800
  init_auth();
@@ -10535,7 +11006,7 @@ async function runMcpPrompts(action2, project, name, opts) {
10535
11006
  if (!opts.file) throw new Error(`Usage: ${B.cli} mcp prompts set <project> <name> --file prompt.json`);
10536
11007
  let raw;
10537
11008
  try {
10538
- raw = JSON.parse(import_fs6.default.readFileSync(opts.file, "utf8"));
11009
+ raw = JSON.parse(import_fs8.default.readFileSync(opts.file, "utf8"));
10539
11010
  } catch (e) {
10540
11011
  throw new Error(`Could not read ${opts.file}: ${e instanceof Error ? e.message : String(e)}`);
10541
11012
  }
@@ -10634,7 +11105,7 @@ function describeJob(job) {
10634
11105
  }
10635
11106
  async function pollJob(jobId, opts = {}) {
10636
11107
  const started = Date.now();
10637
- const path14 = jobPath(jobId, opts.teamId);
11108
+ const path16 = jobPath(jobId, opts.teamId);
10638
11109
  let lastPhase = "";
10639
11110
  if (!opts.quiet) process.stdout.write(import_chalk38.default.dim(opts.label ?? "Deleting"));
10640
11111
  for (; ; ) {
@@ -10646,7 +11117,7 @@ async function pollJob(jobId, opts = {}) {
10646
11117
  );
10647
11118
  }
10648
11119
  await new Promise((r) => setTimeout(r, POLL_MS));
10649
- const job = await admin({ method: "GET", path: path14, summary: "poll cascade job" });
11120
+ const job = await admin({ method: "GET", path: path16, summary: "poll cascade job" });
10650
11121
  if (!opts.quiet) {
10651
11122
  const phase = describeJob(job);
10652
11123
  if (phase !== lastPhase) {
@@ -10791,7 +11262,7 @@ async function runJobStatus(jobId, opts) {
10791
11262
 
10792
11263
  // src/commands/export.ts
10793
11264
  var import_chalk41 = __toESM(require("chalk"));
10794
- var import_fs7 = require("fs");
11265
+ var import_fs9 = require("fs");
10795
11266
  init_admin();
10796
11267
  init_auth();
10797
11268
  init_resolve();
@@ -10840,7 +11311,7 @@ async function runExport(projectArg, versionArg, opts) {
10840
11311
  });
10841
11312
  if (!res.ok) throw new Error(`Download failed (${res.status}): ${await res.text().catch(() => "")}`);
10842
11313
  const buf = Buffer.from(await res.arrayBuffer());
10843
- (0, import_fs7.writeFileSync)(outFile, buf);
11314
+ (0, import_fs9.writeFileSync)(outFile, buf);
10844
11315
  console.log(import_chalk41.default.green(`\u2713 Saved ${outFile} (${(buf.length / 1024).toFixed(0)} KB)`));
10845
11316
  if (job.decryption_key) console.log(import_chalk41.default.yellow(`Decryption key (shown once): ${job.decryption_key}`));
10846
11317
  console.log(import_chalk41.default.dim("Read MIGRATION-REPORT.json inside the zip for the full fidelity ledger."));
@@ -10897,8 +11368,8 @@ function resolveRecipeName(raw, githubHandle, proxyName) {
10897
11368
  function normalizeName3(raw) {
10898
11369
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
10899
11370
  }
10900
- async function publicFetch(path14) {
10901
- const res = await fetch(`${RECIPES_BASE}/api/recipes${path14}`, {
11371
+ async function publicFetch(path16) {
11372
+ const res = await fetch(`${RECIPES_BASE}/api/recipes${path16}`, {
10902
11373
  headers: { accept: "application/json" }
10903
11374
  }).catch((err) => {
10904
11375
  throw new Error(`Could not reach the recipe registry at ${RECIPES_BASE} \u2014 ${err.message}`);
@@ -10951,11 +11422,11 @@ async function fetchRecipeFile(ref, caller) {
10951
11422
  Private recipes are visible only to the publisher's team \u2014 run \`apiblaze login\` if you are on it.`
10952
11423
  );
10953
11424
  }
10954
- const path14 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
11425
+ const path16 = revision !== void 0 ? `/recipes/${ref.handle}/${ref.slug}/${revision}` : `/recipes/${ref.handle}/${ref.slug}/file`;
10955
11426
  try {
10956
11427
  return await producer(caller, {
10957
11428
  method: "GET",
10958
- path: path14,
11429
+ path: path16,
10959
11430
  summary: `Read private recipe ${ref.name}${revision !== void 0 ? `@${revision}` : ""}`
10960
11431
  });
10961
11432
  } catch (err) {
@@ -12091,9 +12562,9 @@ function printEnvelopeViolations(violations) {
12091
12562
  console.log("bring-your-own-key case. Change the rules above, then publish again.");
12092
12563
  console.log();
12093
12564
  }
12094
- async function askPromotion(path14) {
12565
+ async function askPromotion(path16) {
12095
12566
  const { default: inquirer3 } = await import("inquirer");
12096
- const suggestedId = path14.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
12567
+ const suggestedId = path16.split(/[.[\]]/).filter(Boolean).slice(-2).join("_").toLowerCase().replace(/[^a-z0-9_]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "") || "value";
12097
12568
  const answers = await inquirer3.prompt([
12098
12569
  { type: "input", name: "prompt", message: " prompt them with:", validate: (v) => v.trim() ? true : "Required." },
12099
12570
  { type: "input", name: "example", message: " example:" },
@@ -12104,7 +12575,7 @@ async function askPromotion(path14) {
12104
12575
  const id = String(answers.id || suggestedId).toLowerCase().replace(/[^a-z0-9_]/g, "_");
12105
12576
  console.log(import_chalk44.default.dim(` \u2192 replaced with {{${id}}}`));
12106
12577
  return {
12107
- path: path14,
12578
+ path: path16,
12108
12579
  id,
12109
12580
  prompt: String(answers.prompt).trim(),
12110
12581
  ...answers.example ? { example: String(answers.example).trim() } : {},
@@ -13870,7 +14341,7 @@ async function providersMenu(cBase, clientLabel) {
13870
14341
  }
13871
14342
 
13872
14343
  // src/commands/spec.ts
13873
- var fs15 = __toESM(require("fs"));
14344
+ var fs17 = __toESM(require("fs"));
13874
14345
  var import_chalk55 = __toESM(require("chalk"));
13875
14346
  var import_ora25 = __toESM(require("ora"));
13876
14347
  init_admin();
@@ -13893,7 +14364,7 @@ async function runSpecSet(project, opts) {
13893
14364
  }
13894
14365
  let specContent;
13895
14366
  try {
13896
- specContent = fs15.readFileSync(opts.file, "utf-8");
14367
+ specContent = fs17.readFileSync(opts.file, "utf-8");
13897
14368
  } catch {
13898
14369
  console.error(import_chalk55.default.red(`Cannot read file: ${opts.file}`));
13899
14370
  process.exit(1);
@@ -14653,7 +15124,7 @@ async function patchSetting(proj2, s, value, cfg) {
14653
15124
  }
14654
15125
  const body = s.toPatch(value, cfg);
14655
15126
  const method = s.putLimits ? "PUT" : "PATCH";
14656
- const path14 = s.putLimits ? `/projects/${proj2.projectId}/${proj2.apiVersion}/limits` : `/projects/${proj2.projectId}/${proj2.apiVersion}`;
15127
+ const path16 = s.putLimits ? `/projects/${proj2.projectId}/${proj2.apiVersion}/limits` : `/projects/${proj2.projectId}/${proj2.apiVersion}`;
14657
15128
  if (s.needsTenant) {
14658
15129
  const tenant2 = proj2.tenant ?? cfg.tenant ?? cfg.default_tenant;
14659
15130
  if (!tenant2) throw new Error(`Could not tell which tenant ${proj2.projectName} serves \u2014 pass it via \`apiblaze config ${proj2.projectName} --tenant <slug>\` is not supported yet; run \`apiblaze projects\` and check the project has a tenant.`);
@@ -14663,7 +15134,7 @@ async function patchSetting(proj2, s, value, cfg) {
14663
15134
  try {
14664
15135
  await admin({
14665
15136
  method,
14666
- path: path14,
15137
+ path: path16,
14667
15138
  body,
14668
15139
  summary: `Set ${s.key} = ${JSON.stringify(value)}`
14669
15140
  });
@@ -15299,11 +15770,11 @@ async function resolveTransport(opts) {
15299
15770
  const token = getAccessToken();
15300
15771
  return {
15301
15772
  tenant: tenant2,
15302
- call: async (path14, method = "GET", body) => {
15773
+ call: async (path16, method = "GET", body) => {
15303
15774
  const res = await fetch(`${DASHBOARD_BASE9}/api/cli/iam`, {
15304
15775
  method: "POST",
15305
15776
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
15306
- body: JSON.stringify({ tenant: tenant2, path: path14, method, body })
15777
+ body: JSON.stringify({ tenant: tenant2, path: path16, method, body })
15307
15778
  });
15308
15779
  const data = await res.json().catch(() => ({}));
15309
15780
  if (!res.ok) throw new ApiError(res.status, data?.details ?? data?.error ?? res.statusText, data);
@@ -15321,8 +15792,8 @@ async function resolveTransport(opts) {
15321
15792
  return {
15322
15793
  tenant: fresh.tenant,
15323
15794
  selfEmail: fresh.email,
15324
- call: async (path14, method = "GET", body) => {
15325
- const res = await fetch(`${IAM_BASE}${path14}`, {
15795
+ call: async (path16, method = "GET", body) => {
15796
+ const res = await fetch(`${IAM_BASE}${path16}`, {
15326
15797
  method,
15327
15798
  headers: {
15328
15799
  "Content-Type": "application/json",
@@ -15747,28 +16218,28 @@ async function runConsumerApikeys(opts) {
15747
16218
  // src/commands/sidecar.ts
15748
16219
  var import_chalk62 = __toESM(require("chalk"));
15749
16220
  var import_ora31 = __toESM(require("ora"));
15750
- var fs16 = __toESM(require("fs"));
15751
- var path12 = __toESM(require("path"));
16221
+ var fs18 = __toESM(require("fs"));
16222
+ var path14 = __toESM(require("path"));
15752
16223
  init_admin();
15753
16224
  init_resolve();
15754
16225
  init_auth();
15755
16226
  function detectNextProject(root) {
15756
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs16.existsSync(path12.join(root, f)));
16227
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs18.existsSync(path14.join(root, f)));
15757
16228
  let hasDep = false;
15758
16229
  try {
15759
- const pkg = JSON.parse(fs16.readFileSync(path12.join(root, "package.json"), "utf8"));
16230
+ const pkg = JSON.parse(fs18.readFileSync(path14.join(root, "package.json"), "utf8"));
15760
16231
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
15761
16232
  } catch {
15762
16233
  }
15763
- const appDir = fs16.existsSync(path12.join(root, "app")) || fs16.existsSync(path12.join(root, "src", "app"));
15764
- const pagesDir = fs16.existsSync(path12.join(root, "pages")) || fs16.existsSync(path12.join(root, "src", "pages"));
16234
+ const appDir = fs18.existsSync(path14.join(root, "app")) || fs18.existsSync(path14.join(root, "src", "app"));
16235
+ const pagesDir = fs18.existsSync(path14.join(root, "pages")) || fs18.existsSync(path14.join(root, "src", "pages"));
15765
16236
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
15766
16237
  }
15767
16238
  function upsertEnvLocal(root, token) {
15768
- const p = path12.join(root, ".env.local");
16239
+ const p = path14.join(root, ".env.local");
15769
16240
  let existing = "";
15770
16241
  try {
15771
- existing = fs16.readFileSync(p, "utf8");
16242
+ existing = fs18.readFileSync(p, "utf8");
15772
16243
  } catch {
15773
16244
  }
15774
16245
  const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
@@ -15783,15 +16254,15 @@ function upsertEnvLocal(root, token) {
15783
16254
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
15784
16255
  `;
15785
16256
  }
15786
- fs16.writeFileSync(p, next);
16257
+ fs18.writeFileSync(p, next);
15787
16258
  return had ? "rotated" : "created";
15788
16259
  }
15789
16260
  function installSidecarPackage(root) {
15790
- if (fs16.existsSync(path12.join(root, "node_modules", "apiblaze", "package.json"))) {
16261
+ if (fs18.existsSync(path14.join(root, "node_modules", "apiblaze", "package.json"))) {
15791
16262
  console.log(` ${import_chalk62.default.green("\u2713")} apiblaze package already installed`);
15792
16263
  return;
15793
16264
  }
15794
- const has = (f) => fs16.existsSync(path12.join(root, f));
16265
+ const has = (f) => fs18.existsSync(path14.join(root, f));
15795
16266
  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" };
15796
16267
  const spinner = (0, import_ora31.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
15797
16268
  try {
@@ -15804,7 +16275,7 @@ function installSidecarPackage(root) {
15804
16275
  }
15805
16276
  function readEnvKey(root) {
15806
16277
  try {
15807
- const s = fs16.readFileSync(path12.join(root, ".env.local"), "utf8");
16278
+ const s = fs18.readFileSync(path14.join(root, ".env.local"), "utf8");
15808
16279
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
15809
16280
  return m ? m[1].trim() : null;
15810
16281
  } catch {
@@ -15812,16 +16283,16 @@ function readEnvKey(root) {
15812
16283
  }
15813
16284
  }
15814
16285
  function ensureGitignored(root) {
15815
- const p = path12.join(root, ".gitignore");
16286
+ const p = path14.join(root, ".gitignore");
15816
16287
  let c = "";
15817
16288
  try {
15818
- c = fs16.readFileSync(p, "utf8");
16289
+ c = fs18.readFileSync(p, "utf8");
15819
16290
  } catch {
15820
16291
  }
15821
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs16.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
16292
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs18.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
15822
16293
  }
15823
16294
  function wireInstrumentation(root) {
15824
- const existing = ["instrumentation.ts", "instrumentation.js", path12.join("src", "instrumentation.ts")].map((c) => path12.join(root, c)).find((f) => fs16.existsSync(f));
16295
+ const existing = ["instrumentation.ts", "instrumentation.js", path14.join("src", "instrumentation.ts")].map((c) => path14.join(root, c)).find((f) => fs18.existsSync(f));
15825
16296
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
15826
16297
 
15827
16298
  export function register() {
@@ -15829,18 +16300,18 @@ export function register() {
15829
16300
  }
15830
16301
  `;
15831
16302
  if (!existing) {
15832
- fs16.writeFileSync(path12.join(root, "instrumentation.ts"), body);
16303
+ fs18.writeFileSync(path14.join(root, "instrumentation.ts"), body);
15833
16304
  return "created";
15834
16305
  }
15835
- const cur = fs16.readFileSync(existing, "utf8");
16306
+ const cur = fs18.readFileSync(existing, "utf8");
15836
16307
  if (cur.includes("apiblaze/sidecar")) return "present";
15837
16308
  if (/export\s+function\s+register\s*\(/.test(cur)) {
15838
- fs16.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
16309
+ fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
15839
16310
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
15840
16311
  apiblaze();`));
15841
16312
  return "patched";
15842
16313
  }
15843
- fs16.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
16314
+ fs18.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
15844
16315
  ${cur}
15845
16316
  // call apiblaze() inside your register() export.
15846
16317
  `);
@@ -15923,17 +16394,17 @@ export default async function Page() {
15923
16394
  function generateInspector(root, router) {
15924
16395
  try {
15925
16396
  if (router === "pages") {
15926
- const dir2 = fs16.existsSync(path12.join(root, "src", "pages")) ? path12.join(root, "src", "pages") : path12.join(root, "pages");
15927
- const f2 = path12.join(dir2, "abz-inspector.tsx");
15928
- fs16.writeFileSync(f2, INSPECTOR_PAGE);
15929
- return path12.relative(root, f2);
15930
- }
15931
- const base2 = fs16.existsSync(path12.join(root, "src", "app")) ? path12.join(root, "src", "app") : path12.join(root, "app");
15932
- const dir = path12.join(base2, "abz-inspector");
15933
- fs16.mkdirSync(dir, { recursive: true });
15934
- const f = path12.join(dir, "page.tsx");
15935
- fs16.writeFileSync(f, INSPECTOR_PAGE);
15936
- return path12.relative(root, f);
16397
+ const dir2 = fs18.existsSync(path14.join(root, "src", "pages")) ? path14.join(root, "src", "pages") : path14.join(root, "pages");
16398
+ const f2 = path14.join(dir2, "abz-inspector.tsx");
16399
+ fs18.writeFileSync(f2, INSPECTOR_PAGE);
16400
+ return path14.relative(root, f2);
16401
+ }
16402
+ const base2 = fs18.existsSync(path14.join(root, "src", "app")) ? path14.join(root, "src", "app") : path14.join(root, "app");
16403
+ const dir = path14.join(base2, "abz-inspector");
16404
+ fs18.mkdirSync(dir, { recursive: true });
16405
+ const f = path14.join(dir, "page.tsx");
16406
+ fs18.writeFileSync(f, INSPECTOR_PAGE);
16407
+ return path14.relative(root, f);
15937
16408
  } catch {
15938
16409
  return null;
15939
16410
  }
@@ -15973,7 +16444,7 @@ async function runAnonymousInit(root, router, opts) {
15973
16444
  console.log(import_chalk62.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
15974
16445
  }
15975
16446
  async function runSidecar(opts) {
15976
- const root = path12.resolve(opts.dir ?? process.cwd());
16447
+ const root = path14.resolve(opts.dir ?? process.cwd());
15977
16448
  const detected2 = detectNextProject(root);
15978
16449
  if (!detected2.found) {
15979
16450
  console.log(import_chalk62.default.yellow(`No Next.js project detected in ${root}.`));
@@ -16032,7 +16503,7 @@ async function runSidecar(opts) {
16032
16503
  console.log(` 2. The origins your app calls appear as ${import_chalk62.default.bold("candidates")} \u2014 list them: ${import_chalk62.default.cyan("apiblaze sidecar")}`);
16033
16504
  console.log(` 3. Approve the ones to route: ${import_chalk62.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
16034
16505
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
16035
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk62.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path12.dirname(inspectorPath)} before shipping)`);
16506
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk62.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path14.dirname(inspectorPath)} before shipping)`);
16036
16507
  if (switchingTeam) console.log(import_chalk62.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
16037
16508
  console.log("");
16038
16509
  console.log(import_chalk62.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
@@ -16768,17 +17239,17 @@ var import_chalk68 = __toESM(require("chalk"));
16768
17239
  init_consumer_auth();
16769
17240
  var SANDBOX_TENANT = "portalteststenant";
16770
17241
  var SANDBOX_PROXY = "portaltestproxypreapproved@2.7.0";
16771
- function proxyUrlFor(spec2, path14 = "/get") {
17242
+ function proxyUrlFor(spec2, path16 = "/get") {
16772
17243
  const [name, version2 = "1.0.0"] = spec2.split("@");
16773
- return `https://${name}.abz.run/${version2}/prod${path14.startsWith("/") ? path14 : `/${path14}`}`;
17244
+ return `https://${name}.abz.run/${version2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
16774
17245
  }
16775
17246
  function isPortalAudience(aud) {
16776
17247
  return /^https:\/\/[a-z0-9-]+\.portal\.apiblaze\.com\b/i.test(aud);
16777
17248
  }
16778
17249
  var log2 = (s) => process.stderr.write(s + "\n");
16779
- function probeUrlFor(audience, path14 = "/") {
17250
+ function probeUrlFor(audience, path16 = "/") {
16780
17251
  const base2 = audience.replace(/\/+$/, "");
16781
- return `${base2}/prod${path14.startsWith("/") ? path14 : `/${path14}`}`;
17252
+ return `${base2}/prod${path16.startsWith("/") ? path16 : `/${path16}`}`;
16782
17253
  }
16783
17254
  function tamper(token) {
16784
17255
  const parts = token.split(".");
@@ -17723,7 +18194,7 @@ program.command("demo").description(`See what ${B.product} does in a minute: a s
17723
18194
  }
17724
18195
  await runDemo(opts);
17725
18196
  });
17726
- program.command("skill").alias("skills").description(`Print or install the Claude Code skill that teaches Claude to ship your API safely with ${B.product}`).option("--install", "Write it to .claude/skills/ship-safely/SKILL.md in the current project (instead of printing it)").option("--force", "Overwrite an existing SKILL.md").option("--ship", "Print the steps for putting it in front of your own app").action(action((opts) => runSkill(opts)));
18197
+ program.command("skill").alias("skills").description(`What ${B.product} does, for you or your AI assistant: checks this folder for a backend and offers the next step (put it on the internet, or a sample app)`).argument("[page]", "One page instead: ship \xB7 keys \xB7 signin \xB7 rules \xB7 limits \xB7 mcp \xB7 widgets").option("--agent", "Print what an AI assistant reads, even at a terminal").option("--static", "Print the installed text, with no folder check (for generated docs)").option("--install", "Write it to .claude/skills/ship-safely/SKILL.md in the current project (instead of printing it)").option("--force", "Overwrite an existing SKILL.md").option("--ship", "Same as the ship page: the steps for putting it in front of your own app").action(action((page, opts) => runSkill({ ...opts, topic: page })));
17727
18198
  var agent = program.command("agent").description("Chat with an assistant that builds and runs your APIs (billed per turn)").option("--team <id|name>", "Team to work in (defaults to your active team)").action(action((opts) => runAgent(opts)));
17728
18199
  agent.command("authz").description("Chat to design and turn on access rules for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runAuthz(project, apiVersion)));
17729
18200
  program.command("rule").description("Lock resources to the person who created them \u2014 or describe any access rule in plain English (that one is billed per turn)").argument("<project>", "Project name or id").argument("[sentence]", 'Optional rule in plain English, e.g. "users see only their own rows". Omit to pick from what the spec can protect.').option("--enforce", "Turn enforcement on right after saving (default: save in shadow mode, then ask)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((project, sentence, opts) => runRule(project, sentence, opts)));
@@ -17742,7 +18213,7 @@ mcpCommon(mcpOps.command("set").description("Override one tool").argument("<proj
17742
18213
  mcpCommon(mcpOps.command("reset").description("Back to the automatic default for one tool").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpOps("reset", project, tool, opts)));
17743
18214
  mcpCommon(mcpCmd.command("ui").description("Print a tool's MCP Apps UI resource (ui://{project}/{tool}) \u2014 the automatic viewer").argument("<project>", "Project name or id").argument("<tool>", "Tool name")).action(action((project, tool, opts) => runMcpUi(project, tool, opts)));
17744
18215
  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("--verbose", "Show the per-turn proxy curl trace (hidden by default)").option("-p, --prompt <question>", "One-shot question: answered through the external agent CLI after an MCP install, or by apichat itself (exits after answering when there is no TTY)").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.").option("--remove-mcp <cli>", "Disconnect this proxy from an external agent CLI (claude | codex): removes the MCP server and the standing instruction").action(action((project, opts) => {
17745
- const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs9.default.existsSync(project));
18216
+ const looksLikeSource = !!project && (/^https?:\/\//i.test(project) || /\.(ya?ml|json)$/i.test(project) || import_fs11.default.existsSync(project));
17746
18217
  const target2 = opts.target ?? (looksLikeSource ? project : void 0);
17747
18218
  return runApichat({ ...opts, target: target2, project: looksLikeSource ? void 0 : project, openapispec: opts.openapispec ?? opts.openapi });
17748
18219
  }));