apiblaze 0.17.7 → 0.17.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +338 -104
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -137,9 +137,9 @@ async function createProxyAnonymous(body) {
137
137
  }
138
138
  return res.json();
139
139
  }
140
- async function apiFetch(path6, options = {}) {
140
+ async function apiFetch(path7, options = {}) {
141
141
  const token = getAccessToken();
142
- const url = `${DASHBOARD_BASE}${path6}`;
142
+ const url = `${DASHBOARD_BASE}${path7}`;
143
143
  const res = await fetch(url, {
144
144
  ...options,
145
145
  headers: {
@@ -165,12 +165,12 @@ async function apiFetch(path6, options = {}) {
165
165
  }
166
166
  return res.json();
167
167
  }
168
- async function agentCall(path6, method, body) {
168
+ async function agentCall(path7, method, body) {
169
169
  const token = getAccessToken();
170
170
  const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
171
171
  method: "POST",
172
172
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
173
- body: JSON.stringify({ path: path6, method, body })
173
+ body: JSON.stringify({ path: path7, method, body })
174
174
  });
175
175
  let data = null;
176
176
  try {
@@ -699,7 +699,7 @@ var import_commander = require("commander");
699
699
  var import_chalk37 = __toESM(require("chalk"));
700
700
 
701
701
  // package.json
702
- var version = "0.17.7";
702
+ var version = "0.17.9";
703
703
 
704
704
  // src/index.ts
705
705
  init_types();
@@ -949,11 +949,11 @@ function decodeJwt(token) {
949
949
  return null;
950
950
  }
951
951
  }
952
- function maskPath(path6) {
953
- const q = path6.indexOf("?");
954
- if (q < 0) return path6;
955
- const base = path6.slice(0, q);
956
- const query = path6.slice(q + 1);
952
+ function maskPath(path7) {
953
+ const q = path7.indexOf("?");
954
+ if (q < 0) return path7;
955
+ const base = path7.slice(0, q);
956
+ const query = path7.slice(q + 1);
957
957
  const masked = query.split("&").map((pair) => {
958
958
  const eq = pair.indexOf("=");
959
959
  if (eq < 0) return pair;
@@ -4715,6 +4715,7 @@ async function runKeyRevoke(keyId, opts) {
4715
4715
 
4716
4716
  // src/commands/apichat.ts
4717
4717
  var fs8 = __toESM(require("fs"));
4718
+ var path5 = __toESM(require("path"));
4718
4719
  var crypto2 = __toESM(require("crypto"));
4719
4720
  var import_chalk32 = __toESM(require("chalk"));
4720
4721
  var import_ora16 = __toESM(require("ora"));
@@ -4901,40 +4902,75 @@ function resolveTarget(spec2, opts, sourceUrl) {
4901
4902
  fail4(`Could not resolve servers[0].url ("${raw}").`, "Re-run with --target <upstream base URL>.");
4902
4903
  }
4903
4904
  }
4904
- function scanTargetAuth(spec2, force) {
4905
- const schemes = spec2.components && spec2.components.securitySchemes || spec2.securityDefinitions || {};
4906
- const entries2 = Object.entries(schemes);
4907
- if (!entries2.length) return null;
4908
- for (const [id, s] of entries2) {
4909
- const type = String(s && s.type || "").toLowerCase();
4910
- if (type === "oauth2" || type === "openidconnect") {
4911
- if (!force) {
4912
- fail4(
4913
- `The API's security scheme "${id}" is ${s.type} \u2014 apichat cannot mint an OAuth target token non-interactively.`,
4914
- "Re-run with --force to provision anyway (you will configure target auth yourself later),\nor use an api_key / bearer scheme."
4915
- );
4905
+ function schemeToAuth(id, s) {
4906
+ const type = String(s && s.type || "").toLowerCase();
4907
+ if (type === "apikey") {
4908
+ const name = String(s.name || "X-API-Key");
4909
+ const loc = String(s.in || "header").toLowerCase();
4910
+ if (loc === "query") return { destination: `param:${name}`, prefix: "", label: `${id} (query "${name}")` };
4911
+ const dest = loc === "cookie" ? "header:Cookie" : `header:${name}`;
4912
+ const prefix = loc === "cookie" ? `${name}=` : "";
4913
+ return { destination: dest, prefix, label: `${id} (${loc} "${name}")` };
4914
+ }
4915
+ if (type === "http") {
4916
+ const scheme = String(s.scheme || "").toLowerCase();
4917
+ if (scheme === "bearer") return { destination: "header:Authorization", prefix: "Bearer ", label: `${id} (bearer token)` };
4918
+ if (scheme === "basic") return { destination: "header:Authorization", prefix: "Basic ", label: `${id} (basic auth)`, basic: true };
4919
+ }
4920
+ return null;
4921
+ }
4922
+ async function resolveTargetAuth(spec2, opts) {
4923
+ const defs = spec2.components && spec2.components.securitySchemes || spec2.securityDefinitions || {};
4924
+ const req = Array.isArray(spec2.security) ? spec2.security : null;
4925
+ if (!req || req.length === 0) return null;
4926
+ const noneAllowed = req.some((alt) => alt && typeof alt === "object" && Object.keys(alt).length === 0);
4927
+ const seen = /* @__PURE__ */ new Set();
4928
+ const candidates = [];
4929
+ let sawOAuth = false;
4930
+ for (const alt of req) {
4931
+ if (!alt || typeof alt !== "object") continue;
4932
+ for (const name of Object.keys(alt)) {
4933
+ if (seen.has(name)) continue;
4934
+ seen.add(name);
4935
+ const def = defs[name];
4936
+ const t = String(def && def.type || "").toLowerCase();
4937
+ if (t === "oauth2" || t === "openidconnect") {
4938
+ sawOAuth = true;
4939
+ continue;
4916
4940
  }
4917
- console.log(import_chalk32.default.yellow(` --force: skipping OAuth scheme "${id}" \u2014 configure target auth later with \`apiblaze config\`.`));
4918
- return null;
4941
+ const a = def ? schemeToAuth(name, def) : null;
4942
+ if (a) candidates.push(a);
4919
4943
  }
4920
4944
  }
4921
- for (const [id, s] of entries2) {
4922
- const type = String(s && s.type || "").toLowerCase();
4923
- if (type === "apikey") {
4924
- const name = String(s.name || "X-API-Key");
4925
- const loc = String(s.in || "header").toLowerCase();
4926
- if (loc === "query") return { destination: `param:${name}`, prefix: "", label: `${id} (query "${name}")` };
4927
- const dest = loc === "cookie" ? "header:Cookie" : `header:${name}`;
4928
- const prefix = loc === "cookie" ? `${name}=` : "";
4929
- return { destination: dest, prefix, label: `${id} (${loc} "${name}")` };
4930
- }
4931
- if (type === "http") {
4932
- const scheme = String(s.scheme || "").toLowerCase();
4933
- if (scheme === "bearer") return { destination: "header:Authorization", prefix: "Bearer ", label: `${id} (bearer token)` };
4934
- if (scheme === "basic") return { destination: "header:Authorization", prefix: "Basic ", label: `${id} (basic auth)`, basic: true };
4945
+ if (candidates.length === 0) {
4946
+ if (noneAllowed) return null;
4947
+ if (sawOAuth && !opts.force) {
4948
+ fail4(
4949
+ "The API requires OAuth (oauth2/openIdConnect) for the target, which apichat cannot mint non-interactively.",
4950
+ "Re-run with --force to provision anyway (configure target auth later with `apiblaze config`),\nor use an api_key / bearer / basic scheme."
4951
+ );
4935
4952
  }
4953
+ if (sawOAuth) console.log(import_chalk32.default.yellow(" --force: skipping OAuth target auth \u2014 configure it later with `apiblaze config`."));
4954
+ return null;
4936
4955
  }
4937
- return null;
4956
+ if (candidates.length === 1 && !noneAllowed) return candidates[0];
4957
+ const interactive = !!process.stdin.isTTY && !opts.yes;
4958
+ if (!interactive) return noneAllowed ? null : candidates[0];
4959
+ const { default: inquirer3 } = await import("inquirer");
4960
+ const choices = [
4961
+ ...candidates.map((c, i) => ({ name: c.label, value: i })),
4962
+ ...noneAllowed ? [{ name: "None \u2014 the API is public / I'll configure target auth later", value: -1 }] : []
4963
+ ];
4964
+ const { pick: pick2 } = await inquirer3.prompt([
4965
+ {
4966
+ type: "list",
4967
+ name: "pick",
4968
+ message: "This API accepts more than one auth method for the target \u2014 which should the proxy use?",
4969
+ choices,
4970
+ default: noneAllowed ? -1 : 0
4971
+ }
4972
+ ]);
4973
+ return pick2 === -1 ? null : candidates[pick2];
4938
4974
  }
4939
4975
  async function captureTargetSecret(auth, opts) {
4940
4976
  const interactive = !!process.stdin.isTTY;
@@ -4967,65 +5003,92 @@ async function captureTargetSecret(auth, opts) {
4967
5003
  if (!secret) fail4("No credential entered.");
4968
5004
  return secret;
4969
5005
  }
4970
- async function cpPost(anon, path6, body, summary) {
5006
+ async function cpPost(anon, path7, body, summary) {
4971
5007
  if (anon) {
4972
5008
  const cred = loadAnonCred();
4973
5009
  if (!cred) throw new Error("Anonymous workspace credential missing.");
4974
- return cpFetch(cred.cp_key, path6, { method: "POST", body: JSON.stringify(body) });
5010
+ return cpFetch(cred.cp_key, path7, { method: "POST", body: JSON.stringify(body) });
4975
5011
  }
4976
- return admin({ method: "POST", path: path6, body, summary });
5012
+ return admin({ method: "POST", path: path7, body, summary });
4977
5013
  }
4978
5014
  async function provision(spec2, target, opts) {
4979
5015
  const loggedIn = !!loadCredentials();
4980
5016
  const anon = !loggedIn;
4981
- let name = opts.name ? normalizeName2(opts.name) : "";
4982
- if (!name) {
5017
+ const salt = () => Math.random().toString(36).slice(2, 6);
5018
+ let base = opts.name ? normalizeName2(opts.name) : "";
5019
+ if (!base) {
4983
5020
  try {
4984
5021
  const host = new URL(target).hostname;
4985
- name = normalizeName2(host.split(".")[0]);
4986
- if (name.length < 3) name = normalizeName2(host);
5022
+ base = normalizeName2(host.split(".")[0]);
5023
+ if (base.length < 3) base = normalizeName2(host);
4987
5024
  } catch {
4988
5025
  }
4989
5026
  }
4990
- if (!name && spec2.info && typeof spec2.info.title === "string") name = normalizeName2(spec2.info.title);
4991
- if (!name || name.length < 3) name = "apichat" + Math.random().toString(36).slice(2, 7);
5027
+ if (!base && spec2.info && typeof spec2.info.title === "string") base = normalizeName2(spec2.info.title);
5028
+ if (!base || base.length < 3) base = "apichat";
5029
+ let name = opts.name ? base : `${base}-${salt()}`;
4992
5030
  const spinner = (0, import_ora16.default)("Provisioning an api_key proxy...").start();
4993
5031
  let result;
4994
- try {
4995
- if (loggedIn) {
4996
- const creds = loadCredentials();
4997
- result = await createProxy({
4998
- name,
4999
- target_url: target,
5000
- auth_type: "api_key",
5001
- team_id: creds.teamId,
5002
- ...opts.apiversion ? { api_version: opts.apiversion } : {}
5003
- });
5004
- } else {
5005
- const cred = loadAnonCred();
5006
- const body = {
5007
- name,
5008
- subdomain: name,
5009
- target,
5010
- target_url: target,
5011
- auth_type: "api_key",
5012
- ...opts.apiversion ? { api_version: opts.apiversion } : {}
5013
- };
5014
- if (cred) {
5015
- result = await cpFetch(cred.cp_key, "/projects", { method: "POST", body: JSON.stringify(body) });
5032
+ for (let attempt = 0; attempt < 4; attempt++) {
5033
+ try {
5034
+ if (loggedIn) {
5035
+ const creds = loadCredentials();
5036
+ result = await createProxy({
5037
+ name,
5038
+ target_url: target,
5039
+ auth_type: "api_key",
5040
+ team_id: creds.teamId,
5041
+ ...opts.apiversion ? { api_version: opts.apiversion } : {}
5042
+ });
5016
5043
  } else {
5017
- result = await createProxyAnonymous(body);
5018
- if (result.cp_key && result.team_id) saveAnonCred(result.cp_key, result.team_id, result.claim_code);
5044
+ const cred = loadAnonCred();
5045
+ const body = {
5046
+ name,
5047
+ subdomain: name,
5048
+ target,
5049
+ target_url: target,
5050
+ auth_type: "api_key",
5051
+ ...opts.apiversion ? { api_version: opts.apiversion } : {}
5052
+ };
5053
+ if (cred) {
5054
+ result = await cpFetch(cred.cp_key, "/projects", { method: "POST", body: JSON.stringify(body) });
5055
+ } else {
5056
+ result = await createProxyAnonymous(body);
5057
+ if (result.cp_key && result.team_id) saveAnonCred(result.cp_key, result.team_id, result.claim_code);
5058
+ }
5019
5059
  }
5060
+ spinner.succeed(`Proxy provisioned${name !== base ? ` as "${name}"` : ""}.`);
5061
+ break;
5062
+ } catch (err) {
5063
+ const status = err instanceof ApiError ? err.status : void 0;
5064
+ const collision = err instanceof ApiError && (err.status === 409 || /exist|taken|available/i.test(err.message));
5065
+ if (collision && attempt < 3) {
5066
+ name = `${base}-${salt()}`;
5067
+ continue;
5068
+ }
5069
+ if (status === 401) {
5070
+ if (!loggedIn && attempt < 3) {
5071
+ clearAnonCred();
5072
+ continue;
5073
+ }
5074
+ spinner.fail("Provisioning failed.");
5075
+ if (loggedIn) {
5076
+ fail4(
5077
+ "Your login session was rejected \u2014 it may have expired.",
5078
+ "Run `apiblaze login` to refresh, then re-run `apiblaze apichat`."
5079
+ );
5080
+ }
5081
+ fail4(
5082
+ "Could not start an anonymous workspace right now.",
5083
+ "Try again in a moment, or run `apiblaze login` to chat with your account."
5084
+ );
5085
+ }
5086
+ spinner.fail("Provisioning failed.");
5087
+ if (collision) fail4(`A proxy named "${name}" already exists.`, "Re-run with --name <other>.");
5088
+ throw err;
5020
5089
  }
5021
- spinner.succeed("Proxy provisioned.");
5022
- } catch (err) {
5023
- spinner.fail("Provisioning failed.");
5024
- if (err instanceof ApiError && (err.status === 409 || /exist|taken|available/i.test(err.message))) {
5025
- fail4(`A proxy named "${name}" already exists.`, "Re-run with --name <other> (or `apiblaze delete` it first).");
5026
- }
5027
- throw err;
5028
5090
  }
5091
+ if (!result) fail4("Provisioning failed after retries.");
5029
5092
  const projectId = result.project_id;
5030
5093
  const version2 = result.api_version || "1.0.0";
5031
5094
  const keys = result.api_keys ?? {};
@@ -5133,7 +5196,19 @@ function renderToolEvents(events) {
5133
5196
  function billingLine(billing) {
5134
5197
  if (!billing || typeof billing.cents !== "number") return null;
5135
5198
  const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
5136
- return import_chalk32.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk32.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
5199
+ let line = import_chalk32.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk32.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
5200
+ if (typeof billing.credits_remaining === "number") {
5201
+ line += import_chalk32.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
5202
+ }
5203
+ return line;
5204
+ }
5205
+ function freeBudgetWarning(billing, anon) {
5206
+ if (!anon || !billing || typeof billing.free_remaining_cents !== "number") return null;
5207
+ const perTurn = Math.max(billing.cents || 0, 0.02);
5208
+ const left = Math.floor(billing.free_remaining_cents / perTurn);
5209
+ if (left > 8) return null;
5210
+ if (left <= 0) return import_chalk32.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
5211
+ return import_chalk32.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
5137
5212
  }
5138
5213
  function printAssistant(delta) {
5139
5214
  for (let i = delta.length - 1; i >= 0; i--) {
@@ -5207,6 +5282,8 @@ async function replTurn(p, messages, userText) {
5207
5282
  }
5208
5283
  const bl = billingLine(data.billing);
5209
5284
  if (bl) console.log(bl);
5285
+ const warn = freeBudgetWarning(data.billing, p.anon);
5286
+ if (warn) console.log(warn);
5210
5287
  verboseTrace(p, !!llm2);
5211
5288
  if (!data.continue) return;
5212
5289
  }
@@ -5226,10 +5303,144 @@ function renderUpsell(p, upsell) {
5226
5303
  }
5227
5304
  console.log();
5228
5305
  }
5229
- async function runRepl(p) {
5306
+ var apichatsPath = () => path5.join(getApiblazeDir(), "apichats.json");
5307
+ function loadApichats() {
5308
+ try {
5309
+ const list = JSON.parse(fs8.readFileSync(apichatsPath(), "utf-8"));
5310
+ return Array.isArray(list) ? list : [];
5311
+ } catch {
5312
+ return [];
5313
+ }
5314
+ }
5315
+ function writeApichats(list) {
5316
+ fs8.mkdirSync(getApiblazeDir(), { recursive: true });
5317
+ fs8.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
5318
+ try {
5319
+ fs8.chmodSync(apichatsPath(), 384);
5320
+ } catch {
5321
+ }
5322
+ }
5323
+ function apichatKey(a) {
5324
+ return `${a.projectId}::${a.version}::${a.environment}`;
5325
+ }
5326
+ function upsertApichat(entry) {
5327
+ const list = loadApichats();
5328
+ const i = list.findIndex((a) => apichatKey(a) === apichatKey(entry));
5329
+ if (i >= 0) list[i] = { ...list[i], ...entry };
5330
+ else list.unshift(entry);
5331
+ writeApichats(list.slice(0, 30));
5332
+ }
5333
+ function saveTranscript(p, messages) {
5334
+ const list = loadApichats();
5335
+ const i = list.findIndex((a) => apichatKey(a) === apichatKey(p));
5336
+ if (i < 0) return;
5337
+ list[i].messages = messages.slice(-40);
5338
+ list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
5339
+ writeApichats(list);
5340
+ }
5341
+ function discoverLocalSpecs() {
5342
+ const cwd = process.cwd();
5343
+ const known = ["openapi.yaml", "openapi.yml", "openapi.json", "swagger.yaml", "swagger.yml", "swagger.json", "api.yaml", "api.yml", "api.json"];
5344
+ const found = [];
5345
+ for (const n of known) {
5346
+ try {
5347
+ if (fs8.statSync(path5.join(cwd, n)).isFile()) found.push(n);
5348
+ } catch {
5349
+ }
5350
+ }
5351
+ try {
5352
+ const files = fs8.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
5353
+ for (const f of files.slice(0, 60)) {
5354
+ try {
5355
+ const head = fs8.readFileSync(path5.join(cwd, f), "utf-8").slice(0, 4e3);
5356
+ if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
5357
+ found.push(f);
5358
+ }
5359
+ } catch {
5360
+ }
5361
+ }
5362
+ } catch {
5363
+ }
5364
+ return found;
5365
+ }
5366
+ async function noArgsMenu(opts) {
5230
5367
  const { default: inquirer3 } = await import("inquirer");
5231
- const messages = [];
5368
+ const saved = loadApichats();
5369
+ const choices = saved.map((a) => ({
5370
+ name: `Chat with ${import_chalk32.default.bold(a.name)} ${import_chalk32.default.dim(`(${a.target})${a.messages && a.messages.length ? ` \xB7 ${a.messages.length} msgs` : ""}`)}`,
5371
+ value: { type: "existing", a }
5372
+ }));
5373
+ choices.push({ name: import_chalk32.default.green("\uFF0B Create a new apichat"), value: { type: "new" } });
5374
+ const { pick: pick2 } = await inquirer3.prompt([
5375
+ { type: "list", name: "pick", message: "What would you like to do?", choices }
5376
+ ]);
5377
+ if (pick2.type === "existing") {
5378
+ const a = pick2.a;
5379
+ let messages = [];
5380
+ if (a.messages && a.messages.length) {
5381
+ const { mode } = await inquirer3.prompt([
5382
+ {
5383
+ type: "list",
5384
+ name: "mode",
5385
+ message: "Resume the previous conversation, or start fresh?",
5386
+ choices: [
5387
+ { name: `Resume (${a.messages.length} messages)`, value: "resume" },
5388
+ { name: "Start fresh", value: "fresh" }
5389
+ ]
5390
+ }
5391
+ ]);
5392
+ if (mode === "resume") messages = a.messages.slice();
5393
+ }
5394
+ const p = {
5395
+ projectId: a.projectId,
5396
+ version: a.version,
5397
+ environment: a.environment,
5398
+ dpKey: a.dpKey,
5399
+ mcpHost: a.mcpHost,
5400
+ proxyUrl: void 0,
5401
+ anon: a.anon
5402
+ };
5403
+ return { p, messages };
5404
+ }
5405
+ const { source } = await inquirer3.prompt([
5406
+ {
5407
+ type: "list",
5408
+ name: "source",
5409
+ message: "Where is the API spec?",
5410
+ choices: [
5411
+ { name: "A local OpenAPI file", value: "file" },
5412
+ { name: "A spec URL", value: "url" },
5413
+ { name: "Discover it from a running API (target URL)", value: "target" }
5414
+ ]
5415
+ }
5416
+ ]);
5417
+ if (source === "file") {
5418
+ const cands = discoverLocalSpecs();
5419
+ if (cands.length) {
5420
+ const { file } = await inquirer3.prompt([
5421
+ {
5422
+ type: "list",
5423
+ name: "file",
5424
+ message: "Pick a spec file (found in this directory):",
5425
+ choices: [...cands.map((f) => ({ name: f, value: f })), { name: "Enter a path manually\u2026", value: "__manual__" }]
5426
+ }
5427
+ ]);
5428
+ opts.openapispec = file === "__manual__" ? (await inquirer3.prompt([{ type: "input", name: "p", message: "Path to the OpenAPI file:" }])).p : file;
5429
+ } else {
5430
+ opts.openapispec = (await inquirer3.prompt([{ type: "input", name: "p", message: "Path to the OpenAPI file:" }])).p;
5431
+ }
5432
+ } else if (source === "url") {
5433
+ opts.openapispec = (await inquirer3.prompt([{ type: "input", name: "u", message: "OpenAPI spec URL:" }])).u;
5434
+ } else {
5435
+ opts.target = (await inquirer3.prompt([{ type: "input", name: "t", message: "Target API base URL:" }])).t;
5436
+ }
5437
+ return null;
5438
+ }
5439
+ async function runRepl(p, initialMessages) {
5440
+ const { default: inquirer3 } = await import("inquirer");
5441
+ const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
5232
5442
  console.log("\n" + import_chalk32.default.cyan.bold("Chat with your API") + import_chalk32.default.dim(` \xB7 ${p.mcpHost}`));
5443
+ if (messages.length) console.log(import_chalk32.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
5233
5444
  const llm2 = loadLlmConfig();
5234
5445
  console.log(
5235
5446
  import_chalk32.default.dim(
@@ -5272,15 +5483,26 @@ async function runRepl(p) {
5272
5483
  continue;
5273
5484
  }
5274
5485
  await replTurn(p, messages, text);
5486
+ saveTranscript(p, messages);
5275
5487
  }
5276
5488
  console.log(import_chalk32.default.dim("\nBye."));
5277
5489
  }
5278
5490
  async function runApichat(opts) {
5279
5491
  console.log(import_chalk32.default.bold("\napichat \u2014 turn any API into a chat\n"));
5492
+ if (!opts.openapispec && !opts.target) {
5493
+ if (!process.stdin.isTTY) {
5494
+ fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
5495
+ }
5496
+ const resumed = await noArgsMenu(opts);
5497
+ if (resumed) {
5498
+ await runRepl(resumed.p, resumed.messages);
5499
+ return;
5500
+ }
5501
+ }
5280
5502
  const { spec: spec2, sourceUrl } = await loadSpec(opts);
5281
5503
  const target = resolveTarget(spec2, opts, sourceUrl);
5282
5504
  console.log(` ${import_chalk32.default.dim("Target:")} ${import_chalk32.default.bold(target)}`);
5283
- const auth = scanTargetAuth(spec2, !!opts.force);
5505
+ const auth = await resolveTargetAuth(spec2, opts);
5284
5506
  if (auth && !process.stdin.isTTY && !opts.targetAuthEnv) {
5285
5507
  fail4(
5286
5508
  `This API needs target credentials (${auth.label}) and there is no TTY to prompt.`,
@@ -5289,6 +5511,18 @@ async function runApichat(opts) {
5289
5511
  }
5290
5512
  const p = await provision(spec2, target, opts);
5291
5513
  console.log(` ${import_chalk32.default.dim("Proxy: ")} ${import_chalk32.default.bold(p.proxyUrl || `${p.projectId} v${p.version}`)}`);
5514
+ upsertApichat({
5515
+ name: p.projectId,
5516
+ target,
5517
+ projectId: p.projectId,
5518
+ version: p.version,
5519
+ environment: p.environment,
5520
+ mcpHost: p.mcpHost,
5521
+ dpKey: p.dpKey,
5522
+ anon: p.anon,
5523
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
5524
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
5525
+ });
5292
5526
  if (auth) {
5293
5527
  const secret = await captureTargetSecret(auth, opts);
5294
5528
  if (secret) await writeTargetAuth(p, auth, secret);
@@ -5467,23 +5701,23 @@ async function runConsumerApikeys(opts) {
5467
5701
  var import_chalk34 = __toESM(require("chalk"));
5468
5702
  var import_ora18 = __toESM(require("ora"));
5469
5703
  var fs9 = __toESM(require("fs"));
5470
- var path5 = __toESM(require("path"));
5704
+ var path6 = __toESM(require("path"));
5471
5705
  init_admin();
5472
5706
  init_auth();
5473
5707
  function detectNextProject(root) {
5474
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs9.existsSync(path5.join(root, f)));
5708
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs9.existsSync(path6.join(root, f)));
5475
5709
  let hasDep = false;
5476
5710
  try {
5477
- const pkg = JSON.parse(fs9.readFileSync(path5.join(root, "package.json"), "utf8"));
5711
+ const pkg = JSON.parse(fs9.readFileSync(path6.join(root, "package.json"), "utf8"));
5478
5712
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
5479
5713
  } catch {
5480
5714
  }
5481
- const appDir = fs9.existsSync(path5.join(root, "app")) || fs9.existsSync(path5.join(root, "src", "app"));
5482
- const pagesDir = fs9.existsSync(path5.join(root, "pages")) || fs9.existsSync(path5.join(root, "src", "pages"));
5715
+ const appDir = fs9.existsSync(path6.join(root, "app")) || fs9.existsSync(path6.join(root, "src", "app"));
5716
+ const pagesDir = fs9.existsSync(path6.join(root, "pages")) || fs9.existsSync(path6.join(root, "src", "pages"));
5483
5717
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
5484
5718
  }
5485
5719
  function upsertEnvLocal(root, token) {
5486
- const p = path5.join(root, ".env.local");
5720
+ const p = path6.join(root, ".env.local");
5487
5721
  let existing = "";
5488
5722
  try {
5489
5723
  existing = fs9.readFileSync(p, "utf8");
@@ -5505,11 +5739,11 @@ function upsertEnvLocal(root, token) {
5505
5739
  return had ? "rotated" : "created";
5506
5740
  }
5507
5741
  function installSidecarPackage(root) {
5508
- if (fs9.existsSync(path5.join(root, "node_modules", "apiblaze", "package.json"))) {
5742
+ if (fs9.existsSync(path6.join(root, "node_modules", "apiblaze", "package.json"))) {
5509
5743
  console.log(` ${import_chalk34.default.green("\u2713")} apiblaze package already installed`);
5510
5744
  return;
5511
5745
  }
5512
- const has = (f) => fs9.existsSync(path5.join(root, f));
5746
+ const has = (f) => fs9.existsSync(path6.join(root, f));
5513
5747
  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" };
5514
5748
  const spinner = (0, import_ora18.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
5515
5749
  try {
@@ -5522,7 +5756,7 @@ function installSidecarPackage(root) {
5522
5756
  }
5523
5757
  function readEnvKey(root) {
5524
5758
  try {
5525
- const s = fs9.readFileSync(path5.join(root, ".env.local"), "utf8");
5759
+ const s = fs9.readFileSync(path6.join(root, ".env.local"), "utf8");
5526
5760
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
5527
5761
  return m ? m[1].trim() : null;
5528
5762
  } catch {
@@ -5530,7 +5764,7 @@ function readEnvKey(root) {
5530
5764
  }
5531
5765
  }
5532
5766
  function ensureGitignored(root) {
5533
- const p = path5.join(root, ".gitignore");
5767
+ const p = path6.join(root, ".gitignore");
5534
5768
  let c = "";
5535
5769
  try {
5536
5770
  c = fs9.readFileSync(p, "utf8");
@@ -5539,7 +5773,7 @@ function ensureGitignored(root) {
5539
5773
  if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs9.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
5540
5774
  }
5541
5775
  function wireInstrumentation(root) {
5542
- const existing = ["instrumentation.ts", "instrumentation.js", path5.join("src", "instrumentation.ts")].map((c) => path5.join(root, c)).find((f) => fs9.existsSync(f));
5776
+ const existing = ["instrumentation.ts", "instrumentation.js", path6.join("src", "instrumentation.ts")].map((c) => path6.join(root, c)).find((f) => fs9.existsSync(f));
5543
5777
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
5544
5778
 
5545
5779
  export function register() {
@@ -5547,7 +5781,7 @@ export function register() {
5547
5781
  }
5548
5782
  `;
5549
5783
  if (!existing) {
5550
- fs9.writeFileSync(path5.join(root, "instrumentation.ts"), body);
5784
+ fs9.writeFileSync(path6.join(root, "instrumentation.ts"), body);
5551
5785
  return "created";
5552
5786
  }
5553
5787
  const cur = fs9.readFileSync(existing, "utf8");
@@ -5637,17 +5871,17 @@ export default async function Page() {
5637
5871
  function generateInspector(root, router) {
5638
5872
  try {
5639
5873
  if (router === "pages") {
5640
- const dir2 = fs9.existsSync(path5.join(root, "src", "pages")) ? path5.join(root, "src", "pages") : path5.join(root, "pages");
5641
- const f2 = path5.join(dir2, "abz-inspector.tsx");
5874
+ const dir2 = fs9.existsSync(path6.join(root, "src", "pages")) ? path6.join(root, "src", "pages") : path6.join(root, "pages");
5875
+ const f2 = path6.join(dir2, "abz-inspector.tsx");
5642
5876
  fs9.writeFileSync(f2, INSPECTOR_PAGE);
5643
- return path5.relative(root, f2);
5877
+ return path6.relative(root, f2);
5644
5878
  }
5645
- const base = fs9.existsSync(path5.join(root, "src", "app")) ? path5.join(root, "src", "app") : path5.join(root, "app");
5646
- const dir = path5.join(base, "abz-inspector");
5879
+ const base = fs9.existsSync(path6.join(root, "src", "app")) ? path6.join(root, "src", "app") : path6.join(root, "app");
5880
+ const dir = path6.join(base, "abz-inspector");
5647
5881
  fs9.mkdirSync(dir, { recursive: true });
5648
- const f = path5.join(dir, "page.tsx");
5882
+ const f = path6.join(dir, "page.tsx");
5649
5883
  fs9.writeFileSync(f, INSPECTOR_PAGE);
5650
- return path5.relative(root, f);
5884
+ return path6.relative(root, f);
5651
5885
  } catch {
5652
5886
  return null;
5653
5887
  }
@@ -5687,7 +5921,7 @@ async function runAnonymousInit(root, router, opts) {
5687
5921
  console.log(import_chalk34.default.dim(` From another machine: apiblaze claim ${out.claim_code} \xB7 expires in 30 days`));
5688
5922
  }
5689
5923
  async function runSidecar(opts) {
5690
- const root = path5.resolve(opts.dir ?? process.cwd());
5924
+ const root = path6.resolve(opts.dir ?? process.cwd());
5691
5925
  const detected = detectNextProject(root);
5692
5926
  if (!detected.found) {
5693
5927
  console.log(import_chalk34.default.yellow(`No Next.js project detected in ${root}.`));
@@ -5746,7 +5980,7 @@ async function runSidecar(opts) {
5746
5980
  console.log(` 2. The origins your app calls appear as ${import_chalk34.default.bold("candidates")} \u2014 list them: ${import_chalk34.default.cyan("apiblaze sidecar")}`);
5747
5981
  console.log(` 3. Approve the ones to route: ${import_chalk34.default.cyan("apiblaze sidecar approve api.stripe.com")} (or in the dashboard)`);
5748
5982
  console.log(` \u2026within ~5 min your app starts routing that origin through APIblaze.`);
5749
- if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk34.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path5.dirname(inspectorPath)} before shipping)`);
5983
+ if (inspectorPath) console.log(` \u2022 Try it now: open ${import_chalk34.default.underline("http://localhost:3000/abz-inspector")} (dev only; rm ${path6.dirname(inspectorPath)} before shipping)`);
5750
5984
  if (switchingTeam) console.log(import_chalk34.default.dim(` \u2022 Approved origins are per-team \u2014 re-approve them on ${teamName ?? teamId} with \`apiblaze sidecar approve <origin>\`.`));
5751
5985
  console.log("");
5752
5986
  console.log(import_chalk34.default.dim(" Manage: apiblaze sidecar (list/approve/deny/remove)"));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.17.7",
3
+ "version": "0.17.9",
4
4
  "description": "APIblaze CLI — Chat with your APIs, Manage your API keys, users and groups with the APIblaze serverless proxy",
5
5
  "keywords": [
6
6
  "apiblaze",