apiblaze 0.1.22 → 0.3.0
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/README.md +33 -13
- package/dist/index.js +329 -6
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install -g apiblaze
|
|
|
11
11
|
Or run without installing:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
npx apiblaze
|
|
14
|
+
npx apiblaze --help
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Requirements
|
|
@@ -21,14 +21,29 @@ npx apiblaze dev
|
|
|
21
21
|
## Quick start
|
|
22
22
|
|
|
23
23
|
```bash
|
|
24
|
-
#
|
|
25
|
-
apiblaze
|
|
24
|
+
# Create a proxy (no account needed)
|
|
25
|
+
npx apiblaze create --target https://api.example.com
|
|
26
|
+
|
|
27
|
+
# Optional: sign in if you want it under your team
|
|
28
|
+
npx apiblaze login
|
|
29
|
+
|
|
30
|
+
# Create a proxy under your team
|
|
31
|
+
npx apiblaze create --name myapi --target https://api.example.com --auth api_key
|
|
32
|
+
|
|
33
|
+
# Start a dev tunnel (defaults to port 3000)
|
|
34
|
+
npx apiblaze dev
|
|
26
35
|
|
|
27
|
-
#
|
|
28
|
-
apiblaze dev
|
|
36
|
+
# Or specify a port
|
|
37
|
+
npx apiblaze dev 3000
|
|
29
38
|
|
|
30
|
-
|
|
31
|
-
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Help
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
apiblaze --help
|
|
45
|
+
apiblaze help create
|
|
46
|
+
apiblaze help dev
|
|
32
47
|
```
|
|
33
48
|
|
|
34
49
|
## Commands
|
|
@@ -36,20 +51,25 @@ apiblaze dev --port 8080
|
|
|
36
51
|
| Command | Description |
|
|
37
52
|
|---|---|
|
|
38
53
|
| `apiblaze login` | Authenticate with your APIblaze account |
|
|
39
|
-
| `apiblaze
|
|
54
|
+
| `apiblaze create [options]` | Create a new API proxy (anonymous if not logged in) |
|
|
55
|
+
| `apiblaze claim [code]` | Claim an anonymously-created proxy into your team |
|
|
56
|
+
| `apiblaze projects` | List your team projects |
|
|
57
|
+
| `apiblaze dev [port]` | Start a dev tunnel for your localhost projects |
|
|
58
|
+
| `apiblaze team [team]` | Switch the active team |
|
|
59
|
+
| `apiblaze whoami` | Show the signed-in identity and active team |
|
|
60
|
+
| `apiblaze logout` | Sign out and remove stored credentials |
|
|
40
61
|
|
|
41
62
|
## How it works
|
|
42
63
|
|
|
43
64
|
`apiblaze dev` automatically:
|
|
44
65
|
|
|
45
|
-
1.
|
|
46
|
-
2.
|
|
47
|
-
3.
|
|
48
|
-
4. Streams live traffic logs to your terminal in real
|
|
66
|
+
1. Fetches your APIblaze projects that target `localhost` (or other internal targets)
|
|
67
|
+
2. Registers a temporary dev tunnel with APIblaze
|
|
68
|
+
3. Opens a secure connection and forwards incoming requests to your local server
|
|
69
|
+
4. Streams live traffic logs to your terminal in real time
|
|
49
70
|
|
|
50
71
|
On Ctrl+C the tunnel is cleanly deregistered.
|
|
51
72
|
|
|
52
73
|
## License
|
|
53
74
|
|
|
54
75
|
MIT
|
|
55
|
-
# npm-apiblaze
|
package/dist/index.js
CHANGED
|
@@ -93,6 +93,7 @@ var init_auth = __esm({
|
|
|
93
93
|
// src/lib/api.ts
|
|
94
94
|
var api_exports = {};
|
|
95
95
|
__export(api_exports, {
|
|
96
|
+
agentCall: () => agentCall,
|
|
96
97
|
checkProxyName: () => checkProxyName,
|
|
97
98
|
claimProxy: () => claimProxy,
|
|
98
99
|
createProxy: () => createProxy,
|
|
@@ -147,6 +148,20 @@ async function apiFetch(path2, options = {}) {
|
|
|
147
148
|
}
|
|
148
149
|
return res.json();
|
|
149
150
|
}
|
|
151
|
+
async function agentCall(path2, method, body) {
|
|
152
|
+
const token = getAccessToken();
|
|
153
|
+
const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
|
|
154
|
+
method: "POST",
|
|
155
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
156
|
+
body: JSON.stringify({ path: path2, method, body })
|
|
157
|
+
});
|
|
158
|
+
let data = null;
|
|
159
|
+
try {
|
|
160
|
+
data = await res.json();
|
|
161
|
+
} catch {
|
|
162
|
+
}
|
|
163
|
+
return { status: res.status, data };
|
|
164
|
+
}
|
|
150
165
|
async function getTeams() {
|
|
151
166
|
const res = await apiFetch("/api/cli/teams");
|
|
152
167
|
const raw = Array.isArray(res) ? res : res?.teams ?? res?.data ?? [];
|
|
@@ -203,10 +218,10 @@ var init_api = __esm({
|
|
|
203
218
|
|
|
204
219
|
// src/index.ts
|
|
205
220
|
var import_commander = require("commander");
|
|
206
|
-
var
|
|
221
|
+
var import_chalk14 = __toESM(require("chalk"));
|
|
207
222
|
|
|
208
223
|
// package.json
|
|
209
|
-
var version = "0.
|
|
224
|
+
var version = "0.3.0";
|
|
210
225
|
|
|
211
226
|
// src/index.ts
|
|
212
227
|
init_types();
|
|
@@ -1200,6 +1215,290 @@ async function runTeam(arg) {
|
|
|
1200
1215
|
\u2714 Active team: ${import_chalk9.default.bold(chosen.name)}`));
|
|
1201
1216
|
}
|
|
1202
1217
|
|
|
1218
|
+
// src/commands/authz.ts
|
|
1219
|
+
var import_chalk11 = __toESM(require("chalk"));
|
|
1220
|
+
init_auth();
|
|
1221
|
+
init_api();
|
|
1222
|
+
|
|
1223
|
+
// src/lib/agent-chat.ts
|
|
1224
|
+
var import_readline = __toESM(require("readline"));
|
|
1225
|
+
var import_chalk10 = __toESM(require("chalk"));
|
|
1226
|
+
init_api();
|
|
1227
|
+
async function runAgentChatRepl(opts) {
|
|
1228
|
+
const { title, subtitle, endpoint, buildBody, seedPrompt, summarizeProposal, commands } = opts;
|
|
1229
|
+
console.log("\n" + import_chalk10.default.cyan.bold(title));
|
|
1230
|
+
if (subtitle) console.log(import_chalk10.default.dim(subtitle));
|
|
1231
|
+
const messages = [];
|
|
1232
|
+
const ctx = { proposal: null, lastData: null, log: (s) => console.log(s) };
|
|
1233
|
+
const cmdByName = new Map(commands.map((c) => [c.name, c]));
|
|
1234
|
+
const footer = () => {
|
|
1235
|
+
const parts = [
|
|
1236
|
+
import_chalk10.default.dim("type to chat/refine"),
|
|
1237
|
+
...commands.map((c) => import_chalk10.default.dim(`/${c.name} ${c.describe}`)),
|
|
1238
|
+
import_chalk10.default.dim("/show"),
|
|
1239
|
+
import_chalk10.default.dim("/drop discard & exit")
|
|
1240
|
+
];
|
|
1241
|
+
return " " + import_chalk10.default.dim("[ ") + parts.join(import_chalk10.default.dim(" \xB7 ")) + import_chalk10.default.dim(" ]");
|
|
1242
|
+
};
|
|
1243
|
+
async function turn(content) {
|
|
1244
|
+
messages.push({ role: "user", content });
|
|
1245
|
+
process.stdout.write(import_chalk10.default.dim(" \u2026thinking\n"));
|
|
1246
|
+
const { status, data } = await agentCall(endpoint, "POST", { messages, ...buildBody(messages) });
|
|
1247
|
+
if (status === 402) {
|
|
1248
|
+
console.log(import_chalk10.default.red(" Insufficient credits \u2014 top up to keep using the agents.\n"));
|
|
1249
|
+
messages.pop();
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
if (status >= 400 || !data) {
|
|
1253
|
+
console.log(import_chalk10.default.red(` Error (${status}): ${(data && data.error) ?? "request failed"}
|
|
1254
|
+
`));
|
|
1255
|
+
messages.pop();
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
ctx.lastData = data;
|
|
1259
|
+
const reply = data.reply ?? data.message ?? "(no reply)";
|
|
1260
|
+
messages.push({ role: "assistant", content: reply });
|
|
1261
|
+
console.log("\n" + import_chalk10.default.cyan("agent \u203A ") + reply + "\n");
|
|
1262
|
+
if (data.proposal) {
|
|
1263
|
+
ctx.proposal = data.proposal;
|
|
1264
|
+
const summary = summarizeProposal?.(data);
|
|
1265
|
+
if (summary) console.log(import_chalk10.default.yellow(" " + summary));
|
|
1266
|
+
const ready = commands.filter((c) => c.needsProposal).map((c) => `/${c.name}`).join(" or ");
|
|
1267
|
+
if (ready) console.log(import_chalk10.default.dim(` Ready \u2014 ${ready} to make it official, or keep refining.`));
|
|
1268
|
+
console.log();
|
|
1269
|
+
}
|
|
1270
|
+
const llm = data.llm;
|
|
1271
|
+
if (llm) {
|
|
1272
|
+
const credits = data.credits_remaining;
|
|
1273
|
+
const left = typeof credits === "number" ? ` \xB7 $${(credits / 100).toFixed(2)} credit left` : "";
|
|
1274
|
+
console.log(import_chalk10.default.dim(` ~$${(llm.cost_estimate ?? 0).toFixed(4)} \xB7 ${llm.model ?? "?"}${left}`));
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
const rl = import_readline.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
1278
|
+
const ask = (q) => new Promise((res) => rl.question(q, res));
|
|
1279
|
+
await turn(seedPrompt);
|
|
1280
|
+
for (; ; ) {
|
|
1281
|
+
console.log(footer());
|
|
1282
|
+
const line = (await ask(import_chalk10.default.green("you \u203A "))).trim();
|
|
1283
|
+
if (!line) continue;
|
|
1284
|
+
if (line === "/drop" || line === "/exit" || line === "/quit") {
|
|
1285
|
+
console.log(import_chalk10.default.dim(" Dropped \u2014 nothing applied.\n"));
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
if (line === "/show") {
|
|
1289
|
+
if (!ctx.proposal) console.log(import_chalk10.default.dim(" No proposal yet \u2014 keep chatting until the agent proposes one.\n"));
|
|
1290
|
+
else console.log("\n" + JSON.stringify(ctx.proposal, null, 2) + "\n");
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (line.startsWith("/")) {
|
|
1294
|
+
const cmd = cmdByName.get(line.slice(1));
|
|
1295
|
+
if (!cmd) {
|
|
1296
|
+
console.log(import_chalk10.default.red(` Unknown command "${line}". Try one of: ${commands.map((c) => "/" + c.name).join(", ")}, /show, /drop
|
|
1297
|
+
`));
|
|
1298
|
+
continue;
|
|
1299
|
+
}
|
|
1300
|
+
if (cmd.needsProposal && !ctx.proposal) {
|
|
1301
|
+
console.log(import_chalk10.default.yellow(" No proposal yet \u2014 keep chatting until the agent generates one, then try again.\n"));
|
|
1302
|
+
continue;
|
|
1303
|
+
}
|
|
1304
|
+
await cmd.run(ctx);
|
|
1305
|
+
continue;
|
|
1306
|
+
}
|
|
1307
|
+
await turn(line);
|
|
1308
|
+
}
|
|
1309
|
+
rl.close();
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// src/commands/authz.ts
|
|
1313
|
+
async function runAuthz(projectArg, apiVersionArg) {
|
|
1314
|
+
const creds = loadCredentials();
|
|
1315
|
+
if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
|
|
1316
|
+
const teamId = creds.teamId ?? "";
|
|
1317
|
+
const projects = await getProjects(teamId);
|
|
1318
|
+
const match = projects.find((p) => p.projectId === projectArg || p.projectName === projectArg);
|
|
1319
|
+
if (!match) {
|
|
1320
|
+
throw new Error(`Project "${projectArg}" not found in your active team (${creds.teamName ?? teamId}). Run \`apiblaze projects\`.`);
|
|
1321
|
+
}
|
|
1322
|
+
const projectId = match.projectId;
|
|
1323
|
+
const apiVersion = apiVersionArg || match.apiVersion;
|
|
1324
|
+
const tenant = match.tenant || projectId;
|
|
1325
|
+
async function apply(ctx, enable) {
|
|
1326
|
+
const proposal = ctx.proposal;
|
|
1327
|
+
if (!enable) {
|
|
1328
|
+
const m = await agentCall(`/projects/${projectId}/${apiVersion}/policies/model?tenantId=${encodeURIComponent(tenant)}`, "POST", proposal.model);
|
|
1329
|
+
if (m.status === 404) {
|
|
1330
|
+
ctx.log(import_chalk11.default.red(" Authorization store not provisioned yet. Open the dashboard Authorization tab for this project once (it auto-provisions the store), then re-run.\n"));
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (m.status >= 400) {
|
|
1334
|
+
ctx.log(import_chalk11.default.red(` Model save failed (${m.status}): ${m.data?.error ?? ""}
|
|
1335
|
+
`));
|
|
1336
|
+
return;
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
let ok = 0;
|
|
1340
|
+
let fail4 = 0;
|
|
1341
|
+
for (const r of proposal.routes) {
|
|
1342
|
+
const resource = "/" + String(r.resource || "").replace(/^\/+/, "");
|
|
1343
|
+
const policiesBody = {
|
|
1344
|
+
rule_mode: r.rule_mode === "list" ? "list" : "check-write",
|
|
1345
|
+
on_request_read: Array.isArray(r.on_request_read) ? r.on_request_read : [],
|
|
1346
|
+
post_response_write: Array.isArray(r.post_response_write) ? r.post_response_write : [],
|
|
1347
|
+
list_objects_read: r.list_objects_read ?? null,
|
|
1348
|
+
authentication_config: { require_authentication: true },
|
|
1349
|
+
authorization_enabled: enable
|
|
1350
|
+
};
|
|
1351
|
+
const encoded = resource.replace(/\{/g, "%7B").replace(/\}/g, "%7D");
|
|
1352
|
+
const putPath = `/projects/${projectId}/${apiVersion}/policies/route/${r.method.toUpperCase()}${encoded}`;
|
|
1353
|
+
let res = await agentCall(putPath, "PUT", { ...policiesBody, resource });
|
|
1354
|
+
if (res.status === 404) {
|
|
1355
|
+
res = await agentCall(`/projects/${projectId}/${apiVersion}/policies/route`, "POST", { method: r.method.toUpperCase(), resource, ...policiesBody });
|
|
1356
|
+
}
|
|
1357
|
+
if (res.status >= 200 && res.status < 300) ok++;
|
|
1358
|
+
else fail4++;
|
|
1359
|
+
}
|
|
1360
|
+
if (enable) {
|
|
1361
|
+
const e = await agentCall(`/${projectId}/${apiVersion}/config`, "PATCH", { authorization: { enforce_authorization: true }, tenant });
|
|
1362
|
+
if (e.status >= 400) {
|
|
1363
|
+
ctx.log(import_chalk11.default.red(` Enforced ${ok} route(s) but turning on the project-level switch failed (${e.status}). Toggle "Enforce Authorization" on in the dashboard.
|
|
1364
|
+
`));
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
ctx.log(import_chalk11.default.green(` \u2713 Enforcement ON \u2014 ${ok} route(s) + project switch on${fail4 ? `, ${fail4} failed` : ""}.
|
|
1368
|
+
`));
|
|
1369
|
+
} else {
|
|
1370
|
+
ctx.log(import_chalk11.default.green(` \u2713 Published (shadow): model + ${ok} route(s)${fail4 ? `, ${fail4} failed` : ""}. Review, then /enable.
|
|
1371
|
+
`));
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
await runAgentChatRepl({
|
|
1375
|
+
title: `Authorization assistant \u2014 ${projectId} ${apiVersion}`,
|
|
1376
|
+
subtitle: `tenant ${tenant} \xB7 discuss what authorization fits this API, then make it official.`,
|
|
1377
|
+
endpoint: `/projects/${projectId}/${apiVersion}/authz/chat`,
|
|
1378
|
+
buildBody: () => ({ included_sample_ids: [], existing_model: null, existing_routes: [] }),
|
|
1379
|
+
seedPrompt: "Analyze this API and tell me what authorization is feasible. If it is read-only, say so plainly. Then propose options \u2014 do not generate rules yet.",
|
|
1380
|
+
summarizeProposal: (data) => {
|
|
1381
|
+
const proposal = data.proposal;
|
|
1382
|
+
const sim = data.simulation;
|
|
1383
|
+
const cov = sim ? `, ${sim.routes_covered ?? 0} covered by rules` : "";
|
|
1384
|
+
const warn = sim?.blocked_without_backfill ? ` \u26A0 ${sim.blocked_without_backfill} currently-OK request(s) would be denied until tuples exist.` : "";
|
|
1385
|
+
return `Proposal ready: ${proposal.routes.length} route(s)${cov}.${warn}`;
|
|
1386
|
+
},
|
|
1387
|
+
commands: [
|
|
1388
|
+
{ name: "publish", describe: "save in shadow mode", needsProposal: true, run: (ctx) => apply(ctx, false) },
|
|
1389
|
+
{ name: "enable", describe: "turn on enforcement", needsProposal: true, run: (ctx) => apply(ctx, true) }
|
|
1390
|
+
]
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// src/commands/openapi.ts
|
|
1395
|
+
var import_chalk12 = __toESM(require("chalk"));
|
|
1396
|
+
init_auth();
|
|
1397
|
+
init_api();
|
|
1398
|
+
async function runOpenapi(projectArg, apiVersionArg) {
|
|
1399
|
+
const creds = loadCredentials();
|
|
1400
|
+
if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
|
|
1401
|
+
const projects = await getProjects(creds.teamId ?? "");
|
|
1402
|
+
const match = projects.find((p) => p.projectId === projectArg || p.projectName === projectArg);
|
|
1403
|
+
if (!match) throw new Error(`Project "${projectArg}" not found in your active team. Run \`apiblaze projects\`.`);
|
|
1404
|
+
const projectId = match.projectId;
|
|
1405
|
+
const apiVersion = apiVersionArg || match.apiVersion;
|
|
1406
|
+
console.log(import_chalk12.default.dim("Gathering captured traffic samples\u2026"));
|
|
1407
|
+
const routesRes = await agentCall(`/projects/${projectId}/${apiVersion}/samples/routes`, "GET");
|
|
1408
|
+
if (routesRes.status >= 400) {
|
|
1409
|
+
console.log(import_chalk12.default.red(`Could not list traffic (${routesRes.status}): ${routesRes.data?.error ?? ""}`));
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const routes = routesRes.data?.routes ?? (Array.isArray(routesRes.data) ? routesRes.data : []);
|
|
1413
|
+
const hashes = routes.map((r) => r.route_hash).filter((h) => !!h);
|
|
1414
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1415
|
+
for (const h of hashes) {
|
|
1416
|
+
const s = await agentCall(`/projects/${projectId}/${apiVersion}/samples/routes/${encodeURIComponent(h)}/samples`, "GET");
|
|
1417
|
+
const arr = s.data?.samples ?? [];
|
|
1418
|
+
for (const x of arr) if (x.sample_id) ids.add(x.sample_id);
|
|
1419
|
+
}
|
|
1420
|
+
const sampleIds = [...ids];
|
|
1421
|
+
if (sampleIds.length === 0) {
|
|
1422
|
+
console.log(import_chalk12.default.yellow("No captured traffic samples found. Hit the dev environment of this proxy to capture some traces, then retry."));
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
async function publish(ctx) {
|
|
1426
|
+
const patch = ctx.proposal.patch;
|
|
1427
|
+
if (!Array.isArray(patch) || patch.length === 0) {
|
|
1428
|
+
ctx.log(import_chalk12.default.green(" Nothing to publish \u2014 the spec already covers the observed traffic.\n"));
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
const specSource = ctx.lastData?.spec_source ?? "unknown";
|
|
1432
|
+
const endpoint = specSource === "github" ? "open-pr" : "publish-openapi";
|
|
1433
|
+
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/samples/${endpoint}`, "POST", { patch, sample_ids_used: sampleIds });
|
|
1434
|
+
if (pub.status >= 400) {
|
|
1435
|
+
ctx.log(import_chalk12.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
1436
|
+
`));
|
|
1437
|
+
return;
|
|
1438
|
+
}
|
|
1439
|
+
const prUrl = pub.data?.pr_url;
|
|
1440
|
+
ctx.log(import_chalk12.default.green(prUrl ? ` \u2713 Pull request opened: ${prUrl}
|
|
1441
|
+
` : " \u2713 Published updated OpenAPI spec.\n"));
|
|
1442
|
+
}
|
|
1443
|
+
await runAgentChatRepl({
|
|
1444
|
+
title: `API-docs assistant \u2014 ${projectId} ${apiVersion}`,
|
|
1445
|
+
subtitle: `${sampleIds.length} captured sample(s) \xB7 discuss what to document, then make it official.`,
|
|
1446
|
+
endpoint: `/projects/${projectId}/${apiVersion}/openapi/chat`,
|
|
1447
|
+
buildBody: () => ({ included_sample_ids: sampleIds }),
|
|
1448
|
+
seedPrompt: "Compare the captured samples against the current spec and tell me what's missing or wrong. List the changes you'd make \u2014 don't produce a patch yet.",
|
|
1449
|
+
summarizeProposal: (data) => {
|
|
1450
|
+
const patch = data.proposal.patch;
|
|
1451
|
+
const n = Array.isArray(patch) ? patch.length : 0;
|
|
1452
|
+
return n === 0 ? "The spec already covers the observed traffic \u2014 nothing to publish." : `Patch ready: ${n} additive change(s) to the OpenAPI spec.`;
|
|
1453
|
+
},
|
|
1454
|
+
commands: [
|
|
1455
|
+
{ name: "publish", describe: "publish / open PR", needsProposal: true, run: publish }
|
|
1456
|
+
]
|
|
1457
|
+
});
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// src/commands/mcp.ts
|
|
1461
|
+
var import_chalk13 = __toESM(require("chalk"));
|
|
1462
|
+
init_auth();
|
|
1463
|
+
init_api();
|
|
1464
|
+
async function runMcp(projectArg, apiVersionArg, opts) {
|
|
1465
|
+
const creds = loadCredentials();
|
|
1466
|
+
if (!creds) throw new Error("Not authenticated. Run `apiblaze login` first.");
|
|
1467
|
+
const projects = await getProjects(creds.teamId ?? "");
|
|
1468
|
+
const match = projects.find((p) => p.projectId === projectArg || p.projectName === projectArg);
|
|
1469
|
+
if (!match) throw new Error(`Project "${projectArg}" not found in your active team. Run \`apiblaze projects\`.`);
|
|
1470
|
+
const projectId = match.projectId;
|
|
1471
|
+
const apiVersion = apiVersionArg || match.apiVersion;
|
|
1472
|
+
const environment = opts.environment || "prod";
|
|
1473
|
+
async function publish(ctx) {
|
|
1474
|
+
const spec = ctx.proposal;
|
|
1475
|
+
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/mcp/spec`, "PUT", { environment, spec });
|
|
1476
|
+
if (pub.status >= 400) {
|
|
1477
|
+
ctx.log(import_chalk13.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
1478
|
+
`));
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
ctx.log(import_chalk13.default.green(` \u2713 Published MCP server \u2014 ${projectId}.mcp.apiblaze.com/${apiVersion}/${environment}.
|
|
1482
|
+
`));
|
|
1483
|
+
}
|
|
1484
|
+
await runAgentChatRepl({
|
|
1485
|
+
title: `MCP builder \u2014 ${projectId} ${apiVersion} (${environment})`,
|
|
1486
|
+
subtitle: "discuss which routes to expose as tools, then make the catalogue official.",
|
|
1487
|
+
endpoint: `/projects/${projectId}/${apiVersion}/mcp/chat`,
|
|
1488
|
+
buildBody: () => ({ environment, included_sample_ids: [] }),
|
|
1489
|
+
seedPrompt: "Look at this API's routes and recommend which should become MCP tools, with good names and descriptions. Don't finalize yet \u2014 explain first.",
|
|
1490
|
+
summarizeProposal: (data) => {
|
|
1491
|
+
const spec = data.proposal;
|
|
1492
|
+
const tools = Array.isArray(spec.tools) ? spec.tools : [];
|
|
1493
|
+
const names = tools.slice(0, 12).map((t) => t.name ?? "(unnamed)").join(", ");
|
|
1494
|
+
return `Catalogue ready: ${tools.length} tool(s)${names ? ` \u2014 ${names}${tools.length > 12 ? ", \u2026" : ""}` : ""}.`;
|
|
1495
|
+
},
|
|
1496
|
+
commands: [
|
|
1497
|
+
{ name: "publish", describe: `to ${projectId}.mcp.apiblaze.com`, needsProposal: true, run: publish }
|
|
1498
|
+
]
|
|
1499
|
+
});
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1203
1502
|
// src/index.ts
|
|
1204
1503
|
var program = new import_commander.Command();
|
|
1205
1504
|
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version);
|
|
@@ -1259,11 +1558,35 @@ program.command("projects").description("List the projects in your team").action
|
|
|
1259
1558
|
process.exit(1);
|
|
1260
1559
|
}
|
|
1261
1560
|
});
|
|
1561
|
+
program.command("authz").description("Design API authorization interactively (chat), then publish + enable it").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[apiVersion]", "API version (defaults to the project's version)").action(async (project, apiVersion) => {
|
|
1562
|
+
try {
|
|
1563
|
+
await runAuthz(project, apiVersion);
|
|
1564
|
+
} catch (err) {
|
|
1565
|
+
printError(err);
|
|
1566
|
+
process.exit(1);
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
program.command("openapi").description("Design your OpenAPI spec from captured traffic interactively (chat), then publish it").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[apiVersion]", "API version (defaults to the project's version)").action(async (project, apiVersion) => {
|
|
1570
|
+
try {
|
|
1571
|
+
await runOpenapi(project, apiVersion);
|
|
1572
|
+
} catch (err) {
|
|
1573
|
+
printError(err);
|
|
1574
|
+
process.exit(1);
|
|
1575
|
+
}
|
|
1576
|
+
});
|
|
1577
|
+
program.command("mcp").description("Design an MCP server from the spec + traffic interactively (chat), then publish it").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[apiVersion]", "API version (defaults to the project's version)").option("--environment <env>", "Environment to publish (default: prod)").action(async (project, apiVersion, opts) => {
|
|
1578
|
+
try {
|
|
1579
|
+
await runMcp(project, apiVersion, opts);
|
|
1580
|
+
} catch (err) {
|
|
1581
|
+
printError(err);
|
|
1582
|
+
process.exit(1);
|
|
1583
|
+
}
|
|
1584
|
+
});
|
|
1262
1585
|
program.command("dev").description("Start a dev tunnel for your localhost projects").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").action(async (port, opts) => {
|
|
1263
1586
|
try {
|
|
1264
1587
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
1265
1588
|
if (Number.isNaN(resolved)) {
|
|
1266
|
-
console.error(
|
|
1589
|
+
console.error(import_chalk14.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
1267
1590
|
process.exit(1);
|
|
1268
1591
|
}
|
|
1269
1592
|
await runDev({ port: resolved });
|
|
@@ -1296,13 +1619,13 @@ Examples:
|
|
|
1296
1619
|
);
|
|
1297
1620
|
function printError(err) {
|
|
1298
1621
|
if (err instanceof ApiError) {
|
|
1299
|
-
console.error(
|
|
1622
|
+
console.error(import_chalk14.default.red(`
|
|
1300
1623
|
API error (${err.status}): ${err.message}`));
|
|
1301
1624
|
} else if (err instanceof Error) {
|
|
1302
|
-
console.error(
|
|
1625
|
+
console.error(import_chalk14.default.red(`
|
|
1303
1626
|
Error: ${err.message}`));
|
|
1304
1627
|
} else {
|
|
1305
|
-
console.error(
|
|
1628
|
+
console.error(import_chalk14.default.red("\nUnknown error"));
|
|
1306
1629
|
}
|
|
1307
1630
|
}
|
|
1308
1631
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "apiblaze",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Dev tunnel CLI for APIblaze — route localhost projects through your APIblaze endpoints",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"apiblaze",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"build:watch": "tsup --watch",
|
|
28
28
|
"typecheck": "tsc --noEmit",
|
|
29
29
|
"prepublishOnly": "npm run build && npm run typecheck",
|
|
30
|
-
"release": "bash
|
|
30
|
+
"release": "bash release.sh"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"chalk": "^4.1.2",
|