apiblaze 0.19.19 → 0.19.20

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 +113 -13
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -932,7 +932,7 @@ var import_commander = require("commander");
932
932
  var import_chalk44 = __toESM(require("chalk"));
933
933
 
934
934
  // package.json
935
- var version = "0.19.19";
935
+ var version = "0.19.20";
936
936
 
937
937
  // src/index.ts
938
938
  init_types();
@@ -6054,6 +6054,68 @@ async function captureTargetSecret(auth, opts) {
6054
6054
  if (!secret) fail4("No credential entered.");
6055
6055
  return secret;
6056
6056
  }
6057
+ async function dataPlaneAuth(p) {
6058
+ if (!p.consumerAuth) {
6059
+ if (!p.dpKey) throw new Error("No API key for this proxy.");
6060
+ return { "X-API-Key": p.dpKey };
6061
+ }
6062
+ const stored = loadConsumer();
6063
+ if (!stored) {
6064
+ throw new Error("Consumer login required \u2014 run `apiblaze apichat` again to sign in.");
6065
+ }
6066
+ const fresh = await validConsumerToken(stored) ?? stored;
6067
+ if (fresh.accessToken !== stored.accessToken) saveConsumer(fresh);
6068
+ return { Authorization: `Bearer ${fresh.accessToken}` };
6069
+ }
6070
+ async function ensureConsumerLogin(teamId, tenant2) {
6071
+ const existing = loadConsumer();
6072
+ if (existing && existing.tenant === tenant2) {
6073
+ const fresh = await validConsumerToken(existing);
6074
+ if (fresh) {
6075
+ if (fresh.accessToken !== existing.accessToken) saveConsumer(fresh);
6076
+ console.log(import_chalk39.default.dim(` Using your consumer session on ${import_chalk39.default.bold(tenant2)}${fresh.email ? ` (${fresh.email})` : ""}.`));
6077
+ return fresh;
6078
+ }
6079
+ }
6080
+ const spinner = (0, import_ora21.default)(`Finding the login app for ${tenant2}...`).start();
6081
+ const clients = await admin({
6082
+ method: "GET",
6083
+ path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`,
6084
+ summary: `List app clients for ${tenant2}`
6085
+ }).catch(() => []);
6086
+ spinner.stop();
6087
+ const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
6088
+ const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
6089
+ if (!pick2) {
6090
+ throw new Error(
6091
+ `Tenant "${tenant2}" has no login app configured, so there is no way to sign in as a consumer. Add one in the dashboard (Tenants \u2192 app clients).`
6092
+ );
6093
+ }
6094
+ const clientId = pick2.client_id ?? pick2.clientId;
6095
+ console.log(`${import_chalk39.default.cyan("\u2192")} This proxy signs consumers in with OAuth \u2014 logging you in to ${import_chalk39.default.bold(tenant2)}...`);
6096
+ const result = await deviceLogin(clientId, "openid email profile offline_access", ({ verificationUri, userCode }) => {
6097
+ console.log(`
6098
+ Open: ${import_chalk39.default.underline(verificationUri)}`);
6099
+ console.log(` Code: ${import_chalk39.default.bold(userCode)}
6100
+ `);
6101
+ console.log(import_chalk39.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
6102
+ }, `https://${tenant2}.portal.apiblaze.com/1.0.0`);
6103
+ const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
6104
+ const creds = {
6105
+ tenant: tenant2,
6106
+ clientId,
6107
+ accessToken: result.accessToken,
6108
+ refreshToken: result.refreshToken,
6109
+ idToken: result.idToken,
6110
+ expiresAt: Date.now() + (result.expiresIn ?? 3600) * 1e3,
6111
+ scope: result.scope,
6112
+ email: typeof claims.email === "string" ? claims.email : void 0,
6113
+ obtainedAt: Date.now()
6114
+ };
6115
+ saveConsumer(creds);
6116
+ console.log(` ${import_chalk39.default.green("\u2714")} Signed in as${creds.email ? ` ${import_chalk39.default.bold(creds.email)}` : " a consumer"} on ${tenant2}.`);
6117
+ return creds;
6118
+ }
6057
6119
  async function cpPost(anon, path8, body, summary) {
6058
6120
  if (anon) {
6059
6121
  const cred = loadAnonCred();
@@ -6235,7 +6297,7 @@ async function publishMcp(p, spec2) {
6235
6297
  const url = `https://${p.mcpHost}/${p.version}/${p.environment}/mcp/generate`;
6236
6298
  const res = await fetch(url, {
6237
6299
  method: "POST",
6238
- headers: { "Content-Type": "application/json", "X-API-Key": p.dpKey },
6300
+ headers: { "Content-Type": "application/json", ...await dataPlaneAuth(p) },
6239
6301
  body: JSON.stringify({ openapi: spec2, environment: p.environment })
6240
6302
  });
6241
6303
  const out = await res.json().catch(() => null);
@@ -6263,15 +6325,16 @@ function maskKey(k) {
6263
6325
  return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
6264
6326
  }
6265
6327
  var revealAuth = false;
6266
- function renderToolEvents(events, dpKey) {
6328
+ function renderToolEvents(events, cred) {
6267
6329
  for (const e of events ?? []) {
6268
6330
  const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
6269
6331
  const mark = ok ? import_chalk39.default.green("\u2713") : import_chalk39.default.red("\u2717");
6270
6332
  console.log(` ${mark} ${import_chalk39.default.cyan(e.name)} ${import_chalk39.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
6271
6333
  if (isVerbose() && e.method && e.url) {
6272
- console.log(import_chalk39.default.dim(` curl -sS -X ${e.method} '${e.url}'${dpKey ? " \\" : ""}`));
6273
- if (dpKey) {
6274
- const keyLine = ` -H 'X-API-Key: ${revealAuth ? dpKey : maskKey(dpKey)}'`;
6334
+ console.log(import_chalk39.default.dim(` curl -sS -X ${e.method} '${e.url}'${cred ? " \\" : ""}`));
6335
+ if (cred) {
6336
+ const shown = revealAuth ? cred.value : maskKey(cred.value);
6337
+ const keyLine = ` -H '${cred.header}: ${shown}'`;
6275
6338
  const hint = revealAuth ? "" : import_chalk39.default.yellow(" \u2190 /showauth will reveal this");
6276
6339
  console.log(import_chalk39.default.dim(keyLine) + hint);
6277
6340
  }
@@ -6327,7 +6390,7 @@ async function replTurn(p, messages, userText) {
6327
6390
  try {
6328
6391
  res = await fetch(chatUrl(p), {
6329
6392
  method: "POST",
6330
- headers: { "Content-Type": "application/json", "X-API-Key": p.dpKey },
6393
+ headers: { "Content-Type": "application/json", ...await dataPlaneAuth(p) },
6331
6394
  body: JSON.stringify(body)
6332
6395
  });
6333
6396
  } catch (err) {
@@ -6359,7 +6422,13 @@ async function replTurn(p, messages, userText) {
6359
6422
  return;
6360
6423
  }
6361
6424
  if (Array.isArray(data.delta)) {
6362
- renderToolEvents(data.tool_events, p.dpKey);
6425
+ renderToolEvents(
6426
+ data.tool_events,
6427
+ p.consumerAuth ? (() => {
6428
+ const t = loadConsumer()?.accessToken;
6429
+ return t ? { header: "Authorization", value: `Bearer ${t}` } : void 0;
6430
+ })() : p.dpKey ? { header: "X-API-Key", value: p.dpKey } : void 0
6431
+ );
6363
6432
  for (const m of data.delta) messages.push(m);
6364
6433
  printAssistant(data.delta);
6365
6434
  }
@@ -6447,24 +6516,50 @@ async function mintDurableProxyKey(teamId, tenant2) {
6447
6516
  if (!out?.key) throw new Error("key mint returned no key");
6448
6517
  return out.key;
6449
6518
  }
6519
+ async function fetchAcceptedMethods(project) {
6520
+ try {
6521
+ const listing = await admin({
6522
+ method: "GET",
6523
+ path: `/projects?team_id=${encodeURIComponent(project.teamId)}`,
6524
+ summary: `Read the access policy for ${project.projectName}`
6525
+ });
6526
+ const rows = listing?.projects ?? [];
6527
+ const row = rows.find((r) => r.project_id === project.projectId && r.api_version === project.apiVersion) ?? rows.find((r) => r.project_id === project.projectId);
6528
+ const policy = row?.config?.requests_policy;
6529
+ if (!policy || policy.mode !== "authenticate") return null;
6530
+ return Array.isArray(policy.methods) && policy.methods.length ? policy.methods : null;
6531
+ } catch {
6532
+ return null;
6533
+ }
6534
+ }
6450
6535
  async function openServerProxy(project) {
6451
6536
  const version2 = project.apiVersion || "1.0.0";
6452
6537
  const environment = "prod";
6453
6538
  const mcpHost = `${project.projectId}.mcp.abz.run`;
6454
6539
  const tenant2 = project.tenant || project.projectId;
6455
6540
  const me = loadCredentials()?.apiblazeUserId;
6456
- const prior = loadApichats().find((a) => a.projectId === project.projectId && a.dpKey);
6541
+ const prior = loadApichats().find((a) => a.projectId === project.projectId);
6542
+ const acceptedMethods = await fetchAcceptedMethods(project);
6543
+ const acceptsApiKey = acceptedMethods === null || acceptedMethods.includes("api_key");
6457
6544
  let dpKey = prior?.dpKey;
6458
- if (!dpKey) {
6459
- console.log(import_chalk39.default.dim(` Minting an API key for tenant ${import_chalk39.default.bold(tenant2)} to query project ${import_chalk39.default.bold(project.projectName)}\u2026`));
6460
- dpKey = await mintDurableProxyKey(project.teamId, tenant2);
6461
- console.log(` ${import_chalk39.default.green("\u2714")} API key: ${import_chalk39.default.dim(maskKey(dpKey))}`);
6545
+ let consumerAuth = false;
6546
+ if (acceptsApiKey) {
6547
+ if (!dpKey) {
6548
+ console.log(import_chalk39.default.dim(` Minting an API key for tenant ${import_chalk39.default.bold(tenant2)} to query project ${import_chalk39.default.bold(project.projectName)}\u2026`));
6549
+ dpKey = await mintDurableProxyKey(project.teamId, tenant2);
6550
+ console.log(` ${import_chalk39.default.green("\u2714")} API key: ${import_chalk39.default.dim(maskKey(dpKey))}`);
6551
+ }
6552
+ } else {
6553
+ consumerAuth = true;
6554
+ dpKey = void 0;
6555
+ await ensureConsumerLogin(project.teamId, tenant2);
6462
6556
  }
6463
6557
  const p = {
6464
6558
  projectId: project.projectId,
6465
6559
  version: version2,
6466
6560
  environment,
6467
6561
  dpKey,
6562
+ consumerAuth,
6468
6563
  mcpHost,
6469
6564
  proxyUrl: `https://${project.projectId}.abz.run/${version2}/${environment}`,
6470
6565
  anon: false,
@@ -6497,6 +6592,7 @@ async function openServerProxy(project) {
6497
6592
  environment,
6498
6593
  mcpHost,
6499
6594
  dpKey,
6595
+ consumerAuth,
6500
6596
  anon: false,
6501
6597
  ownerUserId: me,
6502
6598
  createdAt: prior?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
@@ -6579,6 +6675,9 @@ async function noArgsMenu(opts) {
6579
6675
  version: a.version,
6580
6676
  environment: a.environment,
6581
6677
  dpKey: a.dpKey,
6678
+ // Carry the door forward — resuming an OAuth-only chat must not fall back to
6679
+ // looking for a key that was never minted.
6680
+ consumerAuth: a.consumerAuth,
6582
6681
  mcpHost: a.mcpHost,
6583
6682
  proxyUrl: void 0,
6584
6683
  anon: a.anon,
@@ -6721,6 +6820,7 @@ async function runApichat(opts) {
6721
6820
  environment: p.environment,
6722
6821
  mcpHost: p.mcpHost,
6723
6822
  dpKey: p.dpKey,
6823
+ consumerAuth: p.consumerAuth,
6724
6824
  anon: p.anon,
6725
6825
  ownerUserId: loadCredentials()?.apiblazeUserId,
6726
6826
  // undefined while anon
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.19.19",
3
+ "version": "0.19.20",
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",