apiblaze 0.20.9 → 0.20.10

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
@@ -472,6 +472,98 @@ var init_admin = __esm({
472
472
  }
473
473
  });
474
474
 
475
+ // src/lib/spec-or-target.ts
476
+ var spec_or_target_exports = {};
477
+ __export(spec_or_target_exports, {
478
+ classifyTargetInput: () => classifyTargetInput,
479
+ looksLikeSpecDoc: () => looksLikeSpecDoc
480
+ });
481
+ function looksLikeSpecDoc(text) {
482
+ const probe = (d) => !!d && typeof d === "object" && !Array.isArray(d) && (typeof d.openapi === "string" || typeof d.swagger === "string") && typeof d.paths === "object";
483
+ try {
484
+ if (probe(JSON.parse(text))) return true;
485
+ } catch {
486
+ }
487
+ try {
488
+ if (probe((0, import_yaml.parse)(text))) return true;
489
+ } catch {
490
+ }
491
+ return false;
492
+ }
493
+ function isHttpUrl(s) {
494
+ try {
495
+ const u = new URL(s);
496
+ return u.protocol === "http:" || u.protocol === "https:";
497
+ } catch {
498
+ return false;
499
+ }
500
+ }
501
+ function smellsLikeSpecUrl(u) {
502
+ try {
503
+ const { pathname } = new URL(u);
504
+ return /\.(ya?ml|json)$/i.test(pathname) || /openapi|swagger/i.test(pathname);
505
+ } catch {
506
+ return false;
507
+ }
508
+ }
509
+ async function fetchTextCapped(url, timeoutMs) {
510
+ const controller = new AbortController();
511
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
512
+ try {
513
+ const res = await fetch(url, {
514
+ signal: controller.signal,
515
+ headers: { accept: "application/yaml, application/json, text/yaml, text/plain;q=0.8, */*;q=0.5" }
516
+ });
517
+ if (!res.ok) return null;
518
+ const text = await res.text();
519
+ return text.length > MAX_SPEC_BYTES ? null : text;
520
+ } catch {
521
+ return null;
522
+ } finally {
523
+ clearTimeout(timer);
524
+ }
525
+ }
526
+ async function classifyTargetInput(input, failFn) {
527
+ const t = (input ?? "").trim();
528
+ if (!isHttpUrl(t)) {
529
+ let isFile = false;
530
+ try {
531
+ isFile = fs4.existsSync(t) && fs4.statSync(t).isFile();
532
+ } catch {
533
+ isFile = false;
534
+ }
535
+ if (isFile) {
536
+ const text2 = fs4.readFileSync(t, "utf-8");
537
+ if (looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
538
+ failFn(
539
+ `${t} exists but is not an OpenAPI/Swagger document (no openapi/swagger version field with paths).`,
540
+ "Pass a spec file, a spec URL, or a target server base URL."
541
+ );
542
+ }
543
+ return { kind: "server" };
544
+ }
545
+ if (smellsLikeSpecUrl(t)) {
546
+ const text2 = await fetchTextCapped(t, 2e4);
547
+ if (text2 && looksLikeSpecDoc(text2)) return { kind: "spec", specText: text2, source: t };
548
+ failFn(
549
+ `${t} looks like an OpenAPI file but did not return a valid OpenAPI/Swagger document.`,
550
+ text2 === null ? "The URL could not be fetched (or is over 5MB). Check it in a browser." : "The response parsed, but has no openapi/swagger version field with paths."
551
+ );
552
+ }
553
+ const text = await fetchTextCapped(t, 6e3);
554
+ if (text && looksLikeSpecDoc(text)) return { kind: "spec", specText: text, source: t };
555
+ return { kind: "server" };
556
+ }
557
+ var fs4, import_yaml, MAX_SPEC_BYTES;
558
+ var init_spec_or_target = __esm({
559
+ "src/lib/spec-or-target.ts"() {
560
+ "use strict";
561
+ fs4 = __toESM(require("fs"));
562
+ import_yaml = require("yaml");
563
+ MAX_SPEC_BYTES = 5 * 1024 * 1024;
564
+ }
565
+ });
566
+
475
567
  // src/lib/resolve.ts
476
568
  function requireAuth() {
477
569
  const creds = loadCredentials();
@@ -932,7 +1024,7 @@ var import_commander = require("commander");
932
1024
  var import_chalk52 = __toESM(require("chalk"));
933
1025
 
934
1026
  // package.json
935
- var version = "0.20.9";
1027
+ var version = "0.20.10";
936
1028
 
937
1029
  // src/index.ts
938
1030
  init_types();
@@ -2007,7 +2099,7 @@ function suggestTenantName(projectSlug, taken) {
2007
2099
  }
2008
2100
 
2009
2101
  // src/commands/create.ts
2010
- var import_yaml = require("yaml");
2102
+ var import_yaml2 = require("yaml");
2011
2103
  var import_chalk11 = __toESM(require("chalk"));
2012
2104
  var import_ora5 = __toESM(require("ora"));
2013
2105
  init_auth();
@@ -2015,7 +2107,7 @@ init_api();
2015
2107
  function normalizeName(raw) {
2016
2108
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
2017
2109
  }
2018
- function isHttpUrl(s) {
2110
+ function isHttpUrl2(s) {
2019
2111
  try {
2020
2112
  const u = new URL((s || "").trim());
2021
2113
  return u.protocol === "http:" || u.protocol === "https:";
@@ -2025,7 +2117,7 @@ function isHttpUrl(s) {
2025
2117
  }
2026
2118
  async function loadOpenapiSource(ref) {
2027
2119
  let text;
2028
- if (isHttpUrl(ref)) {
2120
+ if (isHttpUrl2(ref)) {
2029
2121
  const res = await fetch(ref.trim(), {
2030
2122
  headers: { accept: "application/json, application/yaml, text/yaml, */*" }
2031
2123
  }).catch((err) => fail(`Could not fetch the OpenAPI spec at ${ref} \u2014 ${err.message}`));
@@ -2041,7 +2133,7 @@ async function loadOpenapiSource(ref) {
2041
2133
  if (!text.trim()) fail(`The OpenAPI spec is empty: ${ref}`);
2042
2134
  let parsed;
2043
2135
  try {
2044
- parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml.parse)(text);
2136
+ parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml2.parse)(text);
2045
2137
  } catch (err) {
2046
2138
  fail(`Could not parse the OpenAPI spec at ${ref} as JSON or YAML \u2014 ${err.message}`);
2047
2139
  }
@@ -2070,8 +2162,9 @@ function stripTenantFromPortal(devPortal) {
2070
2162
  return devPortal;
2071
2163
  }
2072
2164
  }
2073
- function fail(message) {
2165
+ function fail(message, hint) {
2074
2166
  console.error(import_chalk11.default.red(`Error: ${message}`));
2167
+ if (hint) console.error(import_chalk11.default.dim(hint));
2075
2168
  process.exit(1);
2076
2169
  }
2077
2170
  function buildTryItCurl(url, authType, apiKey) {
@@ -2094,14 +2187,84 @@ function printCurlExample(url, authType, apiKey, devPortal) {
2094
2187
  }
2095
2188
  }
2096
2189
  var VALID_AUTH = ["api_key", "none", "oauth"];
2190
+ function parseOauthFlag(raw) {
2191
+ if (raw === void 0 || raw === false) return null;
2192
+ if (raw === true || typeof raw === "string" && raw.trim() === "") {
2193
+ return {
2194
+ auth: "oauth",
2195
+ bodyPatch: { login: { providers: [{ type: "github", managed: true }] } },
2196
+ summary: "APIblaze-hosted GitHub sign-in (managed)"
2197
+ };
2198
+ }
2199
+ let parsed;
2200
+ try {
2201
+ parsed = JSON.parse(String(raw));
2202
+ } catch {
2203
+ fail(
2204
+ `--oauth value is not valid JSON: ${raw}`,
2205
+ `Shapes:
2206
+ --oauth APIblaze GitHub sign-in
2207
+ --oauth '{"iss":"https://login.acme.com/","aud":"acme-api","jwks":"https://login.acme.com/.well-known/jwks.json"}'
2208
+ --oauth '{"provider":"google","clientId":"\u2026","clientSecret":"\u2026"}'`
2209
+ );
2210
+ }
2211
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) fail("--oauth must be a JSON object (or bare).");
2212
+ if (typeof parsed.iss === "string") {
2213
+ const iss = parsed.iss.trim();
2214
+ const aud = typeof parsed.aud === "string" ? parsed.aud.trim() : "";
2215
+ const jwks = typeof parsed.jwks === "string" ? parsed.jwks.trim() : typeof parsed.jwks_url === "string" ? parsed.jwks_url.trim() : "";
2216
+ if (!iss || !aud || !jwks) fail("--oauth issuer shape needs all three of: iss, aud, jwks (a JWKS URL).");
2217
+ if (!isHttpUrl2(jwks)) fail("--oauth: jwks must be an http(s) JWKS URL.");
2218
+ return {
2219
+ auth: "oauth",
2220
+ bodyPatch: {
2221
+ requests_auth: {
2222
+ mode: "authenticate",
2223
+ methods: ["jwt"],
2224
+ jwt: { allowed_pairs: [{ iss, aud, jwks_url: jwks }] }
2225
+ }
2226
+ },
2227
+ summary: `your own hosted login \u2014 JWTs from ${iss} (aud ${aud})`
2228
+ };
2229
+ }
2230
+ if (typeof parsed.provider === "string") {
2231
+ const provider = parsed.provider.trim().toLowerCase();
2232
+ const PROVIDERS2 = ["github", "google", "microsoft", "facebook", "auth0"];
2233
+ if (!PROVIDERS2.includes(provider)) fail(`--oauth: unknown provider "${provider}". One of: ${PROVIDERS2.join(" \xB7 ")}.`);
2234
+ const clientId = typeof parsed.clientId === "string" ? parsed.clientId.trim() : typeof parsed.client_id === "string" ? parsed.client_id.trim() : "";
2235
+ const clientSecret = typeof parsed.clientSecret === "string" ? parsed.clientSecret.trim() : typeof parsed.client_secret === "string" ? parsed.client_secret.trim() : "";
2236
+ if (!clientId || !clientSecret) fail("--oauth provider shape needs clientId and clientSecret (from your OAuth app).");
2237
+ return {
2238
+ auth: "oauth",
2239
+ bodyPatch: {
2240
+ login: {
2241
+ providers: [{ type: provider, client_id: clientId, client_secret: clientSecret }]
2242
+ }
2243
+ },
2244
+ summary: `APIblaze-hosted login page with your own ${provider} app`
2245
+ };
2246
+ }
2247
+ fail('--oauth JSON must contain either "iss" (your JWT issuer) or "provider" (your OAuth app).');
2248
+ }
2097
2249
  async function runCreate(opts = {}) {
2250
+ if (opts.target && opts.openapi === void 0) {
2251
+ const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
2252
+ const c = await classifyTargetInput2(opts.target, fail);
2253
+ if (c.kind === "spec") {
2254
+ console.log(import_chalk11.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 creating FROM the spec.`));
2255
+ opts.openapi = opts.target;
2256
+ opts.target = void 0;
2257
+ }
2258
+ }
2098
2259
  const creds = loadCredentials();
2099
2260
  if (!creds) {
2100
2261
  await runAnonymousCreate(opts);
2101
2262
  return;
2102
2263
  }
2103
2264
  const interactive = !!process.stdin.isTTY && !opts.json;
2104
- const auth = (opts.auth ?? "api_key").toLowerCase();
2265
+ if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
2266
+ const oauthPlan = parseOauthFlag(opts.oauth);
2267
+ const auth = (oauthPlan ? oauthPlan.auth : opts.apikey ? "api_key" : opts.auth ?? "api_key").toLowerCase();
2105
2268
  if (!VALID_AUTH.includes(auth)) {
2106
2269
  fail(`Invalid --auth "${auth}". Use one of: ${VALID_AUTH.join(", ")}.`);
2107
2270
  }
@@ -2171,7 +2334,7 @@ async function runCreate(opts = {}) {
2171
2334
  let targetUrl = "";
2172
2335
  if (openapiContent) {
2173
2336
  } else if (opts.target !== void 0) {
2174
- if (!isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
2337
+ if (!isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
2175
2338
  targetUrl = opts.target.trim();
2176
2339
  } else if (interactive) {
2177
2340
  const { default: inquirer3 } = await import("inquirer");
@@ -2181,7 +2344,7 @@ async function runCreate(opts = {}) {
2181
2344
  name: "url",
2182
2345
  message: "Target URL to forward requests to (e.g. https://httpbin.org):"
2183
2346
  }]);
2184
- if (!isHttpUrl(url)) {
2347
+ if (!isHttpUrl2(url)) {
2185
2348
  console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
2186
2349
  continue;
2187
2350
  }
@@ -2193,7 +2356,7 @@ async function runCreate(opts = {}) {
2193
2356
  }
2194
2357
  if (interactive && !opts.yes) {
2195
2358
  const { default: inquirer3 } = await import("inquirer");
2196
- console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : ""}`);
2359
+ console.log(`${import_chalk11.default.cyan("\u2192")} Auth: ${import_chalk11.default.bold(auth)}${auth === "api_key" ? " \u2014 consumers send an X-API-Key header" : oauthPlan ? ` \u2014 ${oauthPlan.summary}` : ""}`);
2197
2360
  const { ok } = await inquirer3.prompt([{
2198
2361
  type: "confirm",
2199
2362
  name: "ok",
@@ -2218,6 +2381,7 @@ async function runCreate(opts = {}) {
2218
2381
  ...openapiContent ? { openapi: openapiContent } : { target_url: targetUrl },
2219
2382
  auth_type: auth,
2220
2383
  team_id: teamId,
2384
+ ...oauthPlan ? oauthPlan.bodyPatch : {},
2221
2385
  ...chosenTenant ? { tenant: chosenTenant } : {},
2222
2386
  ...opts.apiversion ? { api_version: opts.apiversion } : {}
2223
2387
  });
@@ -2298,7 +2462,7 @@ async function runAnonymousCreate(opts) {
2298
2462
  if (opts.openapi !== void 0) {
2299
2463
  body.openapi = await loadOpenapiSource(opts.openapi);
2300
2464
  }
2301
- if (opts.target && !isHttpUrl(opts.target)) fail("--target must be a valid http(s) URL.");
2465
+ if (opts.target && !isHttpUrl2(opts.target)) fail("--target must be a valid http(s) URL.");
2302
2466
  let target = opts.target?.trim() || (typeof body.target === "string" ? body.target : "") || (typeof body.target_url === "string" ? body.target_url : "");
2303
2467
  const hasOtherSource = !!(body.openapi || body.github);
2304
2468
  if (!target && !hasOtherSource) {
@@ -2320,7 +2484,7 @@ async function runAnonymousCreate(opts) {
2320
2484
  name: "url",
2321
2485
  message: "Target URL to forward requests to (e.g. https://httpbin.org):"
2322
2486
  }]);
2323
- if (!isHttpUrl(url)) {
2487
+ if (!isHttpUrl2(url)) {
2324
2488
  console.log(import_chalk11.default.yellow(" Enter a valid http(s) URL.\n"));
2325
2489
  continue;
2326
2490
  }
@@ -2344,7 +2508,15 @@ async function runAnonymousCreate(opts) {
2344
2508
  if (opts.product) body.product_slug = normalizeName(opts.product);
2345
2509
  if (opts.displayName) body.display_name = opts.displayName;
2346
2510
  if (opts.apiversion) body.api_version = opts.apiversion;
2347
- if (opts.auth && opts.auth !== "api_key") body.auth_type = opts.auth;
2511
+ if (opts.apikey && opts.oauth !== void 0 && opts.oauth !== false) fail("Pass either --apikey or --oauth, not both.");
2512
+ const oauthPlan = parseOauthFlag(opts.oauth);
2513
+ if (oauthPlan) {
2514
+ Object.assign(body, oauthPlan.bodyPatch);
2515
+ if (oauthPlan.auth !== "api_key" && !("requests_auth" in oauthPlan.bodyPatch)) body.auth_type = oauthPlan.auth;
2516
+ if (!opts.json) console.log(`${import_chalk11.default.cyan("\u2192")} Login: ${oauthPlan.summary}`);
2517
+ } else if (opts.auth && opts.auth !== "api_key" && !opts.apikey) {
2518
+ body.auth_type = opts.auth;
2519
+ }
2348
2520
  const { loadAnonCred: loadAnonCred2, saveAnonCred: saveAnonCred2, clearAnonCred: clearAnonCred2, cpFetch: cpFetch2 } = await Promise.resolve().then(() => (init_anon_cred(), anon_cred_exports));
2349
2521
  if (opts.newSession) clearAnonCred2();
2350
2522
  const cred = loadAnonCred2();
@@ -2487,7 +2659,7 @@ var import_chalk13 = __toESM(require("chalk"));
2487
2659
  init_auth();
2488
2660
 
2489
2661
  // src/lib/consumer-auth.ts
2490
- var fs5 = __toESM(require("fs"));
2662
+ var fs6 = __toESM(require("fs"));
2491
2663
  var os3 = __toESM(require("os"));
2492
2664
  var path3 = __toESM(require("path"));
2493
2665
  var crypto = __toESM(require("crypto"));
@@ -2498,19 +2670,19 @@ var CONSUMER_PATH = path3.join(APIBLAZE_DIR2, "consumer.json");
2498
2670
  var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
2499
2671
  function loadConsumer() {
2500
2672
  try {
2501
- return JSON.parse(fs5.readFileSync(CONSUMER_PATH, "utf-8"));
2673
+ return JSON.parse(fs6.readFileSync(CONSUMER_PATH, "utf-8"));
2502
2674
  } catch {
2503
2675
  return null;
2504
2676
  }
2505
2677
  }
2506
2678
  function saveConsumer(creds) {
2507
- fs5.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2508
- fs5.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2509
- fs5.chmodSync(CONSUMER_PATH, 384);
2679
+ fs6.mkdirSync(APIBLAZE_DIR2, { recursive: true });
2680
+ fs6.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
2681
+ fs6.chmodSync(CONSUMER_PATH, 384);
2510
2682
  }
2511
2683
  function clearConsumer() {
2512
2684
  try {
2513
- fs5.unlinkSync(CONSUMER_PATH);
2685
+ fs6.unlinkSync(CONSUMER_PATH);
2514
2686
  return true;
2515
2687
  } catch {
2516
2688
  return false;
@@ -2655,12 +2827,12 @@ async function runLogout(opts = {}) {
2655
2827
 
2656
2828
  // src/commands/flush.ts
2657
2829
  var import_chalk14 = __toESM(require("chalk"));
2658
- var fs6 = __toESM(require("fs"));
2830
+ var fs7 = __toESM(require("fs"));
2659
2831
  var path4 = __toESM(require("path"));
2660
2832
  init_auth();
2661
2833
  async function runFlush(opts) {
2662
2834
  const dir = getApiblazeDir();
2663
- const entries2 = fs6.existsSync(dir) ? fs6.readdirSync(dir) : [];
2835
+ const entries2 = fs7.existsSync(dir) ? fs7.readdirSync(dir) : [];
2664
2836
  if (!entries2.length) {
2665
2837
  console.log(import_chalk14.default.dim("Nothing to flush \u2014 no local apiblaze config found."));
2666
2838
  return;
@@ -2684,7 +2856,7 @@ async function runFlush(opts) {
2684
2856
  let removed = 0;
2685
2857
  for (const e of entries2) {
2686
2858
  try {
2687
- fs6.rmSync(path4.join(dir, e), { recursive: true, force: true });
2859
+ fs7.rmSync(path4.join(dir, e), { recursive: true, force: true });
2688
2860
  removed++;
2689
2861
  } catch (err) {
2690
2862
  console.log(import_chalk14.default.red(` \u2717 could not remove ${e}: ${err instanceof Error ? err.message : "error"}`));
@@ -6310,7 +6482,7 @@ async function providersMenu(cBase, clientLabel) {
6310
6482
  }
6311
6483
 
6312
6484
  // src/commands/spec.ts
6313
- var fs7 = __toESM(require("fs"));
6485
+ var fs8 = __toESM(require("fs"));
6314
6486
  var import_chalk37 = __toESM(require("chalk"));
6315
6487
  var import_ora16 = __toESM(require("ora"));
6316
6488
  init_admin();
@@ -6333,7 +6505,7 @@ async function runSpecSet(project, opts) {
6333
6505
  }
6334
6506
  let specContent;
6335
6507
  try {
6336
- specContent = fs7.readFileSync(opts.file, "utf-8");
6508
+ specContent = fs8.readFileSync(opts.file, "utf-8");
6337
6509
  } catch {
6338
6510
  console.error(import_chalk37.default.red(`Cannot read file: ${opts.file}`));
6339
6511
  process.exit(1);
@@ -6862,6 +7034,15 @@ var SETTINGS = [
6862
7034
  read: (cfg) => cfg.chat_sponsorship?.max_chars_per_message ?? 2e3,
6863
7035
  toPatch: (v) => ({ chat_sponsorship: { max_chars_per_message: v } })
6864
7036
  },
7037
+ {
7038
+ key: "chat_prompt_addendum",
7039
+ label: "AI chat: steering notes",
7040
+ group: "Portal & MCP",
7041
+ type: "string",
7042
+ desc: `Notes the chat assistant follows on this API \u2014 tone, suggestions, house rules ("always suggest the daily special"). Injected beneath the assistant's safety rules, so it can steer style but never override them. Max 1000 chars; empty clears`,
7043
+ read: (cfg) => cfg.chat_prompt_addendum ?? "",
7044
+ toPatch: (v) => ({ chat_prompt_addendum: typeof v === "string" && v.trim() ? v.trim() : null })
7045
+ },
6865
7046
  // ── Advanced (specs/missing.md): all optional, default-off; null clears ────
6866
7047
  {
6867
7048
  key: "ip_access.mode",
@@ -8088,19 +8269,19 @@ async function runPreapprove(who, opts) {
8088
8269
  }
8089
8270
 
8090
8271
  // src/commands/apichat.ts
8091
- var fs9 = __toESM(require("fs"));
8272
+ var fs10 = __toESM(require("fs"));
8092
8273
  var path6 = __toESM(require("path"));
8093
8274
  var crypto2 = __toESM(require("crypto"));
8094
8275
  var import_chalk46 = __toESM(require("chalk"));
8095
8276
  var import_ora23 = __toESM(require("ora"));
8096
- var import_yaml2 = require("yaml");
8277
+ var import_yaml3 = require("yaml");
8097
8278
  init_auth();
8098
8279
  init_anon_cred();
8099
8280
  init_api();
8100
8281
  init_admin();
8101
8282
 
8102
8283
  // src/commands/llm.ts
8103
- var fs8 = __toESM(require("fs"));
8284
+ var fs9 = __toESM(require("fs"));
8104
8285
  var path5 = __toESM(require("path"));
8105
8286
  var import_chalk45 = __toESM(require("chalk"));
8106
8287
  var import_inquirer2 = __toESM(require("inquirer"));
@@ -8116,15 +8297,15 @@ function detectProvider(key) {
8116
8297
  }
8117
8298
  function loadLlmConfig() {
8118
8299
  try {
8119
- return JSON.parse(fs8.readFileSync(LLM_PATH, "utf-8"));
8300
+ return JSON.parse(fs9.readFileSync(LLM_PATH, "utf-8"));
8120
8301
  } catch {
8121
8302
  return null;
8122
8303
  }
8123
8304
  }
8124
8305
  function saveLlmConfig(cfg) {
8125
- fs8.mkdirSync(getApiblazeDir(), { recursive: true });
8126
- fs8.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
8127
- fs8.chmodSync(LLM_PATH, 384);
8306
+ fs9.mkdirSync(getApiblazeDir(), { recursive: true });
8307
+ fs9.writeFileSync(LLM_PATH, JSON.stringify(cfg, null, 2), "utf-8");
8308
+ fs9.chmodSync(LLM_PATH, 384);
8128
8309
  }
8129
8310
  async function runLlmSetKey(keyArg, opts) {
8130
8311
  let key = keyArg?.trim();
@@ -8163,7 +8344,7 @@ async function runLlmShow() {
8163
8344
  }
8164
8345
  async function runLlmClearKey() {
8165
8346
  try {
8166
- fs8.unlinkSync(LLM_PATH);
8347
+ fs9.unlinkSync(LLM_PATH);
8167
8348
  console.log(`${import_chalk45.default.green("\u2713")} Removed local LLM key.`);
8168
8349
  } catch {
8169
8350
  console.log(import_chalk45.default.gray("No LLM key was set."));
@@ -8182,7 +8363,7 @@ Error: ${message}`));
8182
8363
  function normalizeName3(raw) {
8183
8364
  return (raw || "").toLowerCase().replace(/[^a-z0-9]/g, "");
8184
8365
  }
8185
- function isHttpUrl2(s) {
8366
+ function isHttpUrl3(s) {
8186
8367
  try {
8187
8368
  const u = new URL((s || "").trim());
8188
8369
  return u.protocol === "http:" || u.protocol === "https:";
@@ -8196,7 +8377,7 @@ function parseSpec(text) {
8196
8377
  parsed = JSON.parse(text);
8197
8378
  } catch {
8198
8379
  try {
8199
- parsed = (0, import_yaml2.parse)(text);
8380
+ parsed = (0, import_yaml3.parse)(text);
8200
8381
  } catch {
8201
8382
  fail4("Could not parse the spec as JSON or YAML.");
8202
8383
  }
@@ -8233,21 +8414,21 @@ async function discoverSpec(target) {
8233
8414
  }
8234
8415
  async function loadSpec(opts) {
8235
8416
  if (opts.openapispec) {
8236
- if (isHttpUrl2(opts.openapispec)) {
8417
+ if (isHttpUrl3(opts.openapispec)) {
8237
8418
  const text2 = await fetchText(opts.openapispec);
8238
8419
  if (!text2) fail4(`Could not fetch the spec at ${opts.openapispec}.`);
8239
8420
  return { spec: parseSpec(text2), sourceUrl: opts.openapispec };
8240
8421
  }
8241
8422
  let text;
8242
8423
  try {
8243
- text = fs9.readFileSync(opts.openapispec, "utf-8");
8424
+ text = fs10.readFileSync(opts.openapispec, "utf-8");
8244
8425
  } catch {
8245
8426
  fail4(`Cannot read spec file: ${opts.openapispec}`);
8246
8427
  }
8247
8428
  return { spec: parseSpec(text) };
8248
8429
  }
8249
8430
  if (opts.target) {
8250
- if (!isHttpUrl2(opts.target)) fail4("--target must be a valid http(s) URL.");
8431
+ if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
8251
8432
  const found = await discoverSpec(opts.target);
8252
8433
  if (!found) {
8253
8434
  fail4(`No OpenAPI spec found at ${opts.target} (tried /openapi.json, /openapi.yaml, /swagger.json).`, GENERATOR_HINT);
@@ -8258,7 +8439,7 @@ async function loadSpec(opts) {
8258
8439
  }
8259
8440
  function resolveTarget(spec2, opts, sourceUrl) {
8260
8441
  if (opts.target) {
8261
- if (!isHttpUrl2(opts.target)) fail4("--target must be a valid http(s) URL.");
8442
+ if (!isHttpUrl3(opts.target)) fail4("--target must be a valid http(s) URL.");
8262
8443
  return opts.target.trim();
8263
8444
  }
8264
8445
  const servers = Array.isArray(spec2.servers) ? spec2.servers : [];
@@ -8268,7 +8449,7 @@ function resolveTarget(spec2, opts, sourceUrl) {
8268
8449
  }
8269
8450
  try {
8270
8451
  const resolved = sourceUrl ? new URL(raw, sourceUrl).toString() : raw;
8271
- if (!isHttpUrl2(resolved)) {
8452
+ if (!isHttpUrl3(resolved)) {
8272
8453
  fail4(`servers[0].url ("${raw}") is not an absolute URL and could not be resolved.`, "Re-run with --target <upstream base URL>.");
8273
8454
  }
8274
8455
  return resolved;
@@ -8378,9 +8559,10 @@ async function captureTargetSecret(auth, opts) {
8378
8559
  return secret;
8379
8560
  }
8380
8561
  async function dataPlaneAuth(p) {
8562
+ const idHeader = p.endUserId ? { "X-End-User-Id": p.endUserId } : {};
8381
8563
  if (!p.consumerAuth) {
8382
- if (!p.dpKey) throw new Error("No API key for this proxy.");
8383
- return { "X-API-Key": p.dpKey };
8564
+ if (!p.dpKey) return idHeader;
8565
+ return { "X-API-Key": p.dpKey, ...idHeader };
8384
8566
  }
8385
8567
  const stored = loadConsumer();
8386
8568
  if (!stored) {
@@ -8388,7 +8570,7 @@ async function dataPlaneAuth(p) {
8388
8570
  }
8389
8571
  const fresh = await validConsumerToken(stored) ?? stored;
8390
8572
  if (fresh.accessToken !== stored.accessToken) saveConsumer(fresh);
8391
- return { Authorization: `Bearer ${fresh.accessToken}` };
8573
+ return { Authorization: `Bearer ${fresh.accessToken}`, ...idHeader };
8392
8574
  }
8393
8575
  async function ensureConsumerLogin(teamId, tenant2, version2) {
8394
8576
  const resource = `https://${tenant2}.portal.apiblaze.com/${version2}`;
@@ -8648,7 +8830,6 @@ async function publishMcp(p, spec2) {
8648
8830
  return null;
8649
8831
  }
8650
8832
  }
8651
- var CLIENT_ROUND_CAP = 12;
8652
8833
  function chatUrl(p) {
8653
8834
  return `https://${p.mcpHost}/${p.version}/${p.environment}/runtime-chat`;
8654
8835
  }
@@ -8656,91 +8837,66 @@ function maskKey(k) {
8656
8837
  return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
8657
8838
  }
8658
8839
  var revealAuth = false;
8659
- function renderToolEvents(events, cred) {
8660
- for (const e of events ?? []) {
8661
- const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
8662
- const mark = ok ? import_chalk46.default.green("\u2713") : import_chalk46.default.red("\u2717");
8663
- console.log(` ${mark} ${import_chalk46.default.cyan(e.name)} ${import_chalk46.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
8664
- if (isVerbose() && e.method && e.url) {
8665
- console.log(import_chalk46.default.dim(` curl -sS -X ${e.method} '${e.url}'${cred ? " \\" : ""}`));
8666
- if (cred) {
8667
- const shown = revealAuth ? cred.value : maskKey(cred.value);
8668
- const keyLine = ` -H '${cred.header}: ${shown}'`;
8669
- const hint = revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth will reveal this");
8670
- console.log(import_chalk46.default.dim(keyLine) + hint);
8671
- }
8672
- }
8673
- }
8674
- }
8675
- function renderToolResults(delta, events) {
8676
- const anyFailed = (events ?? []).some((e) => e.status === "error");
8677
- if (!anyFailed && !isVerbose()) return;
8678
- const cap = anyFailed ? 24 : 12;
8679
- for (const m of delta ?? []) {
8680
- if (!m || m.role !== "tool" || typeof m.content !== "string") continue;
8681
- const body = m.content.trim();
8682
- if (!body) continue;
8683
- const pretty = (() => {
8684
- try {
8685
- return JSON.stringify(JSON.parse(body), null, 2);
8686
- } catch {
8687
- return body;
8688
- }
8689
- })();
8690
- const lines = pretty.split("\n");
8691
- console.log(import_chalk46.default.dim(" response:"));
8692
- for (const line of lines.slice(0, cap)) {
8693
- console.log(import_chalk46.default.dim(` ${line}`));
8694
- }
8695
- if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
8696
- }
8697
- }
8698
- function billingLine(billing) {
8699
- if (!billing || typeof billing.cents !== "number") return null;
8700
- if (typeof billing.free_turns_remaining === "number") return null;
8701
- const usd = (billing.cents / 100).toFixed(Math.abs(billing.cents - Math.round(billing.cents)) < 1e-9 ? 2 : 4);
8702
- let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`) + import_chalk46.default.dim(billing.model ? ` \xB7 ${billing.model}` : "");
8703
- if (typeof billing.credits_remaining === "number") {
8704
- line += import_chalk46.default.dim(` \xB7 balance $${(billing.credits_remaining / 100).toFixed(2)}`);
8840
+ function billingLine(info) {
8841
+ if (!info || typeof info.charged_cents !== "number" || info.charged_cents <= 0) return null;
8842
+ if (typeof info.free_turns_remaining === "number") return null;
8843
+ const cents = info.charged_cents;
8844
+ const usd = (cents / 100).toFixed(Math.abs(cents - Math.round(cents)) < 1e-9 ? 2 : 4);
8845
+ let line = import_chalk46.default.magenta(` \u{1F4B3} $${usd}`);
8846
+ if (typeof info.credits_remaining === "number") {
8847
+ line += import_chalk46.default.dim(` \xB7 balance $${(info.credits_remaining / 100).toFixed(2)}`);
8705
8848
  }
8706
8849
  return line;
8707
8850
  }
8708
- function freeBudgetWarning(billing, anon) {
8709
- if (!anon || !billing) return null;
8710
- if (typeof billing.free_turns_remaining === "number") {
8711
- const left2 = billing.free_turns_remaining;
8851
+ function freeBudgetWarning(info, anon) {
8852
+ if (!anon || !info) return null;
8853
+ if (typeof info.free_turns_remaining === "number") {
8854
+ const left2 = info.free_turns_remaining;
8712
8855
  if (left2 <= 0) return import_chalk46.default.yellow(" Free chats used up \u2014 `npx apiblaze login` (free) to keep going.");
8713
8856
  return import_chalk46.default.dim(` ${left2} free chat${left2 === 1 ? "" : "s"} left \xB7 /login to get more`);
8714
8857
  }
8715
- if (typeof billing.free_remaining_cents !== "number") return null;
8716
- const perTurn = Math.max(billing.cents || 0, 0.02);
8717
- const left = Math.floor(billing.free_remaining_cents / perTurn);
8858
+ if (typeof info.free_remaining_cents !== "number") return null;
8859
+ const perTurn = Math.max(info.charged_cents || 0, 0.02);
8860
+ const left = Math.floor(info.free_remaining_cents / perTurn);
8718
8861
  if (left > 8) return null;
8719
8862
  if (left <= 0) return import_chalk46.default.yellow(" Free messages used up \u2014 `npx apiblaze login` (free) to keep chatting.");
8720
8863
  return import_chalk46.default.yellow(` \u26A0 About ${left} free message${left === 1 ? "" : "s"} left \u2014 \`npx apiblaze login\` (free) for more.`);
8721
8864
  }
8722
- function printAssistant(delta) {
8723
- for (let i = delta.length - 1; i >= 0; i--) {
8724
- const m = delta[i];
8725
- if (m && m.role === "assistant" && typeof m.content === "string" && m.content.trim()) {
8726
- console.log("\n" + import_chalk46.default.green("assistant \u203A ") + m.content + "\n");
8727
- return;
8865
+ async function readSse(body, onEvent) {
8866
+ const reader = body.getReader();
8867
+ const decoder = new TextDecoder();
8868
+ let buf = "";
8869
+ for (; ; ) {
8870
+ const { done, value } = await reader.read();
8871
+ if (done) break;
8872
+ buf += decoder.decode(value, { stream: true });
8873
+ for (; ; ) {
8874
+ const at = buf.indexOf("\n\n");
8875
+ if (at === -1) break;
8876
+ const rawEvent = buf.slice(0, at);
8877
+ buf = buf.slice(at + 2);
8878
+ const data = rawEvent.split("\n").filter((l) => l.startsWith("data:")).map((l) => l.slice(5).replace(/^ /, "")).join("\n");
8879
+ if (!data || data === "[DONE]") continue;
8880
+ try {
8881
+ onEvent(JSON.parse(data));
8882
+ } catch {
8883
+ }
8728
8884
  }
8729
8885
  }
8730
8886
  }
8731
8887
  async function replTurn(p, messages, userText) {
8732
- messages.push({ role: "user", content: userText });
8888
+ messages.push({ id: crypto2.randomUUID(), role: "user", parts: [{ type: "text", text: userText }] });
8733
8889
  const llm2 = loadLlmConfig();
8734
8890
  const turnId = crypto2.randomUUID();
8735
- for (let round = 0; round < CLIENT_ROUND_CAP; round++) {
8736
- const spinner = (0, import_ora23.default)({ text: round === 0 ? "thinking..." : "working...", color: "magenta" }).start();
8737
- const body = {
8738
- turn_id: turnId,
8739
- messages,
8740
- environment: p.environment,
8741
- ...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
8742
- };
8743
- let res;
8891
+ const spinner = (0, import_ora23.default)({ text: "thinking...", color: "magenta" }).start();
8892
+ const body = {
8893
+ turn_id: turnId,
8894
+ messages,
8895
+ environment: p.environment,
8896
+ ...llm2 ? { llm_api_key: llm2.key, llm_provider: llm2.provider } : {}
8897
+ };
8898
+ let res = null;
8899
+ for (let attempt = 0; attempt < 3; attempt++) {
8744
8900
  try {
8745
8901
  res = await fetch(chatUrl(p), {
8746
8902
  method: "POST",
@@ -8752,57 +8908,248 @@ async function replTurn(p, messages, userText) {
8752
8908
  console.log(import_chalk46.default.red(` Could not reach ${p.mcpHost}: ${err instanceof Error ? err.message : String(err)}`));
8753
8909
  return;
8754
8910
  }
8911
+ if ((res.headers.get("content-type") ?? "").includes("text/event-stream") && res.ok && res.body) break;
8912
+ spinner.stop();
8755
8913
  let data = null;
8756
8914
  try {
8757
8915
  data = await res.json();
8758
8916
  } catch {
8759
8917
  }
8760
- spinner.stop();
8761
- if (data && data.upsell) {
8762
- renderUpsell(p, data.upsell);
8918
+ const errObj = data && typeof data.error === "object" ? data.error : null;
8919
+ const code = errObj && errObj.code || null;
8920
+ const msg = String(errObj && errObj.message || data && (data.error || data.message) || "");
8921
+ const tty = !!process.stdin.isTTY;
8922
+ if (res.status === 404 && /project not found/i.test(msg)) {
8923
+ const flipped = p.mcpHost.includes(".tryabz.run") ? p.mcpHost.replace(".tryabz.run", ".abz.run") : p.mcpHost.replace(".abz.run", ".tryabz.run");
8924
+ if (flipped !== p.mcpHost && attempt === 0) {
8925
+ p.mcpHost = flipped;
8926
+ p.proxyUrl = p.proxyUrl?.includes("tryabz.run") ? p.proxyUrl.replace("tryabz.run", "abz.run") : p.proxyUrl?.replace("abz.run", "tryabz.run");
8927
+ p.anon = flipped.includes(".tryabz.run");
8928
+ spinner.start("retrying on the " + (p.anon ? "trial" : "claimed") + " plane\u2026");
8929
+ continue;
8930
+ }
8931
+ console.log(import_chalk46.default.red(` No proxy named ${p.projectId} was found (tried both abz.run and tryabz.run).`));
8763
8932
  return;
8764
8933
  }
8765
- if (res.status === 402 || res.status === 403) {
8766
- renderUpsell(p, { reason: "PAUSED", message: data && data.error || "This turn is not available on your current plan." });
8934
+ if (code === "identity_required" || /identif/i.test(msg) && !code) {
8935
+ if (!p.endUserId && tty) {
8936
+ console.log(import_chalk46.default.yellow(" This API requires every call to say WHO is calling."));
8937
+ const { default: inquirer3 } = await import("inquirer");
8938
+ const { id } = await inquirer3.prompt([{ type: "input", name: "id", message: "Your end-user id (usually your email):" }]);
8939
+ if (typeof id === "string" && id.trim()) {
8940
+ p.endUserId = id.trim();
8941
+ persistAuthState(p);
8942
+ spinner.start("retrying\u2026");
8943
+ continue;
8944
+ }
8945
+ }
8946
+ console.log(import_chalk46.default.red(" This API requires an identified caller."));
8947
+ console.log(import_chalk46.default.dim(" Re-run with --xenduserid <your id> (usually your email)."));
8767
8948
  return;
8768
8949
  }
8769
- if (res.status === 401) {
8770
- const reason = data && (data.error || data.message) || null;
8771
- const usingKey = !p.consumerAuth;
8772
- console.log(import_chalk46.default.red(` The proxy rejected the request (401)${reason ? `: ${reason}` : "."}`));
8773
- console.log(
8774
- import_chalk46.default.dim(
8775
- usingKey ? " Sent an API key. If the key was revoked, re-run `apiblaze apichat` to re-provision." : " Sent your consumer OAuth token. Re-run `apiblaze apichat` to sign in again."
8776
- )
8777
- );
8950
+ if (code === "user_not_preapproved") {
8951
+ console.log(import_chalk46.default.yellow(` ${msg || "You are not pre-approved on this API."}`));
8952
+ if (p.endUserId) console.log(import_chalk46.default.dim(` Identity sent: ${p.endUserId}`));
8953
+ const accessUrl = errObj && errObj.request_access_url;
8954
+ if (accessUrl) console.log(` Request access: ${import_chalk46.default.bold(String(accessUrl))}`);
8955
+ console.log(import_chalk46.default.dim(" Or ask the producer to pre-approve you: `apiblaze preapprove <your-email> --tenant <tenant>`."));
8778
8956
  return;
8779
8957
  }
8780
- if (!res.ok || !data) {
8781
- const err = data && data.error || `HTTP ${res.status}`;
8782
- console.log(import_chalk46.default.red(` Chat error: ${err}`));
8958
+ if (code === "user_frozen") {
8959
+ console.log(import_chalk46.default.red(` ${msg || "Your access to this API has been frozen by the producer."}`));
8783
8960
  return;
8784
8961
  }
8785
- if (Array.isArray(data.delta)) {
8786
- renderToolEvents(
8787
- data.tool_events,
8788
- p.consumerAuth ? (() => {
8789
- const t = loadConsumer()?.accessToken;
8790
- return t ? { header: "Authorization", value: `Bearer ${t}` } : void 0;
8791
- })() : p.dpKey ? { header: "X-API-Key", value: p.dpKey } : void 0
8792
- );
8793
- renderToolResults(data.delta, data.tool_events);
8794
- for (const m of data.delta) messages.push(m);
8795
- printAssistant(data.delta);
8962
+ const oauthWanted = /oauth token required|authorization: bearer/i.test(msg);
8963
+ const keyWanted = /api key required|x-api-key/i.test(msg);
8964
+ if (oauthWanted && !p.consumerAuth) {
8965
+ if (p.teamId && p.tenant && loadCredentials()) {
8966
+ console.log(import_chalk46.default.dim(" This proxy signs consumers in with OAuth \u2014 starting the login\u2026"));
8967
+ try {
8968
+ await ensureConsumerLogin(p.teamId, p.tenant, p.version);
8969
+ p.consumerAuth = true;
8970
+ p.dpKey = void 0;
8971
+ persistAuthState(p);
8972
+ spinner.start("retrying\u2026");
8973
+ continue;
8974
+ } catch (err) {
8975
+ console.log(import_chalk46.default.red(` Login failed: ${err instanceof Error ? err.message : String(err)}`));
8976
+ return;
8977
+ }
8978
+ }
8979
+ console.log(import_chalk46.default.red(" This proxy signs consumers in with OAuth (a login), not an API key."));
8980
+ console.log(import_chalk46.default.dim(" Sign in with: `apiblaze consumer login --tenant <tenant> --client <app-client-id>`, then re-run apichat."));
8981
+ return;
8796
8982
  }
8797
- const bl = billingLine(data.billing);
8798
- if (bl) console.log(bl);
8799
- const warn = freeBudgetWarning(data.billing, p.anon);
8800
- if (warn) console.log(warn);
8801
- if (!data.continue) return;
8983
+ if (keyWanted) {
8984
+ if (tty) {
8985
+ console.log(import_chalk46.default.yellow(` ${msg || "This API requires an API key."}`));
8986
+ const { default: inquirer3 } = await import("inquirer");
8987
+ const { key } = await inquirer3.prompt([{ type: "password", name: "key", mask: "*", message: "API key for this proxy:" }]);
8988
+ if (typeof key === "string" && key.trim()) {
8989
+ p.dpKey = key.trim();
8990
+ p.consumerAuth = false;
8991
+ persistAuthState(p);
8992
+ spinner.start("retrying\u2026");
8993
+ continue;
8994
+ }
8995
+ }
8996
+ console.log(import_chalk46.default.red(` ${msg || "This API requires an API key."}`));
8997
+ console.log(import_chalk46.default.dim(" Re-run with --apikey <key> (mint one from the producer's site or dev portal)."));
8998
+ return;
8999
+ }
9000
+ if (res && (res.status === 402 || res.status === 403)) {
9001
+ renderUpsell(p, { reason: "PAUSED", message: msg || "This turn is not available on your current plan." });
9002
+ return;
9003
+ }
9004
+ if (res && res.status === 401) {
9005
+ console.log(import_chalk46.default.red(` The proxy rejected the request (401)${msg ? `: ${msg}` : "."}`));
9006
+ console.log(import_chalk46.default.dim(p.consumerAuth ? " Sent your consumer OAuth token. Run `apiblaze consumer login` again." : " Sent an API key. Pass a different one with --apikey <key>."));
9007
+ return;
9008
+ }
9009
+ console.log(import_chalk46.default.red(` Chat error: ${msg || (res ? `HTTP ${res.status}` : "request failed")}`));
9010
+ return;
8802
9011
  }
8803
- console.log(import_chalk46.default.dim(" (stopped after several tool rounds \u2014 ask again to continue)"));
8804
- }
8805
- function renderUpsell(p, upsell) {
9012
+ if (!res || !res.body || !(res.headers.get("content-type") ?? "").includes("text/event-stream")) {
9013
+ spinner.stop();
9014
+ console.log(import_chalk46.default.red(" Chat error: could not authenticate to this proxy after several attempts."));
9015
+ return;
9016
+ }
9017
+ let spinnerLive = true;
9018
+ const stopSpinner = () => {
9019
+ if (spinnerLive) {
9020
+ spinner.stop();
9021
+ spinnerLive = false;
9022
+ }
9023
+ };
9024
+ const parts = [];
9025
+ let openTextIdx = -1;
9026
+ let assistantOpen = false;
9027
+ const toolMeta = /* @__PURE__ */ new Map();
9028
+ let upsell = null;
9029
+ let turnInfo = null;
9030
+ let errorText = null;
9031
+ let messageId = `msg_${Date.now()}`;
9032
+ const credHint = () => {
9033
+ if (!isVerbose()) return null;
9034
+ if (p.consumerAuth) {
9035
+ const t = loadConsumer()?.accessToken;
9036
+ return t ? `Authorization: Bearer ${revealAuth ? t : maskKey(t)}` : null;
9037
+ }
9038
+ return p.dpKey ? `X-API-Key: ${revealAuth ? p.dpKey : maskKey(p.dpKey)}` : null;
9039
+ };
9040
+ try {
9041
+ await readSse(res.body, (ev) => {
9042
+ switch (ev?.type) {
9043
+ case "start":
9044
+ if (typeof ev.messageId === "string") messageId = ev.messageId;
9045
+ break;
9046
+ case "tool-input-start": {
9047
+ stopSpinner();
9048
+ if (assistantOpen) {
9049
+ process.stdout.write("\n");
9050
+ assistantOpen = false;
9051
+ }
9052
+ const name = String(ev.toolName ?? "tool");
9053
+ parts.push({ type: `tool-${name}`, toolCallId: String(ev.toolCallId ?? ""), state: "input-streaming" });
9054
+ toolMeta.set(String(ev.toolCallId ?? ""), { name, startedAt: Date.now(), partIdx: parts.length - 1 });
9055
+ console.log(` ${import_chalk46.default.cyan("\u2699")} ${import_chalk46.default.cyan(name)}${import_chalk46.default.dim("\u2026")}`);
9056
+ break;
9057
+ }
9058
+ case "tool-input-available": {
9059
+ const id = String(ev.toolCallId ?? "");
9060
+ const meta = toolMeta.get(id);
9061
+ const input = ev.input && typeof ev.input === "object" ? ev.input : {};
9062
+ if (meta) {
9063
+ meta.input = input;
9064
+ Object.assign(parts[meta.partIdx], { state: "input-available", input });
9065
+ }
9066
+ if (isVerbose()) {
9067
+ console.log(import_chalk46.default.dim(` args ${JSON.stringify(input)}`));
9068
+ const hint = credHint();
9069
+ if (hint) console.log(import_chalk46.default.dim(` auth ${hint}`) + (revealAuth ? "" : import_chalk46.default.yellow(" \u2190 /showauth reveals")));
9070
+ }
9071
+ break;
9072
+ }
9073
+ case "tool-output-available":
9074
+ case "tool-output-error": {
9075
+ stopSpinner();
9076
+ const id = String(ev.toolCallId ?? "");
9077
+ const meta = toolMeta.get(id);
9078
+ const ok = ev.type === "tool-output-available";
9079
+ const ms = meta ? Date.now() - meta.startedAt : void 0;
9080
+ const mark = ok ? import_chalk46.default.green("\u2713") : import_chalk46.default.red("\u2717");
9081
+ console.log(` ${mark} ${import_chalk46.default.cyan(meta?.name ?? "tool")} ${import_chalk46.default.dim(`(${ok ? "ok" : "error"}${ms != null ? `, ${ms}ms` : ""})`)}`);
9082
+ const detail = ok ? String(ev.output ?? "") : String(ev.errorText ?? "Tool call failed.");
9083
+ if (meta) {
9084
+ Object.assign(parts[meta.partIdx], ok ? { state: "output-available", output: detail } : { state: "output-error", errorText: detail });
9085
+ }
9086
+ if (!ok || isVerbose()) {
9087
+ const pretty = (() => {
9088
+ try {
9089
+ return JSON.stringify(JSON.parse(detail), null, 2);
9090
+ } catch {
9091
+ return detail;
9092
+ }
9093
+ })();
9094
+ const lines = pretty.split("\n");
9095
+ const cap = ok ? 12 : 24;
9096
+ console.log(import_chalk46.default.dim(" response:"));
9097
+ for (const line of lines.slice(0, cap)) console.log(import_chalk46.default.dim(` ${line}`));
9098
+ if (lines.length > cap) console.log(import_chalk46.default.dim(` \u2026${lines.length - cap} more lines`));
9099
+ }
9100
+ break;
9101
+ }
9102
+ case "text-start":
9103
+ stopSpinner();
9104
+ parts.push({ type: "text", text: "" });
9105
+ openTextIdx = parts.length - 1;
9106
+ if (!assistantOpen) {
9107
+ process.stdout.write("\n" + import_chalk46.default.green("assistant \u203A "));
9108
+ assistantOpen = true;
9109
+ }
9110
+ break;
9111
+ case "text-delta": {
9112
+ const delta = String(ev.delta ?? "");
9113
+ if (openTextIdx >= 0) parts[openTextIdx].text = String(parts[openTextIdx].text ?? "") + delta;
9114
+ process.stdout.write(delta);
9115
+ break;
9116
+ }
9117
+ case "text-end":
9118
+ openTextIdx = -1;
9119
+ break;
9120
+ case "data-apiblaze-upsell": {
9121
+ const d = ev.data ?? {};
9122
+ upsell = { reason: String(d.reason ?? ""), message: String(d.message ?? "") };
9123
+ break;
9124
+ }
9125
+ case "data-apiblaze-turn":
9126
+ turnInfo = ev.data ?? {};
9127
+ break;
9128
+ case "error":
9129
+ stopSpinner();
9130
+ errorText = String(ev.errorText ?? "Something went wrong.");
9131
+ break;
9132
+ default:
9133
+ break;
9134
+ }
9135
+ });
9136
+ } catch (err) {
9137
+ stopSpinner();
9138
+ console.log(import_chalk46.default.red(` Stream error: ${err instanceof Error ? err.message : String(err)}`));
9139
+ }
9140
+ stopSpinner();
9141
+ if (assistantOpen) process.stdout.write("\n\n");
9142
+ if (parts.length) messages.push({ id: messageId, role: "assistant", parts });
9143
+ if (errorText) console.log(import_chalk46.default.red(` ${errorText}`));
9144
+ if (upsell) {
9145
+ renderUpsell(p, upsell, { messageAlreadyShown: true });
9146
+ }
9147
+ const bl = billingLine(turnInfo);
9148
+ if (bl) console.log(bl);
9149
+ const warn = freeBudgetWarning(turnInfo, p.anon);
9150
+ if (warn) console.log(warn);
9151
+ }
9152
+ function renderUpsell(p, upsell, opts = {}) {
8806
9153
  const loggedIn = !!loadCredentials();
8807
9154
  if (upsell.reason === "CAPPED" && !loggedIn) {
8808
9155
  console.log("\n" + import_chalk46.default.yellow(" Type `npx apiblaze login` to claim the rest of your balance."));
@@ -8810,7 +9157,9 @@ function renderUpsell(p, upsell) {
8810
9157
  console.log();
8811
9158
  return;
8812
9159
  }
8813
- console.log("\n" + import_chalk46.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
9160
+ if (!opts.messageAlreadyShown) {
9161
+ console.log("\n" + import_chalk46.default.yellow(` ${upsell.message || "This turn is not available right now."}`));
9162
+ }
8814
9163
  if (upsell.reason === "INSUFFICIENT" || upsell.reason === "BREAKER" || upsell.reason === "CAPPED" || upsell.reason === "QUOTA" || upsell.reason === "PROXY_CAP" || upsell.reason === "PAUSED" || upsell.reason === "BYO_REQUIRED") {
8815
9164
  if (!loggedIn) {
8816
9165
  console.log(import_chalk46.default.dim(" Options: `/login` for more free chats and requests, or `apiblaze llm set-key` to bring your own model key."));
@@ -8825,17 +9174,17 @@ function renderUpsell(p, upsell) {
8825
9174
  var apichatsPath = () => path6.join(getApiblazeDir(), "apichats.json");
8826
9175
  function loadApichats() {
8827
9176
  try {
8828
- const list = JSON.parse(fs9.readFileSync(apichatsPath(), "utf-8"));
9177
+ const list = JSON.parse(fs10.readFileSync(apichatsPath(), "utf-8"));
8829
9178
  return Array.isArray(list) ? list : [];
8830
9179
  } catch {
8831
9180
  return [];
8832
9181
  }
8833
9182
  }
8834
9183
  function writeApichats(list) {
8835
- fs9.mkdirSync(getApiblazeDir(), { recursive: true });
8836
- fs9.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
9184
+ fs10.mkdirSync(getApiblazeDir(), { recursive: true });
9185
+ fs10.writeFileSync(apichatsPath(), JSON.stringify(list, null, 2), "utf-8");
8837
9186
  try {
8838
- fs9.chmodSync(apichatsPath(), 384);
9187
+ fs10.chmodSync(apichatsPath(), 384);
8839
9188
  } catch {
8840
9189
  }
8841
9190
  }
@@ -8849,6 +9198,16 @@ function upsertApichat(entry) {
8849
9198
  else list.unshift(entry);
8850
9199
  writeApichats(list.slice(0, 30));
8851
9200
  }
9201
+ function persistAuthState(p) {
9202
+ const list = loadApichats();
9203
+ const i = list.findIndex((a) => a.projectId === p.projectId && a.version === p.version);
9204
+ if (i < 0) return;
9205
+ list[i].dpKey = p.dpKey;
9206
+ list[i].consumerAuth = p.consumerAuth;
9207
+ list[i].endUserId = p.endUserId;
9208
+ list[i].updatedAt = (/* @__PURE__ */ new Date()).toISOString();
9209
+ writeApichats(list);
9210
+ }
8852
9211
  function saveTranscript(p, messages) {
8853
9212
  const list = loadApichats();
8854
9213
  const i = list.findIndex((a) => apichatKey(a) === apichatKey(p));
@@ -8898,6 +9257,57 @@ async function fetchProxyDoor(teamId, projectId, apiVersion) {
8898
9257
  return { methods: null };
8899
9258
  }
8900
9259
  }
9260
+ async function openDirectProject(projectId, opts) {
9261
+ const saved = loadApichats().find((a) => a.projectId === projectId);
9262
+ const version2 = opts.apiversion || saved?.version || "1.0.0";
9263
+ const environment = opts.environment || saved?.environment || "prod";
9264
+ const anon = saved?.anon ?? !loadCredentials();
9265
+ const mcpHost = saved?.mcpHost || `${projectId}.mcp.${anon ? "tryabz" : "abz"}.run`;
9266
+ const p = {
9267
+ projectId,
9268
+ version: version2,
9269
+ environment,
9270
+ dpKey: opts.apikey ?? saved?.dpKey,
9271
+ consumerAuth: opts.apikey ? false : saved?.consumerAuth,
9272
+ mcpHost,
9273
+ proxyUrl: `https://${projectId}.${anon ? "tryabz" : "abz"}.run/${version2}/${environment}`,
9274
+ anon,
9275
+ access: "invite",
9276
+ tenant: saved?.tenant,
9277
+ teamId: saved?.teamId,
9278
+ endUserId: opts.xenduserid ?? saved?.endUserId
9279
+ };
9280
+ if (!p.dpKey && !p.consumerAuth) {
9281
+ if (process.stdin.isTTY) {
9282
+ const { default: inquirer3 } = await import("inquirer");
9283
+ const { key } = await inquirer3.prompt([{
9284
+ type: "password",
9285
+ name: "key",
9286
+ mask: "*",
9287
+ message: `API key for ${projectId} (leave empty if it uses a login):`
9288
+ }]);
9289
+ if (typeof key === "string" && key.trim()) p.dpKey = key.trim();
9290
+ else p.consumerAuth = true;
9291
+ }
9292
+ }
9293
+ console.log(` ${import_chalk46.default.dim("Proxy:")} ${import_chalk46.default.bold(p.proxyUrl)}`);
9294
+ if (p.endUserId) console.log(` ${import_chalk46.default.dim("Acting as:")} ${import_chalk46.default.bold(p.endUserId)}`);
9295
+ upsertApichat({
9296
+ name: projectId,
9297
+ target: p.proxyUrl,
9298
+ projectId,
9299
+ version: version2,
9300
+ environment,
9301
+ mcpHost,
9302
+ teamId: p.teamId,
9303
+ tenant: p.tenant,
9304
+ dpKey: p.dpKey,
9305
+ consumerAuth: p.consumerAuth,
9306
+ anon,
9307
+ endUserId: p.endUserId
9308
+ });
9309
+ return { p, messages: saved?.messages?.filter((m) => Array.isArray(m.parts)) ?? [] };
9310
+ }
8901
9311
  async function openServerProxy(project) {
8902
9312
  const version2 = project.apiVersion || "1.0.0";
8903
9313
  const environment = "prod";
@@ -8976,15 +9386,15 @@ function discoverLocalSpecs() {
8976
9386
  const found = [];
8977
9387
  for (const n of known) {
8978
9388
  try {
8979
- if (fs9.statSync(path6.join(cwd, n)).isFile()) found.push(n);
9389
+ if (fs10.statSync(path6.join(cwd, n)).isFile()) found.push(n);
8980
9390
  } catch {
8981
9391
  }
8982
9392
  }
8983
9393
  try {
8984
- const files = fs9.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
9394
+ const files = fs10.readdirSync(cwd).filter((f) => /\.(ya?ml|json)$/i.test(f) && !found.includes(f));
8985
9395
  for (const f of files.slice(0, 60)) {
8986
9396
  try {
8987
- const head = fs9.readFileSync(path6.join(cwd, f), "utf-8").slice(0, 4e3);
9397
+ const head = fs10.readFileSync(path6.join(cwd, f), "utf-8").slice(0, 4e3);
8988
9398
  if (/["']?openapi["']?\s*:/i.test(head) || /["']?swagger["']?\s*:/i.test(head) || /^\s*paths\s*:/im.test(head) || /"paths"\s*:/.test(head)) {
8989
9399
  found.push(f);
8990
9400
  }
@@ -9119,7 +9529,7 @@ async function noArgsMenu(opts) {
9119
9529
  }
9120
9530
  async function runRepl(p, initialMessages) {
9121
9531
  const { default: inquirer3 } = await import("inquirer");
9122
- const messages = initialMessages && initialMessages.length ? initialMessages.slice() : [];
9532
+ const messages = (initialMessages ?? []).filter((m) => Array.isArray(m.parts));
9123
9533
  console.log("\n" + import_chalk46.default.cyan.bold("Chat with your API") + import_chalk46.default.dim(` \xB7 ${p.mcpHost}`));
9124
9534
  if (messages.length) console.log(import_chalk46.default.dim(` Resumed \u2014 ${messages.length} prior messages.`));
9125
9535
  const llm2 = loadLlmConfig();
@@ -9184,6 +9594,20 @@ async function runRepl(p, initialMessages) {
9184
9594
  async function runApichat(opts) {
9185
9595
  setVerbose(opts.verbose !== false);
9186
9596
  console.log(import_chalk46.default.bold("\napichat \u2014 turn any API into a chat\n"));
9597
+ if (opts.target && !opts.openapispec) {
9598
+ const { classifyTargetInput: classifyTargetInput2 } = await Promise.resolve().then(() => (init_spec_or_target(), spec_or_target_exports));
9599
+ const c = await classifyTargetInput2(opts.target, fail4);
9600
+ if (c.kind === "spec") {
9601
+ console.log(import_chalk46.default.dim(` --target is an OpenAPI document (${c.source}) \u2014 using it as the spec.`));
9602
+ opts.openapispec = opts.target;
9603
+ opts.target = void 0;
9604
+ }
9605
+ }
9606
+ if (opts.project) {
9607
+ const opened = await openDirectProject(opts.project, opts);
9608
+ await runRepl(opened.p, opened.messages);
9609
+ return;
9610
+ }
9187
9611
  if (!opts.openapispec && !opts.target) {
9188
9612
  if (!process.stdin.isTTY) {
9189
9613
  fail4("No spec source. Pass --openapispec <file|url> or --target <url>.", GENERATOR_HINT);
@@ -9406,28 +9830,28 @@ async function runConsumerApikeys(opts) {
9406
9830
  // src/commands/sidecar.ts
9407
9831
  var import_chalk48 = __toESM(require("chalk"));
9408
9832
  var import_ora25 = __toESM(require("ora"));
9409
- var fs10 = __toESM(require("fs"));
9833
+ var fs11 = __toESM(require("fs"));
9410
9834
  var path7 = __toESM(require("path"));
9411
9835
  init_admin();
9412
9836
  init_resolve();
9413
9837
  init_auth();
9414
9838
  function detectNextProject(root) {
9415
- const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs10.existsSync(path7.join(root, f)));
9839
+ const hasConfig = ["next.config.js", "next.config.mjs", "next.config.ts"].some((f) => fs11.existsSync(path7.join(root, f)));
9416
9840
  let hasDep = false;
9417
9841
  try {
9418
- const pkg = JSON.parse(fs10.readFileSync(path7.join(root, "package.json"), "utf8"));
9842
+ const pkg = JSON.parse(fs11.readFileSync(path7.join(root, "package.json"), "utf8"));
9419
9843
  hasDep = !!(pkg.dependencies?.next || pkg.devDependencies?.next);
9420
9844
  } catch {
9421
9845
  }
9422
- const appDir = fs10.existsSync(path7.join(root, "app")) || fs10.existsSync(path7.join(root, "src", "app"));
9423
- const pagesDir = fs10.existsSync(path7.join(root, "pages")) || fs10.existsSync(path7.join(root, "src", "pages"));
9846
+ const appDir = fs11.existsSync(path7.join(root, "app")) || fs11.existsSync(path7.join(root, "src", "app"));
9847
+ const pagesDir = fs11.existsSync(path7.join(root, "pages")) || fs11.existsSync(path7.join(root, "src", "pages"));
9424
9848
  return { found: hasConfig || hasDep || appDir || pagesDir, router: appDir ? "app" : pagesDir ? "pages" : null };
9425
9849
  }
9426
9850
  function upsertEnvLocal(root, token) {
9427
9851
  const p = path7.join(root, ".env.local");
9428
9852
  let existing = "";
9429
9853
  try {
9430
- existing = fs10.readFileSync(p, "utf8");
9854
+ existing = fs11.readFileSync(p, "utf8");
9431
9855
  } catch {
9432
9856
  }
9433
9857
  const had = /^APIBLAZE_API_KEY=/m.test(existing) || /^APIBLAZE_TOKEN=/m.test(existing);
@@ -9442,15 +9866,15 @@ function upsertEnvLocal(root, token) {
9442
9866
  next = (next.endsWith("\n") ? next : next + "\n") + `APIBLAZE_SIDECAR_VERBOSE=true
9443
9867
  `;
9444
9868
  }
9445
- fs10.writeFileSync(p, next);
9869
+ fs11.writeFileSync(p, next);
9446
9870
  return had ? "rotated" : "created";
9447
9871
  }
9448
9872
  function installSidecarPackage(root) {
9449
- if (fs10.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
9873
+ if (fs11.existsSync(path7.join(root, "node_modules", "apiblaze", "package.json"))) {
9450
9874
  console.log(` ${import_chalk48.default.green("\u2713")} apiblaze package already installed`);
9451
9875
  return;
9452
9876
  }
9453
- const has = (f) => fs10.existsSync(path7.join(root, f));
9877
+ const has = (f) => fs11.existsSync(path7.join(root, f));
9454
9878
  const pm = has("bun.lockb") || has("bun.lock") ? { cmd: "bun", add: "add" } : has("pnpm-lock.yaml") ? { cmd: "pnpm", add: "add" } : has("yarn.lock") ? { cmd: "yarn", add: "add" } : { cmd: "npm", add: "install" };
9455
9879
  const spinner = (0, import_ora25.default)(`Installing the apiblaze package (${pm.cmd})\u2026`).start();
9456
9880
  try {
@@ -9463,7 +9887,7 @@ function installSidecarPackage(root) {
9463
9887
  }
9464
9888
  function readEnvKey(root) {
9465
9889
  try {
9466
- const s = fs10.readFileSync(path7.join(root, ".env.local"), "utf8");
9890
+ const s = fs11.readFileSync(path7.join(root, ".env.local"), "utf8");
9467
9891
  const m = s.match(/^APIBLAZE_API_KEY=(.+)$/m) ?? s.match(/^APIBLAZE_TOKEN=(.+)$/m);
9468
9892
  return m ? m[1].trim() : null;
9469
9893
  } catch {
@@ -9474,13 +9898,13 @@ function ensureGitignored(root) {
9474
9898
  const p = path7.join(root, ".gitignore");
9475
9899
  let c = "";
9476
9900
  try {
9477
- c = fs10.readFileSync(p, "utf8");
9901
+ c = fs11.readFileSync(p, "utf8");
9478
9902
  } catch {
9479
9903
  }
9480
- if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs10.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
9904
+ if (!/^\.env\.local$/m.test(c) && !/^\.env\*/m.test(c)) fs11.writeFileSync(p, (c && !c.endsWith("\n") ? c + "\n" : c) + ".env.local\n");
9481
9905
  }
9482
9906
  function wireInstrumentation(root) {
9483
- const existing = ["instrumentation.ts", "instrumentation.js", path7.join("src", "instrumentation.ts")].map((c) => path7.join(root, c)).find((f) => fs10.existsSync(f));
9907
+ const existing = ["instrumentation.ts", "instrumentation.js", path7.join("src", "instrumentation.ts")].map((c) => path7.join(root, c)).find((f) => fs11.existsSync(f));
9484
9908
  const body = `import { register as apiblaze } from "apiblaze/sidecar";
9485
9909
 
9486
9910
  export function register() {
@@ -9488,18 +9912,18 @@ export function register() {
9488
9912
  }
9489
9913
  `;
9490
9914
  if (!existing) {
9491
- fs10.writeFileSync(path7.join(root, "instrumentation.ts"), body);
9915
+ fs11.writeFileSync(path7.join(root, "instrumentation.ts"), body);
9492
9916
  return "created";
9493
9917
  }
9494
- const cur = fs10.readFileSync(existing, "utf8");
9918
+ const cur = fs11.readFileSync(existing, "utf8");
9495
9919
  if (cur.includes("apiblaze/sidecar")) return "present";
9496
9920
  if (/export\s+function\s+register\s*\(/.test(cur)) {
9497
- fs10.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9921
+ fs11.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9498
9922
  ` + cur.replace(/export\s+function\s+register\s*\(\s*\)\s*\{/, (m) => `${m}
9499
9923
  apiblaze();`));
9500
9924
  return "patched";
9501
9925
  }
9502
- fs10.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9926
+ fs11.writeFileSync(existing, `import { register as apiblaze } from "apiblaze/sidecar";
9503
9927
  ${cur}
9504
9928
  // call apiblaze() inside your register() export.
9505
9929
  `);
@@ -9578,16 +10002,16 @@ export default async function Page() {
9578
10002
  function generateInspector(root, router) {
9579
10003
  try {
9580
10004
  if (router === "pages") {
9581
- const dir2 = fs10.existsSync(path7.join(root, "src", "pages")) ? path7.join(root, "src", "pages") : path7.join(root, "pages");
10005
+ const dir2 = fs11.existsSync(path7.join(root, "src", "pages")) ? path7.join(root, "src", "pages") : path7.join(root, "pages");
9582
10006
  const f2 = path7.join(dir2, "abz-inspector.tsx");
9583
- fs10.writeFileSync(f2, INSPECTOR_PAGE);
10007
+ fs11.writeFileSync(f2, INSPECTOR_PAGE);
9584
10008
  return path7.relative(root, f2);
9585
10009
  }
9586
- const base2 = fs10.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
10010
+ const base2 = fs11.existsSync(path7.join(root, "src", "app")) ? path7.join(root, "src", "app") : path7.join(root, "app");
9587
10011
  const dir = path7.join(base2, "abz-inspector");
9588
- fs10.mkdirSync(dir, { recursive: true });
10012
+ fs11.mkdirSync(dir, { recursive: true });
9589
10013
  const f = path7.join(dir, "page.tsx");
9590
- fs10.writeFileSync(f, INSPECTOR_PAGE);
10014
+ fs11.writeFileSync(f, INSPECTOR_PAGE);
9591
10015
  return path7.relative(root, f);
9592
10016
  } catch {
9593
10017
  return null;
@@ -10382,10 +10806,11 @@ Slowest requests \u2014 last ${data.window_hours}h, over ${data.min_ms}ms
10382
10806
  console.log(import_chalk51.default.bold(`
10383
10807
  LLM timing \u2014 last ${data.window_hours}h
10384
10808
  `));
10385
- console.log(import_chalk51.default.dim(" requests gen p95 reserve p95 in/out tokens p95 model"));
10809
+ console.log(import_chalk51.default.dim(" turns turn p95 ttfc p95 gen p95 reserve p95 in/out tokens p95 model"));
10386
10810
  for (const r of rows2) {
10811
+ const n = r.turns ?? r.requests ?? 0;
10387
10812
  console.log(
10388
- ` ${String(Math.round(r.requests)).padStart(8)} ${String(Math.round(r.gen_p95_ms)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95)).padStart(6)}/${String(Math.round(r.output_tokens_p95)).padEnd(6)} ${r.model || "-"}`
10813
+ ` ${String(Math.round(n)).padStart(8)} ${String(Math.round(r.turn_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.ttfc_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.gen_p95_ms ?? 0)).padStart(6)}ms ${String(Math.round(r.reserve_p95_ms ?? 0)).padStart(9)}ms ${String(Math.round(r.input_tokens_p95 ?? 0)).padStart(6)}/${String(Math.round(r.output_tokens_p95 ?? 0)).padEnd(6)} ${r.model || "-"}`
10389
10814
  );
10390
10815
  }
10391
10816
  console.log(import_chalk51.default.dim(`
@@ -10451,7 +10876,7 @@ program.command("login").description("Authenticate with APIblaze").option("--tea
10451
10876
  process.exit(1);
10452
10877
  }
10453
10878
  });
10454
- program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "Target URL to forward requests to").option("--openapi <file|url>", "Create FROM an OpenAPI spec instead of --target \u2014 a local file or a URL (e.g. https://pokeapi.co/openapi.yaml). Routes, API version and environments (one per `servers` entry) all come from the spec").option("--openapispec <file|url>", "Alias for --openapi").option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
10879
+ program.command("create").description("Create a new API proxy (no login needed \u2014 without auth it creates an anonymous proxy and prints a claim URL)").option("--name <name>", "Proxy name (becomes <name>.abz.run)").option("--target <url>", "Target URL to forward requests to").option("--openapi <file|url>", "Create FROM an OpenAPI spec instead of --target \u2014 a local file or a URL (e.g. https://pokeapi.co/openapi.yaml). Routes, API version and environments (one per `servers` entry) all come from the spec").option("--openapispec <file|url>", "Alias for --openapi").option("--team <id|name>", "Team to create under (defaults to your active team)").option("--auth <type>", "Auth type: api_key | none | oauth", "api_key").option("--apikey", "Protect with API keys and print the bootstrap keys (the default, made explicit). Consumers send X-API-Key.").option("--oauth [config]", `Login door. Bare = APIblaze-hosted GitHub sign-in. '{"iss","aud","jwks"}' = trust YOUR hosted login's JWTs. '{"provider","clientId","clientSecret"}' = APIblaze-hosted login page with YOUR OAuth app (github \xB7 google \xB7 microsoft \xB7 facebook \xB7 auth0).`).option("--identified", "Require every call to identify its end user (X-End-User-Id or a login token); unattributed calls are rejected").option("--iam", "Turn IAM on for the proxy's tenant so users & groups apply to identified calls").option("--apiversion <version>", "API version to create (e.g. 2.0.0). Creating a new version of a proxy you own adds a version to the existing project.").option("--tenant <slug>", "Tenant to attach the proxy to (created if new). Omitted \u2192 your team's most-recently-used tenant.").option("--product <slug>", "Product tag to group this project under in the portal (team-scoped; anonymous create). Defaults to your team's existing/placeholder tag.").option("--display-name <name>", "Human-friendly display name").option("--subdomain <slug>", "Explicit subdomain (defaults to --name)").option("--config <file>", "JSON file with the full request body (anonymous create): requests_auth, login providers + client/server token types, scopes, callback URLs, etc. See apiblaze_anonymous.yaml. Flags override its fields.").option("-y, --yes", "Skip the confirmation prompt").option("--new-session", "Start a fresh anonymous session (do not group with prior anonymous creates)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
10455
10880
  try {
10456
10881
  await runCreate({ ...opts, openapi: opts.openapi ?? opts.openapispec });
10457
10882
  } catch (err) {
@@ -10464,7 +10889,7 @@ agent.command("authz").description("Chat to design and turn on access rules for
10464
10889
  program.command("rule").description("Author an object-level access rule in plain English, in one shot (billed per turn)").argument("<rule>", 'The rule in plain English, e.g. "users see only their own rows"').argument("<project>", "Project name or id").option("--enforce", "Turn enforcement on immediately (default: shadow-publish only)").option("--apiversion <version>", "API version (defaults to the project's)").action(action((rule, project, opts) => runRule(rule, project, opts)));
10465
10890
  agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
10466
10891
  agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
10467
- program.command("apichat").description("Turn any API into a chat: point at an OpenAPI spec and start asking questions (no login needed)").option("--openapi <file|url>", "OpenAPI/Swagger spec to build from (JSON or YAML; file path or URL)").option("--openapispec <file|url>", "Alias for --openapi").option("--target <url>", "Upstream base URL (overrides servers[0].url; enables spec discovery when --openapi is omitted)").option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").action(action((opts) => runApichat({ ...opts, openapispec: opts.openapispec ?? opts.openapi })));
10892
+ program.command("apichat [project]").description("Turn any API into a chat: point at an OpenAPI spec \u2014 or chat an EXISTING proxy by name (no login needed)").option("--openapi <file|url>", "OpenAPI/Swagger spec to build from (JSON or YAML; file path or URL)").option("--openapispec <file|url>", "Alias for --openapi").option("--target <url>", "Upstream base URL (overrides servers[0].url; enables spec discovery when --openapi is omitted)").option("--name <name>", "Proxy name (defaults to the target host)").option("--apiversion <version>", "API version to create (e.g. 1.0.0)").option("--environment <env>", "Environment to chat against (default: prod anonymous / dev logged-in)").option("--access <mode>", 'Who can call this API once connected (e.g. via Claude): "open" = anyone who signs in, "invite" = only you + emails you pre-approve. Default: invite when logged in, open when anonymous.').option("--target-auth-env <ENV_VAR>", "Read the upstream credential from this env var (CI-safe; required when there is no TTY and the API needs auth)").option("--force", "Proceed even if the API uses oauth2/openIdConnect target auth (you configure target auth yourself later)").option("-y, --yes", "Skip confirmation prompts").option("--tenant <slug>", "Tenant (consumer namespace: portal, login, users) for the new proxy; omit to be asked").option("--apikey <key>", "Use this API key for the proxy's door (api_key proxies). Without it, apichat detects the door and asks \u2014 or runs the consumer login for OAuth doors.").option("--xenduserid <id>", "Assert this end-user id (X-End-User-Id) \u2014 required by proxies with identified/pre-approved enforcement; you are asked for one when the proxy demands it.").option("--no-verbose", "Hide the per-turn proxy curl trace (shown by default for apichat)").action(action((project, opts) => runApichat({ ...opts, project, openapispec: opts.openapispec ?? opts.openapi })));
10468
10893
  var llm = program.command("llm").description("Manage a local LLM provider key for chat (optional \u2014 lifts model quality, bills your key)");
10469
10894
  llm.command("set-key").description("Store an LLM provider key locally (OpenRouter/Anthropic/DeepSeek/OpenAI)").argument("[key]", "The API key (omit to enter it hidden at a prompt)").option("--model <id>", "Model id to use with this key (e.g. anthropic/claude-haiku-4.5)").action(action((key, opts) => runLlmSetKey(key, opts)));
10470
10895
  llm.command("show").description("Show the locally stored LLM key (masked)").action(action(() => runLlmShow()));