apiblaze 0.4.1 → 0.4.4
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 -24
- package/dist/index.js +502 -122
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -53,7 +53,7 @@ apiblaze throttle myapi --rate 50 --verbose
|
|
|
53
53
|
|
|
54
54
|
## Commands
|
|
55
55
|
|
|
56
|
-
### Chat
|
|
56
|
+
### Chat
|
|
57
57
|
|
|
58
58
|
| Command | What it does |
|
|
59
59
|
|---|---|
|
|
@@ -64,19 +64,32 @@ apiblaze throttle myapi --rate 50 --verbose
|
|
|
64
64
|
|
|
65
65
|
Every chat turn shows its cost.
|
|
66
66
|
|
|
67
|
-
###
|
|
67
|
+
### Setup
|
|
68
68
|
|
|
69
69
|
| Command | What it does |
|
|
70
70
|
|---|---|
|
|
71
71
|
| `apiblaze create --target <url>` | Make an API from a backend (no account needed) |
|
|
72
72
|
| `apiblaze dev [port]` | Put your localhost behind a public URL |
|
|
73
|
-
| `apiblaze login` / `logout` | Sign in / out |
|
|
74
|
-
| `apiblaze whoami` | Who am I
|
|
73
|
+
| `apiblaze login` / `logout` | Sign in / out (logout asks producer or consumer) |
|
|
74
|
+
| `apiblaze whoami` | Who am I — both Producer and Consumer |
|
|
75
75
|
| `apiblaze team [name]` | Switch team |
|
|
76
|
-
| `apiblaze projects` | List your APIs |
|
|
77
76
|
| `apiblaze claim [code]` | Claim an API you made before signing in |
|
|
78
77
|
|
|
79
|
-
###
|
|
78
|
+
### Producer — your APIs
|
|
79
|
+
|
|
80
|
+
| Command | What it does |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `apiblaze projects` | List your APIs |
|
|
83
|
+
| `apiblaze delete <project>` | Delete it and everything under it (asks first) |
|
|
84
|
+
| `apiblaze domain add <project> --domain <host>` | Add your own domain (shows the DNS records to set) |
|
|
85
|
+
| `apiblaze domain status / list / rm <project>` | Check / list / remove custom domains |
|
|
86
|
+
| `apiblaze domain set-base <project> [--env <e>]` | Pick which version/env your main URL serves |
|
|
87
|
+
| `apiblaze tenant create --name <name>` | Create a tenant (a separate group of your API's users) |
|
|
88
|
+
| `apiblaze tenant attach <project> --tenant <slug>` | Give a proxy its own set of users |
|
|
89
|
+
| `apiblaze tenant cors --tenant <slug> --origins <a,b>` | Set which websites can call it |
|
|
90
|
+
| `apiblaze tenant list / delete <slug>` | List / delete tenants |
|
|
91
|
+
|
|
92
|
+
### Producer — change a proxy's config
|
|
80
93
|
|
|
81
94
|
| Command | What it does |
|
|
82
95
|
|---|---|
|
|
@@ -85,30 +98,26 @@ Every chat turn shows its cost.
|
|
|
85
98
|
| `apiblaze rename <project> --display-name <name>` | Rename it |
|
|
86
99
|
| `apiblaze spec get <project>` | Print its OpenAPI spec |
|
|
87
100
|
| `apiblaze spec set <project> --file <path>` | Replace its OpenAPI spec from a file |
|
|
88
|
-
| `apiblaze delete <project>` | Delete it and everything under it (asks first) |
|
|
89
101
|
|
|
90
|
-
|
|
102
|
+
> Every command in these two sections accepts `--verbose` (see the equivalent API
|
|
103
|
+
> calls), plus `--team`, `--apiversion`, and `--json`.
|
|
91
104
|
|
|
92
|
-
|
|
93
|
-
|---|---|
|
|
94
|
-
| `apiblaze domain add <project> --domain <host>` | Add your domain (shows the DNS records to set) |
|
|
95
|
-
| `apiblaze domain status <project> --id <id>` | Check if it's verified yet |
|
|
96
|
-
| `apiblaze domain list <project>` / `rm <project> --id <id>` | List / remove |
|
|
97
|
-
| `apiblaze domain set-base <project> [--env <e>]` | Pick which version/env your main URL serves |
|
|
105
|
+
### Consumer — use an API
|
|
98
106
|
|
|
99
|
-
|
|
107
|
+
Log in to one of your tenants' portals **as a consumer** — handy for testing auth
|
|
108
|
+
and keys end-to-end. This is a separate identity from your producer login; `whoami`
|
|
109
|
+
shows both, and `logout` asks which to drop.
|
|
100
110
|
|
|
101
111
|
| Command | What it does |
|
|
102
112
|
|---|---|
|
|
103
|
-
| `apiblaze
|
|
104
|
-
| `apiblaze
|
|
105
|
-
| `apiblaze
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
>
|
|
111
|
-
> commands take `--team`, `--apiversion`, and `--json`.
|
|
113
|
+
| `apiblaze consumer login` | Pick a tenant and log in to its portal (device flow in your browser) |
|
|
114
|
+
| `apiblaze consumer tokens` | Show your consumer access / refresh / id tokens |
|
|
115
|
+
| `apiblaze consumer apikeys` | List your API keys (reveals expiring ones in clear), then offer to create one |
|
|
116
|
+
|
|
117
|
+
> A standalone consumer (no producer account) can log in with
|
|
118
|
+
> `apiblaze consumer login --tenant <slug> --client <appClientId>`.
|
|
119
|
+
>
|
|
120
|
+
> Advanced: `apiblaze apikeys` manages producer control-plane keys for scripting.
|
|
112
121
|
|
|
113
122
|
## How it works
|
|
114
123
|
|
package/dist/index.js
CHANGED
|
@@ -8,9 +8,9 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
9
|
var __copyProps = (to, from, except, desc) => {
|
|
10
10
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let
|
|
12
|
-
if (!__hasOwnProp.call(to,
|
|
13
|
-
__defProp(to,
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
14
|
}
|
|
15
15
|
return to;
|
|
16
16
|
};
|
|
@@ -25,10 +25,10 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/index.ts
|
|
27
27
|
var import_commander = require("commander");
|
|
28
|
-
var
|
|
28
|
+
var import_chalk26 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// package.json
|
|
31
|
-
var version = "0.4.
|
|
31
|
+
var version = "0.4.4";
|
|
32
32
|
|
|
33
33
|
// src/types.ts
|
|
34
34
|
var ApiError = class extends Error {
|
|
@@ -105,9 +105,9 @@ async function createProxyAnonymous(body) {
|
|
|
105
105
|
}
|
|
106
106
|
return res.json();
|
|
107
107
|
}
|
|
108
|
-
async function apiFetch(
|
|
108
|
+
async function apiFetch(path3, options = {}) {
|
|
109
109
|
const token = getAccessToken();
|
|
110
|
-
const url = `${DASHBOARD_BASE}${
|
|
110
|
+
const url = `${DASHBOARD_BASE}${path3}`;
|
|
111
111
|
const res = await fetch(url, {
|
|
112
112
|
...options,
|
|
113
113
|
headers: {
|
|
@@ -132,12 +132,12 @@ async function apiFetch(path2, options = {}) {
|
|
|
132
132
|
}
|
|
133
133
|
return res.json();
|
|
134
134
|
}
|
|
135
|
-
async function agentCall(
|
|
135
|
+
async function agentCall(path3, method, body) {
|
|
136
136
|
const token = getAccessToken();
|
|
137
137
|
const res = await fetch(`${DASHBOARD_BASE}/api/cli/agents`, {
|
|
138
138
|
method: "POST",
|
|
139
139
|
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
140
|
-
body: JSON.stringify({ path:
|
|
140
|
+
body: JSON.stringify({ path: path3, method, body })
|
|
141
141
|
});
|
|
142
142
|
let data = null;
|
|
143
143
|
try {
|
|
@@ -420,11 +420,11 @@ function decodeJwt(token) {
|
|
|
420
420
|
return null;
|
|
421
421
|
}
|
|
422
422
|
}
|
|
423
|
-
function maskPath(
|
|
424
|
-
const q =
|
|
425
|
-
if (q < 0) return
|
|
426
|
-
const base =
|
|
427
|
-
const query =
|
|
423
|
+
function maskPath(path3) {
|
|
424
|
+
const q = path3.indexOf("?");
|
|
425
|
+
if (q < 0) return path3;
|
|
426
|
+
const base = path3.slice(0, q);
|
|
427
|
+
const query = path3.slice(q + 1);
|
|
428
428
|
const masked = query.split("&").map((pair) => {
|
|
429
429
|
const eq = pair.indexOf("=");
|
|
430
430
|
if (eq < 0) return pair;
|
|
@@ -586,8 +586,8 @@ var STRIP_HEADERS = /* @__PURE__ */ new Set([
|
|
|
586
586
|
]);
|
|
587
587
|
function stripHeaders(headers) {
|
|
588
588
|
const out = {};
|
|
589
|
-
for (const
|
|
590
|
-
if (!STRIP_HEADERS.has(
|
|
589
|
+
for (const key of Object.keys(headers)) {
|
|
590
|
+
if (!STRIP_HEADERS.has(key.toLowerCase())) out[key] = headers[key];
|
|
591
591
|
}
|
|
592
592
|
return out;
|
|
593
593
|
}
|
|
@@ -702,8 +702,8 @@ function startTunnelClient(opts) {
|
|
|
702
702
|
const status = resp.status;
|
|
703
703
|
const buf = Buffer.from(await resp.arrayBuffer());
|
|
704
704
|
const headers = {};
|
|
705
|
-
resp.headers.forEach((value,
|
|
706
|
-
if (!STRIP_HEADERS.has(
|
|
705
|
+
resp.headers.forEach((value, key) => {
|
|
706
|
+
if (!STRIP_HEADERS.has(key.toLowerCase())) headers[key] = value;
|
|
707
707
|
});
|
|
708
708
|
if (capturing) {
|
|
709
709
|
capturing = false;
|
|
@@ -822,9 +822,9 @@ async function offerAutoCreate(teamId, port) {
|
|
|
822
822
|
throw err;
|
|
823
823
|
}
|
|
824
824
|
if (auth === "api_key") {
|
|
825
|
-
const
|
|
826
|
-
if (
|
|
827
|
-
console.log(` ${import_chalk4.default.dim("API key (dev):")} ${import_chalk4.default.bold.green(
|
|
825
|
+
const key = result.api_keys?.dev ?? Object.values(result.api_keys ?? {})[0];
|
|
826
|
+
if (key) {
|
|
827
|
+
console.log(` ${import_chalk4.default.dim("API key (dev):")} ${import_chalk4.default.bold.green(key)}`);
|
|
828
828
|
console.log(import_chalk4.default.dim(" Send it as the X-API-Key header. It may not be shown again."));
|
|
829
829
|
}
|
|
830
830
|
}
|
|
@@ -1104,14 +1104,23 @@ function fail(message) {
|
|
|
1104
1104
|
console.error(import_chalk6.default.red(`Error: ${message}`));
|
|
1105
1105
|
process.exit(1);
|
|
1106
1106
|
}
|
|
1107
|
-
function
|
|
1107
|
+
function buildTryItCurl(url, authType, apiKey) {
|
|
1108
|
+
if (authType === "api_key") {
|
|
1109
|
+
if (!apiKey) return null;
|
|
1110
|
+
return `curl ${url} -H "X-API-Key: ${apiKey}"`;
|
|
1111
|
+
}
|
|
1112
|
+
if (authType === "none") return `curl ${url}`;
|
|
1113
|
+
return null;
|
|
1114
|
+
}
|
|
1115
|
+
function printCurlExample(url, authType, apiKey, devPortal) {
|
|
1116
|
+
const curl = buildTryItCurl(url, authType, apiKey);
|
|
1108
1117
|
console.log();
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
console.log(` ${import_chalk6.default.cyan(
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
console.log(`
|
|
1118
|
+
if (curl) {
|
|
1119
|
+
console.log(` ${import_chalk6.default.dim("Try it \u2014 copy/paste:")}`);
|
|
1120
|
+
console.log(` ${import_chalk6.default.cyan(curl)}`);
|
|
1121
|
+
} else if (authType === "oauth") {
|
|
1122
|
+
console.log(` ${import_chalk6.default.dim("Try it:")} this proxy uses OAuth \u2014 sign in at ${import_chalk6.default.bold(devPortal ?? "the dev portal")} to get a token,`);
|
|
1123
|
+
console.log(` ${import_chalk6.default.dim(`then call ${url} with`)} ${import_chalk6.default.cyan('-H "Authorization: Bearer <token>"')}`);
|
|
1115
1124
|
}
|
|
1116
1125
|
}
|
|
1117
1126
|
var VALID_AUTH = ["api_key", "none", "oauth"];
|
|
@@ -1259,7 +1268,7 @@ async function runCreate(opts = {}) {
|
|
|
1259
1268
|
console.log(import_chalk6.default.dim(` (Separate keys were also created for: ${otherEnvs.join(", ")}.)`));
|
|
1260
1269
|
}
|
|
1261
1270
|
}
|
|
1262
|
-
printCurlExample(proxyUrl, auth
|
|
1271
|
+
printCurlExample(proxyUrl, auth, adminKey, devPortal);
|
|
1263
1272
|
console.log();
|
|
1264
1273
|
}
|
|
1265
1274
|
async function runAnonymousCreate(opts) {
|
|
@@ -1370,7 +1379,7 @@ async function runAnonymousCreate(opts) {
|
|
|
1370
1379
|
console.log(` ${import_chalk6.default.bold.green(apiKey)}`);
|
|
1371
1380
|
console.log(import_chalk6.default.dim("\n Save this now \u2014 send it as the X-API-Key header. It may not be shown again."));
|
|
1372
1381
|
}
|
|
1373
|
-
if (prodEndpoint) printCurlExample(prodEndpoint, apiKey);
|
|
1382
|
+
if (prodEndpoint) printCurlExample(prodEndpoint, opts.auth || "api_key", apiKey, result.portal);
|
|
1374
1383
|
if (result.claim_url) {
|
|
1375
1384
|
console.log();
|
|
1376
1385
|
console.log(` ${import_chalk6.default.yellow("\u26A0 Anonymous proxy \u2014 claim it to your account within 30 days or it expires:")}`);
|
|
@@ -1452,51 +1461,204 @@ async function runClaim(claimCodeArg, opts) {
|
|
|
1452
1461
|
|
|
1453
1462
|
// src/commands/logout.ts
|
|
1454
1463
|
var import_chalk8 = __toESM(require("chalk"));
|
|
1455
|
-
|
|
1464
|
+
|
|
1465
|
+
// src/lib/consumer-auth.ts
|
|
1466
|
+
var fs4 = __toESM(require("fs"));
|
|
1467
|
+
var os2 = __toESM(require("os"));
|
|
1468
|
+
var path2 = __toESM(require("path"));
|
|
1469
|
+
var crypto = __toESM(require("crypto"));
|
|
1470
|
+
var import_child_process = require("child_process");
|
|
1471
|
+
var AUTH_BASE = process.env.APIBLAZE_AUTH_BASE || "https://auth.apiblaze.com";
|
|
1472
|
+
var APIBLAZE_DIR2 = path2.join(os2.homedir(), ".apiblaze");
|
|
1473
|
+
var CONSUMER_PATH = path2.join(APIBLAZE_DIR2, "consumer.json");
|
|
1474
|
+
var DEVICE_GRANT = "urn:ietf:params:oauth:grant-type:device_code";
|
|
1475
|
+
function loadConsumer() {
|
|
1476
|
+
try {
|
|
1477
|
+
return JSON.parse(fs4.readFileSync(CONSUMER_PATH, "utf-8"));
|
|
1478
|
+
} catch {
|
|
1479
|
+
return null;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function saveConsumer(creds) {
|
|
1483
|
+
fs4.mkdirSync(APIBLAZE_DIR2, { recursive: true });
|
|
1484
|
+
fs4.writeFileSync(CONSUMER_PATH, JSON.stringify(creds, null, 2), "utf-8");
|
|
1485
|
+
fs4.chmodSync(CONSUMER_PATH, 384);
|
|
1486
|
+
}
|
|
1487
|
+
function clearConsumer() {
|
|
1488
|
+
try {
|
|
1489
|
+
fs4.unlinkSync(CONSUMER_PATH);
|
|
1490
|
+
return true;
|
|
1491
|
+
} catch {
|
|
1492
|
+
return false;
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
function b64url(buf) {
|
|
1496
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
1497
|
+
}
|
|
1498
|
+
function pkce() {
|
|
1499
|
+
const verifier = b64url(crypto.randomBytes(32));
|
|
1500
|
+
const challenge = b64url(crypto.createHash("sha256").update(verifier).digest());
|
|
1501
|
+
return { verifier, challenge };
|
|
1502
|
+
}
|
|
1503
|
+
function decodeJwt2(token) {
|
|
1504
|
+
const parts = token.split(".");
|
|
1505
|
+
if (parts.length < 2) return null;
|
|
1506
|
+
try {
|
|
1507
|
+
return JSON.parse(Buffer.from(parts[1], "base64url").toString());
|
|
1508
|
+
} catch {
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function openBrowser2(url) {
|
|
1513
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
1514
|
+
try {
|
|
1515
|
+
(0, import_child_process.spawn)(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
1516
|
+
} catch {
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
async function deviceLogin(clientId, scope, onPrompt) {
|
|
1520
|
+
const { verifier, challenge } = pkce();
|
|
1521
|
+
const startRes = await fetch(`${AUTH_BASE}/device_authorization`, {
|
|
1522
|
+
method: "POST",
|
|
1523
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1524
|
+
body: new URLSearchParams({ client_id: clientId, scope, code_challenge: challenge, code_challenge_method: "S256" })
|
|
1525
|
+
});
|
|
1526
|
+
const start = await startRes.json().catch(() => ({}));
|
|
1527
|
+
if (!startRes.ok) {
|
|
1528
|
+
throw new Error(`device_authorization failed (${startRes.status}): ${start.error_description ?? start.error ?? startRes.statusText}`);
|
|
1529
|
+
}
|
|
1530
|
+
const deviceCode = String(start.device_code);
|
|
1531
|
+
const userCode = String(start.user_code);
|
|
1532
|
+
const verificationUri = String(start.verification_uri_complete ?? start.verification_uri);
|
|
1533
|
+
const interval = Math.max(2, Number(start.interval) || 5);
|
|
1534
|
+
const expiresIn = Number(start.expires_in) || 900;
|
|
1535
|
+
onPrompt({ verificationUri, userCode });
|
|
1536
|
+
openBrowser2(verificationUri);
|
|
1537
|
+
const deadline = Date.now() + expiresIn * 1e3;
|
|
1538
|
+
while (true) {
|
|
1539
|
+
if (Date.now() > deadline) throw new Error("Login timed out. Run the command again.");
|
|
1540
|
+
await new Promise((r) => setTimeout(r, interval * 1e3));
|
|
1541
|
+
const tokRes = await fetch(`${AUTH_BASE}/token`, {
|
|
1542
|
+
method: "POST",
|
|
1543
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
1544
|
+
body: new URLSearchParams({ grant_type: DEVICE_GRANT, device_code: deviceCode, code_verifier: verifier })
|
|
1545
|
+
});
|
|
1546
|
+
const tok = await tokRes.json().catch(() => ({}));
|
|
1547
|
+
if (tokRes.ok && tok.access_token) {
|
|
1548
|
+
return {
|
|
1549
|
+
accessToken: String(tok.access_token),
|
|
1550
|
+
refreshToken: tok.refresh_token ? String(tok.refresh_token) : void 0,
|
|
1551
|
+
idToken: tok.id_token ? String(tok.id_token) : void 0,
|
|
1552
|
+
expiresIn: tok.expires_in ? Number(tok.expires_in) : void 0,
|
|
1553
|
+
scope: tok.scope ? String(tok.scope) : void 0
|
|
1554
|
+
};
|
|
1555
|
+
}
|
|
1556
|
+
if (tok.error === "authorization_pending" || tok.error === "slow_down") continue;
|
|
1557
|
+
throw new Error(`Login failed: ${tok.error_description ?? tok.error ?? tokRes.statusText}`);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
async function refreshConsumer(creds) {
|
|
1561
|
+
if (!creds.refreshToken) return null;
|
|
1562
|
+
const res = await fetch(`${AUTH_BASE}/refresh`, {
|
|
1563
|
+
method: "POST",
|
|
1564
|
+
headers: { "Content-Type": "application/json" },
|
|
1565
|
+
body: JSON.stringify({ refresh_token: creds.refreshToken })
|
|
1566
|
+
});
|
|
1567
|
+
const data = await res.json().catch(() => ({}));
|
|
1568
|
+
if (!res.ok || !data.access_token) return null;
|
|
1569
|
+
const updated = {
|
|
1570
|
+
...creds,
|
|
1571
|
+
accessToken: String(data.access_token),
|
|
1572
|
+
refreshToken: data.refresh_token ? String(data.refresh_token) : creds.refreshToken,
|
|
1573
|
+
idToken: data.id_token ? String(data.id_token) : creds.idToken,
|
|
1574
|
+
expiresAt: Date.now() + (Number(data.expires_in) || 3600) * 1e3,
|
|
1575
|
+
obtainedAt: Date.now()
|
|
1576
|
+
};
|
|
1577
|
+
saveConsumer(updated);
|
|
1578
|
+
return updated;
|
|
1579
|
+
}
|
|
1580
|
+
async function validConsumerToken(creds) {
|
|
1581
|
+
if (Date.now() < creds.expiresAt - 3e4) return creds;
|
|
1582
|
+
return refreshConsumer(creds);
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
// src/commands/logout.ts
|
|
1586
|
+
async function runLogout(opts = {}) {
|
|
1456
1587
|
const creds = loadCredentials();
|
|
1457
|
-
const
|
|
1458
|
-
if (!
|
|
1588
|
+
const consumer2 = loadConsumer();
|
|
1589
|
+
if (!creds && !consumer2) {
|
|
1459
1590
|
console.log(import_chalk8.default.yellow("You were not logged in."));
|
|
1460
1591
|
return;
|
|
1461
1592
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1593
|
+
let target;
|
|
1594
|
+
if (opts.all) target = "all";
|
|
1595
|
+
else if (opts.producer) target = "producer";
|
|
1596
|
+
else if (opts.consumer) target = "consumer";
|
|
1597
|
+
else if (creds && !consumer2) target = "producer";
|
|
1598
|
+
else if (consumer2 && !creds) target = "consumer";
|
|
1599
|
+
else {
|
|
1600
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
1601
|
+
const { chosen } = await inquirer2.prompt([
|
|
1602
|
+
{
|
|
1603
|
+
type: "list",
|
|
1604
|
+
name: "chosen",
|
|
1605
|
+
message: "Log out of which identity?",
|
|
1606
|
+
choices: [
|
|
1607
|
+
{ name: `API Producer (${creds?.githubHandle ?? creds?.email ?? "producer"})`, value: "producer" },
|
|
1608
|
+
{ name: `API Consumer (${consumer2?.email ?? consumer2?.tenant})`, value: "consumer" },
|
|
1609
|
+
{ name: "Both", value: "all" }
|
|
1610
|
+
]
|
|
1611
|
+
}
|
|
1612
|
+
]);
|
|
1613
|
+
target = chosen;
|
|
1614
|
+
}
|
|
1615
|
+
if (target === "producer" || target === "all") {
|
|
1616
|
+
clearCredentials();
|
|
1617
|
+
console.log(import_chalk8.default.green(`\u2714 Logged out of API Producer${creds?.githubHandle ? ` (${creds.githubHandle})` : ""}.`));
|
|
1618
|
+
}
|
|
1619
|
+
if (target === "consumer" || target === "all") {
|
|
1620
|
+
clearConsumer();
|
|
1621
|
+
console.log(import_chalk8.default.green(`\u2714 Logged out of API Consumer${consumer2?.tenant ? ` (${consumer2.tenant})` : ""}.`));
|
|
1622
|
+
}
|
|
1464
1623
|
}
|
|
1465
1624
|
|
|
1466
1625
|
// src/commands/whoami.ts
|
|
1467
1626
|
var import_chalk9 = __toESM(require("chalk"));
|
|
1468
1627
|
async function runWhoami(opts = {}) {
|
|
1469
1628
|
const creds = loadCredentials();
|
|
1470
|
-
|
|
1471
|
-
if (opts.json) {
|
|
1472
|
-
console.log(JSON.stringify({ loggedIn: false }, null, 2));
|
|
1473
|
-
} else {
|
|
1474
|
-
console.log(import_chalk9.default.yellow("Not logged in. Run `apiblaze login` first."));
|
|
1475
|
-
}
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
const expired = Date.now() >= creds.expiresAt;
|
|
1629
|
+
const consumer2 = loadConsumer();
|
|
1479
1630
|
if (opts.json) {
|
|
1480
1631
|
console.log(JSON.stringify({
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1632
|
+
producer: creds ? {
|
|
1633
|
+
loggedIn: true,
|
|
1634
|
+
expired: Date.now() >= creds.expiresAt,
|
|
1635
|
+
apiblazeUserId: creds.apiblazeUserId ?? null,
|
|
1636
|
+
githubHandle: creds.githubHandle ?? null,
|
|
1637
|
+
email: creds.email ?? null,
|
|
1638
|
+
teamId: creds.teamId ?? null,
|
|
1639
|
+
teamName: creds.teamName ?? null
|
|
1640
|
+
} : { loggedIn: false },
|
|
1641
|
+
consumer: consumer2 ? { loggedIn: true, expired: Date.now() >= consumer2.expiresAt, tenant: consumer2.tenant, email: consumer2.email ?? null } : { loggedIn: false }
|
|
1488
1642
|
}, null, 2));
|
|
1489
1643
|
return;
|
|
1490
1644
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
console.log(`
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
console.log(import_chalk9.default.yellow("\
|
|
1645
|
+
console.log(import_chalk9.default.bold("API Producer"));
|
|
1646
|
+
if (!creds) {
|
|
1647
|
+
console.log(import_chalk9.default.dim(" Not logged in. Run `apiblaze login`."));
|
|
1648
|
+
} else {
|
|
1649
|
+
const who = creds.githubHandle ?? creds.email ?? creds.apiblazeUserId ?? "unknown";
|
|
1650
|
+
console.log(` ${import_chalk9.default.cyan("Signed in as")} ${import_chalk9.default.bold(who)}`);
|
|
1651
|
+
if (creds.email && creds.email !== who) console.log(` Email: ${creds.email}`);
|
|
1652
|
+
if (creds.teamName || creds.teamId) console.log(` Team: ${import_chalk9.default.bold(creds.teamName ?? creds.teamId)}`);
|
|
1653
|
+
if (Date.now() >= creds.expiresAt) console.log(import_chalk9.default.yellow(" \u26A0 Session expired \u2014 run `apiblaze login`."));
|
|
1654
|
+
}
|
|
1655
|
+
console.log(import_chalk9.default.bold("\nAPI Consumer"));
|
|
1656
|
+
if (!consumer2) {
|
|
1657
|
+
console.log(import_chalk9.default.dim(" Not logged in. Run `apiblaze consumer login`."));
|
|
1658
|
+
} else {
|
|
1659
|
+
console.log(` ${import_chalk9.default.cyan("Signed in as")} ${import_chalk9.default.bold(consumer2.email ?? consumer2.tenant)}`);
|
|
1660
|
+
console.log(` Tenant: ${import_chalk9.default.bold(consumer2.tenant)}`);
|
|
1661
|
+
if (Date.now() >= consumer2.expiresAt) console.log(import_chalk9.default.yellow(" \u26A0 Token expired \u2014 it will refresh on next use, or re-run `apiblaze consumer login`."));
|
|
1500
1662
|
}
|
|
1501
1663
|
}
|
|
1502
1664
|
|
|
@@ -1831,6 +1993,7 @@ var import_chalk16 = __toESM(require("chalk"));
|
|
|
1831
1993
|
|
|
1832
1994
|
// src/lib/trace.ts
|
|
1833
1995
|
var import_chalk15 = __toESM(require("chalk"));
|
|
1996
|
+
var CONTROL_API_VERSION = "1.0.0";
|
|
1834
1997
|
var verbose = false;
|
|
1835
1998
|
var entries = [];
|
|
1836
1999
|
function setVerbose(v) {
|
|
@@ -1849,24 +2012,23 @@ function renderTrace() {
|
|
|
1849
2012
|
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
1850
2013
|
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
1851
2014
|
console.log(
|
|
1852
|
-
import_chalk15.default.dim("
|
|
2015
|
+
import_chalk15.default.dim("The same thing on the official API \u2014 copy/paste with your control-plane key\n(get one with `apiblaze apikeys`, then `export APIBLAZE_KEY=sk_...`):\n")
|
|
1853
2016
|
);
|
|
1854
2017
|
entries.forEach((e, i) => {
|
|
1855
2018
|
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
1856
2019
|
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
2020
|
+
const url = `https://api.apiblaze.com/${CONTROL_API_VERSION}/prod${e.path}`;
|
|
1857
2021
|
const masked = maskBody(e.body);
|
|
1858
|
-
const
|
|
1859
|
-
console.log(import_chalk15.default.green(
|
|
1860
|
-
console.log(import_chalk15.default.green(' -H "
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
` # \u2192 admin-api leaf (Lane 3, X-User-Assertion minted server-side): ${e.method} ${e.path}`
|
|
1866
|
-
)
|
|
1867
|
-
);
|
|
2022
|
+
const hasBody = e.method !== "GET" && masked !== void 0;
|
|
2023
|
+
console.log(import_chalk15.default.green(` curl -sS -X ${e.method} ${url}` + (hasBody ? " \\" : "")));
|
|
2024
|
+
console.log(import_chalk15.default.green(' -H "X-API-Key: $APIBLAZE_KEY"' + (hasBody ? " \\" : "")));
|
|
2025
|
+
if (hasBody) {
|
|
2026
|
+
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
2027
|
+
console.log(import_chalk15.default.green(` -d '${masked}'`));
|
|
2028
|
+
}
|
|
1868
2029
|
if (i < entries.length - 1) console.log();
|
|
1869
2030
|
});
|
|
2031
|
+
entries.length = 0;
|
|
1870
2032
|
}
|
|
1871
2033
|
|
|
1872
2034
|
// src/lib/admin.ts
|
|
@@ -1995,7 +2157,7 @@ async function runDelete(project, version2, opts) {
|
|
|
1995
2157
|
try {
|
|
1996
2158
|
await admin({
|
|
1997
2159
|
method: "DELETE",
|
|
1998
|
-
path:
|
|
2160
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
1999
2161
|
summary: `Delete proxy ${proj2.projectName} v${proj2.apiVersion} (full cascade)`
|
|
2000
2162
|
});
|
|
2001
2163
|
s2.succeed(`Deleted ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
@@ -2016,7 +2178,7 @@ async function patchConfig(project, opts, body, summary) {
|
|
|
2016
2178
|
try {
|
|
2017
2179
|
const out = await admin({
|
|
2018
2180
|
method: "PATCH",
|
|
2019
|
-
path:
|
|
2181
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}`,
|
|
2020
2182
|
body,
|
|
2021
2183
|
summary
|
|
2022
2184
|
});
|
|
@@ -2075,9 +2237,10 @@ async function runDomainAdd(project, opts) {
|
|
|
2075
2237
|
try {
|
|
2076
2238
|
const out = await admin({
|
|
2077
2239
|
method: "POST",
|
|
2078
|
-
path: `/projects/${proj2.projectId}/
|
|
2240
|
+
path: `/projects/${proj2.projectId}/domains`,
|
|
2241
|
+
// --tenant binds the domain to one of the project's tenants (per-tenant scope).
|
|
2079
2242
|
body: { hostname: opts.domain, api_version: proj2.apiVersion, ...opts.tenant ? { tenant_name: opts.tenant } : {} },
|
|
2080
|
-
summary: `Add custom domain ${opts.domain} to ${proj2.projectName} v${proj2.apiVersion}`
|
|
2243
|
+
summary: `Add custom domain ${opts.domain} to ${proj2.projectName} v${proj2.apiVersion}${opts.tenant ? ` (tenant ${opts.tenant})` : ""}`
|
|
2081
2244
|
});
|
|
2082
2245
|
spinner.succeed(`Registered ${opts.domain}. Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
2083
2246
|
if (opts.json) {
|
|
@@ -2097,7 +2260,7 @@ async function runDomainList(project, opts) {
|
|
|
2097
2260
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2098
2261
|
const out = await admin({
|
|
2099
2262
|
method: "GET",
|
|
2100
|
-
path: `/projects/${proj2.projectId}/
|
|
2263
|
+
path: `/projects/${proj2.projectId}/domains?api_version=${encodeURIComponent(proj2.apiVersion)}`,
|
|
2101
2264
|
summary: `List custom domains for ${proj2.projectName} v${proj2.apiVersion}`
|
|
2102
2265
|
});
|
|
2103
2266
|
const domains = out?.domains ?? [];
|
|
@@ -2120,7 +2283,7 @@ async function runDomainStatus(project, opts) {
|
|
|
2120
2283
|
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2121
2284
|
const out = await admin({
|
|
2122
2285
|
method: "GET",
|
|
2123
|
-
path: `/projects/${proj2.projectId}/
|
|
2286
|
+
path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}/status`,
|
|
2124
2287
|
summary: `Check custom-domain status`
|
|
2125
2288
|
});
|
|
2126
2289
|
console.log(opts.json ? JSON.stringify(out) : ` ${import_chalk20.default.bold(out?.hostname ?? opts.id)}: ${import_chalk20.default.cyan(out?.status ?? "unknown")}`);
|
|
@@ -2136,7 +2299,7 @@ async function runDomainRemove(project, opts) {
|
|
|
2136
2299
|
try {
|
|
2137
2300
|
await admin({
|
|
2138
2301
|
method: "DELETE",
|
|
2139
|
-
path: `/projects/${proj2.projectId}/
|
|
2302
|
+
path: `/projects/${proj2.projectId}/domains/${encodeURIComponent(opts.id)}`,
|
|
2140
2303
|
summary: `Remove custom domain ${opts.id}`
|
|
2141
2304
|
});
|
|
2142
2305
|
spinner.succeed("Removed.");
|
|
@@ -2283,6 +2446,19 @@ async function runTenantCors(opts) {
|
|
|
2283
2446
|
// src/commands/key.ts
|
|
2284
2447
|
var import_chalk22 = __toESM(require("chalk"));
|
|
2285
2448
|
var import_ora9 = __toESM(require("ora"));
|
|
2449
|
+
async function runApikeysMenu(opts) {
|
|
2450
|
+
await runKeyList(opts);
|
|
2451
|
+
if (opts.json) return;
|
|
2452
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2453
|
+
const { make } = await inquirer2.prompt([
|
|
2454
|
+
{ type: "confirm", name: "make", message: "Generate a new control-plane API key?", default: false }
|
|
2455
|
+
]);
|
|
2456
|
+
if (!make) return;
|
|
2457
|
+
const { desc } = await inquirer2.prompt([
|
|
2458
|
+
{ type: "input", name: "desc", message: "Description (optional):" }
|
|
2459
|
+
]);
|
|
2460
|
+
await runKeyMint({ ...opts, desc: desc || void 0 });
|
|
2461
|
+
}
|
|
2286
2462
|
async function runKeyList(opts) {
|
|
2287
2463
|
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2288
2464
|
const out = await admin({
|
|
@@ -2346,7 +2522,7 @@ async function runKeyRevoke(keyId, opts) {
|
|
|
2346
2522
|
}
|
|
2347
2523
|
|
|
2348
2524
|
// src/commands/spec.ts
|
|
2349
|
-
var
|
|
2525
|
+
var fs5 = __toESM(require("fs"));
|
|
2350
2526
|
var import_chalk23 = __toESM(require("chalk"));
|
|
2351
2527
|
var import_ora10 = __toESM(require("ora"));
|
|
2352
2528
|
async function runSpecGet(project, opts) {
|
|
@@ -2366,7 +2542,7 @@ async function runSpecSet(project, opts) {
|
|
|
2366
2542
|
}
|
|
2367
2543
|
let specContent;
|
|
2368
2544
|
try {
|
|
2369
|
-
specContent =
|
|
2545
|
+
specContent = fs5.readFileSync(opts.file, "utf-8");
|
|
2370
2546
|
} catch {
|
|
2371
2547
|
console.error(import_chalk23.default.red(`Cannot read file: ${opts.file}`));
|
|
2372
2548
|
process.exit(1);
|
|
@@ -2398,6 +2574,21 @@ async function proj(teamId, name, version2) {
|
|
|
2398
2574
|
return resolveProject(teamId, name, version2);
|
|
2399
2575
|
}
|
|
2400
2576
|
var TOOLS = [
|
|
2577
|
+
{
|
|
2578
|
+
name: "create_proxy",
|
|
2579
|
+
description: "Create a new API proxy (project) from a backend URL. auth is one of: api_key, none, oauth.",
|
|
2580
|
+
params: { name: "proxy name (becomes its subdomain)", target: "backend URL to forward to", auth: "api_key | none | oauth (default api_key)" },
|
|
2581
|
+
run: async (a, { teamId }) => {
|
|
2582
|
+
const auth = a.auth ?? a.auth_type ?? "api_key";
|
|
2583
|
+
const res = await createProxy({ name: a.name, target_url: a.target ?? a.target_url ?? a.url, auth_type: auth, team_id: teamId });
|
|
2584
|
+
const version2 = res.api_version || "1.0.0";
|
|
2585
|
+
const keys = res.api_keys ?? {};
|
|
2586
|
+
const key = keys.dev ?? Object.values(keys)[0];
|
|
2587
|
+
const url = `https://${a.name}.apiblaze.com/${version2}/dev`;
|
|
2588
|
+
const tryIt = buildTryItCurl(url, auth, key);
|
|
2589
|
+
return { ...res, proxy_url: url, ...tryIt ? { try_it: tryIt } : {} };
|
|
2590
|
+
}
|
|
2591
|
+
},
|
|
2401
2592
|
{
|
|
2402
2593
|
name: "list_projects",
|
|
2403
2594
|
description: "List the proxies (projects) in the active team.",
|
|
@@ -2411,7 +2602,7 @@ var TOOLS = [
|
|
|
2411
2602
|
run: async (a, { teamId }) => {
|
|
2412
2603
|
const p = await proj(teamId, a.project, a.apiversion);
|
|
2413
2604
|
const body = a.env ? { environments: { [a.env]: { target: a.url } } } : { target_url: a.url };
|
|
2414
|
-
return admin({ method: "PATCH", path:
|
|
2605
|
+
return admin({ method: "PATCH", path: `/projects/${p.projectId}/${p.apiVersion}`, body, summary: `Set target for ${p.projectName}` });
|
|
2415
2606
|
}
|
|
2416
2607
|
},
|
|
2417
2608
|
{
|
|
@@ -2424,7 +2615,7 @@ var TOOLS = [
|
|
|
2424
2615
|
if (a.rate != null) throttling.userRateLimit = Number(a.rate);
|
|
2425
2616
|
if (a.quota != null) throttling.proxyQuota = Number(a.quota);
|
|
2426
2617
|
if (a.period) throttling.quotaPeriod = a.period;
|
|
2427
|
-
return admin({ method: "PATCH", path:
|
|
2618
|
+
return admin({ method: "PATCH", path: `/projects/${p.projectId}/${p.apiVersion}`, body: { throttling }, summary: `Throttle ${p.projectName}` });
|
|
2428
2619
|
}
|
|
2429
2620
|
},
|
|
2430
2621
|
{
|
|
@@ -2433,7 +2624,7 @@ var TOOLS = [
|
|
|
2433
2624
|
params: { project: "project name or id", display_name: "new display name" },
|
|
2434
2625
|
run: async (a, { teamId }) => {
|
|
2435
2626
|
const p = await proj(teamId, a.project, a.apiversion);
|
|
2436
|
-
return admin({ method: "PATCH", path:
|
|
2627
|
+
return admin({ method: "PATCH", path: `/projects/${p.projectId}/${p.apiVersion}`, body: { display_name: a.display_name }, summary: `Rename ${p.projectName}` });
|
|
2437
2628
|
}
|
|
2438
2629
|
},
|
|
2439
2630
|
{
|
|
@@ -2442,7 +2633,7 @@ var TOOLS = [
|
|
|
2442
2633
|
params: { project: "project name or id", version: "optional api version" },
|
|
2443
2634
|
run: async (a, { teamId }) => {
|
|
2444
2635
|
const p = await proj(teamId, a.project, a.version);
|
|
2445
|
-
return admin({ method: "DELETE", path:
|
|
2636
|
+
return admin({ method: "DELETE", path: `/projects/${p.projectId}/${p.apiVersion}`, summary: `Delete ${p.projectName} (cascade)` });
|
|
2446
2637
|
}
|
|
2447
2638
|
},
|
|
2448
2639
|
{
|
|
@@ -2489,7 +2680,7 @@ var TOOLS = [
|
|
|
2489
2680
|
params: { project: "project name or id" },
|
|
2490
2681
|
run: async (a, { teamId }) => {
|
|
2491
2682
|
const p = await proj(teamId, a.project, a.apiversion);
|
|
2492
|
-
return admin({ method: "GET", path: `/projects/${p.projectId}/
|
|
2683
|
+
return admin({ method: "GET", path: `/projects/${p.projectId}/domains?api_version=${encodeURIComponent(p.apiVersion)}`, summary: `List domains for ${p.projectName}` });
|
|
2493
2684
|
}
|
|
2494
2685
|
},
|
|
2495
2686
|
{
|
|
@@ -2596,6 +2787,7 @@ async function runAgent(opts) {
|
|
|
2596
2787
|
const msg = err instanceof Error ? err.message : String(err);
|
|
2597
2788
|
history.push({ role: "user", content: `TOOL_RESULT ${tool.name}: error \u2014 ${truncate(msg, 400)}` });
|
|
2598
2789
|
}
|
|
2790
|
+
renderTrace();
|
|
2599
2791
|
if (step === MAX_TOOL_STEPS - 1) {
|
|
2600
2792
|
console.log(import_chalk24.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
2601
2793
|
}
|
|
@@ -2604,6 +2796,175 @@ async function runAgent(opts) {
|
|
|
2604
2796
|
console.log(import_chalk24.default.dim("\nBye."));
|
|
2605
2797
|
}
|
|
2606
2798
|
|
|
2799
|
+
// src/commands/consumer.ts
|
|
2800
|
+
var import_chalk25 = __toESM(require("chalk"));
|
|
2801
|
+
var import_ora12 = __toESM(require("ora"));
|
|
2802
|
+
var APIKEYS_VER = "1.0.0";
|
|
2803
|
+
var DEFAULT_SCOPE = "openid email profile offline_access";
|
|
2804
|
+
function apikeysBase(tenant2) {
|
|
2805
|
+
const tmpl = process.env.APIBLAZE_APIKEYS_BASE_TMPL;
|
|
2806
|
+
return tmpl ? tmpl.replace("{tenant}", tenant2) : `https://${tenant2}.apikeys.apiblaze.com`;
|
|
2807
|
+
}
|
|
2808
|
+
async function consumerFetch(creds, suffix, init) {
|
|
2809
|
+
const fresh = await validConsumerToken(creds) ?? creds;
|
|
2810
|
+
const res = await fetch(`${apikeysBase(fresh.tenant)}/${APIKEYS_VER}${suffix}`, {
|
|
2811
|
+
...init,
|
|
2812
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${fresh.accessToken}`, ...init?.headers ?? {} }
|
|
2813
|
+
});
|
|
2814
|
+
let data = null;
|
|
2815
|
+
try {
|
|
2816
|
+
data = await res.json();
|
|
2817
|
+
} catch {
|
|
2818
|
+
}
|
|
2819
|
+
return { status: res.status, data, creds: fresh };
|
|
2820
|
+
}
|
|
2821
|
+
function requireConsumer() {
|
|
2822
|
+
const c = loadConsumer();
|
|
2823
|
+
if (!c) {
|
|
2824
|
+
console.error(import_chalk25.default.red("Not logged in as a consumer. Run `apiblaze consumer login` first."));
|
|
2825
|
+
process.exit(1);
|
|
2826
|
+
}
|
|
2827
|
+
return c;
|
|
2828
|
+
}
|
|
2829
|
+
async function runConsumerLogin(opts) {
|
|
2830
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2831
|
+
let tenant2 = opts.tenant;
|
|
2832
|
+
let clientId = opts.client;
|
|
2833
|
+
if (clientId) {
|
|
2834
|
+
if (!tenant2) {
|
|
2835
|
+
console.error(import_chalk25.default.red("When using --client, also pass --tenant <slug> (it sets which portal/keys host to use)."));
|
|
2836
|
+
process.exit(1);
|
|
2837
|
+
}
|
|
2838
|
+
} else {
|
|
2839
|
+
requireAuth();
|
|
2840
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2841
|
+
const spinner = (0, import_ora12.default)("Loading your tenants...").start();
|
|
2842
|
+
const tdata = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: `List tenants for ${teamName ?? teamId}` });
|
|
2843
|
+
spinner.stop();
|
|
2844
|
+
const tenants = (tdata?.tenants ?? []).map(
|
|
2845
|
+
(t) => typeof t === "string" ? { tenant_name: t } : t
|
|
2846
|
+
);
|
|
2847
|
+
if (!tenants.length) {
|
|
2848
|
+
console.error(import_chalk25.default.red("This team has no tenants. Create one with `apiblaze tenant create`."));
|
|
2849
|
+
process.exit(1);
|
|
2850
|
+
}
|
|
2851
|
+
if (!tenant2) {
|
|
2852
|
+
if (tenants.length === 1) tenant2 = tenants[0].tenant_name;
|
|
2853
|
+
else {
|
|
2854
|
+
const { chosen } = await inquirer2.prompt([{ type: "list", name: "chosen", message: "Which tenant portal?", choices: tenants.map((t) => ({ name: t.display_name ? `${t.display_name} (${t.tenant_name})` : t.tenant_name, value: t.tenant_name })) }]);
|
|
2855
|
+
tenant2 = chosen;
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
const s2 = (0, import_ora12.default)("Finding the login app...").start();
|
|
2859
|
+
const clients = await admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(tenant2)}/app-clients`, summary: `List app clients for ${tenant2}` }).catch(() => []);
|
|
2860
|
+
s2.stop();
|
|
2861
|
+
const usable = (Array.isArray(clients) ? clients : []).filter((c) => c && (c.client_id || c.clientId));
|
|
2862
|
+
const pick2 = usable.find((c) => c.is_default || c.default) ?? usable.find((c) => c.verified !== false) ?? usable[0];
|
|
2863
|
+
if (!pick2) {
|
|
2864
|
+
console.error(import_chalk25.default.red(`Tenant "${tenant2}" has no login app configured. Set one up in the dashboard (or \`apiblaze create\` with auth).`));
|
|
2865
|
+
process.exit(1);
|
|
2866
|
+
}
|
|
2867
|
+
clientId = pick2.client_id ?? pick2.clientId;
|
|
2868
|
+
}
|
|
2869
|
+
console.log(`${import_chalk25.default.cyan("\u2192")} Logging in to ${import_chalk25.default.bold(tenant2)} as a consumer...`);
|
|
2870
|
+
const result = await deviceLogin(clientId, DEFAULT_SCOPE, ({ verificationUri, userCode }) => {
|
|
2871
|
+
console.log(`
|
|
2872
|
+
Open: ${import_chalk25.default.underline(verificationUri)}`);
|
|
2873
|
+
console.log(` Code: ${import_chalk25.default.bold(userCode)}
|
|
2874
|
+
`);
|
|
2875
|
+
console.log(import_chalk25.default.dim(" (opening your browser\u2026 waiting for you to finish)"));
|
|
2876
|
+
});
|
|
2877
|
+
const claims = result.idToken && decodeJwt2(result.idToken) || (decodeJwt2(result.accessToken) ?? {});
|
|
2878
|
+
const creds = {
|
|
2879
|
+
tenant: tenant2,
|
|
2880
|
+
clientId,
|
|
2881
|
+
accessToken: result.accessToken,
|
|
2882
|
+
refreshToken: result.refreshToken,
|
|
2883
|
+
idToken: result.idToken,
|
|
2884
|
+
expiresAt: Date.now() + (result.expiresIn ?? 3600) * 1e3,
|
|
2885
|
+
scope: result.scope,
|
|
2886
|
+
email: typeof claims.email === "string" ? claims.email : void 0,
|
|
2887
|
+
obtainedAt: Date.now()
|
|
2888
|
+
};
|
|
2889
|
+
saveConsumer(creds);
|
|
2890
|
+
console.log(import_chalk25.default.green(`\u2714 Logged in as consumer${creds.email ? ` ${creds.email}` : ""} on ${tenant2}.`));
|
|
2891
|
+
}
|
|
2892
|
+
async function runConsumerTokens(opts) {
|
|
2893
|
+
const creds = requireConsumer();
|
|
2894
|
+
const fresh = await validConsumerToken(creds) ?? creds;
|
|
2895
|
+
const exp = (t) => {
|
|
2896
|
+
const p = t ? decodeJwt2(t) : null;
|
|
2897
|
+
return p && typeof p.exp === "number" ? new Date(p.exp * 1e3).toISOString() : void 0;
|
|
2898
|
+
};
|
|
2899
|
+
if (opts.json) {
|
|
2900
|
+
console.log(JSON.stringify({ tenant: fresh.tenant, access_token: fresh.accessToken, refresh_token: fresh.refreshToken, id_token: fresh.idToken, expires_at: new Date(fresh.expiresAt).toISOString() }, null, 2));
|
|
2901
|
+
return;
|
|
2902
|
+
}
|
|
2903
|
+
console.log(`${import_chalk25.default.cyan("Consumer")} ${import_chalk25.default.bold(fresh.email ?? fresh.tenant)} on ${import_chalk25.default.bold(fresh.tenant)}
|
|
2904
|
+
`);
|
|
2905
|
+
console.log(`${import_chalk25.default.bold("access_token")} ${import_chalk25.default.dim("exp " + (exp(fresh.accessToken) ?? "?"))}
|
|
2906
|
+
${fresh.accessToken}
|
|
2907
|
+
`);
|
|
2908
|
+
if (fresh.idToken) console.log(`${import_chalk25.default.bold("id_token")} ${import_chalk25.default.dim("exp " + (exp(fresh.idToken) ?? "?"))}
|
|
2909
|
+
${fresh.idToken}
|
|
2910
|
+
`);
|
|
2911
|
+
if (fresh.refreshToken) console.log(`${import_chalk25.default.bold("refresh_token")}
|
|
2912
|
+
${fresh.refreshToken}
|
|
2913
|
+
`);
|
|
2914
|
+
console.log(import_chalk25.default.dim("These are your own tokens \u2014 keep them secret."));
|
|
2915
|
+
}
|
|
2916
|
+
async function runConsumerApikeys(opts) {
|
|
2917
|
+
const creds = requireConsumer();
|
|
2918
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2919
|
+
const spinner = (0, import_ora12.default)("Loading your API keys...").start();
|
|
2920
|
+
const list = await consumerFetch(creds, "/apikeys");
|
|
2921
|
+
const revealed = await consumerFetch(list.creds, "/apikeys/reveal").catch(() => ({ status: 0, data: null, creds: list.creds }));
|
|
2922
|
+
spinner.stop();
|
|
2923
|
+
if (list.status >= 400) {
|
|
2924
|
+
console.error(import_chalk25.default.red(`Failed to list keys (${list.status}): ${list.data?.error ?? ""}`));
|
|
2925
|
+
if (list.status === 401) console.error(import_chalk25.default.dim("Your consumer session may have expired \u2014 run `apiblaze consumer login` again."));
|
|
2926
|
+
process.exit(1);
|
|
2927
|
+
}
|
|
2928
|
+
const keys = list.data?.keys ?? [];
|
|
2929
|
+
const revealMap = revealed.data?.keys ?? {};
|
|
2930
|
+
if (opts.json) {
|
|
2931
|
+
console.log(JSON.stringify({ keys, revealed: revealMap }, null, 2));
|
|
2932
|
+
} else if (!keys.length) {
|
|
2933
|
+
console.log(import_chalk25.default.yellow("No API keys yet."));
|
|
2934
|
+
} else {
|
|
2935
|
+
for (const k of keys) {
|
|
2936
|
+
const clear = revealMap[k.environment]?.key;
|
|
2937
|
+
const shown = clear ? import_chalk25.default.green(clear) : import_chalk25.default.dim(`${k.key_prefix ?? ""}\u2026${k.key_suffix ?? ""}`);
|
|
2938
|
+
const exp = k.expires_at ? import_chalk25.default.dim(`exp ${k.expires_at}`) : import_chalk25.default.dim("no expiry");
|
|
2939
|
+
console.log(` ${import_chalk25.default.bold(k.environment ?? "")} ${shown} ${exp} ${import_chalk25.default.dim(k.description ?? "")}`);
|
|
2940
|
+
}
|
|
2941
|
+
if (Object.keys(revealMap).length === 0 && keys.some((k) => !k.expires_at)) {
|
|
2942
|
+
console.log(import_chalk25.default.dim("\n(Only expiring keys can be shown in clear; non-expiring keys show a prefix only.)"));
|
|
2943
|
+
}
|
|
2944
|
+
}
|
|
2945
|
+
if (opts.json) return;
|
|
2946
|
+
const { make } = await inquirer2.prompt([{ type: "confirm", name: "make", message: "Create a new API key?", default: false }]);
|
|
2947
|
+
if (!make) return;
|
|
2948
|
+
const answers = await inquirer2.prompt([
|
|
2949
|
+
{ type: "input", name: "environment", message: "Environment:", default: "production" },
|
|
2950
|
+
{ type: "input", name: "description", message: "Description (optional):" },
|
|
2951
|
+
{ type: "input", name: "expiresDays", message: "Expires in how many days? (blank = no expiry):" }
|
|
2952
|
+
]);
|
|
2953
|
+
const body = { environment: answers.environment };
|
|
2954
|
+
if (answers.description) body.description = answers.description;
|
|
2955
|
+
if (answers.expiresDays) body.expires_in_seconds = Number(answers.expiresDays) * 86400;
|
|
2956
|
+
const s2 = (0, import_ora12.default)("Creating key...").start();
|
|
2957
|
+
const created = await consumerFetch(list.creds, "/apikeys", { method: "POST", body: JSON.stringify(body) });
|
|
2958
|
+
if (created.status >= 400) {
|
|
2959
|
+
s2.fail(`Create failed (${created.status}): ${created.data?.error ?? ""}`);
|
|
2960
|
+
return;
|
|
2961
|
+
}
|
|
2962
|
+
s2.succeed("Key created.");
|
|
2963
|
+
const key = created.data?.key ?? created.data?.fullKey;
|
|
2964
|
+
if (key) console.log(` ${import_chalk25.default.green(key)} ${import_chalk25.default.dim("(shown once \u2014 store it now)")}`);
|
|
2965
|
+
else console.log(import_chalk25.default.dim(" Key created; run `apiblaze consumer apikeys` to reveal it if it expires."));
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2607
2968
|
// src/index.ts
|
|
2608
2969
|
var program = new import_commander.Command();
|
|
2609
2970
|
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version).option("-v, --verbose", "Print the exact series of API calls each command makes (curl-equivalent you could run yourself)");
|
|
@@ -2615,7 +2976,9 @@ function action(fn) {
|
|
|
2615
2976
|
return async (...args) => {
|
|
2616
2977
|
try {
|
|
2617
2978
|
await fn(...args);
|
|
2979
|
+
renderTrace();
|
|
2618
2980
|
} catch (err) {
|
|
2981
|
+
renderTrace();
|
|
2619
2982
|
printError(err);
|
|
2620
2983
|
process.exit(1);
|
|
2621
2984
|
}
|
|
@@ -2629,7 +2992,7 @@ program.command("login").description("Authenticate with APIblaze").action(async
|
|
|
2629
2992
|
process.exit(1);
|
|
2630
2993
|
}
|
|
2631
2994
|
});
|
|
2632
|
-
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>.apiblaze.com)").option("--target <url>", "Target URL to forward requests to").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("--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("--
|
|
2995
|
+
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>.apiblaze.com)").option("--target <url>", "Target URL to forward requests to").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("--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("--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("--json", "Output machine-readable JSON (non-interactive)").action(async (opts) => {
|
|
2633
2996
|
try {
|
|
2634
2997
|
await runCreate(opts);
|
|
2635
2998
|
} catch (err) {
|
|
@@ -2645,7 +3008,7 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
2645
3008
|
try {
|
|
2646
3009
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
2647
3010
|
if (Number.isNaN(resolved)) {
|
|
2648
|
-
console.error(
|
|
3011
|
+
console.error(import_chalk26.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
2649
3012
|
process.exit(1);
|
|
2650
3013
|
}
|
|
2651
3014
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -2654,15 +3017,12 @@ program.command("dev").description("Put your localhost behind a public URL (dev
|
|
|
2654
3017
|
process.exit(1);
|
|
2655
3018
|
}
|
|
2656
3019
|
});
|
|
2657
|
-
program.command("
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
}
|
|
2664
|
-
});
|
|
2665
|
-
program.command("whoami").description("Show the signed-in identity and active team").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
3020
|
+
var consumer = program.command("consumer").description("Act as a consumer of your API \u2014 log in, get tokens, manage API keys");
|
|
3021
|
+
consumer.command("login").description("Log in to a tenant's portal as a consumer (device flow)").option("--team <id|name>", "Team whose tenants to choose from (producer mode)").option("--tenant <slug>", "Tenant to log in to (skips the picker)").option("--client <clientId>", "App client id \u2014 for standalone login without a producer session").action(action((opts) => runConsumerLogin(opts)));
|
|
3022
|
+
consumer.command("tokens").description("Show the logged-in consumer access / refresh / id tokens").option("--json", "Output machine-readable JSON").action(action((opts) => runConsumerTokens(opts)));
|
|
3023
|
+
consumer.command("apikeys").description("List your consumer API keys (reveals expiring ones), then offer to create one").option("--json", "Output machine-readable JSON").action(action((opts) => runConsumerApikeys(opts)));
|
|
3024
|
+
program.command("logout").description("Sign out (asks whether to drop the Producer or Consumer login)").option("--producer", "Log out of the API Producer identity").option("--consumer", "Log out of the API Consumer identity").option("--all", "Log out of both").action(action((opts) => runLogout(opts)));
|
|
3025
|
+
program.command("whoami").description("Show who you are \u2014 both API Producer and API Consumer").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
2666
3026
|
try {
|
|
2667
3027
|
await runWhoami(opts);
|
|
2668
3028
|
} catch (err) {
|
|
@@ -2699,7 +3059,7 @@ program.command("target").description("Change where a proxy forwards requests").
|
|
|
2699
3059
|
program.command("throttle").description("Set rate limits and quotas for a proxy").argument("<project>", "Project name or id").option("--rate <n>", "User rate limit (requests/sec)").option("--end-user-rate <n>", "Per-end-user rate limit (requests/sec)").option("--quota <n>", "Proxy quota (requests/period)").option("--period <p>", "Quota period: daily | weekly | monthly").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runThrottleSet(project, opts)));
|
|
2700
3060
|
program.command("rename").description("Change a proxy's display name").argument("<project>", "Project name or id").requiredOption("--display-name <name>", "New human-friendly display name").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runRename(project, opts)));
|
|
2701
3061
|
var domain = program.command("domain").description("Use your own domain for a proxy");
|
|
2702
|
-
domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("<project>", "Project name or id").requiredOption("--domain <host>", "Custom hostname to add").option("--tenant <slug>", "
|
|
3062
|
+
domain.command("add").description("Add your own domain (shows the DNS records to set)").argument("<project>", "Project name or id").requiredOption("--domain <host>", "Custom hostname to add").option("--tenant <slug>", "Bind the domain to one of the project's tenants (per-tenant scope)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainAdd(project, opts)));
|
|
2703
3063
|
domain.command("list").description("List custom domains for a proxy").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainList(project, opts)));
|
|
2704
3064
|
domain.command("status").description("Check a custom domain's validation status").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runDomainStatus(project, opts)));
|
|
2705
3065
|
domain.command("rm").description("Remove a custom domain").argument("<project>", "Project name or id").requiredOption("--id <domainId>", "Domain id (see `domain list`)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainRemove(project, opts)));
|
|
@@ -2710,42 +3070,62 @@ tenant.command("create").description("Create a tenant in your team").requiredOpt
|
|
|
2710
3070
|
tenant.command("attach").description("Attach a tenant to a proxy").argument("<project>", "Project name or id").requiredOption("--tenant <slug>", "Tenant slug to attach").option("--auth-config <id>", "Auth config id to bind").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTenantAttach(project, opts)));
|
|
2711
3071
|
tenant.command("delete").description("Delete a tenant (full cascade)").argument("<slug>", "Tenant slug to delete").option("--team <id|name>", "Team (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").action(action((slug, opts) => runTenantDelete(slug, opts)));
|
|
2712
3072
|
tenant.command("cors").description("Set the CORS allow-list for a tenant").requiredOption("--tenant <slug>", "Tenant slug").option("--origins <list>", 'Comma-separated origins (or "*"); empty clears').option("--team <id|name>", "Team (defaults to active team)").action(action((opts) => runTenantCors(opts)));
|
|
2713
|
-
var
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
3073
|
+
var apikeys = new import_commander.Command("apikeys").description("Producer control-plane API keys (list, then offer to create one)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runApikeysMenu(opts)));
|
|
3074
|
+
apikeys.command("list").description("List control-plane API keys in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runKeyList(opts)));
|
|
3075
|
+
apikeys.command("mint").description("Create a control-plane API key (secret shown once)").option("--desc <text>", "Description").option("--expires-days <n>", "Expiry in days (default 90 server-side)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runKeyMint(opts)));
|
|
3076
|
+
apikeys.command("revoke").description("Revoke a control-plane API key").argument("<keyId>", "Key id (see `apikeys list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts) => runKeyRevoke(keyId, opts)));
|
|
3077
|
+
program.addCommand(apikeys, { hidden: true });
|
|
2717
3078
|
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
2718
3079
|
spec.command("get").description("Print the current OpenAPI document").argument("<project>", "Project name or id").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Compact JSON output").action(action((project, opts) => runSpecGet(project, opts)));
|
|
2719
3080
|
spec.command("set").description("Replace the stored OpenAPI spec from a local file").argument("<project>", "Project name or id").requiredOption("--file <path>", "OpenAPI JSON or YAML file to upload").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").option("--json", "Output machine-readable JSON").action(action((project, opts) => runSpecSet(project, opts)));
|
|
2720
|
-
|
|
2721
|
-
"
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
3081
|
+
var HELP_GROUPS = [
|
|
3082
|
+
{ title: "Chat", commands: ["agent"] },
|
|
3083
|
+
{ title: "Setup", commands: ["login", "create", "dev", "claim", "team", "whoami", "logout"] },
|
|
3084
|
+
{ title: "Producer \xB7 your APIs", commands: ["projects", "tenant", "domain", "delete"] },
|
|
3085
|
+
{ title: "Producer \xB7 change a proxy", commands: ["target", "throttle", "rename", "spec"] },
|
|
3086
|
+
{ title: "Consumer \xB7 use an API", commands: ["consumer"] }
|
|
3087
|
+
];
|
|
3088
|
+
function groupedCommandHelp() {
|
|
3089
|
+
const byName = new Map(program.commands.map((c) => [c.name(), c]));
|
|
3090
|
+
const width = Math.max(...HELP_GROUPS.flatMap((g) => g.commands).map((n) => n.length)) + 2;
|
|
3091
|
+
return HELP_GROUPS.map((g) => {
|
|
3092
|
+
const rows = g.commands.map((n) => {
|
|
3093
|
+
const c = byName.get(n);
|
|
3094
|
+
return c ? ` ${n.padEnd(width)}${c.description()}` : "";
|
|
3095
|
+
}).filter(Boolean).join("\n");
|
|
3096
|
+
return `${import_chalk26.default.bold(g.title)}
|
|
3097
|
+
${rows}`;
|
|
3098
|
+
}).join("\n\n");
|
|
3099
|
+
}
|
|
3100
|
+
program.configureHelp({
|
|
3101
|
+
visibleCommands(cmd) {
|
|
3102
|
+
const all = import_commander.Help.prototype.visibleCommands.call(this, cmd);
|
|
3103
|
+
return cmd === program ? [] : all;
|
|
3104
|
+
}
|
|
3105
|
+
});
|
|
3106
|
+
program.addHelpText("after", () => `
|
|
3107
|
+
${groupedCommandHelp()}
|
|
2729
3108
|
|
|
2730
|
-
|
|
2731
|
-
|
|
3109
|
+
Tips:
|
|
3110
|
+
\u2022 Add --verbose to any command to see the equivalent API calls.
|
|
3111
|
+
\u2022 Run \`apiblaze <command> --help\` (e.g. \`apiblaze consumer --help\`) for sub-commands.
|
|
2732
3112
|
|
|
2733
|
-
|
|
2734
|
-
$ npx apiblaze
|
|
2735
|
-
$ npx apiblaze
|
|
2736
|
-
$ npx apiblaze
|
|
2737
|
-
$ npx apiblaze
|
|
2738
|
-
|
|
2739
|
-
);
|
|
3113
|
+
Examples:
|
|
3114
|
+
$ npx apiblaze agent # just chat
|
|
3115
|
+
$ npx apiblaze create --target https://api.example.com # one-line API
|
|
3116
|
+
$ npx apiblaze dev 3000 # localhost \u2192 public URL
|
|
3117
|
+
$ npx apiblaze throttle myapi --rate 50 --verbose # configure + show the API call
|
|
3118
|
+
$ npx apiblaze consumer login # act as a consumer of your API
|
|
3119
|
+
`);
|
|
2740
3120
|
function printError(err) {
|
|
2741
3121
|
if (err instanceof ApiError) {
|
|
2742
|
-
console.error(
|
|
3122
|
+
console.error(import_chalk26.default.red(`
|
|
2743
3123
|
API error (${err.status}): ${err.message}`));
|
|
2744
3124
|
} else if (err instanceof Error) {
|
|
2745
|
-
console.error(
|
|
3125
|
+
console.error(import_chalk26.default.red(`
|
|
2746
3126
|
Error: ${err.message}`));
|
|
2747
3127
|
} else {
|
|
2748
|
-
console.error(
|
|
3128
|
+
console.error(import_chalk26.default.red("\nUnknown error"));
|
|
2749
3129
|
}
|
|
2750
3130
|
}
|
|
2751
3131
|
program.parse(process.argv);
|