apiblaze 0.19.18 → 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.
- package/dist/index.js +137 -19
- 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.
|
|
935
|
+
var version = "0.19.20";
|
|
936
936
|
|
|
937
937
|
// src/index.ts
|
|
938
938
|
init_types();
|
|
@@ -1689,6 +1689,9 @@ async function probeLocalServer(port) {
|
|
|
1689
1689
|
}
|
|
1690
1690
|
}
|
|
1691
1691
|
async function runDev(options) {
|
|
1692
|
+
if (options.newSession && !(loadCredentials() && Date.now() < loadCredentials().expiresAt)) {
|
|
1693
|
+
clearAnonCred();
|
|
1694
|
+
}
|
|
1692
1695
|
let auth = resolveDevAuth();
|
|
1693
1696
|
let keyAuth = auth.mode === "key" ? { apiKey: auth.apiKey } : void 0;
|
|
1694
1697
|
let teamId = "";
|
|
@@ -1933,6 +1936,7 @@ ${projects.length} project${projects.length === 1 ? "" : "s"}`));
|
|
|
1933
1936
|
|
|
1934
1937
|
// src/commands/create.ts
|
|
1935
1938
|
var import_fs2 = __toESM(require("fs"));
|
|
1939
|
+
var import_yaml = require("yaml");
|
|
1936
1940
|
var import_chalk10 = __toESM(require("chalk"));
|
|
1937
1941
|
var import_ora5 = __toESM(require("ora"));
|
|
1938
1942
|
init_auth();
|
|
@@ -1964,8 +1968,22 @@ async function loadOpenapiSource(ref) {
|
|
|
1964
1968
|
}
|
|
1965
1969
|
}
|
|
1966
1970
|
if (!text.trim()) fail(`The OpenAPI spec is empty: ${ref}`);
|
|
1967
|
-
|
|
1968
|
-
|
|
1971
|
+
let parsed;
|
|
1972
|
+
try {
|
|
1973
|
+
parsed = text.trimStart().startsWith("{") ? JSON.parse(text) : (0, import_yaml.parse)(text);
|
|
1974
|
+
} catch (err) {
|
|
1975
|
+
fail(`Could not parse the OpenAPI spec at ${ref} as JSON or YAML \u2014 ${err.message}`);
|
|
1976
|
+
}
|
|
1977
|
+
const doc = parsed;
|
|
1978
|
+
if (!doc || typeof doc !== "object") {
|
|
1979
|
+
fail(`${ref} is not an OpenAPI/Swagger document.
|
|
1980
|
+
If that link opens a web page, use the raw file URL instead.`);
|
|
1981
|
+
}
|
|
1982
|
+
if (typeof doc.openapi !== "string" && typeof doc.swagger !== "string") {
|
|
1983
|
+
fail(
|
|
1984
|
+
`${ref} has no "openapi" or "swagger" version field, so it is not a usable API description.
|
|
1985
|
+
If the file looks right otherwise, check its first line \u2014 the copy at that URL may be damaged.`
|
|
1986
|
+
);
|
|
1969
1987
|
}
|
|
1970
1988
|
return text;
|
|
1971
1989
|
}
|
|
@@ -5752,7 +5770,7 @@ var path6 = __toESM(require("path"));
|
|
|
5752
5770
|
var crypto2 = __toESM(require("crypto"));
|
|
5753
5771
|
var import_chalk39 = __toESM(require("chalk"));
|
|
5754
5772
|
var import_ora21 = __toESM(require("ora"));
|
|
5755
|
-
var
|
|
5773
|
+
var import_yaml2 = require("yaml");
|
|
5756
5774
|
init_auth();
|
|
5757
5775
|
init_anon_cred();
|
|
5758
5776
|
init_api();
|
|
@@ -5855,7 +5873,7 @@ function parseSpec(text) {
|
|
|
5855
5873
|
parsed = JSON.parse(text);
|
|
5856
5874
|
} catch {
|
|
5857
5875
|
try {
|
|
5858
|
-
parsed = (0,
|
|
5876
|
+
parsed = (0, import_yaml2.parse)(text);
|
|
5859
5877
|
} catch {
|
|
5860
5878
|
fail4("Could not parse the spec as JSON or YAML.");
|
|
5861
5879
|
}
|
|
@@ -6036,6 +6054,68 @@ async function captureTargetSecret(auth, opts) {
|
|
|
6036
6054
|
if (!secret) fail4("No credential entered.");
|
|
6037
6055
|
return secret;
|
|
6038
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
|
+
}
|
|
6039
6119
|
async function cpPost(anon, path8, body, summary) {
|
|
6040
6120
|
if (anon) {
|
|
6041
6121
|
const cred = loadAnonCred();
|
|
@@ -6217,7 +6297,7 @@ async function publishMcp(p, spec2) {
|
|
|
6217
6297
|
const url = `https://${p.mcpHost}/${p.version}/${p.environment}/mcp/generate`;
|
|
6218
6298
|
const res = await fetch(url, {
|
|
6219
6299
|
method: "POST",
|
|
6220
|
-
headers: { "Content-Type": "application/json",
|
|
6300
|
+
headers: { "Content-Type": "application/json", ...await dataPlaneAuth(p) },
|
|
6221
6301
|
body: JSON.stringify({ openapi: spec2, environment: p.environment })
|
|
6222
6302
|
});
|
|
6223
6303
|
const out = await res.json().catch(() => null);
|
|
@@ -6245,15 +6325,16 @@ function maskKey(k) {
|
|
|
6245
6325
|
return k.length <= 8 ? "****" : `${k.slice(0, 4)}\u2026${k.slice(-4)}`;
|
|
6246
6326
|
}
|
|
6247
6327
|
var revealAuth = false;
|
|
6248
|
-
function renderToolEvents(events,
|
|
6328
|
+
function renderToolEvents(events, cred) {
|
|
6249
6329
|
for (const e of events ?? []) {
|
|
6250
6330
|
const ok = typeof e.status === "number" ? e.status < 400 : String(e.status).toLowerCase() === "ok";
|
|
6251
6331
|
const mark = ok ? import_chalk39.default.green("\u2713") : import_chalk39.default.red("\u2717");
|
|
6252
6332
|
console.log(` ${mark} ${import_chalk39.default.cyan(e.name)} ${import_chalk39.default.dim(`(${e.status}, ${e.ms}ms)`)}`);
|
|
6253
6333
|
if (isVerbose() && e.method && e.url) {
|
|
6254
|
-
console.log(import_chalk39.default.dim(` curl -sS -X ${e.method} '${e.url}'${
|
|
6255
|
-
if (
|
|
6256
|
-
const
|
|
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}'`;
|
|
6257
6338
|
const hint = revealAuth ? "" : import_chalk39.default.yellow(" \u2190 /showauth will reveal this");
|
|
6258
6339
|
console.log(import_chalk39.default.dim(keyLine) + hint);
|
|
6259
6340
|
}
|
|
@@ -6309,7 +6390,7 @@ async function replTurn(p, messages, userText) {
|
|
|
6309
6390
|
try {
|
|
6310
6391
|
res = await fetch(chatUrl(p), {
|
|
6311
6392
|
method: "POST",
|
|
6312
|
-
headers: { "Content-Type": "application/json",
|
|
6393
|
+
headers: { "Content-Type": "application/json", ...await dataPlaneAuth(p) },
|
|
6313
6394
|
body: JSON.stringify(body)
|
|
6314
6395
|
});
|
|
6315
6396
|
} catch (err) {
|
|
@@ -6341,7 +6422,13 @@ async function replTurn(p, messages, userText) {
|
|
|
6341
6422
|
return;
|
|
6342
6423
|
}
|
|
6343
6424
|
if (Array.isArray(data.delta)) {
|
|
6344
|
-
renderToolEvents(
|
|
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
|
+
);
|
|
6345
6432
|
for (const m of data.delta) messages.push(m);
|
|
6346
6433
|
printAssistant(data.delta);
|
|
6347
6434
|
}
|
|
@@ -6429,24 +6516,50 @@ async function mintDurableProxyKey(teamId, tenant2) {
|
|
|
6429
6516
|
if (!out?.key) throw new Error("key mint returned no key");
|
|
6430
6517
|
return out.key;
|
|
6431
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
|
+
}
|
|
6432
6535
|
async function openServerProxy(project) {
|
|
6433
6536
|
const version2 = project.apiVersion || "1.0.0";
|
|
6434
6537
|
const environment = "prod";
|
|
6435
6538
|
const mcpHost = `${project.projectId}.mcp.abz.run`;
|
|
6436
6539
|
const tenant2 = project.tenant || project.projectId;
|
|
6437
6540
|
const me = loadCredentials()?.apiblazeUserId;
|
|
6438
|
-
const prior = loadApichats().find((a) => a.projectId === project.projectId
|
|
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");
|
|
6439
6544
|
let dpKey = prior?.dpKey;
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
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);
|
|
6444
6556
|
}
|
|
6445
6557
|
const p = {
|
|
6446
6558
|
projectId: project.projectId,
|
|
6447
6559
|
version: version2,
|
|
6448
6560
|
environment,
|
|
6449
6561
|
dpKey,
|
|
6562
|
+
consumerAuth,
|
|
6450
6563
|
mcpHost,
|
|
6451
6564
|
proxyUrl: `https://${project.projectId}.abz.run/${version2}/${environment}`,
|
|
6452
6565
|
anon: false,
|
|
@@ -6479,6 +6592,7 @@ async function openServerProxy(project) {
|
|
|
6479
6592
|
environment,
|
|
6480
6593
|
mcpHost,
|
|
6481
6594
|
dpKey,
|
|
6595
|
+
consumerAuth,
|
|
6482
6596
|
anon: false,
|
|
6483
6597
|
ownerUserId: me,
|
|
6484
6598
|
createdAt: prior?.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -6561,6 +6675,9 @@ async function noArgsMenu(opts) {
|
|
|
6561
6675
|
version: a.version,
|
|
6562
6676
|
environment: a.environment,
|
|
6563
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,
|
|
6564
6681
|
mcpHost: a.mcpHost,
|
|
6565
6682
|
proxyUrl: void 0,
|
|
6566
6683
|
anon: a.anon,
|
|
@@ -6703,6 +6820,7 @@ async function runApichat(opts) {
|
|
|
6703
6820
|
environment: p.environment,
|
|
6704
6821
|
mcpHost: p.mcpHost,
|
|
6705
6822
|
dpKey: p.dpKey,
|
|
6823
|
+
consumerAuth: p.consumerAuth,
|
|
6706
6824
|
anon: p.anon,
|
|
6707
6825
|
ownerUserId: loadCredentials()?.apiblazeUserId,
|
|
6708
6826
|
// undefined while anon
|
|
@@ -7505,14 +7623,14 @@ withSetupOptions(sidecar.command("setup").description("Wire a Next.js app to rou
|
|
|
7505
7623
|
sidecar.command("approve").description("Route an origin through APIblaze (creates its proxy)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Machine-readable output").action(action((origin, opts) => runOriginsApprove(origin, opts)));
|
|
7506
7624
|
sidecar.command("deny").description("Dismiss a candidate origin so it stops being suggested").argument("<origin>", "Origin, e.g. sentry.io").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsDeny(origin, opts)));
|
|
7507
7625
|
sidecar.command("remove").description("Un-route an approved origin (deletes its proxy; the app goes direct again)").argument("<origin>", "Origin, e.g. api.stripe.com").option("--team <id|name>", "Team (defaults to active team)").action(action((origin, opts) => runOriginsRemove(origin, opts)));
|
|
7508
|
-
program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("--project <nameOrId>", "Tunnel this specific project (skips the picker \u2014 for scripts)").option("-y, --yes", "Skip confirmation prompts (non-interactive)").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
|
|
7626
|
+
program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("--project <nameOrId>", "Tunnel this specific project (skips the picker \u2014 for scripts)").option("-y, --yes", "Skip confirmation prompts (non-interactive)").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").option("--new-session", "Logged-out only: start a fresh anonymous workspace instead of reusing this machine's (each run = a throwaway proxy)").action(async (port, opts) => {
|
|
7509
7627
|
try {
|
|
7510
7628
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
7511
7629
|
if (Number.isNaN(resolved)) {
|
|
7512
7630
|
console.error(import_chalk44.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
7513
7631
|
process.exit(1);
|
|
7514
7632
|
}
|
|
7515
|
-
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile });
|
|
7633
|
+
await runDev({ port: resolved, project: opts.project, yes: opts.yes, captureFile: opts.captureFile, newSession: opts.newSession });
|
|
7516
7634
|
} catch (err) {
|
|
7517
7635
|
await printError(err);
|
|
7518
7636
|
process.exit(1);
|