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