apiblaze 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -302,10 +302,10 @@ var init_anon_cred = __esm({
302
302
 
303
303
  // src/index.ts
304
304
  var import_commander = require("commander");
305
- var import_chalk30 = __toESM(require("chalk"));
305
+ var import_chalk32 = __toESM(require("chalk"));
306
306
 
307
307
  // package.json
308
- var version = "0.9.0";
308
+ var version = "0.11.0";
309
309
 
310
310
  // src/index.ts
311
311
  init_types();
@@ -2207,7 +2207,7 @@ function renderTrace() {
2207
2207
  console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
2208
2208
  console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
2209
2209
  console.log(
2210
- import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`):\n")
2210
+ import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one from the Developers section of dashboard.apiblaze.com, then\n`export APIBLAZE_CONTROLPLANE_APIKEY=sk_...`).\nFull API reference: https://api.apiblaze.com/openapi.json\n")
2211
2211
  );
2212
2212
  entries.forEach((e, i) => {
2213
2213
  const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
@@ -2477,6 +2477,11 @@ async function runRename(project, opts) {
2477
2477
  await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
2478
2478
  }
2479
2479
 
2480
+ // src/commands/config-browse.ts
2481
+ var import_chalk26 = __toESM(require("chalk"));
2482
+ var import_ora11 = __toESM(require("ora"));
2483
+ init_auth();
2484
+
2480
2485
  // src/commands/domain.ts
2481
2486
  var import_chalk21 = __toESM(require("chalk"));
2482
2487
  var import_ora7 = __toESM(require("ora"));
@@ -2680,15 +2685,15 @@ async function runTenantCors(opts) {
2680
2685
  process.exit(1);
2681
2686
  }
2682
2687
  const { teamId } = await resolveTeam(opts.team);
2683
- const origins2 = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2684
- const cors = origins2.length ? { allowed_origins: origins2 } : null;
2688
+ const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
2689
+ const cors = origins.length ? { allowed_origins: origins } : null;
2685
2690
  const spinner = (0, import_ora8.default)("Updating CORS...").start();
2686
2691
  try {
2687
2692
  await admin({
2688
2693
  method: "PUT",
2689
2694
  path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(opts.tenant)}/cors`,
2690
2695
  body: { cors },
2691
- summary: `Set CORS for tenant ${opts.tenant} \u2192 ${origins2.length ? origins2.join(", ") : "(cleared)"}`
2696
+ summary: `Set CORS for tenant ${opts.tenant} \u2192 ${origins.length ? origins.join(", ") : "(cleared)"}`
2692
2697
  });
2693
2698
  spinner.succeed(`CORS updated for ${opts.tenant}.`);
2694
2699
  } catch (err) {
@@ -2697,88 +2702,10 @@ async function runTenantCors(opts) {
2697
2702
  }
2698
2703
  }
2699
2704
 
2700
- // src/commands/key.ts
2701
- var import_chalk23 = __toESM(require("chalk"));
2702
- var import_ora9 = __toESM(require("ora"));
2703
- async function runApikeysMenu(opts) {
2704
- await runKeyList(opts);
2705
- if (opts.json) return;
2706
- const { default: inquirer2 } = await import("inquirer");
2707
- const { make } = await inquirer2.prompt([
2708
- { type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
2709
- ]);
2710
- if (!make) return;
2711
- const { desc } = await inquirer2.prompt([
2712
- { type: "input", name: "desc", message: "Description (optional):" }
2713
- ]);
2714
- await runKeyMint({ ...opts, desc: desc || void 0 });
2715
- }
2716
- async function runKeyList(opts) {
2717
- const { teamId, teamName } = await resolveTeam(opts.team);
2718
- const out = await admin({
2719
- method: "GET",
2720
- path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
2721
- summary: `List developer keys for team ${teamName ?? teamId}`
2722
- });
2723
- const keys = out?.keys ?? [];
2724
- if (opts.json) {
2725
- console.log(JSON.stringify(keys));
2726
- return;
2727
- }
2728
- if (!keys.length) {
2729
- console.log(import_chalk23.default.yellow("No developer keys."));
2730
- return;
2731
- }
2732
- for (const k of keys) {
2733
- console.log(` ${import_chalk23.default.bold(k.key_id ?? k.id)} ${import_chalk23.default.dim(k.description ?? "")} ${import_chalk23.default.dim(k.expires_at ?? "no expiry")}`);
2734
- }
2735
- }
2736
- async function runKeyMint(opts) {
2737
- const { teamId } = await resolveTeam(opts.team);
2738
- const body = { role: "consumer-admin" };
2739
- if (opts.desc) body.description = opts.desc;
2740
- if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
2741
- const spinner = (0, import_ora9.default)("Minting key...").start();
2742
- try {
2743
- const out = await admin({
2744
- method: "POST",
2745
- path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
2746
- body,
2747
- summary: `Mint a consumer-admin developer key`
2748
- });
2749
- spinner.succeed("Key minted.");
2750
- if (opts.json) {
2751
- console.log(JSON.stringify(out));
2752
- return;
2753
- }
2754
- console.log(` ${import_chalk23.default.bold("key_id")}: ${out?.key_id}`);
2755
- console.log(` ${import_chalk23.default.bold("key")}: ${import_chalk23.default.green(out?.key)} ${import_chalk23.default.dim("(shown once \u2014 store it now)")}`);
2756
- if (out?.expires_at) console.log(` ${import_chalk23.default.dim("expires:")} ${out.expires_at}`);
2757
- } catch (err) {
2758
- spinner.fail("Mint failed.");
2759
- throw err;
2760
- }
2761
- }
2762
- async function runKeyRevoke(keyId, opts) {
2763
- const { teamId } = await resolveTeam(opts.team);
2764
- const spinner = (0, import_ora9.default)("Revoking key...").start();
2765
- try {
2766
- await admin({
2767
- method: "DELETE",
2768
- path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
2769
- summary: `Revoke developer key ${keyId}`
2770
- });
2771
- spinner.succeed(`Revoked ${keyId}.`);
2772
- } catch (err) {
2773
- spinner.fail("Revoke failed.");
2774
- throw err;
2775
- }
2776
- }
2777
-
2778
2705
  // src/commands/spec.ts
2779
2706
  var fs6 = __toESM(require("fs"));
2780
- var import_chalk24 = __toESM(require("chalk"));
2781
- var import_ora10 = __toESM(require("ora"));
2707
+ var import_chalk23 = __toESM(require("chalk"));
2708
+ var import_ora9 = __toESM(require("ora"));
2782
2709
  async function runSpecGet(project, opts) {
2783
2710
  const { teamId } = await resolveTeam(opts.team);
2784
2711
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
@@ -2791,19 +2718,19 @@ async function runSpecGet(project, opts) {
2791
2718
  }
2792
2719
  async function runSpecSet(project, opts) {
2793
2720
  if (!opts.file) {
2794
- console.error(import_chalk24.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
2721
+ console.error(import_chalk23.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
2795
2722
  process.exit(1);
2796
2723
  }
2797
2724
  let specContent;
2798
2725
  try {
2799
2726
  specContent = fs6.readFileSync(opts.file, "utf-8");
2800
2727
  } catch {
2801
- console.error(import_chalk24.default.red(`Cannot read file: ${opts.file}`));
2728
+ console.error(import_chalk23.default.red(`Cannot read file: ${opts.file}`));
2802
2729
  process.exit(1);
2803
2730
  }
2804
2731
  const { teamId } = await resolveTeam(opts.team);
2805
2732
  const proj2 = await resolveProject(teamId, project, opts.apiversion);
2806
- const spinner = (0, import_ora10.default)("Uploading spec...").start();
2733
+ const spinner = (0, import_ora9.default)("Uploading spec...").start();
2807
2734
  try {
2808
2735
  const out = await admin({
2809
2736
  method: "POST",
@@ -2820,12 +2747,12 @@ async function runSpecSet(project, opts) {
2820
2747
  }
2821
2748
 
2822
2749
  // src/commands/agent.ts
2823
- var import_chalk26 = __toESM(require("chalk"));
2824
- var import_ora11 = __toESM(require("ora"));
2750
+ var import_chalk25 = __toESM(require("chalk"));
2751
+ var import_ora10 = __toESM(require("ora"));
2825
2752
  init_auth();
2826
2753
 
2827
2754
  // src/lib/tools.ts
2828
- var import_chalk25 = __toESM(require("chalk"));
2755
+ var import_chalk24 = __toESM(require("chalk"));
2829
2756
  init_api();
2830
2757
  async function proj(teamId, name, version2) {
2831
2758
  return resolveProject(teamId, name, version2);
@@ -2843,15 +2770,15 @@ var TOOLS = [
2843
2770
  const key = keys.dev ?? Object.values(keys)[0];
2844
2771
  const url = `https://${a.name}.abz.run/${version2}/dev`;
2845
2772
  const tryIt = buildTryItCurl(url, auth, key);
2846
- const lines = [` ${import_chalk25.default.dim("Proxy URL:")} ${import_chalk25.default.bold(url)}`];
2847
- if (res.devPortal) lines.push(` ${import_chalk25.default.dim("Dev portal:")} ${res.devPortal}`);
2773
+ const lines = [` ${import_chalk24.default.dim("Proxy URL:")} ${import_chalk24.default.bold(url)}`];
2774
+ if (res.devPortal) lines.push(` ${import_chalk24.default.dim("Dev portal:")} ${res.devPortal}`);
2848
2775
  const envs = Object.keys(keys);
2849
2776
  if (envs.length) {
2850
- lines.push("", ` ${import_chalk25.default.bold("API keys")} ${import_chalk25.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
2777
+ lines.push("", ` ${import_chalk24.default.bold("API keys")} ${import_chalk24.default.dim("(bootstrapped \u2014 send as the X-API-Key header; shown once):")}`);
2851
2778
  const w = Math.max(...envs.map((e) => e.length));
2852
- for (const env of envs) lines.push(` ${import_chalk25.default.cyan(env.padEnd(w))} ${import_chalk25.default.green(keys[env])}`);
2779
+ for (const env of envs) lines.push(` ${import_chalk24.default.cyan(env.padEnd(w))} ${import_chalk24.default.green(keys[env])}`);
2853
2780
  }
2854
- if (tryIt) lines.push("", ` ${import_chalk25.default.dim("Try it:")}`, ` ${import_chalk25.default.cyan(tryIt)}`);
2781
+ if (tryIt) lines.push("", ` ${import_chalk24.default.dim("Try it:")}`, ` ${import_chalk24.default.cyan(tryIt)}`);
2855
2782
  return { ...res, proxy_url: url, keys, ...tryIt ? { try_it: tryIt } : {}, display: lines.join("\n") };
2856
2783
  }
2857
2784
  },
@@ -3006,23 +2933,23 @@ function truncate(value, max = 1500) {
3006
2933
  }
3007
2934
  function printCost(llm) {
3008
2935
  const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
3009
- console.log(import_chalk26.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk26.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
2936
+ console.log(import_chalk25.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk25.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
3010
2937
  }
3011
2938
  async function runAgent(opts) {
3012
2939
  requireAuth();
3013
2940
  const { teamId, teamName } = await resolveTeam(opts.team);
3014
2941
  const { default: inquirer2 } = await import("inquirer");
3015
- console.log(import_chalk26.default.bold("APIblaze agent") + import_chalk26.default.dim(` \xB7 team ${teamName ?? teamId}`));
3016
- console.log(import_chalk26.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
2942
+ console.log(import_chalk25.default.bold("APIblaze agent") + import_chalk25.default.dim(` \xB7 team ${teamName ?? teamId}`));
2943
+ console.log(import_chalk25.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
3017
2944
  const history = [];
3018
2945
  while (true) {
3019
- const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk26.default.cyan("you") + " \u203A" }]);
2946
+ const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk25.default.cyan("you") + " \u203A" }]);
3020
2947
  const text = (input ?? "").trim();
3021
2948
  if (!text) continue;
3022
2949
  if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
3023
2950
  history.push({ role: "user", content: text });
3024
2951
  for (let step = 0; step < MAX_TOOL_STEPS; step++) {
3025
- const spinner = (0, import_ora11.default)({ text: "thinking...", color: "magenta" }).start();
2952
+ const spinner = (0, import_ora10.default)({ text: "thinking...", color: "magenta" }).start();
3026
2953
  let resp;
3027
2954
  try {
3028
2955
  resp = await callAgent(history, teamId);
@@ -3030,21 +2957,21 @@ async function runAgent(opts) {
3030
2957
  } catch (err) {
3031
2958
  spinner.stop();
3032
2959
  if (err instanceof ApiError && err.status === 402) {
3033
- console.log(import_chalk26.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
2960
+ console.log(import_chalk25.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
3034
2961
  break;
3035
2962
  }
3036
2963
  throw err;
3037
2964
  }
3038
2965
  history.push({ role: "assistant", content: resp.raw });
3039
2966
  printCost(resp.llm);
3040
- if (resp.reply) console.log(import_chalk26.default.green("agent") + " \u203A " + resp.reply);
2967
+ if (resp.reply) console.log(import_chalk25.default.green("agent") + " \u203A " + resp.reply);
3041
2968
  if (!resp.action) break;
3042
2969
  const tool = findTool(resp.action.tool);
3043
2970
  if (!tool) {
3044
2971
  history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
3045
2972
  continue;
3046
2973
  }
3047
- const runSpinner = (0, import_ora11.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
2974
+ const runSpinner = (0, import_ora10.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
3048
2975
  try {
3049
2976
  const result = await tool.run(resp.action.args, { teamId });
3050
2977
  runSpinner.succeed(`${tool.name} \u2713`);
@@ -3062,16 +2989,763 @@ async function runAgent(opts) {
3062
2989
  }
3063
2990
  renderTrace();
3064
2991
  if (step === MAX_TOOL_STEPS - 1) {
3065
- console.log(import_chalk26.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
2992
+ console.log(import_chalk25.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
3066
2993
  }
3067
2994
  }
3068
2995
  }
3069
- console.log(import_chalk26.default.dim("\nBye."));
2996
+ console.log(import_chalk25.default.dim("\nBye."));
3070
2997
  }
3071
2998
 
3072
- // src/commands/consumer.ts
2999
+ // src/commands/config-browse.ts
3000
+ var fullThrottle = (cfg, patch) => ({
3001
+ throttling: { ...cfg.throttling ?? {}, ...patch }
3002
+ });
3003
+ var fullPolicy = (cfg, patch) => {
3004
+ const merged = { mode: "passthrough", ...cfg.requests_policy ?? {}, ...patch };
3005
+ return { requests_policy: merged };
3006
+ };
3007
+ var SETTINGS = [
3008
+ {
3009
+ key: "display_name",
3010
+ label: "Display name",
3011
+ group: "Basics",
3012
+ type: "string",
3013
+ desc: "Human-friendly label shown in the dashboard and portal",
3014
+ read: (cfg) => cfg.display_name ?? cfg.project_display_name,
3015
+ toPatch: (v) => ({ display_name: v })
3016
+ },
3017
+ {
3018
+ key: "target_url",
3019
+ label: "Upstream target URL",
3020
+ group: "Basics",
3021
+ type: "string",
3022
+ desc: "Where the proxy forwards requests (cascades to every environment)",
3023
+ read: (cfg) => cfg.target_url ?? dig(cfg, "environments.prod.target") ?? Object.values(cfg.environments ?? {})[0]?.target,
3024
+ toPatch: (v) => ({ target_url: v })
3025
+ },
3026
+ {
3027
+ key: "enabled",
3028
+ label: "Proxy serving",
3029
+ group: "Traffic & limits",
3030
+ type: "boolean",
3031
+ desc: "Master on/off switch \u2014 off stops serving traffic",
3032
+ read: (cfg) => cfg.enabled !== false,
3033
+ toPatch: (v) => ({ enabled: v })
3034
+ },
3035
+ {
3036
+ key: "listenToTraffic",
3037
+ label: "Traffic capture",
3038
+ group: "Traffic & limits",
3039
+ type: "boolean",
3040
+ desc: "Capture request samples to build your OpenAPI spec from real traffic",
3041
+ read: (cfg) => cfg.listenToTraffic !== false,
3042
+ toPatch: (v) => ({ listenToTraffic: v })
3043
+ },
3044
+ {
3045
+ key: "throttling.userRateLimit",
3046
+ label: "Caller rate limit (req/s)",
3047
+ group: "Traffic & limits",
3048
+ type: "number",
3049
+ desc: "Requests/second per authenticated caller",
3050
+ toPatch: (v, cfg) => fullThrottle(cfg, { userRateLimit: v })
3051
+ },
3052
+ {
3053
+ key: "throttling.endUserRateLimit",
3054
+ label: "End-user rate limit (req/s)",
3055
+ group: "Traffic & limits",
3056
+ type: "number",
3057
+ desc: "Requests/second per end user (x-end-user-id)",
3058
+ toPatch: (v, cfg) => fullThrottle(cfg, { endUserRateLimit: v })
3059
+ },
3060
+ {
3061
+ key: "throttling.proxyQuota",
3062
+ label: "Quota (requests/period)",
3063
+ group: "Traffic & limits",
3064
+ type: "number",
3065
+ desc: "Total requests per quota period for the whole proxy",
3066
+ toPatch: (v, cfg) => fullThrottle(cfg, { proxyQuota: v })
3067
+ },
3068
+ {
3069
+ key: "throttling.quotaPeriod",
3070
+ label: "Quota period",
3071
+ group: "Traffic & limits",
3072
+ type: "enum",
3073
+ enum: ["daily", "weekly", "monthly"],
3074
+ desc: "Window the quota applies to",
3075
+ toPatch: (v, cfg) => fullThrottle(cfg, { quotaPeriod: v })
3076
+ },
3077
+ {
3078
+ key: "requests_policy.mode",
3079
+ label: "Credential policy",
3080
+ group: "Access & auth",
3081
+ type: "enum",
3082
+ enum: ["passthrough", "authenticate"],
3083
+ desc: "passthrough = forward anything; authenticate = require a credential",
3084
+ toPatch: (v, cfg) => fullPolicy(cfg, v === "authenticate" ? { mode: v, methods: cfg.requests_policy?.methods?.length ? cfg.requests_policy.methods : ["api_key"] } : { mode: v })
3085
+ },
3086
+ {
3087
+ key: "requests_policy.methods",
3088
+ label: "Accepted credentials",
3089
+ group: "Access & auth",
3090
+ type: "json",
3091
+ desc: "Array from api_key | jwt | opaque (used when policy = authenticate)",
3092
+ toPatch: (v, cfg) => fullPolicy(cfg, { mode: cfg.requests_policy?.mode ?? "authenticate", methods: v })
3093
+ },
3094
+ {
3095
+ key: "auth_type",
3096
+ label: "Consumer login",
3097
+ group: "Access & auth",
3098
+ type: "enum",
3099
+ enum: ["oauth", "none"],
3100
+ desc: "OAuth login for your API consumers (portal sign-in)",
3101
+ read: (cfg) => cfg.auth_type ?? "none",
3102
+ toPatch: (v) => ({ auth_type: v })
3103
+ },
3104
+ {
3105
+ key: "cors",
3106
+ label: "CORS",
3107
+ group: "Access & auth",
3108
+ type: "json",
3109
+ desc: "Per-proxy CORS config object; null clears the override",
3110
+ toPatch: (v) => ({ cors: v })
3111
+ },
3112
+ {
3113
+ key: "enable_portal",
3114
+ label: "Developer portal",
3115
+ group: "Portal & MCP",
3116
+ type: "boolean",
3117
+ desc: "The hosted docs + key-management portal for your consumers",
3118
+ read: (cfg) => cfg.enable_portal !== false,
3119
+ toPatch: (v) => ({ enable_portal: v })
3120
+ },
3121
+ {
3122
+ key: "mcp_enabled",
3123
+ label: "MCP server",
3124
+ group: "Portal & MCP",
3125
+ type: "boolean",
3126
+ desc: "Serve this API as an MCP server for AI agents",
3127
+ read: (cfg) => cfg.mcp_enabled !== false,
3128
+ toPatch: (v) => ({ mcp_enabled: v })
3129
+ },
3130
+ {
3131
+ key: "product_slug",
3132
+ label: "Product tag",
3133
+ group: "Portal & MCP",
3134
+ type: "string",
3135
+ desc: "Team-scoped tag grouping projects in the consumer portal",
3136
+ toPatch: (v) => ({ product_slug: v })
3137
+ }
3138
+ ];
3139
+ var SETTING_GROUPS = ["Basics", "Traffic & limits", "Access & auth", "Portal & MCP"];
3140
+ var FEATURES = [
3141
+ { go: "transforms", label: "Transforms", desc: "Rewrite requests/responses (headers, body fields) without touching your upstream" },
3142
+ { go: "mappings", label: "Mapping tables", desc: "Reusable value-mapping tables used by map transforms (values can be hidden/encrypted)" },
3143
+ { go: "tenants", label: "Tenants", desc: "Separate groups of your API consumers \u2014 each with its own portal, login, and keys" },
3144
+ { go: "domains", label: "Custom domains", desc: "Serve the proxy on your own hostname + choose what the bare URL serves" },
3145
+ { go: "spec", label: "OpenAPI spec & traffic", desc: "View the stored spec, refresh it from source, or build it from captured traffic" },
3146
+ { go: "agents", label: "AI agents", desc: "Chat to build your spec, design access rules, or publish an MCP server (billed per turn)" },
3147
+ { go: "danger", label: "Danger zone", desc: "Delete this proxy and everything under it" }
3148
+ ];
3149
+ function dig(blob, dotted) {
3150
+ let cur = blob;
3151
+ for (const part of dotted.split(".")) {
3152
+ if (cur == null || typeof cur !== "object") return void 0;
3153
+ cur = cur[part];
3154
+ }
3155
+ return cur;
3156
+ }
3157
+ var readSetting = (s, cfg) => s.read ? s.read(cfg) : dig(cfg, s.key);
3158
+ function show(v) {
3159
+ if (v === void 0) return import_chalk26.default.dim("(unset)");
3160
+ if (v === null) return import_chalk26.default.dim("null");
3161
+ if (typeof v === "object") return import_chalk26.default.cyan(JSON.stringify(v));
3162
+ if (typeof v === "boolean") return v ? import_chalk26.default.green("on") : import_chalk26.default.red("off");
3163
+ return import_chalk26.default.cyan(String(v));
3164
+ }
3165
+ function parseValue(raw) {
3166
+ if (raw === "true") return true;
3167
+ if (raw === "false") return false;
3168
+ if (raw === "null") return null;
3169
+ if (raw !== "" && !Number.isNaN(Number(raw))) return Number(raw);
3170
+ if (/^[[{]/.test(raw.trim())) {
3171
+ try {
3172
+ return JSON.parse(raw);
3173
+ } catch {
3174
+ }
3175
+ }
3176
+ return raw;
3177
+ }
3178
+ async function fetchConfigBlob(proj2) {
3179
+ const out = await admin({
3180
+ method: "GET",
3181
+ path: `/projects?team_id=${encodeURIComponent(proj2.teamId)}`,
3182
+ summary: `Read settings for ${proj2.projectName}`
3183
+ });
3184
+ const rows = out?.projects ?? [];
3185
+ const row = rows.find((r) => r.project_id === proj2.projectId && r.api_version === proj2.apiVersion) ?? rows.find((r) => r.project_id === proj2.projectId);
3186
+ return row?.config ?? {};
3187
+ }
3188
+ async function patchSetting(proj2, s, value, cfg) {
3189
+ const body = s.toPatch(value, cfg);
3190
+ const spinner = (0, import_ora11.default)(`Set ${s.key}...`).start();
3191
+ try {
3192
+ await admin({
3193
+ method: "PATCH",
3194
+ path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
3195
+ body,
3196
+ summary: `Set ${s.key} = ${JSON.stringify(value)}`
3197
+ });
3198
+ spinner.succeed(`${s.key} = ${JSON.stringify(value)}`);
3199
+ } catch (err) {
3200
+ spinner.fail(`Set ${s.key} failed.`);
3201
+ throw err;
3202
+ }
3203
+ }
3204
+ var loginFirst = (what) => {
3205
+ console.log(import_chalk26.default.yellow(`
3206
+ Log in first to ${what}.`));
3207
+ console.log(import_chalk26.default.dim(" Run `npx apiblaze login` \u2014 or `npx apiblaze claim` if you created this proxy"));
3208
+ console.log(import_chalk26.default.dim(" anonymously and want to bring it into your account.\n"));
3209
+ };
3210
+ async function runConfig(project, key, value, opts) {
3211
+ const creds = loadCredentials();
3212
+ if (!creds) {
3213
+ await discoveryMenu(project);
3214
+ return;
3215
+ }
3216
+ const { teamId } = await resolveTeam(opts.team);
3217
+ const proj2 = project ? await resolveProject(teamId, project, opts.apiversion) : await pickProject(teamId);
3218
+ const cfg = await fetchConfigBlob(proj2);
3219
+ if (opts.list || key === void 0) {
3220
+ if (opts.list) return printAll(proj2, cfg, opts.json);
3221
+ return navigator(proj2, cfg, opts);
3222
+ }
3223
+ const setting = SETTINGS.find((s) => s.key === key);
3224
+ if (!setting) {
3225
+ console.error(import_chalk26.default.red(`Unknown setting "${key}".`));
3226
+ console.error(import_chalk26.default.dim(" Known: " + SETTINGS.map((s) => s.key).join(", ")));
3227
+ console.error(import_chalk26.default.dim(" (Features like transforms/domains/tenants live in the menu: `apiblaze config <project>`.)"));
3228
+ process.exit(1);
3229
+ }
3230
+ if (value === void 0) {
3231
+ const v = readSetting(setting, cfg);
3232
+ if (opts.json) console.log(JSON.stringify(v ?? null));
3233
+ else console.log(`${setting.key} = ${show(v)}`);
3234
+ return;
3235
+ }
3236
+ await patchSetting(proj2, setting, parseValue(value), cfg);
3237
+ }
3238
+ async function pickProject(teamId) {
3239
+ const { getProjects: getProjects2 } = await Promise.resolve().then(() => (init_api(), api_exports));
3240
+ const projects = await getProjects2(teamId).catch(() => []);
3241
+ if (!projects.length) {
3242
+ console.error(import_chalk26.default.red("No projects in this team. Create one: `npx apiblaze create`."));
3243
+ process.exit(1);
3244
+ }
3245
+ const { default: inquirer2 } = await import("inquirer");
3246
+ const { picked } = await inquirer2.prompt([{
3247
+ type: "list",
3248
+ name: "picked",
3249
+ message: "Which project?",
3250
+ choices: projects.map((p) => ({ name: `${p.projectName} ${import_chalk26.default.dim("v" + p.apiVersion)}`, value: p }))
3251
+ }]);
3252
+ return { projectId: picked.projectId, projectName: picked.projectName, apiVersion: picked.apiVersion, teamId, tenant: picked.tenant };
3253
+ }
3254
+ function printAll(proj2, cfg, json) {
3255
+ if (json) {
3256
+ const out = {};
3257
+ for (const s of SETTINGS) out[s.key] = readSetting(s, cfg) ?? null;
3258
+ console.log(JSON.stringify(out, null, 2));
3259
+ return;
3260
+ }
3261
+ console.log(import_chalk26.default.bold(`
3262
+ ${proj2.projectName} v${proj2.apiVersion} \u2014 settings
3263
+ `));
3264
+ for (const group of SETTING_GROUPS) {
3265
+ console.log(import_chalk26.default.bold(group));
3266
+ for (const s of SETTINGS.filter((x) => x.group === group)) {
3267
+ console.log(` ${s.key.padEnd(32)} ${show(readSetting(s, cfg))} ${import_chalk26.default.dim(s.desc)}`);
3268
+ }
3269
+ console.log();
3270
+ }
3271
+ console.log(import_chalk26.default.dim("Change one: apiblaze config " + proj2.projectName + " <key> <value> (add --verbose for the API call)"));
3272
+ }
3273
+ async function discoveryMenu(project) {
3274
+ const { default: inquirer2 } = await import("inquirer");
3275
+ console.log(import_chalk26.default.bold(`
3276
+ APIblaze proxy configuration${project ? ` \u2014 ${project}` : ""}
3277
+ `));
3278
+ console.log(import_chalk26.default.dim("You are not logged in \u2014 browsing what's configurable. Everything below works"));
3279
+ console.log(import_chalk26.default.dim("from this menu once you log in (`npx apiblaze login`).\n"));
3280
+ for (; ; ) {
3281
+ const { pick: pick2 } = await inquirer2.prompt([{
3282
+ type: "list",
3283
+ name: "pick",
3284
+ message: "Explore:",
3285
+ pageSize: 20,
3286
+ choices: [
3287
+ new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
3288
+ ...SETTING_GROUPS.map((g) => ({
3289
+ name: `${g} ${import_chalk26.default.dim(SETTINGS.filter((s) => s.group === g).map((s) => s.label).join(", "))}`,
3290
+ value: { kind: "settings", g }
3291
+ })),
3292
+ new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
3293
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: "feature", f } })),
3294
+ new inquirer2.Separator(),
3295
+ { name: "Exit", value: { kind: "exit" } }
3296
+ ]
3297
+ }]);
3298
+ if (pick2.kind === "exit") return;
3299
+ if (pick2.kind === "settings") {
3300
+ console.log();
3301
+ for (const s of SETTINGS.filter((x) => x.group === pick2.g)) {
3302
+ console.log(` ${import_chalk26.default.bold(s.label.padEnd(28))} ${import_chalk26.default.dim(s.desc)}`);
3303
+ console.log(` ${import_chalk26.default.dim(" key: " + s.key)}`);
3304
+ }
3305
+ loginFirst("view or change these settings");
3306
+ } else {
3307
+ const f = pick2.f;
3308
+ console.log(`
3309
+ ${import_chalk26.default.bold(f.label)} \u2014 ${f.desc}`);
3310
+ loginFirst(`use ${f.label.toLowerCase()}`);
3311
+ }
3312
+ }
3313
+ }
3314
+ async function navigator(proj2, cfg, opts) {
3315
+ const { default: inquirer2 } = await import("inquirer");
3316
+ console.log(import_chalk26.default.bold(`
3317
+ ${proj2.projectName} v${proj2.apiVersion} \u2014 configuration
3318
+ `));
3319
+ console.log(import_chalk26.default.dim("Tip: every change is one API call \u2014 add --verbose to see the curl equivalent.\n"));
3320
+ let blob = cfg;
3321
+ for (; ; ) {
3322
+ const { pick: pick2 } = await inquirer2.prompt([{
3323
+ type: "list",
3324
+ name: "pick",
3325
+ message: "Where to?",
3326
+ pageSize: 20,
3327
+ choices: [
3328
+ new inquirer2.Separator(import_chalk26.default.bold("\u2014 Settings \u2014")),
3329
+ ...SETTING_GROUPS.map((g) => ({ name: g, value: { kind: "settings", g } })),
3330
+ new inquirer2.Separator(import_chalk26.default.bold("\u2014 Features \u2014")),
3331
+ ...FEATURES.map((f) => ({ name: `${f.label} ${import_chalk26.default.dim(f.desc)}`, value: { kind: f.go } })),
3332
+ new inquirer2.Separator(),
3333
+ { name: "Show all settings", value: { kind: "list" } },
3334
+ { name: "Exit", value: { kind: "exit" } }
3335
+ ]
3336
+ }]);
3337
+ try {
3338
+ switch (pick2.kind) {
3339
+ case "exit":
3340
+ return;
3341
+ case "list":
3342
+ printAll(proj2, blob);
3343
+ break;
3344
+ case "settings":
3345
+ blob = await settingsGroup(proj2, blob, pick2.g);
3346
+ break;
3347
+ case "transforms":
3348
+ await transformsMenu(proj2);
3349
+ break;
3350
+ case "mappings":
3351
+ await mappingsMenu(proj2);
3352
+ break;
3353
+ case "tenants":
3354
+ await tenantsMenu(proj2, opts);
3355
+ break;
3356
+ case "domains":
3357
+ await domainsMenu(proj2, opts);
3358
+ break;
3359
+ case "spec":
3360
+ await specMenu(proj2, opts);
3361
+ break;
3362
+ case "agents":
3363
+ await agentsMenu(proj2, opts);
3364
+ break;
3365
+ case "danger": {
3366
+ await runDelete(proj2.projectName, proj2.apiVersion, { team: opts.team });
3367
+ return;
3368
+ }
3369
+ }
3370
+ } catch (err) {
3371
+ console.error(import_chalk26.default.red(` ${err instanceof Error ? err.message : String(err)}`));
3372
+ }
3373
+ }
3374
+ }
3375
+ async function settingsGroup(proj2, cfg, group) {
3376
+ const { default: inquirer2 } = await import("inquirer");
3377
+ for (; ; ) {
3378
+ const items = SETTINGS.filter((s2) => s2.group === group);
3379
+ const { pick: pick2 } = await inquirer2.prompt([{
3380
+ type: "list",
3381
+ name: "pick",
3382
+ message: group + ":",
3383
+ pageSize: 16,
3384
+ choices: [
3385
+ ...items.map((s2) => ({ name: `${s2.label.padEnd(30)} ${show(readSetting(s2, cfg))} ${import_chalk26.default.dim(s2.desc)}`, value: s2 })),
3386
+ new inquirer2.Separator(),
3387
+ { name: "\u2190 Back", value: null }
3388
+ ]
3389
+ }]);
3390
+ if (!pick2) return cfg;
3391
+ const s = pick2;
3392
+ let value;
3393
+ if (s.type === "boolean") {
3394
+ const cur = readSetting(s, cfg);
3395
+ const { v } = await inquirer2.prompt([{ type: "confirm", name: "v", message: `${s.label} on?`, default: cur !== false }]);
3396
+ value = v;
3397
+ } else if (s.type === "enum") {
3398
+ const { v } = await inquirer2.prompt([{ type: "list", name: "v", message: s.label + ":", choices: s.enum, default: readSetting(s, cfg) }]);
3399
+ value = v;
3400
+ } else if (s.type === "number") {
3401
+ const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (number):`, default: readSetting(s, cfg) }]);
3402
+ if (v === "" || Number.isNaN(Number(v))) {
3403
+ console.log(import_chalk26.default.yellow(" Not a number \u2014 unchanged."));
3404
+ continue;
3405
+ }
3406
+ value = Number(v);
3407
+ } else if (s.type === "json") {
3408
+ const cur = readSetting(s, cfg);
3409
+ const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: `${s.label} (JSON, or "null" to clear):`, default: cur === void 0 ? "" : JSON.stringify(cur) }]);
3410
+ if (v === "") continue;
3411
+ value = parseValue(v);
3412
+ } else {
3413
+ const { v } = await inquirer2.prompt([{ type: "input", name: "v", message: s.label + ":", default: readSetting(s, cfg) }]);
3414
+ if (v === "") continue;
3415
+ value = v;
3416
+ }
3417
+ await patchSetting(proj2, s, value, cfg);
3418
+ cfg = await fetchConfigBlob(proj2);
3419
+ }
3420
+ }
3421
+ async function transformsMenu(proj2) {
3422
+ const { default: inquirer2 } = await import("inquirer");
3423
+ const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/transforms`;
3424
+ for (; ; ) {
3425
+ const out = await admin({ method: "GET", path: base, summary: "List transform rules" });
3426
+ const rules = out?.rules ?? [];
3427
+ console.log();
3428
+ if (!rules.length) console.log(import_chalk26.default.dim(" No transform rules yet."));
3429
+ for (const r of rules) {
3430
+ const a = r.action ?? {};
3431
+ const what = a.type === "hardcode" ? `${a.destination} = "${a.value}"` : a.type === "remove" ? `remove ${a.field}` : `${a.source} \u2192 ${a.destination}${a.lookup ? " (mapped)" : ""}`;
3432
+ console.log(` ${r.enabled ? import_chalk26.default.green("\u25CF") : import_chalk26.default.dim("\u25CB")} ${import_chalk26.default.bold(r.name)} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)} ${what}`);
3433
+ }
3434
+ const { act } = await inquirer2.prompt([{
3435
+ type: "list",
3436
+ name: "act",
3437
+ message: "Transforms:",
3438
+ choices: [
3439
+ { name: "Add a rule", value: "add" },
3440
+ ...rules.length ? [
3441
+ { name: "Enable/disable a rule", value: "toggle" },
3442
+ { name: "Delete a rule", value: "delete" }
3443
+ ] : [],
3444
+ { name: "\u2190 Back", value: "back" }
3445
+ ]
3446
+ }]);
3447
+ if (act === "back") return;
3448
+ if (act === "add") {
3449
+ const ans = await inquirer2.prompt([
3450
+ { type: "input", name: "name", message: "Rule name:", validate: (s) => !!s || "required" },
3451
+ { type: "list", name: "phase", message: "Phase:", choices: [
3452
+ { name: "request \u2014 before forwarding upstream", value: "request" },
3453
+ { name: "response \u2014 on the reply before it returns", value: "response" }
3454
+ ] },
3455
+ { type: "list", name: "kind", message: "Action:", choices: [
3456
+ { name: "Set a value (header/param/body field)", value: "hardcode" },
3457
+ { name: "Copy/map one field to another", value: "map" },
3458
+ { name: "Remove a field", value: "remove" }
3459
+ ] }
3460
+ ]);
3461
+ const fieldHint = import_chalk26.default.dim("(e.g. header:x-api-version, param:limit, bodyvar:user.id)");
3462
+ let action2;
3463
+ if (ans.kind === "hardcode") {
3464
+ const a = await inquirer2.prompt([
3465
+ { type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
3466
+ { type: "input", name: "value", message: "Value:" }
3467
+ ]);
3468
+ action2 = { type: "hardcode", destination: a.destination, value: a.value };
3469
+ } else if (ans.kind === "remove") {
3470
+ const a = await inquirer2.prompt([
3471
+ { type: "input", name: "field", message: `Field to remove ${fieldHint}:`, validate: (s) => !!s || "required" }
3472
+ ]);
3473
+ action2 = { type: "remove", field: a.field };
3474
+ } else {
3475
+ const a = await inquirer2.prompt([
3476
+ { type: "input", name: "source", message: `Source field ${fieldHint}:`, validate: (s) => !!s || "required" },
3477
+ { type: "input", name: "destination", message: `Destination field ${fieldHint}:`, validate: (s) => !!s || "required" },
3478
+ { type: "confirm", name: "strip", message: "Remove the source field after copying?", default: false }
3479
+ ]);
3480
+ action2 = { type: "copy", source: a.source, destination: a.destination, ...a.strip ? { strip_source: true } : {} };
3481
+ }
3482
+ const spinner = (0, import_ora11.default)("Creating rule...").start();
3483
+ try {
3484
+ await admin({ method: "POST", path: base, body: { name: ans.name, phase: ans.phase, enabled: true, action: action2 }, summary: `Create transform "${ans.name}"` });
3485
+ spinner.succeed(`Rule "${ans.name}" created.`);
3486
+ } catch (err) {
3487
+ spinner.fail("Create failed.");
3488
+ throw err;
3489
+ }
3490
+ } else {
3491
+ const { rule } = await inquirer2.prompt([{
3492
+ type: "list",
3493
+ name: "rule",
3494
+ message: act === "toggle" ? "Which rule?" : "Delete which rule?",
3495
+ choices: [...rules.map((r) => ({ name: `${r.name} ${import_chalk26.default.dim(`[${r.phase ?? "request"}]`)}`, value: r })), { name: "\u2190 Back", value: null }]
3496
+ }]);
3497
+ if (!rule) continue;
3498
+ if (act === "toggle") {
3499
+ const flipped = { ...rule, enabled: rule.enabled === false };
3500
+ await admin({ method: "PUT", path: `${base}/${rule.id}`, body: flipped, summary: `${flipped.enabled ? "Enable" : "Disable"} transform "${rule.name}"` });
3501
+ console.log(import_chalk26.default.green(` ${rule.name} \u2192 ${flipped.enabled ? "enabled" : "disabled"}`));
3502
+ } else {
3503
+ await admin({ method: "DELETE", path: `${base}/${rule.id}`, summary: `Delete transform "${rule.name}"` });
3504
+ console.log(import_chalk26.default.green(` ${rule.name} deleted.`));
3505
+ }
3506
+ }
3507
+ }
3508
+ }
3509
+ async function mappingsMenu(proj2) {
3510
+ const { default: inquirer2 } = await import("inquirer");
3511
+ const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/mappings`;
3512
+ for (; ; ) {
3513
+ const out = await admin({ method: "GET", path: base, summary: "List mapping tables" });
3514
+ const tables = out?.mappings ?? out?.tables ?? [];
3515
+ console.log();
3516
+ if (!tables.length) console.log(import_chalk26.default.dim(" No mapping tables yet."));
3517
+ for (const t of tables) {
3518
+ console.log(` ${import_chalk26.default.bold(t.name)} ${import_chalk26.default.dim(`${t.entries?.length ?? "?"} entries${t.hide_map_values ? ", hidden" : ""}${t.encrypt_values ? ", encrypted" : ""}`)}`);
3519
+ }
3520
+ const { act } = await inquirer2.prompt([{
3521
+ type: "list",
3522
+ name: "act",
3523
+ message: "Mapping tables:",
3524
+ choices: [
3525
+ { name: "Create a table", value: "add" },
3526
+ ...tables.length ? [{ name: "Delete a table", value: "delete" }] : [],
3527
+ { name: "\u2190 Back", value: "back" }
3528
+ ]
3529
+ }]);
3530
+ if (act === "back") return;
3531
+ if (act === "add") {
3532
+ const a = await inquirer2.prompt([
3533
+ { type: "input", name: "name", message: "Table name:", validate: (s) => !!s || "required" },
3534
+ { type: "input", name: "entries", message: 'Entries as JSON, e.g. [{"from":"a","to":"b"}]:', default: "[]" }
3535
+ ]);
3536
+ const entries2 = parseValue(a.entries);
3537
+ if (!Array.isArray(entries2)) {
3538
+ console.log(import_chalk26.default.yellow(" Entries must be a JSON array \u2014 not created."));
3539
+ continue;
3540
+ }
3541
+ await admin({ method: "POST", path: base, body: { name: a.name, entries: entries2 }, summary: `Create mapping table "${a.name}"` });
3542
+ console.log(import_chalk26.default.green(` Table "${a.name}" created.`));
3543
+ } else {
3544
+ const { table } = await inquirer2.prompt([{
3545
+ type: "list",
3546
+ name: "table",
3547
+ message: "Delete which table?",
3548
+ choices: [...tables.map((t) => ({ name: t.name, value: t })), { name: "\u2190 Back", value: null }]
3549
+ }]);
3550
+ if (!table) continue;
3551
+ await admin({ method: "DELETE", path: `${base}/${table.id}`, summary: `Delete mapping table "${table.name}"` });
3552
+ console.log(import_chalk26.default.green(` ${table.name} deleted.`));
3553
+ }
3554
+ }
3555
+ }
3556
+ async function tenantsMenu(proj2, opts) {
3557
+ const { default: inquirer2 } = await import("inquirer");
3558
+ const base = `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`;
3559
+ for (; ; ) {
3560
+ const out = await admin({ method: "GET", path: base, summary: "List attached tenants" });
3561
+ const tenants = out?.tenants ?? [];
3562
+ console.log();
3563
+ if (!tenants.length) console.log(import_chalk26.default.dim(" No tenants attached (consumers use the default tenant)."));
3564
+ for (const t of tenants) console.log(` ${import_chalk26.default.bold(t.tenant_name ?? t.name)} ${import_chalk26.default.dim(t.display_name ?? "")}`);
3565
+ const { act } = await inquirer2.prompt([{
3566
+ type: "list",
3567
+ name: "act",
3568
+ message: "Tenants:",
3569
+ choices: [
3570
+ { name: "Attach a tenant", value: "attach" },
3571
+ ...tenants.length ? [{ name: "Detach a tenant", value: "detach" }] : [],
3572
+ { name: import_chalk26.default.dim("Manage team tenants (create/CORS/login) \u2192 `apiblaze tenant --help`"), value: "hint" },
3573
+ { name: "\u2190 Back", value: "back" }
3574
+ ]
3575
+ }]);
3576
+ if (act === "back") return;
3577
+ if (act === "hint") {
3578
+ console.log(import_chalk26.default.dim("\n apiblaze tenant list | create | attach | cors | delete\n"));
3579
+ continue;
3580
+ }
3581
+ if (act === "attach") {
3582
+ const { slug } = await inquirer2.prompt([{ type: "input", name: "slug", message: "Tenant slug to attach:", validate: (s) => !!s || "required" }]);
3583
+ await runTenantAttach(proj2.projectName, { tenant: slug, team: opts.team, apiversion: proj2.apiVersion });
3584
+ } else {
3585
+ const { t } = await inquirer2.prompt([{
3586
+ type: "list",
3587
+ name: "t",
3588
+ message: "Detach which tenant?",
3589
+ choices: [...tenants.map((x) => ({ name: x.tenant_name ?? x.name, value: x })), { name: "\u2190 Back", value: null }]
3590
+ }]);
3591
+ if (!t) continue;
3592
+ await admin({ method: "DELETE", path: `${base}/${encodeURIComponent(t.tenant_name ?? t.name)}`, summary: `Detach tenant ${t.tenant_name ?? t.name}` });
3593
+ console.log(import_chalk26.default.green(` Detached ${t.tenant_name ?? t.name}.`));
3594
+ }
3595
+ }
3596
+ }
3597
+ async function domainsMenu(proj2, opts) {
3598
+ const { default: inquirer2 } = await import("inquirer");
3599
+ for (; ; ) {
3600
+ const { act } = await inquirer2.prompt([{
3601
+ type: "list",
3602
+ name: "act",
3603
+ message: "Custom domains:",
3604
+ choices: [
3605
+ { name: "List domains", value: "list" },
3606
+ { name: "Add a domain (shows the DNS records to set)", value: "add" },
3607
+ { name: "Remove a domain", value: "remove" },
3608
+ { name: "Set what the bare URL serves (base domain)", value: "base" },
3609
+ { name: "\u2190 Back", value: "back" }
3610
+ ]
3611
+ }]);
3612
+ const common = { team: opts.team, apiversion: proj2.apiVersion };
3613
+ if (act === "back") return;
3614
+ if (act === "list") await runDomainList(proj2.projectName, common);
3615
+ else if (act === "add") {
3616
+ const { host } = await inquirer2.prompt([{ type: "input", name: "host", message: "Hostname (e.g. api.example.com):", validate: (s) => !!s || "required" }]);
3617
+ await runDomainAdd(proj2.projectName, { ...common, domain: host });
3618
+ } else if (act === "remove") {
3619
+ const { id } = await inquirer2.prompt([{ type: "input", name: "id", message: "Domain id (see List):", validate: (s) => !!s || "required" }]);
3620
+ await runDomainRemove(proj2.projectName, { ...common, id });
3621
+ } else {
3622
+ const { env } = await inquirer2.prompt([{ type: "input", name: "env", message: "Environment (e.g. prod):", default: "prod" }]);
3623
+ await runDomainSetBase(proj2.projectName, { ...common, env });
3624
+ }
3625
+ }
3626
+ }
3627
+ async function specMenu(proj2, opts) {
3628
+ const { default: inquirer2 } = await import("inquirer");
3629
+ const { act } = await inquirer2.prompt([{
3630
+ type: "list",
3631
+ name: "act",
3632
+ message: "OpenAPI spec & traffic:",
3633
+ choices: [
3634
+ { name: "Print the stored spec", value: "get" },
3635
+ { name: "Refresh the spec from its source", value: "refresh" },
3636
+ { name: import_chalk26.default.dim("Build the spec by chatting over real traffic \u2192 agent"), value: "agent" },
3637
+ { name: "\u2190 Back", value: "back" }
3638
+ ]
3639
+ }]);
3640
+ if (act === "back") return;
3641
+ if (act === "get") await runSpecGet(proj2.projectName, { team: opts.team, apiversion: proj2.apiVersion });
3642
+ else if (act === "refresh") {
3643
+ await admin({ method: "POST", path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`, summary: "Refresh spec from source" });
3644
+ console.log(import_chalk26.default.green(" Spec refresh triggered."));
3645
+ } else await runOpenapi(proj2.projectName, proj2.apiVersion);
3646
+ }
3647
+ async function agentsMenu(proj2, opts) {
3648
+ const { default: inquirer2 } = await import("inquirer");
3649
+ const { act } = await inquirer2.prompt([{
3650
+ type: "list",
3651
+ name: "act",
3652
+ message: "AI agents (billed per turn):",
3653
+ choices: [
3654
+ { name: "General chat \u2014 build and run your APIs", value: "agent" },
3655
+ { name: "OpenAPI builder \u2014 build your spec from real traffic", value: "openapi" },
3656
+ { name: "Authorization designer \u2014 draft access rules", value: "authz" },
3657
+ { name: "MCP builder \u2014 publish this API as an MCP server", value: "mcp" },
3658
+ { name: "\u2190 Back", value: "back" }
3659
+ ]
3660
+ }]);
3661
+ if (act === "back") return;
3662
+ if (act === "agent") await runAgent({ team: opts.team });
3663
+ else if (act === "openapi") await runOpenapi(proj2.projectName, proj2.apiVersion);
3664
+ else if (act === "authz") await runAuthz(proj2.projectName, proj2.apiVersion);
3665
+ else await runMcp(proj2.projectName, proj2.apiVersion, {});
3666
+ }
3667
+
3668
+ // src/commands/key.ts
3073
3669
  var import_chalk27 = __toESM(require("chalk"));
3074
3670
  var import_ora12 = __toESM(require("ora"));
3671
+ async function runApikeysMenu(opts) {
3672
+ await runKeyList(opts);
3673
+ if (opts.json) return;
3674
+ const { default: inquirer2 } = await import("inquirer");
3675
+ const { make } = await inquirer2.prompt([
3676
+ { type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
3677
+ ]);
3678
+ if (!make) return;
3679
+ const { desc } = await inquirer2.prompt([
3680
+ { type: "input", name: "desc", message: "Description (optional):" }
3681
+ ]);
3682
+ await runKeyMint({ ...opts, desc: desc || void 0 });
3683
+ }
3684
+ async function runKeyList(opts) {
3685
+ const { teamId, teamName } = await resolveTeam(opts.team);
3686
+ const out = await admin({
3687
+ method: "GET",
3688
+ path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
3689
+ summary: `List developer keys for team ${teamName ?? teamId}`
3690
+ });
3691
+ const keys = out?.keys ?? [];
3692
+ if (opts.json) {
3693
+ console.log(JSON.stringify(keys));
3694
+ return;
3695
+ }
3696
+ if (!keys.length) {
3697
+ console.log(import_chalk27.default.yellow("No developer keys."));
3698
+ return;
3699
+ }
3700
+ for (const k of keys) {
3701
+ console.log(` ${import_chalk27.default.bold(k.key_id ?? k.id)} ${import_chalk27.default.dim(k.description ?? "")} ${import_chalk27.default.dim(k.expires_at ?? "no expiry")}`);
3702
+ }
3703
+ }
3704
+ async function runKeyMint(opts) {
3705
+ const { teamId } = await resolveTeam(opts.team);
3706
+ const body = { role: "consumer-admin" };
3707
+ if (opts.desc) body.description = opts.desc;
3708
+ if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
3709
+ const spinner = (0, import_ora12.default)("Minting key...").start();
3710
+ try {
3711
+ const out = await admin({
3712
+ method: "POST",
3713
+ path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
3714
+ body,
3715
+ summary: `Mint a consumer-admin developer key`
3716
+ });
3717
+ spinner.succeed("Key minted.");
3718
+ if (opts.json) {
3719
+ console.log(JSON.stringify(out));
3720
+ return;
3721
+ }
3722
+ console.log(` ${import_chalk27.default.bold("key_id")}: ${out?.key_id}`);
3723
+ console.log(` ${import_chalk27.default.bold("key")}: ${import_chalk27.default.green(out?.key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
3724
+ if (out?.expires_at) console.log(` ${import_chalk27.default.dim("expires:")} ${out.expires_at}`);
3725
+ } catch (err) {
3726
+ spinner.fail("Mint failed.");
3727
+ throw err;
3728
+ }
3729
+ }
3730
+ async function runKeyRevoke(keyId, opts) {
3731
+ const { teamId } = await resolveTeam(opts.team);
3732
+ const spinner = (0, import_ora12.default)("Revoking key...").start();
3733
+ try {
3734
+ await admin({
3735
+ method: "DELETE",
3736
+ path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
3737
+ summary: `Revoke developer key ${keyId}`
3738
+ });
3739
+ spinner.succeed(`Revoked ${keyId}.`);
3740
+ } catch (err) {
3741
+ spinner.fail("Revoke failed.");
3742
+ throw err;
3743
+ }
3744
+ }
3745
+
3746
+ // src/commands/consumer.ts
3747
+ var import_chalk28 = __toESM(require("chalk"));
3748
+ var import_ora13 = __toESM(require("ora"));
3075
3749
  var DEFAULT_SCOPE = "openid email profile offline_access";
3076
3750
  var APIKEYS_BASE = process.env.APIBLAZE_APIKEYS_BASE || "https://apikeys.apiblaze.com";
3077
3751
  async function consumerFetch(creds, suffix, init) {
@@ -3090,7 +3764,7 @@ async function consumerFetch(creds, suffix, init) {
3090
3764
  function requireConsumer() {
3091
3765
  const c = loadConsumer();
3092
3766
  if (!c) {
3093
- console.error(import_chalk27.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
3767
+ console.error(import_chalk28.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
3094
3768
  process.exit(1);
3095
3769
  }
3096
3770
  return c;
@@ -3101,20 +3775,20 @@ async function runConsumerLogin(opts) {
3101
3775
  let clientId = opts.client;
3102
3776
  if (clientId) {
3103
3777
  if (!tenant2) {
3104
- console.error(import_chalk27.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
3778
+ console.error(import_chalk28.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
3105
3779
  process.exit(1);
3106
3780
  }
3107
3781
  } else {
3108
3782
  requireAuth();
3109
3783
  const { teamId, teamName } = await resolveTeam(opts.team);
3110
- const spinner = (0, import_ora12.default)("Loading your tenants...").start();
3784
+ const spinner = (0, import_ora13.default)("Loading your tenants...").start();
3111
3785
  const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
3112
3786
  spinner.stop();
3113
3787
  const tenants = (tdata?.tenants ?? []).map(
3114
3788
  (t) => typeof t === "string" ? { tenant_name: t } : t
3115
3789
  );
3116
3790
  if (!tenants.length) {
3117
- console.error(import_chalk27.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
3791
+ console.error(import_chalk28.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
3118
3792
  process.exit(1);
3119
3793
  }
3120
3794
  if (!tenant2) {
@@ -3124,25 +3798,25 @@ async function runConsumerLogin(opts) {
3124
3798
  tenant2 = chosen;
3125
3799
  }
3126
3800
  }
3127
- const s2 = (0, import_ora12.default)("Finding the login app...").start();
3801
+ const s2 = (0, import_ora13.default)("Finding the login app...").start();
3128
3802
  const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
3129
3803
  s2.stop();
3130
3804
  const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
3131
3805
  const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
3132
3806
  if (!pick2) {
3133
- console.error(import_chalk27.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
3807
+ console.error(import_chalk28.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
3134
3808
  process.exit(1);
3135
3809
  }
3136
3810
  clientId = pick2.client_id ?? pick2.clientId;
3137
3811
  }
3138
3812
  const portalResource = `https://${tenant2}.portal.apiblaze.com/1.0.0`;
3139
- console.log(`${import_chalk27.default.cyan("\u2192")} Logging in to ${import_chalk27.default.bold(tenant2)} as a consumer...`);
3813
+ console.log(`${import_chalk28.default.cyan("\u2192")} Logging in to ${import_chalk28.default.bold(tenant2)} as a consumer...`);
3140
3814
  const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
3141
3815
  console.log(`
3142
- Open: ${import_chalk27.default.underline(verificationUri)}`);
3143
- console.log(` Code: ${import_chalk27.default.bold(userCode)}
3816
+ Open: ${import_chalk28.default.underline(verificationUri)}`);
3817
+ console.log(` Code: ${import_chalk28.default.bold(userCode)}
3144
3818
  `);
3145
- console.log(import_chalk27.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
3819
+ console.log(import_chalk28.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
3146
3820
  }, portalResource);
3147
3821
  const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
3148
3822
  const creds = {
@@ -3157,7 +3831,7 @@ async function runConsumerLogin(opts) {
3157
3831
  obtainedAt: Date.now()
3158
3832
  };
3159
3833
  saveConsumer(creds);
3160
- console.log(import_chalk27.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
3834
+ console.log(import_chalk28.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
3161
3835
  }
3162
3836
  async function runConsumerTokens(opts) {
3163
3837
  const creds = requireConsumer();
@@ -3170,29 +3844,29 @@ async function runConsumerTokens(opts) {
3170
3844
  console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
3171
3845
  return;
3172
3846
  }
3173
- console.log(`${import_chalk27.default.cyan("Consumer")} ${import_chalk27.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk27.default.bold(fresh.tenant)}
3847
+ console.log(`${import_chalk28.default.cyan("Consumer")} ${import_chalk28.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk28.default.bold(fresh.tenant)}
3174
3848
  `);
3175
- console.log(`${import_chalk27.default.bold("access_token")} ${import_chalk27.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
3849
+ console.log(`${import_chalk28.default.bold("access_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
3176
3850
  ${fresh.accessToken}
3177
3851
  `);
3178
- if (fresh.idToken) console.log(`${import_chalk27.default.bold("id_token")} ${import_chalk27.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
3852
+ if (fresh.idToken) console.log(`${import_chalk28.default.bold("id_token")} ${import_chalk28.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
3179
3853
  ${fresh.idToken}
3180
3854
  `);
3181
- if (fresh.refreshToken) console.log(`${import_chalk27.default.bold("refresh_token")}
3855
+ if (fresh.refreshToken) console.log(`${import_chalk28.default.bold("refresh_token")}
3182
3856
  ${fresh.refreshToken}
3183
3857
  `);
3184
- console.log(import_chalk27.default.dim("These are your own tokens \u2014 keep them secret."));
3858
+ console.log(import_chalk28.default.dim("These are your own tokens \u2014 keep them secret."));
3185
3859
  }
3186
3860
  async function runConsumerApikeys(opts) {
3187
3861
  const creds = requireConsumer();
3188
3862
  const { default: inquirer2 } = await import("inquirer");
3189
- const spinner = (0, import_ora12.default)("Loading your API keys...").start();
3863
+ const spinner = (0, import_ora13.default)("Loading your API keys...").start();
3190
3864
  const list = await consumerFetch(creds, "/apikeys");
3191
3865
  const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
3192
3866
  spinner.stop();
3193
3867
  if (list.status >= 400) {
3194
- console.error(import_chalk27.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
3195
- if (list.status === 401) console.error(import_chalk27.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
3868
+ console.error(import_chalk28.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
3869
+ if (list.status === 401) console.error(import_chalk28.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
3196
3870
  process.exit(1);
3197
3871
  }
3198
3872
  const keys = list.data?.keys ?? [];
@@ -3200,16 +3874,16 @@ async function runConsumerApikeys(opts) {
3200
3874
  if (opts.json) {
3201
3875
  console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
3202
3876
  } else if (!keys.length) {
3203
- console.log(import_chalk27.default.yellow("No API keys yet."));
3877
+ console.log(import_chalk28.default.yellow("No API keys yet."));
3204
3878
  } else {
3205
3879
  for (const k of keys) {
3206
3880
  const clear = revealMap[k.environment]?.key;
3207
- const shown = clear ? import_chalk27.default.green(clear) : import_chalk27.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
3208
- const exp = k.expires_at ? import_chalk27.default.dim(`exp ${k.expires_at}`) : import_chalk27.default.dim("no expiry");
3209
- console.log(` ${import_chalk27.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk27.default.dim(k.description ?? "")}`);
3881
+ const shown = clear ? import_chalk28.default.green(clear) : import_chalk28.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
3882
+ const exp = k.expires_at ? import_chalk28.default.dim(`exp ${k.expires_at}`) : import_chalk28.default.dim("no expiry");
3883
+ console.log(` ${import_chalk28.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk28.default.dim(k.description ?? "")}`);
3210
3884
  }
3211
3885
  if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
3212
- console.log(import_chalk27.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
3886
+ console.log(import_chalk28.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
3213
3887
  }
3214
3888
  }
3215
3889
  if (opts.json) return;
@@ -3223,7 +3897,7 @@ async function runConsumerApikeys(opts) {
3223
3897
  const body = { environment: answers.environment };
3224
3898
  if (answers.description) body.description = answers.description;
3225
3899
  if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
3226
- const s2 = (0, import_ora12.default)("Creating key...").start();
3900
+ const s2 = (0, import_ora13.default)("Creating key...").start();
3227
3901
  const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
3228
3902
  if (created.status >= 400) {
3229
3903
  s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
@@ -3231,13 +3905,13 @@ async function runConsumerApikeys(opts) {
3231
3905
  }
3232
3906
  s2.succeed("Key created.");
3233
3907
  const key = created.data?.key ?? created.data?.fullKey;
3234
- if (key) console.log(` ${import_chalk27.default.green(key)} ${import_chalk27.default.dim("(shown once \u2014 store it now)")}`);
3235
- else console.log(import_chalk27.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
3908
+ if (key) console.log(` ${import_chalk28.default.green(key)} ${import_chalk28.default.dim("(shown once \u2014 store it now)")}`);
3909
+ else console.log(import_chalk28.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
3236
3910
  }
3237
3911
 
3238
3912
  // src/commands/sidecar.ts
3239
- var import_chalk28 = __toESM(require("chalk"));
3240
- var import_ora13 = __toESM(require("ora"));
3913
+ var import_chalk29 = __toESM(require("chalk"));
3914
+ var import_ora14 = __toESM(require("ora"));
3241
3915
  var fs7 = __toESM(require("fs"));
3242
3916
  var path4 = __toESM(require("path"));
3243
3917
  init_auth();
@@ -3266,11 +3940,31 @@ function upsertEnvLocal(root, token) {
3266
3940
  `;
3267
3941
  if (!/^APIBLAZE_SIDECAR=/m.test(next)) {
3268
3942
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR=on
3943
+ `;
3944
+ }
3945
+ if (!/^APIBLAZE_SIDECAR_VERBOSE=/m.test(next)) {
3946
+ next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
3269
3947
  `;
3270
3948
  }
3271
3949
  fs7.writeFileSync(p, next);
3272
3950
  return had ? "rotated" : "created";
3273
3951
  }
3952
+ function installSidecarPackage(root) {
3953
+ if (fs7.existsSync(path4.join(root, "node_modules", "apiblaze", "package.json"))) {
3954
+ console.log(` ${import_chalk29.default.green("\u2713")} apiblaze package already installed`);
3955
+ return;
3956
+ }
3957
+ const has = (f) => fs7.existsSync(path4.join(root, f));
3958
+ 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" };
3959
+ const spinner = (0, import_ora14.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
3960
+ try {
3961
+ const { execSync } = require("child_process");
3962
+ execSync(`${pm.cmd} ${pm.add} apiblaze`, { cwd: root, stdio: "ignore" });
3963
+ spinner.succeed("Installed apiblaze (the sidecar runtime).");
3964
+ } catch {
3965
+ spinner.warn(`Couldn't auto-install \u2014 run ${import_chalk29.default.cyan(`${pm.cmd} ${pm.add} apiblaze`)} yourself before ${import_chalk29.default.cyan("npm run dev")}.`);
3966
+ }
3967
+ }
3274
3968
  function readEnvKey(root) {
3275
3969
  try {
3276
3970
  const s = fs7.readFileSync(path4.join(root, ".env.local"), "utf8");
@@ -3323,21 +4017,64 @@ export const dynamic = "force-dynamic";
3323
4017
  async function probe(u: string) {
3324
4018
  try {
3325
4019
  const res = await fetch(u, { headers: { "x-api-key": "demo-secret-value" }, cache: "no-store" });
3326
- return { url: u, status: res.status, echo: await res.json().catch(() => ({})) };
4020
+ const echo = await res.json().catch(() => ({}));
4021
+ // A routed call returns THROUGH APIblaze (Cloudflare) \u2192 cf-ray / server:cloudflare.
4022
+ // A direct call to httpbingo (fly.io) has neither. This is the reliable signal.
4023
+ const server = (res.headers.get("server") || "").toLowerCase();
4024
+ const routed = Boolean(res.headers.get("cf-ray")) || server.indexOf("cloudflare") >= 0;
4025
+ return { url: u, status: res.status, routed, echo };
3327
4026
  } catch (e: any) { return { url: u, error: e?.message ?? String(e) }; }
3328
4027
  }
3329
4028
 
3330
4029
  export default async function Page() {
3331
- if (process.env.NODE_ENV === "production") return <main style={{padding:24}}>Inspector disabled in production.</main>;
3332
- const r = await probe("https://httpbingo.org/headers");
4030
+ if (process.env.NODE_ENV === "production") return <main style={{ padding: 24 }}>Inspector disabled in production.</main>;
4031
+ const r: any = await probe("https://httpbingo.org/headers");
4032
+ const routed = Boolean(r && r.routed);
4033
+ // An anonymous trial proxy routes, but completing the upstream call needs credits.
4034
+ const needsCredits = Boolean(r && r.echo && r.echo.claim_required);
4035
+ const card: any = { background: "#0b0f17", border: "1px solid #232a36", borderRadius: 14, padding: 20 };
4036
+ const pill: any = { display: "inline-block", padding: "4px 10px", borderRadius: 999, fontSize: 12, fontWeight: 700 };
4037
+ const codeBox: any = { background: "#070a10", border: "1px solid #232a36", borderRadius: 8, padding: 12, margin: 0, color: "#8ab4ff", overflowX: "auto" };
3333
4038
  return (
3334
- <main style={{ fontFamily: "ui-monospace, monospace", padding: 24, lineHeight: 1.6 }}>
3335
- <h1>APIblaze sidecar inspector</h1>
3336
- <p>Fetched httpbingo through the sidecar. If httpbingo.org is <b>not yet approved</b>, this went
3337
- <b> direct</b> and now appears as a candidate in your dashboard \u2014 approve it, wait ~5 min, reload,
3338
- and it will route through APIblaze (the echo below will show it arrived via your proxy).</p>
3339
- <pre>{JSON.stringify(r, null, 2)}</pre>
3340
- <p style={{opacity:.6}}>Delete this folder before shipping.</p>
4039
+ <main style={{ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", background: "#070a10", minHeight: "100vh", padding: 32, color: "#e6e9ef", lineHeight: 1.6 }}>
4040
+ <div style={{ maxWidth: 720, margin: "0 auto" }}>
4041
+ <h1 style={{ fontSize: 20, margin: "0 0 6px" }}>APIblaze sidecar \u2014 live inspector</h1>
4042
+ <p style={{ color: "#9aa4b2", margin: "0 0 20px", fontSize: 14 }}>
4043
+ This dev-only page makes one real call to <b style={{ color: "#e6e9ef" }}>httpbingo.org</b> from your server so you can watch the sidecar work. Every external fetch your app makes either <b>routes</b> through APIblaze (once you approve that origin) or goes <b>direct</b>, untouched.
4044
+ </p>
4045
+ <div style={card}>
4046
+ <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, marginBottom: 14, flexWrap: "wrap" }}>
4047
+ <span style={{ fontSize: 15 }}>GET https://httpbingo.org/headers</span>
4048
+ {routed
4049
+ ? <span style={{ ...pill, background: "#0f2e20", color: "#4ade80" }}>\u25CF routed through APIblaze</span>
4050
+ : <span style={{ ...pill, background: "#33280c", color: "#fbbf24" }}>\u25CB going direct \u2014 not approved</span>}
4051
+ </div>
4052
+ {routed && needsCredits ? (
4053
+ <div style={{ color: "#9aa4b2", fontSize: 14 }}>
4054
+ <p style={{ margin: "0 0 10px" }}>Your call <b style={{ color: "#4ade80" }}>routed through APIblaze</b> \u2014 auth, rate limits, and observability, with no code change. It reached your proxy (not httpbingo directly).</p>
4055
+ <p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>This is an anonymous trial proxy, so completing the upstream call needs credits. Keep it and add credits by claiming your workspace:</p>
4056
+ <pre style={codeBox}>apiblaze login && apiblaze claim</pre>
4057
+ <p style={{ margin: "10px 0 0" }}>\u2026then reload \u2014 the call completes end to end.</p>
4058
+ </div>
4059
+ ) : routed ? (
4060
+ <p style={{ color: "#9aa4b2", fontSize: 14, margin: 0 }}>
4061
+ Your call <b style={{ color: "#4ade80" }}>routed through APIblaze</b> \u2014 auth, rate limits, and observability \u2014 and your app code never changed. The proxy carried your original <code>x-api-key</code> upstream so httpbingo still sees it.
4062
+ </p>
4063
+ ) : (
4064
+ <div style={{ color: "#9aa4b2", fontSize: 14 }}>
4065
+ <p style={{ margin: "0 0 10px" }}>This call went <b style={{ color: "#fbbf24" }}>direct</b> \u2014 httpbingo.org isn&apos;t approved yet, so the sidecar left it untouched and reported it as a candidate.</p>
4066
+ <p style={{ margin: "0 0 6px", color: "#e6e9ef" }}>Route it through APIblaze:</p>
4067
+ <pre style={codeBox}>npx apiblaze sidecar approve httpbingo.org</pre>
4068
+ <p style={{ margin: "10px 0 0" }}>\u2026wait ~5 min (or restart the dev server), then <b style={{ color: "#e6e9ef" }}>reload</b>. The badge above turns green.</p>
4069
+ </div>
4070
+ )}
4071
+ </div>
4072
+ <details style={{ marginTop: 16, color: "#9aa4b2", fontSize: 13 }}>
4073
+ <summary style={{ cursor: "pointer" }}>Raw response {r && r.status ? "(HTTP " + r.status + ")" : "(error)"}</summary>
4074
+ <pre style={{ background: "#0b0f17", border: "1px solid #232a36", borderRadius: 10, padding: 14, overflowX: "auto", marginTop: 8 }}>{JSON.stringify(r, null, 2)}</pre>
4075
+ </details>
4076
+ <p style={{ color: "#5b6472", fontSize: 12, marginTop: 20 }}>Dev-only \u2014 delete <code>app/abz-inspector</code> before shipping.</p>
4077
+ </div>
3341
4078
  </main>
3342
4079
  );
3343
4080
  }
@@ -3364,7 +4101,7 @@ async function runAnonymousInit(root, router, opts) {
3364
4101
  const { sidecarInitAnonymous: sidecarInitAnonymous2 } = await Promise.resolve().then(() => (init_api(), api_exports));
3365
4102
  const { saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
3366
4103
  if (opts.newSession) clearAnonCred2();
3367
- const spinner = (0, import_ora13.default)("Setting up a sidecar (no login needed)...").start();
4104
+ const spinner = (0, import_ora14.default)("Setting up a sidecar (no login needed)...").start();
3368
4105
  let out;
3369
4106
  try {
3370
4107
  out = await sidecarInitAnonymous2();
@@ -3376,28 +4113,29 @@ async function runAnonymousInit(root, router, opts) {
3376
4113
  if (out.cp_key && out.team_id) saveAnonCred2(out.cp_key, out.team_id, out.claim_code);
3377
4114
  const envState = upsertEnvLocal(root, out.token);
3378
4115
  ensureGitignored(root);
3379
- console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3380
- console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4116
+ console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4117
+ console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4118
+ installSidecarPackage(root);
3381
4119
  let inspectorPath = null;
3382
4120
  if (!opts.noInspector) {
3383
4121
  inspectorPath = generateInspector(root, router);
3384
- if (inspectorPath) console.log(` ${import_chalk28.default.green("\u2713")} inspector at ${inspectorPath}`);
4122
+ if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
3385
4123
  }
3386
4124
  console.log("");
3387
- console.log(import_chalk28.default.bold("Done (no account needed). What happens next:"));
3388
- console.log(` 1. ${import_chalk28.default.cyan("npm install apiblaze")} then ${import_chalk28.default.cyan("npm run dev")} and use your app.`);
4125
+ console.log(import_chalk29.default.bold("Done (no account needed). What happens next:"));
4126
+ console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app.`);
3389
4127
  console.log(` 2. Each external origin your app calls is logged in the console \u2014 approve one with:`);
3390
- console.log(` ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (no login needed)`);
4128
+ console.log(` ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (no login needed)`);
3391
4129
  console.log("");
3392
- console.log(import_chalk28.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
3393
- console.log(` ${import_chalk28.default.cyan("apiblaze login")} then ${import_chalk28.default.cyan("apiblaze claim")} ${import_chalk28.default.dim("(no code needed here)")}`);
3394
- console.log(import_chalk28.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
4130
+ console.log(import_chalk29.default.bold(" \u{1F511} Keep your setup \u2014 claim it into an account:"));
4131
+ console.log(` ${import_chalk29.default.cyan("apiblaze login")} then ${import_chalk29.default.cyan("apiblaze claim")} ${import_chalk29.default.dim("(no code needed here)")}`);
4132
+ console.log(import_chalk29.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
3395
4133
  }
3396
4134
  async function runSidecar(opts) {
3397
4135
  const root = path4.resolve(opts.dir ?? process.cwd());
3398
4136
  const detected = detectNextProject(root);
3399
4137
  if (!detected.found) {
3400
- console.log(import_chalk28.default.yellow(`No Next.js project detected in ${root}.`));
4138
+ console.log(import_chalk29.default.yellow(`No Next.js project detected in ${root}.`));
3401
4139
  console.log("Create one (e.g. `npx create-next-app`) and re-run `apiblaze init` inside it.");
3402
4140
  return;
3403
4141
  }
@@ -3408,9 +4146,10 @@ async function runSidecar(opts) {
3408
4146
  if (!loadCredentials()) {
3409
4147
  upsertEnvLocal(root, readEnvKey(root));
3410
4148
  ensureGitignored(root);
3411
- console.log(` ${import_chalk28.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
3412
- console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
3413
- console.log(import_chalk28.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
4149
+ console.log(` ${import_chalk29.default.green("\u2713")} .env.local present (APIBLAZE_API_KEY) \u2014 reusing`);
4150
+ console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireInstrumentation(root)}`);
4151
+ installSidecarPackage(root);
4152
+ console.log(import_chalk29.default.dim(" Log in and run `apiblaze claim <code>` to keep this setup, or `apiblaze login` to manage it."));
3414
4153
  return;
3415
4154
  }
3416
4155
  const { teamId, teamName } = await resolveTeam(opts.team);
@@ -3419,7 +4158,7 @@ async function runSidecar(opts) {
3419
4158
  const mustMint = !existingKey || opts.rotate || switchingTeam;
3420
4159
  let token = existingKey ?? "";
3421
4160
  if (mustMint) {
3422
- const spinner = (0, import_ora13.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
4161
+ const spinner = (0, import_ora14.default)(existingKey ? "Re-establishing the sidecar (minting a fresh invoke key)..." : "Setting up the sidecar (tenant + non-expiring invoke key)...").start();
3423
4162
  try {
3424
4163
  const out = await admin({
3425
4164
  method: "POST",
@@ -3433,39 +4172,39 @@ async function runSidecar(opts) {
3433
4172
  throw err;
3434
4173
  }
3435
4174
  } else {
3436
- console.log(import_chalk28.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
4175
+ console.log(import_chalk29.default.dim(` Reusing the existing APIBLAZE_API_KEY (run with --rotate to mint a fresh one, or --team <name> to switch teams).`));
3437
4176
  }
3438
4177
  const envState = upsertEnvLocal(root, token);
3439
4178
  ensureGitignored(root);
3440
- console.log(` ${import_chalk28.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
4179
+ console.log(` ${import_chalk29.default.green("\u2713")} .env.local ${envState} (APIBLAZE_API_KEY) \u2014 gitignored`);
3441
4180
  const wireState = wireInstrumentation(root);
3442
- console.log(` ${import_chalk28.default.green("\u2713")} instrumentation.ts ${wireState}`);
4181
+ console.log(` ${import_chalk29.default.green("\u2713")} instrumentation.ts ${wireState}`);
4182
+ installSidecarPackage(root);
3443
4183
  let inspectorPath = null;
3444
4184
  if (!opts.noInspector) {
3445
4185
  inspectorPath = generateInspector(root, detected.router);
3446
- if (inspectorPath) console.log(` ${import_chalk28.default.green("\u2713")} inspector at ${inspectorPath}`);
4186
+ if (inspectorPath) console.log(` ${import_chalk29.default.green("\u2713")} inspector at ${inspectorPath}`);
3447
4187
  }
3448
4188
  console.log("");
3449
- console.log(import_chalk28.default.bold("Done. What happens next:"));
3450
- console.log(` 1. ${import_chalk28.default.cyan("npm install apiblaze")}`);
3451
- console.log(` 2. ${import_chalk28.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
3452
- console.log(` 3. The origins your app calls appear as ${import_chalk28.default.bold("candidates")} \u2014 list them: ${import_chalk28.default.cyan("apiblaze origins")}`);
3453
- console.log(` 4. Approve the ones to route: ${import_chalk28.default.cyan("apiblaze origins approve api.stripe.com")} (or in the dashboard)`);
4189
+ console.log(import_chalk29.default.bold("Done. What happens next:"));
4190
+ console.log(` 1. ${import_chalk29.default.cyan("npm run dev")} and use your app \u2014 it works exactly as before (all calls go direct).`);
4191
+ console.log(` 2. The origins your app calls appear as ${import_chalk29.default.bold("candidates")} \u2014 list them: ${import_chalk29.default.cyan("apiblaze sidecar")}`);
4192
+ console.log(` 3. Approve the ones to route: ${import_chalk29.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
3454
4193
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
3455
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk28.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
3456
- if (switchingTeam) console.log(import_chalk28.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze origins approve <origin>\`.`));
4194
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk29.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path4.dirname(inspectorPath)} before shipping)`);
4195
+ if (switchingTeam) console.log(import_chalk29.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
3457
4196
  console.log("");
3458
- console.log(import_chalk28.default.dim(" Manage: apiblaze origins (list/approve/deny/remove)"));
3459
- console.log(import_chalk28.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
3460
- console.log(import_chalk28.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
4197
+ console.log(import_chalk29.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
4198
+ console.log(import_chalk29.default.dim(" Rotate: apiblaze init --rotate \xB7 Switch team: apiblaze init --team <name>"));
4199
+ console.log(import_chalk29.default.dim(" Turn off: set APIBLAZE_SIDECAR=off in .env.local (flip back to on anytime; key stays put)."));
3461
4200
  console.log("");
3462
- console.log(import_chalk28.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
3463
- console.log(import_chalk28.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
4201
+ console.log(import_chalk29.default.yellow(" \u26A0 APIBLAZE_API_KEY is long-lived and lets a holder call your team's proxies. Never commit it."));
4202
+ console.log(import_chalk29.default.dim(" Your control-plane login stays in ~/.apiblaze \u2014 it never entered this project."));
3464
4203
  }
3465
4204
 
3466
4205
  // src/commands/origins.ts
3467
- var import_chalk29 = __toESM(require("chalk"));
3468
- var import_ora14 = __toESM(require("ora"));
4206
+ var import_chalk30 = __toESM(require("chalk"));
4207
+ var import_ora15 = __toESM(require("ora"));
3469
4208
  init_auth();
3470
4209
  init_anon_cred();
3471
4210
  async function runOriginsList(opts) {
@@ -3473,7 +4212,7 @@ async function runOriginsList(opts) {
3473
4212
  if (!loadCredentials()) {
3474
4213
  const cred = loadAnonCred();
3475
4214
  if (!cred) {
3476
- console.log(import_chalk29.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
4215
+ console.log(import_chalk30.default.yellow("No anonymous workspace here. Run `apiblaze init` first."));
3477
4216
  return;
3478
4217
  }
3479
4218
  out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/candidates`, { method: "GET" });
@@ -3491,30 +4230,30 @@ async function runOriginsList(opts) {
3491
4230
  }
3492
4231
  const routed = out.routed ?? [];
3493
4232
  const candidates = out.candidates ?? [];
3494
- console.log(import_chalk29.default.bold(`
4233
+ console.log(import_chalk30.default.bold(`
3495
4234
  Routed through APIblaze (${routed.length})`));
3496
- if (!routed.length) console.log(import_chalk29.default.dim(" none yet"));
3497
- for (const r of routed) console.log(` ${import_chalk29.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk29.default.dim(`\u2192 ${r.project_id}`)}`);
3498
- console.log(import_chalk29.default.bold(`
4235
+ if (!routed.length) console.log(import_chalk30.default.dim(" none yet"));
4236
+ for (const r of routed) console.log(` ${import_chalk30.default.green("\u25CF")} ${r.sidecar_origin} ${import_chalk30.default.dim(`\u2192 ${r.project_id}`)}`);
4237
+ console.log(import_chalk30.default.bold(`
3499
4238
  Candidates \u2014 going direct, not yet approved (${candidates.length})`));
3500
- if (!candidates.length) console.log(import_chalk29.default.dim(" none \u2014 run your app to discover the origins it calls"));
4239
+ if (!candidates.length) console.log(import_chalk30.default.dim(" none \u2014 run your app to discover the origins it calls"));
3501
4240
  for (const c of candidates) {
3502
- console.log(` ${import_chalk29.default.yellow("\u25CB")} ${c.origin} ${import_chalk29.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
4241
+ console.log(` ${import_chalk30.default.yellow("\u25CB")} ${c.origin} ${import_chalk30.default.dim(`seen ${c.request_count}\xD7, last ${c.last_seen}`)}`);
3503
4242
  }
3504
4243
  if (candidates.length) {
3505
- console.log(import_chalk29.default.dim(`
3506
- Approve: apiblaze origins approve ${candidates[0].origin.replace("https://", "")}`));
3507
- console.log(import_chalk29.default.dim(` Dismiss: apiblaze origins deny ${candidates[0].origin.replace("https://", "")}`));
4244
+ console.log(import_chalk30.default.dim(`
4245
+ Approve: apiblaze sidecar approve ${candidates[0].origin.replace("https://", "")}`));
4246
+ console.log(import_chalk30.default.dim(` Dismiss: apiblaze sidecar deny ${candidates[0].origin.replace("https://", "")}`));
3508
4247
  }
3509
4248
  }
3510
4249
  async function runOriginsApprove(origin, opts) {
3511
4250
  if (!loadCredentials()) {
3512
4251
  const cred = loadAnonCred();
3513
4252
  if (!cred) {
3514
- console.error(import_chalk29.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
4253
+ console.error(import_chalk30.default.red("Not logged in and no anonymous workspace. Run `apiblaze init` first."));
3515
4254
  process.exit(1);
3516
4255
  }
3517
- const spinner2 = (0, import_ora14.default)(`Approving ${origin} (anonymous)...`).start();
4256
+ const spinner2 = (0, import_ora15.default)(`Approving ${origin} (anonymous)...`).start();
3518
4257
  try {
3519
4258
  const out = await cpFetch(cred.cp_key, `/teams/${encodeURIComponent(cred.team_id)}/sidecar/approve`, { method: "POST", body: JSON.stringify({ origin }) });
3520
4259
  spinner2.succeed(`Approved ${origin} \u2192 proxy ${out.project_id}. Routing within ~5 min.`);
@@ -3525,7 +4264,7 @@ async function runOriginsApprove(origin, opts) {
3525
4264
  return;
3526
4265
  }
3527
4266
  const { teamId } = await resolveTeam(opts.team);
3528
- const spinner = (0, import_ora14.default)(`Approving ${origin}...`).start();
4267
+ const spinner = (0, import_ora15.default)(`Approving ${origin}...`).start();
3529
4268
  try {
3530
4269
  const out = await admin({
3531
4270
  method: "POST",
@@ -3542,7 +4281,7 @@ async function runOriginsApprove(origin, opts) {
3542
4281
  }
3543
4282
  async function runOriginsDeny(origin, opts) {
3544
4283
  const { teamId } = await resolveTeam(opts.team);
3545
- const spinner = (0, import_ora14.default)(`Dismissing ${origin}...`).start();
4284
+ const spinner = (0, import_ora15.default)(`Dismissing ${origin}...`).start();
3546
4285
  try {
3547
4286
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/dismiss`, body: { origin }, summary: `Dismiss sidecar origin ${origin}` });
3548
4287
  spinner.succeed(`Dismissed ${origin}. It won't be suggested again.`);
@@ -3553,7 +4292,7 @@ async function runOriginsDeny(origin, opts) {
3553
4292
  }
3554
4293
  async function runOriginsRemove(origin, opts) {
3555
4294
  const { teamId } = await resolveTeam(opts.team);
3556
- const spinner = (0, import_ora14.default)(`Removing the proxy for ${origin}...`).start();
4295
+ const spinner = (0, import_ora15.default)(`Removing the proxy for ${origin}...`).start();
3557
4296
  try {
3558
4297
  await admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/sidecar/remove`, body: { origin }, summary: `Un-route sidecar origin ${origin}` });
3559
4298
  spinner.succeed(`Removed ${origin}. Your app will stop routing it (goes direct) within ~5 min.`);
@@ -3563,6 +4302,131 @@ async function runOriginsRemove(origin, opts) {
3563
4302
  }
3564
4303
  }
3565
4304
 
4305
+ // src/commands/op.ts
4306
+ var import_chalk31 = __toESM(require("chalk"));
4307
+ init_auth();
4308
+ init_types();
4309
+ var OPERATOR_EMAILS = /* @__PURE__ */ new Set(["julienpmjacquet@gmail.com", "chkev@umich.edu"]);
4310
+ var DASHBOARD_BASE6 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
4311
+ function isOperatorLogin() {
4312
+ const email = loadCredentials()?.email?.toLowerCase();
4313
+ return !!email && OPERATOR_EMAILS.has(email);
4314
+ }
4315
+ async function opCall(call) {
4316
+ const token = getAccessToken();
4317
+ const res = await fetch(`${DASHBOARD_BASE6}/api/cli/op`, {
4318
+ method: "POST",
4319
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
4320
+ body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
4321
+ });
4322
+ let data = null;
4323
+ try {
4324
+ data = await res.json();
4325
+ } catch {
4326
+ }
4327
+ recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
4328
+ if (!res.ok) {
4329
+ const msg = data?.details ?? data?.error ?? res.statusText;
4330
+ throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
4331
+ }
4332
+ return data;
4333
+ }
4334
+ function printResidue(report, applied) {
4335
+ const up = report?.upstash ?? {};
4336
+ const fga = report?.fga ?? {};
4337
+ console.log(import_chalk31.default.bold(applied ? "\nExternal-residue sweep" : "\nExternal residue (dry-run \u2014 nothing deleted)"));
4338
+ console.log(import_chalk31.default.bold("\n Upstash"));
4339
+ const orphans = up.orphans ?? [];
4340
+ if (orphans.length === 0) console.log(import_chalk31.default.green(" no orphaned keys"));
4341
+ for (const o of orphans) console.log(` ${import_chalk31.default.yellow(o.key)} ${import_chalk31.default.dim(`\u2014 ${o.reason}`)}`);
4342
+ console.log(import_chalk31.default.dim(` kept (live principals): ${up.kept ?? 0} \xB7 anon wallets (untouched): ${up.anon_wallets ?? 0}`));
4343
+ if (up.unknown?.length) console.log(import_chalk31.default.dim(` unknown (never deleted): ${up.unknown.join(", ")}`));
4344
+ if (applied) console.log(` ${import_chalk31.default.bold(String(up.deleted ?? 0))} key(s) deleted`);
4345
+ for (const e of up.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
4346
+ console.log(import_chalk31.default.bold("\n OpenFGA / Neon"));
4347
+ if (applied) {
4348
+ const swept = fga?.swept ?? [];
4349
+ if (swept.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
4350
+ for (const s of swept) {
4351
+ console.log(
4352
+ ` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 store ${s.openfga_deleted ? "deleted" : "DEFERRED"}, ${s.neon_deleted} Neon tuple(s) purged`)}`
4353
+ );
4354
+ }
4355
+ if (fga?.remaining) console.log(import_chalk31.default.yellow(` ${fga.remaining} more orphan store(s) \u2014 re-run to drain`));
4356
+ } else {
4357
+ const fgaOrphans = fga?.orphans ?? [];
4358
+ if (fgaOrphans.length === 0) console.log(import_chalk31.default.green(" no orphaned stores"));
4359
+ for (const s of fgaOrphans) {
4360
+ const src = s.in_openfga ? "live in OpenFGA" : "Neon tuples only";
4361
+ console.log(` ${import_chalk31.default.yellow(s.store_id)} ${import_chalk31.default.dim(`\u2014 ${src}${s.name ? ` (${s.name})` : ""}, ${s.neon_tuples} Neon tuple(s)`)}`);
4362
+ }
4363
+ console.log(import_chalk31.default.dim(` kept stores: ${(fga?.kept_store_ids ?? []).length}`));
4364
+ }
4365
+ for (const e of fga?.errors ?? []) console.log(import_chalk31.default.red(` error: ${e}`));
4366
+ console.log();
4367
+ }
4368
+ async function runOp(sub, opts = {}) {
4369
+ if (!loadCredentials()) {
4370
+ console.log(import_chalk31.default.dim("Not logged in. Run `apiblaze login`."));
4371
+ return;
4372
+ }
4373
+ if (!isOperatorLogin()) {
4374
+ console.log(import_chalk31.default.dim("`apiblaze op` is only available to platform operators."));
4375
+ return;
4376
+ }
4377
+ switch (sub) {
4378
+ case void 0:
4379
+ case "menu": {
4380
+ console.log(import_chalk31.default.bold("\nOperator menu"));
4381
+ console.log(` ${import_chalk31.default.cyan("apiblaze op residue")} external-store residue report (Upstash + Neon/OpenFGA, dry-run)`);
4382
+ console.log(` ${import_chalk31.default.cyan("apiblaze op sweep")} delete the orphans the report shows (asks first; ${import_chalk31.default.dim("-y to skip")})`);
4383
+ console.log(` ${import_chalk31.default.cyan("apiblaze op credits")} list credit wallets
4384
+ `);
4385
+ return;
4386
+ }
4387
+ case "residue": {
4388
+ const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
4389
+ if (opts.json) return void console.log(JSON.stringify(report, null, 2));
4390
+ printResidue(report, false);
4391
+ return;
4392
+ }
4393
+ case "sweep": {
4394
+ const report = await opCall({ method: "GET", path: "/operator/external-residue", summary: "external residue report" });
4395
+ const nUp = report?.upstash?.orphans?.length ?? 0;
4396
+ const nFga = report?.fga?.orphans?.length ?? 0;
4397
+ printResidue(report, false);
4398
+ if (nUp + nFga === 0) {
4399
+ console.log(import_chalk31.default.green("Nothing to sweep."));
4400
+ return;
4401
+ }
4402
+ if (!opts.yes) {
4403
+ const readline2 = await import("readline/promises");
4404
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
4405
+ const answer = await rl.question(import_chalk31.default.red(`Delete ${nUp} Upstash key(s) + ${nFga} OpenFGA store(s)? Type 'sweep' to confirm: `));
4406
+ rl.close();
4407
+ if (answer.trim() !== "sweep") return void console.log(import_chalk31.default.dim("Aborted."));
4408
+ }
4409
+ const result = await opCall({ method: "POST", path: "/operator/external-residue/sweep", summary: "external residue sweep" });
4410
+ if (opts.json) return void console.log(JSON.stringify(result, null, 2));
4411
+ printResidue(result, true);
4412
+ return;
4413
+ }
4414
+ case "credits": {
4415
+ const data = await opCall({ method: "GET", path: "/operator/credits", summary: "list credit wallets" });
4416
+ if (opts.json) return void console.log(JSON.stringify(data, null, 2));
4417
+ const accounts = data?.accounts ?? [];
4418
+ if (accounts.length === 0) return void console.log(import_chalk31.default.dim("No credit wallets."));
4419
+ for (const a of accounts) {
4420
+ const bal = typeof a.balance_cents === "number" ? `$${(a.balance_cents / 100).toFixed(2)}` : "?";
4421
+ console.log(` ${import_chalk31.default.bold(bal.padStart(9))} ${a.walletId}${a.owner_email ? import_chalk31.default.dim(` \u2014 ${a.owner_email}`) : a.anon ? import_chalk31.default.dim(" \u2014 anon") : ""}`);
4422
+ }
4423
+ return;
4424
+ }
4425
+ default:
4426
+ console.log(import_chalk31.default.red(`Unknown op subcommand '${sub}'. Run \`apiblaze op\` for the menu.`));
4427
+ }
4428
+ }
4429
+
3566
4430
  // src/index.ts
3567
4431
  var program = new import_commander.Command();
3568
4432
  program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
@@ -3602,16 +4466,19 @@ var agent = program.command("agent").description("Chat with an assistant that bu
3602
4466
  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)));
3603
4467
  agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
3604
4468
  agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
3605
- program.command("init").aliases(["sidecar"]).description("Wire a Next.js app to route its external fetches through APIblaze (one command)").option("--team <id|name>", "Team to set up under (defaults to your active team)").option("--dir <path>", "Project directory (defaults to cwd)").option("--no-inspector", "Skip generating the dev-only /abz-inspector page").option("--rotate", "Mint a fresh invoke key even if one already exists").option("--new-session", "Start a fresh anonymous session (logged-out only)").option("-y, --yes", "Skip prompts").action(action((opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false, rotate: opts.rotate, newSession: opts.newSession })));
3606
- var origins = program.command("origins").description("See which external origins your app calls; approve the ones to route through APIblaze").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((opts) => runOriginsList(opts)));
3607
- origins.command("approve").description("Route an origin through APIblaze (creates its proxy)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((origin, opts) => runOriginsApprove(origin, opts)));
3608
- origins.command("deny").description("Dismiss a candidate origin so it stops being suggested").argument("<origin>", "Origin, e.g. sentry.io").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsDeny(origin, opts)));
3609
- origins.command("remove").description("Un-route an approved origin (deletes its proxy; the app goes direct again)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsRemove(origin, opts)));
4469
+ var runSetup = (opts) => runSidecar({ team: opts.team, dir: opts.dir, yes: opts.yes, noInspector: opts.inspector === false, rotate: opts.rotate, newSession: opts.newSession });
4470
+ var withSetupOptions = (cmd) => cmd.option("--team <id|name>", "Team to set up under (defaults to your active team)").option("--dir <path>", "Project directory (defaults to cwd)").option("--no-inspector", "Skip generating the dev-only /abz-inspector page").option("--rotate", "Mint a fresh invoke key even if one already exists").option("--new-session", "Start a fresh anonymous session (logged-out only)").option("-y, --yes", "Skip prompts");
4471
+ withSetupOptions(program.command("init").description("Set up the APIblaze sidecar in a Next.js app (shortcut for `apiblaze sidecar setup`)")).action(action((opts) => runSetup(opts)));
4472
+ var sidecar = program.command("sidecar").description("The APIblaze sidecar \u2014 set it up, then approve which origins route through APIblaze").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((opts) => runOriginsList(opts)));
4473
+ withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to route its external fetches through APIblaze")).action(action((opts) => runSetup(opts)));
4474
+ sidecar.command("approve").description("Route an origin through APIblaze (creates its proxy)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((origin, opts) => runOriginsApprove(origin, opts)));
4475
+ sidecar.command("deny").description("Dismiss a candidate origin so it stops being suggested").argument("<origin>", "Origin, e.g. sentry.io").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsDeny(origin, opts)));
4476
+ sidecar.command("remove").description("Un-route an approved origin (deletes its proxy; the app goes direct again)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsRemove(origin, opts)));
3610
4477
  program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
3611
4478
  try {
3612
4479
  const resolved = parseInt(port ?? opts.port, 10);
3613
4480
  if (Number.isNaN(resolved)) {
3614
- console.error(import_chalk30.default.red(`Invalid port: ${port ?? opts.port}`));
4481
+ console.error(import_chalk32.default.red(`Invalid port: ${port ?? opts.port}`));
3615
4482
  process.exit(1);
3616
4483
  }
3617
4484
  await runDev({ port: resolved, captureFile: opts.captureFile });
@@ -3659,6 +4526,7 @@ program.command("projects").description("List the projects in your team").action
3659
4526
  });
3660
4527
  program.command("delete").description("Delete a proxy and everything under it (asks first)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--team <id|name>", "Team the project is in (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON").action(action((project, version2, opts) => runDelete(project, version2, opts)));
3661
4528
  program.command("export").description("Export config and data for migration out of APIblaze (Kong, ...)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--kong", "Produce a runnable Kong OSS bundle (decK config + plugins + docker-compose)").option("--data", "Plain data export (default)").option("--secrets", "Include decrypted producer-supplied secrets (member role; audit-logged)").option("--keys <mode>", "API-key export: hashes (default, consumers keep keys) | mint | none").option("--no-consumers", "Skip the end-user lane (users, groups, keys)").option("-o, --out <file>", "Output zip path").option("--team <id|name>", "Team the project is in (defaults to active team)").action(action((project, version2, opts) => runExport(project, version2, { ...opts, noConsumers: opts.consumers === false })));
4529
+ program.command("config").description("Browse and change every proxy setting & feature (interactive; git-config-style get/set)").argument("[project]", "Project name or id (omit to pick interactively)").argument("[key]", "Setting key, e.g. throttling.proxyQuota (omit for the menu)").argument("[value]", "New value (omit to read the current value)").option("--list", "Print every setting and its current value").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Machine-readable output (with --list or a key read)").action(action((project, key, value, opts) => runConfig(project, key, value, opts)));
3662
4530
  program.command("target").description("Change where a proxy forwards requests").argument("<project>", "Project name or id").requiredOption("--url <url>", "Target URL to forward to").option("--env <env>", "Environment to scope the target to (e.g. prod, dev)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTargetSet(project, opts)));
3663
4531
  program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "User rate limit (requests/sec)").option("--end-user-rate <n>", "Per-end-user rate limit (requests/sec)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
3664
4532
  program.command("rename").description("Change a proxy's display name").argument("<project>", "Project name or id").requiredOption("--display-name <name>", "New human-friendly display name").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRename(project, opts)));
@@ -3679,13 +4547,15 @@ apikeys.command("list").description("List control-plane API keys in your team").
3679
4547
  apikeys.command("mint").description("Create a control-plane API key (secret shown once)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runKeyMint(opts)));
3680
4548
  apikeys.command("revoke").description("Revoke a control-plane API key").argument("<keyId>", "Key id (see `apikeys list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts) => runKeyRevoke(keyId, opts)));
3681
4549
  program.addCommand(apikeys, { hidden: true });
4550
+ var op = new import_commander.Command("op").description("Operator menu (platform operators only)").argument("[action]", "residue | sweep | credits (omit for the menu)").option("-y, --yes", "Skip the sweep confirmation prompt").option("--json", "Output machine-readable JSON").action(action((sub, opts) => runOp(sub, opts)));
4551
+ program.addCommand(op, { hidden: true });
3682
4552
  var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
3683
4553
  spec.command("get").description("Print the current OpenAPI document").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Compact JSON output").action(action((project, opts) => runSpecGet(project, opts)));
3684
4554
  spec.command("set").description("Replace the stored OpenAPI spec from a local file").argument("<project>", "Project name or id").requiredOption("--file <path>", "OpenAPI JSON or YAML file to upload").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runSpecSet(project, opts)));
3685
4555
  var HELP_GROUPS = [
3686
4556
  { title: "Chat", commands: ["agent"] },
3687
- { title: "Setup", commands: ["login", "create", "init", "origins", "dev", "claim", "team", "whoami", "logout"] },
3688
- { title: "Control plane commands", commands: ["projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
4557
+ { title: "Setup", commands: ["login", "create", "init", "sidecar", "dev", "claim", "team", "whoami", "logout"] },
4558
+ { title: "Control plane commands", commands: ["config", "projects", "tenant", "domain", "delete", "target", "throttle", "rename", "spec", "export"] },
3689
4559
  { title: "Data plane commands", commands: [
3690
4560
  { parent: "consumer", sub: "login" },
3691
4561
  { parent: "consumer", sub: "apikeys" }
@@ -3704,7 +4574,7 @@ function groupedCommandHelp() {
3704
4574
  const sub = byName.get(e.parent)?.commands.find((s) => s.name() === e.sub);
3705
4575
  return sub ? ` ${helpLabel(e).padEnd(width)}${sub.description()}` : "";
3706
4576
  }).filter(Boolean).join("\n");
3707
- return `${import_chalk30.default.bold(g.title)}
4577
+ return `${import_chalk32.default.bold(g.title)}
3708
4578
  ${rows}`;
3709
4579
  }).join("\n\n");
3710
4580
  }
@@ -3718,7 +4588,9 @@ program.addHelpText("after", () => `
3718
4588
  ${groupedCommandHelp()}
3719
4589
 
3720
4590
  Tips:
4591
+ \u2022 \`apiblaze config <project>\` browses EVERY setting & feature (works logged-out to explore).
3721
4592
  \u2022 Add --verbose to any command to see the equivalent API calls.
4593
+ \u2022 Full API reference: https://api.apiblaze.com/openapi.json
3722
4594
  \u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
3723
4595
 
3724
4596
  Examples:
@@ -3730,13 +4602,13 @@ Examples:
3730
4602
  `);
3731
4603
  function printError(err) {
3732
4604
  if (err instanceof ApiError) {
3733
- console.error(import_chalk30.default.red(`
4605
+ console.error(import_chalk32.default.red(`
3734
4606
  API error (${err.status}): ${err.message}`));
3735
4607
  } else if (err instanceof Error) {
3736
- console.error(import_chalk30.default.red(`
4608
+ console.error(import_chalk32.default.red(`
3737
4609
  Error: ${err.message}`));
3738
4610
  } else {
3739
- console.error(import_chalk30.default.red("\nUnknown error"));
4611
+ console.error(import_chalk32.default.red("\nUnknown error"));
3740
4612
  }
3741
4613
  }
3742
4614
  program.parse(process.argv);