apiblaze 0.3.8 → 0.4.1
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 +83 -35
- package/dist/index.js +867 -74
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,66 +1,114 @@
|
|
|
1
1
|
# apiblaze
|
|
2
2
|
|
|
3
|
-
CLI for [APIblaze](https://apiblaze.com) —
|
|
3
|
+
CLI for [APIblaze](https://apiblaze.com) — turn any backend into a managed API: add a key, auth, rate limits, a spec, and your own domain, in seconds.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npx apiblaze --help # run without installing
|
|
9
|
+
npm install -g apiblaze # or install globally
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
Requires Node.js 18+.
|
|
13
|
+
|
|
14
|
+
## Quick start
|
|
15
|
+
|
|
16
|
+
The easiest way is to just chat with it:
|
|
12
17
|
|
|
13
18
|
```bash
|
|
14
|
-
npx apiblaze
|
|
19
|
+
npx apiblaze agent
|
|
15
20
|
```
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
- Node.js 18+
|
|
22
|
+
> Talk in plain English — "make an API for httpbin.org", "rate-limit it to 50/sec",
|
|
23
|
+
> "give me a key". It does the work and shows what each step costs.
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
Prefer commands? A few one-liners:
|
|
22
26
|
|
|
23
27
|
```bash
|
|
24
|
-
#
|
|
28
|
+
# Make an API in one line — no account needed (prints a claim URL)
|
|
25
29
|
npx apiblaze create --target https://api.example.com
|
|
26
30
|
|
|
27
|
-
#
|
|
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
|
|
35
|
-
|
|
36
|
-
# Or specify a port
|
|
31
|
+
# Run your localhost through a public URL
|
|
37
32
|
npx apiblaze dev 3000
|
|
38
33
|
|
|
39
|
-
#
|
|
40
|
-
npx apiblaze
|
|
41
|
-
|
|
34
|
+
# Sign in to manage APIs under your team
|
|
35
|
+
npx apiblaze login
|
|
42
36
|
```
|
|
43
37
|
|
|
44
38
|
## Help
|
|
45
39
|
|
|
46
40
|
```bash
|
|
47
|
-
apiblaze --help
|
|
48
|
-
apiblaze help create
|
|
49
|
-
apiblaze help
|
|
41
|
+
apiblaze --help # all commands
|
|
42
|
+
apiblaze help create # help for one command
|
|
43
|
+
apiblaze domain --help # subcommands of a group (domain/tenant/key/spec)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add `--verbose` (or `-v`) to **any** command to print the exact series of API
|
|
47
|
+
calls it makes — as copy-pasteable `curl` you could run yourself with your own
|
|
48
|
+
login token, plus the underlying admin-api leaf each one drives:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
apiblaze throttle myapi --rate 50 --verbose
|
|
50
52
|
```
|
|
51
53
|
|
|
52
54
|
## Commands
|
|
53
55
|
|
|
54
|
-
|
|
56
|
+
### Chat (the easy way)
|
|
57
|
+
|
|
58
|
+
| Command | What it does |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `apiblaze agent` | Chat about anything — create, configure, and inspect your APIs |
|
|
61
|
+
| `apiblaze agent openapi <project>` | Chat to build your API spec from real traffic |
|
|
62
|
+
| `apiblaze agent authz <project>` | Chat to design and turn on access rules |
|
|
63
|
+
| `apiblaze agent mcp <project>` | Chat to build an MCP server for your API |
|
|
64
|
+
|
|
65
|
+
Every chat turn shows its cost.
|
|
66
|
+
|
|
67
|
+
### Getting started
|
|
68
|
+
|
|
69
|
+
| Command | What it does |
|
|
70
|
+
|---|---|
|
|
71
|
+
| `apiblaze create --target <url>` | Make an API from a backend (no account needed) |
|
|
72
|
+
| `apiblaze dev [port]` | Put your localhost behind a public URL |
|
|
73
|
+
| `apiblaze login` / `logout` | Sign in / out |
|
|
74
|
+
| `apiblaze whoami` | Who am I, and which team |
|
|
75
|
+
| `apiblaze team [name]` | Switch team |
|
|
76
|
+
| `apiblaze projects` | List your APIs |
|
|
77
|
+
| `apiblaze claim [code]` | Claim an API you made before signing in |
|
|
78
|
+
|
|
79
|
+
### Manage an API
|
|
80
|
+
|
|
81
|
+
| Command | What it does |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `apiblaze target <project> --url <url> [--env <e>]` | Change where it forwards requests |
|
|
84
|
+
| `apiblaze throttle <project> [--rate n] [--quota n] [--period daily\|weekly\|monthly]` | Set rate limits and quotas |
|
|
85
|
+
| `apiblaze rename <project> --display-name <name>` | Rename it |
|
|
86
|
+
| `apiblaze spec get <project>` | Print its OpenAPI spec |
|
|
87
|
+
| `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
|
+
|
|
90
|
+
### Your own domain
|
|
91
|
+
|
|
92
|
+
| Command | What it does |
|
|
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 |
|
|
98
|
+
|
|
99
|
+
### Users & keys
|
|
100
|
+
|
|
101
|
+
| Command | What it does |
|
|
55
102
|
|---|---|
|
|
56
|
-
| `apiblaze
|
|
57
|
-
| `apiblaze
|
|
58
|
-
| `apiblaze
|
|
59
|
-
| `apiblaze
|
|
60
|
-
| `apiblaze
|
|
61
|
-
| `apiblaze
|
|
62
|
-
|
|
63
|
-
|
|
103
|
+
| `apiblaze tenant create --name <name>` | Create a tenant (a separate group of your API's users) |
|
|
104
|
+
| `apiblaze tenant attach <project> --tenant <slug>` | Give a proxy its own set of users |
|
|
105
|
+
| `apiblaze tenant cors --tenant <slug> --origins <a,b>` | Set which websites can call it |
|
|
106
|
+
| `apiblaze tenant list` / `delete <slug>` | List / delete |
|
|
107
|
+
| `apiblaze key mint [--desc <text>]` | Make a key to manage your account from scripts |
|
|
108
|
+
| `apiblaze key list` / `revoke <id>` | List / revoke |
|
|
109
|
+
|
|
110
|
+
> See what any command does under the hood with `--verbose`. Most management
|
|
111
|
+
> commands take `--team`, `--apiversion`, and `--json`.
|
|
64
112
|
|
|
65
113
|
## How it works
|
|
66
114
|
|
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 key2 of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key2) && key2 !== except)
|
|
13
|
+
__defProp(to, key2, { get: () => from[key2], enumerable: !(desc = __getOwnPropDesc(from, key2)) || 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_chalk25 = __toESM(require("chalk"));
|
|
29
29
|
|
|
30
30
|
// package.json
|
|
31
|
-
var version = "0.
|
|
31
|
+
var version = "0.4.1";
|
|
32
32
|
|
|
33
33
|
// src/types.ts
|
|
34
34
|
var ApiError = class extends Error {
|
|
@@ -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 key2 of Object.keys(headers)) {
|
|
590
|
+
if (!STRIP_HEADERS.has(key2.toLowerCase())) out[key2] = headers[key2];
|
|
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, key2) => {
|
|
706
|
+
if (!STRIP_HEADERS.has(key2.toLowerCase())) headers[key2] = 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 key2 = result.api_keys?.dev ?? Object.values(result.api_keys ?? {})[0];
|
|
826
|
+
if (key2) {
|
|
827
|
+
console.log(` ${import_chalk4.default.dim("API key (dev):")} ${import_chalk4.default.bold.green(key2)}`);
|
|
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
|
}
|
|
@@ -1647,11 +1647,11 @@ async function runAuthz(projectArg, apiVersionArg) {
|
|
|
1647
1647
|
}
|
|
1648
1648
|
const projectId = match.projectId;
|
|
1649
1649
|
const apiVersion = apiVersionArg || match.apiVersion;
|
|
1650
|
-
const
|
|
1650
|
+
const tenant2 = match.tenant || projectId;
|
|
1651
1651
|
async function apply(ctx, enable) {
|
|
1652
1652
|
const proposal = ctx.proposal;
|
|
1653
1653
|
if (!enable) {
|
|
1654
|
-
const m = await agentCall(`/projects/${projectId}/${apiVersion}/policies/model?tenantId=${encodeURIComponent(
|
|
1654
|
+
const m = await agentCall(`/projects/${projectId}/${apiVersion}/policies/model?tenantId=${encodeURIComponent(tenant2)}`, "POST", proposal.model);
|
|
1655
1655
|
if (m.status === 404) {
|
|
1656
1656
|
ctx.log(import_chalk12.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"));
|
|
1657
1657
|
return;
|
|
@@ -1684,7 +1684,7 @@ async function runAuthz(projectArg, apiVersionArg) {
|
|
|
1684
1684
|
else fail4++;
|
|
1685
1685
|
}
|
|
1686
1686
|
if (enable) {
|
|
1687
|
-
const e = await agentCall(`/${projectId}/${apiVersion}/config`, "PATCH", { authorization: { enforce_authorization: true }, tenant });
|
|
1687
|
+
const e = await agentCall(`/${projectId}/${apiVersion}/config`, "PATCH", { authorization: { enforce_authorization: true }, tenant: tenant2 });
|
|
1688
1688
|
if (e.status >= 400) {
|
|
1689
1689
|
ctx.log(import_chalk12.default.red(` Enforced ${ok} route(s) but turning on the project-level switch failed (${e.status}). Toggle "Enforce Authorization" on in the dashboard.
|
|
1690
1690
|
`));
|
|
@@ -1699,7 +1699,7 @@ async function runAuthz(projectArg, apiVersionArg) {
|
|
|
1699
1699
|
}
|
|
1700
1700
|
await runAgentChatRepl({
|
|
1701
1701
|
title: `Authorization assistant \u2014 ${projectId} ${apiVersion}`,
|
|
1702
|
-
subtitle: `tenant ${
|
|
1702
|
+
subtitle: `tenant ${tenant2} \xB7 discuss what authorization fits this API, then make it official.`,
|
|
1703
1703
|
endpoint: `/projects/${projectId}/${apiVersion}/authz/chat`,
|
|
1704
1704
|
buildBody: () => ({ included_sample_ids: [], existing_model: null, existing_routes: [] }),
|
|
1705
1705
|
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.",
|
|
@@ -1794,8 +1794,8 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
1794
1794
|
const environment = opts.environment || "prod";
|
|
1795
1795
|
const mcpHost = match.tenant ? `${projectId}-${match.tenant}` : projectId;
|
|
1796
1796
|
async function publish(ctx) {
|
|
1797
|
-
const
|
|
1798
|
-
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/mcp/spec`, "PUT", { environment, spec });
|
|
1797
|
+
const spec2 = ctx.proposal;
|
|
1798
|
+
const pub = await agentCall(`/projects/${projectId}/${apiVersion}/mcp/spec`, "PUT", { environment, spec: spec2 });
|
|
1799
1799
|
if (pub.status >= 400) {
|
|
1800
1800
|
ctx.log(import_chalk14.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
1801
1801
|
`));
|
|
@@ -1811,8 +1811,8 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
1811
1811
|
buildBody: () => ({ environment, included_sample_ids: [] }),
|
|
1812
1812
|
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.",
|
|
1813
1813
|
summarizeProposal: (data) => {
|
|
1814
|
-
const
|
|
1815
|
-
const tools = Array.isArray(
|
|
1814
|
+
const spec2 = data.proposal;
|
|
1815
|
+
const tools = Array.isArray(spec2.tools) ? spec2.tools : [];
|
|
1816
1816
|
const names = tools.slice(0, 12).map((t) => t.name ?? "(unnamed)").join(", ");
|
|
1817
1817
|
return `Catalogue ready: ${tools.length} tool(s)${names ? ` \u2014 ${names}${tools.length > 12 ? ", \u2026" : ""}` : ""}.`;
|
|
1818
1818
|
},
|
|
@@ -1822,137 +1822,930 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
1822
1822
|
});
|
|
1823
1823
|
}
|
|
1824
1824
|
|
|
1825
|
-
// src/
|
|
1826
|
-
var
|
|
1827
|
-
|
|
1828
|
-
|
|
1825
|
+
// src/commands/delete.ts
|
|
1826
|
+
var import_chalk18 = __toESM(require("chalk"));
|
|
1827
|
+
var import_ora5 = __toESM(require("ora"));
|
|
1828
|
+
|
|
1829
|
+
// src/lib/admin.ts
|
|
1830
|
+
var import_chalk16 = __toESM(require("chalk"));
|
|
1831
|
+
|
|
1832
|
+
// src/lib/trace.ts
|
|
1833
|
+
var import_chalk15 = __toESM(require("chalk"));
|
|
1834
|
+
var verbose = false;
|
|
1835
|
+
var entries = [];
|
|
1836
|
+
function setVerbose(v) {
|
|
1837
|
+
verbose = v;
|
|
1838
|
+
}
|
|
1839
|
+
function recordCall(e) {
|
|
1840
|
+
if (verbose) entries.push(e);
|
|
1841
|
+
}
|
|
1842
|
+
var SECRET_KEY = /secret|token|password|api[_-]?key|client_secret/i;
|
|
1843
|
+
function maskBody(body) {
|
|
1844
|
+
if (body === void 0) return void 0;
|
|
1845
|
+
return JSON.stringify(body, (k, v) => SECRET_KEY.test(k) && typeof v === "string" ? "***" : v);
|
|
1846
|
+
}
|
|
1847
|
+
function renderTrace() {
|
|
1848
|
+
if (!verbose || entries.length === 0) return;
|
|
1849
|
+
console.log(import_chalk15.default.dim("\n" + "\u2500".repeat(64)));
|
|
1850
|
+
console.log(import_chalk15.default.bold(`--verbose: ${entries.length} API call${entries.length === 1 ? "" : "s"} this command made`));
|
|
1851
|
+
console.log(
|
|
1852
|
+
import_chalk15.default.dim("Copy/paste the curl below to do it yourself \u2014 it uses your own login token.\n")
|
|
1853
|
+
);
|
|
1854
|
+
entries.forEach((e, i) => {
|
|
1855
|
+
const n = entries.length > 1 ? import_chalk15.default.bold(`${i + 1}. `) : "";
|
|
1856
|
+
if (e.summary) console.log(`${n}${import_chalk15.default.cyan(e.summary)}${e.status ? import_chalk15.default.dim(` (HTTP ${e.status})`) : ""}`);
|
|
1857
|
+
const masked = maskBody(e.body);
|
|
1858
|
+
const payload = JSON.stringify({ path: e.path, method: e.method, ...masked ? { body: JSON.parse(masked) } : {} });
|
|
1859
|
+
console.log(import_chalk15.default.green(" curl -sS -X POST https://dashboard.apiblaze.com/api/cli/admin \\"));
|
|
1860
|
+
console.log(import_chalk15.default.green(' -H "Authorization: Bearer $(jq -r .accessToken ~/.apiblaze/credentials.json)" \\'));
|
|
1861
|
+
console.log(import_chalk15.default.green(" -H 'Content-Type: application/json' \\"));
|
|
1862
|
+
console.log(import_chalk15.default.green(` -d '${payload}'`));
|
|
1863
|
+
console.log(
|
|
1864
|
+
import_chalk15.default.dim(
|
|
1865
|
+
` # \u2192 admin-api leaf (Lane 3, X-User-Assertion minted server-side): ${e.method} ${e.path}`
|
|
1866
|
+
)
|
|
1867
|
+
);
|
|
1868
|
+
if (i < entries.length - 1) console.log();
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// src/lib/admin.ts
|
|
1873
|
+
var DASHBOARD_BASE3 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
1874
|
+
async function admin(call) {
|
|
1875
|
+
const token = getAccessToken();
|
|
1876
|
+
const res = await fetch(`${DASHBOARD_BASE3}/api/cli/admin`, {
|
|
1877
|
+
method: "POST",
|
|
1878
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
1879
|
+
body: JSON.stringify({ path: call.path, method: call.method, body: call.body })
|
|
1880
|
+
});
|
|
1881
|
+
let data = null;
|
|
1829
1882
|
try {
|
|
1830
|
-
await
|
|
1883
|
+
data = await res.json();
|
|
1884
|
+
} catch {
|
|
1885
|
+
}
|
|
1886
|
+
recordCall({ method: call.method, path: call.path, body: call.body, status: res.status, summary: call.summary });
|
|
1887
|
+
maybePrintBilling(data);
|
|
1888
|
+
if (!res.ok) {
|
|
1889
|
+
const msg = data?.details ?? data?.error ?? res.statusText;
|
|
1890
|
+
throw new ApiError(res.status, typeof msg === "string" ? msg : JSON.stringify(msg), data);
|
|
1891
|
+
}
|
|
1892
|
+
return data;
|
|
1893
|
+
}
|
|
1894
|
+
function maybePrintBilling(data) {
|
|
1895
|
+
const b = data?.billing;
|
|
1896
|
+
if (b && typeof b.charged_cents === "number") {
|
|
1897
|
+
const usd = (b.charged_cents / 100).toFixed(2);
|
|
1898
|
+
const rem = typeof b.credits_remaining === "number" ? ` \xB7 $${(b.credits_remaining / 100).toFixed(2)} credit left` : "";
|
|
1899
|
+
console.log(import_chalk16.default.magenta(` \u{1F4B3} Charged $${usd}${rem}`));
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// src/lib/resolve.ts
|
|
1904
|
+
var import_chalk17 = __toESM(require("chalk"));
|
|
1905
|
+
function requireAuth() {
|
|
1906
|
+
const creds = loadCredentials();
|
|
1907
|
+
if (!creds) {
|
|
1908
|
+
console.error(import_chalk17.default.red("Not logged in. Run `apiblaze login` first."));
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
return creds;
|
|
1912
|
+
}
|
|
1913
|
+
async function resolveTeam(opt) {
|
|
1914
|
+
const creds = requireAuth();
|
|
1915
|
+
if (!opt) {
|
|
1916
|
+
if (!creds.teamId) {
|
|
1917
|
+
console.error(import_chalk17.default.red("No active team. Run `apiblaze login` or pass --team."));
|
|
1918
|
+
process.exit(1);
|
|
1919
|
+
}
|
|
1920
|
+
return { teamId: creds.teamId, teamName: creds.teamName };
|
|
1921
|
+
}
|
|
1922
|
+
if (opt.startsWith("team_")) return { teamId: opt };
|
|
1923
|
+
const teams = await getTeams().catch(() => []);
|
|
1924
|
+
const match = teams.find((t) => t.name === opt || t.teamId === opt);
|
|
1925
|
+
if (!match) {
|
|
1926
|
+
console.error(import_chalk17.default.red(`Team "${opt}" not found.${teams.length ? " Available: " + teams.map((t) => t.name).join(", ") : ""}`));
|
|
1927
|
+
process.exit(1);
|
|
1928
|
+
}
|
|
1929
|
+
return { teamId: match.teamId, teamName: match.name };
|
|
1930
|
+
}
|
|
1931
|
+
async function resolveProject(teamId, nameOrId, version2) {
|
|
1932
|
+
const projects = await getProjects(teamId).catch(() => []);
|
|
1933
|
+
const candidates = projects.filter(
|
|
1934
|
+
(p) => p.projectId === nameOrId || p.projectName === nameOrId
|
|
1935
|
+
);
|
|
1936
|
+
if (candidates.length === 0) {
|
|
1937
|
+
console.error(import_chalk17.default.red(`Project "${nameOrId}" not found in this team.`));
|
|
1938
|
+
if (projects.length) console.error(import_chalk17.default.dim(" Known: " + projects.map((p) => p.projectName).join(", ")));
|
|
1939
|
+
process.exit(1);
|
|
1940
|
+
}
|
|
1941
|
+
const chosen = version2 ? candidates.find((p) => p.apiVersion === version2) : candidates[0];
|
|
1942
|
+
if (!chosen) {
|
|
1943
|
+
console.error(import_chalk17.default.red(`Project "${nameOrId}" has no version ${version2}. Versions: ${candidates.map((p) => p.apiVersion).join(", ")}`));
|
|
1944
|
+
process.exit(1);
|
|
1945
|
+
}
|
|
1946
|
+
return {
|
|
1947
|
+
projectId: chosen.projectId,
|
|
1948
|
+
projectName: chosen.projectName,
|
|
1949
|
+
apiVersion: chosen.apiVersion,
|
|
1950
|
+
teamId,
|
|
1951
|
+
tenant: chosen.tenant
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
// src/commands/delete.ts
|
|
1956
|
+
async function runDelete(project, version2, opts) {
|
|
1957
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
1958
|
+
const proj2 = await resolveProject(teamId, project, version2);
|
|
1959
|
+
const spinner = (0, import_ora5.default)("Checking delete impact...").start();
|
|
1960
|
+
let impact;
|
|
1961
|
+
try {
|
|
1962
|
+
impact = await admin({
|
|
1963
|
+
method: "GET",
|
|
1964
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}/delete-impact`,
|
|
1965
|
+
summary: `Preview what deleting ${proj2.projectName} v${proj2.apiVersion} removes`
|
|
1966
|
+
});
|
|
1967
|
+
spinner.stop();
|
|
1831
1968
|
} catch (err) {
|
|
1832
|
-
|
|
1969
|
+
spinner.stop();
|
|
1970
|
+
impact = null;
|
|
1971
|
+
}
|
|
1972
|
+
if (opts.json) {
|
|
1973
|
+
if (!opts.yes) {
|
|
1974
|
+
console.log(JSON.stringify({ project_id: proj2.projectId, api_version: proj2.apiVersion, impact, deleted: false, hint: "pass --yes to delete" }));
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
} else {
|
|
1978
|
+
console.log(`${import_chalk18.default.red.bold("Delete")} ${import_chalk18.default.bold(proj2.projectName)} ${import_chalk18.default.dim("v" + proj2.apiVersion)} ${import_chalk18.default.dim("(" + proj2.projectId + ")")}`);
|
|
1979
|
+
if (impact && typeof impact === "object") {
|
|
1980
|
+
const counts = impact.counts ?? impact.impact ?? impact;
|
|
1981
|
+
console.log(import_chalk18.default.dim(" This removes (cascade): ") + import_chalk18.default.yellow(JSON.stringify(counts)));
|
|
1982
|
+
}
|
|
1983
|
+
if (!opts.yes) {
|
|
1984
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
1985
|
+
const { confirm } = await inquirer2.prompt([
|
|
1986
|
+
{ type: "confirm", name: "confirm", message: `Permanently delete ${proj2.projectName} v${proj2.apiVersion}? This cannot be undone.`, default: false }
|
|
1987
|
+
]);
|
|
1988
|
+
if (!confirm) {
|
|
1989
|
+
console.log(import_chalk18.default.dim("Aborted."));
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
const s2 = (0, import_ora5.default)("Deleting...").start();
|
|
1995
|
+
try {
|
|
1996
|
+
await admin({
|
|
1997
|
+
method: "DELETE",
|
|
1998
|
+
path: `/${proj2.projectId}/${proj2.apiVersion}`,
|
|
1999
|
+
summary: `Delete proxy ${proj2.projectName} v${proj2.apiVersion} (full cascade)`
|
|
2000
|
+
});
|
|
2001
|
+
s2.succeed(`Deleted ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
2002
|
+
} catch (err) {
|
|
2003
|
+
s2.fail("Delete failed.");
|
|
2004
|
+
throw err;
|
|
2005
|
+
}
|
|
2006
|
+
if (opts.json) console.log(JSON.stringify({ project_id: proj2.projectId, api_version: proj2.apiVersion, deleted: true }));
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
// src/commands/config.ts
|
|
2010
|
+
var import_chalk19 = __toESM(require("chalk"));
|
|
2011
|
+
var import_ora6 = __toESM(require("ora"));
|
|
2012
|
+
async function patchConfig(project, opts, body, summary) {
|
|
2013
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2014
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2015
|
+
const spinner = (0, import_ora6.default)(summary + "...").start();
|
|
2016
|
+
try {
|
|
2017
|
+
const out = await admin({
|
|
2018
|
+
method: "PATCH",
|
|
2019
|
+
path: `/${proj2.projectId}/${proj2.apiVersion}/config`,
|
|
2020
|
+
body,
|
|
2021
|
+
summary
|
|
2022
|
+
});
|
|
2023
|
+
spinner.succeed(`${summary} \u2014 ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
2024
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
2025
|
+
} catch (err) {
|
|
2026
|
+
spinner.fail(summary + " failed.");
|
|
2027
|
+
throw err;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
async function runTargetSet(project, opts) {
|
|
2031
|
+
if (!opts.url) {
|
|
2032
|
+
console.error(import_chalk19.default.red("--url is required."));
|
|
1833
2033
|
process.exit(1);
|
|
1834
2034
|
}
|
|
1835
|
-
}
|
|
1836
|
-
|
|
2035
|
+
const body = opts.env ? { environments: { [opts.env]: { target: opts.url } } } : { target_url: opts.url };
|
|
2036
|
+
await patchConfig(project, opts, body, `Set target${opts.env ? ` for env ${opts.env}` : ""} \u2192 ${opts.url}`);
|
|
2037
|
+
}
|
|
2038
|
+
async function runThrottleSet(project, opts) {
|
|
2039
|
+
const throttling = {};
|
|
2040
|
+
if (opts.rate !== void 0) throttling.userRateLimit = Number(opts.rate);
|
|
2041
|
+
if (opts.endUserRate !== void 0) throttling.endUserRateLimit = Number(opts.endUserRate);
|
|
2042
|
+
if (opts.quota !== void 0) throttling.proxyQuota = Number(opts.quota);
|
|
2043
|
+
if (opts.period !== void 0) {
|
|
2044
|
+
if (!["daily", "weekly", "monthly"].includes(opts.period)) {
|
|
2045
|
+
console.error(import_chalk19.default.red("--period must be daily, weekly, or monthly."));
|
|
2046
|
+
process.exit(1);
|
|
2047
|
+
}
|
|
2048
|
+
throttling.quotaPeriod = opts.period;
|
|
2049
|
+
}
|
|
2050
|
+
if (Object.keys(throttling).length === 0) {
|
|
2051
|
+
console.error(import_chalk19.default.red("Nothing to set. Pass at least one of --rate, --end-user-rate, --quota, --period."));
|
|
2052
|
+
process.exit(1);
|
|
2053
|
+
}
|
|
2054
|
+
await patchConfig(project, opts, { throttling }, `Update throttling ${JSON.stringify(throttling)}`);
|
|
2055
|
+
}
|
|
2056
|
+
async function runRename(project, opts) {
|
|
2057
|
+
if (!opts.displayName) {
|
|
2058
|
+
console.error(import_chalk19.default.red("--display-name is required."));
|
|
2059
|
+
process.exit(1);
|
|
2060
|
+
}
|
|
2061
|
+
await patchConfig(project, opts, { display_name: opts.displayName }, `Rename \u2192 "${opts.displayName}"`);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// src/commands/domain.ts
|
|
2065
|
+
var import_chalk20 = __toESM(require("chalk"));
|
|
2066
|
+
var import_ora7 = __toESM(require("ora"));
|
|
2067
|
+
async function runDomainAdd(project, opts) {
|
|
2068
|
+
if (!opts.domain) {
|
|
2069
|
+
console.error(import_chalk20.default.red("--domain is required."));
|
|
2070
|
+
process.exit(1);
|
|
2071
|
+
}
|
|
2072
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2073
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2074
|
+
const spinner = (0, import_ora7.default)(`Registering ${opts.domain}...`).start();
|
|
1837
2075
|
try {
|
|
1838
|
-
await
|
|
2076
|
+
const out = await admin({
|
|
2077
|
+
method: "POST",
|
|
2078
|
+
path: `/projects/${proj2.projectId}/cf-domains`,
|
|
2079
|
+
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}`
|
|
2081
|
+
});
|
|
2082
|
+
spinner.succeed(`Registered ${opts.domain}. Add these DNS records, then run \`apiblaze domain status\`:`);
|
|
2083
|
+
if (opts.json) {
|
|
2084
|
+
console.log(JSON.stringify(out));
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2087
|
+
if (out?.cnameRecord) console.log(` ${import_chalk20.default.cyan("CNAME")} ${out.cnameRecord.name ?? opts.domain} \u2192 ${out.cnameRecord.value ?? out.cnameRecord.target}`);
|
|
2088
|
+
if (out?.dcv) console.log(` ${import_chalk20.default.cyan("TXT")} ${out.dcv.name} = ${out.dcv.value}`);
|
|
2089
|
+
if (out?.id) console.log(import_chalk20.default.dim(` domain id: ${out.id}`));
|
|
1839
2090
|
} catch (err) {
|
|
1840
|
-
|
|
2091
|
+
spinner.fail("Domain registration failed.");
|
|
2092
|
+
throw err;
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
async function runDomainList(project, opts) {
|
|
2096
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2097
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2098
|
+
const out = await admin({
|
|
2099
|
+
method: "GET",
|
|
2100
|
+
path: `/projects/${proj2.projectId}/cf-domains?api_version=${encodeURIComponent(proj2.apiVersion)}`,
|
|
2101
|
+
summary: `List custom domains for ${proj2.projectName} v${proj2.apiVersion}`
|
|
2102
|
+
});
|
|
2103
|
+
const domains = out?.domains ?? [];
|
|
2104
|
+
if (opts.json) {
|
|
2105
|
+
console.log(JSON.stringify(domains));
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (!domains.length) {
|
|
2109
|
+
console.log(import_chalk20.default.yellow("No custom domains."));
|
|
2110
|
+
return;
|
|
2111
|
+
}
|
|
2112
|
+
for (const d of domains) console.log(` ${import_chalk20.default.bold(d.hostname)} ${import_chalk20.default.dim(d.status ?? "")} ${import_chalk20.default.dim(d.id ?? "")}`);
|
|
2113
|
+
}
|
|
2114
|
+
async function runDomainStatus(project, opts) {
|
|
2115
|
+
if (!opts.id) {
|
|
2116
|
+
console.error(import_chalk20.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
1841
2117
|
process.exit(1);
|
|
1842
2118
|
}
|
|
1843
|
-
});
|
|
1844
|
-
|
|
2119
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2120
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2121
|
+
const out = await admin({
|
|
2122
|
+
method: "GET",
|
|
2123
|
+
path: `/projects/${proj2.projectId}/cf-domains/${encodeURIComponent(opts.id)}/status`,
|
|
2124
|
+
summary: `Check custom-domain status`
|
|
2125
|
+
});
|
|
2126
|
+
console.log(opts.json ? JSON.stringify(out) : ` ${import_chalk20.default.bold(out?.hostname ?? opts.id)}: ${import_chalk20.default.cyan(out?.status ?? "unknown")}`);
|
|
2127
|
+
}
|
|
2128
|
+
async function runDomainRemove(project, opts) {
|
|
2129
|
+
if (!opts.id) {
|
|
2130
|
+
console.error(import_chalk20.default.red("--id <domainId> is required (see `apiblaze domain list`)."));
|
|
2131
|
+
process.exit(1);
|
|
2132
|
+
}
|
|
2133
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2134
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2135
|
+
const spinner = (0, import_ora7.default)("Removing domain...").start();
|
|
1845
2136
|
try {
|
|
1846
|
-
await
|
|
2137
|
+
await admin({
|
|
2138
|
+
method: "DELETE",
|
|
2139
|
+
path: `/projects/${proj2.projectId}/cf-domains/${encodeURIComponent(opts.id)}`,
|
|
2140
|
+
summary: `Remove custom domain ${opts.id}`
|
|
2141
|
+
});
|
|
2142
|
+
spinner.succeed("Removed.");
|
|
1847
2143
|
} catch (err) {
|
|
1848
|
-
|
|
2144
|
+
spinner.fail("Remove failed.");
|
|
2145
|
+
throw err;
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
async function runDomainSetBase(project, opts) {
|
|
2149
|
+
const env = opts.env ?? "prod";
|
|
2150
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2151
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2152
|
+
const spinner = (0, import_ora7.default)("Setting base domain...").start();
|
|
2153
|
+
try {
|
|
2154
|
+
await admin({
|
|
2155
|
+
method: "PUT",
|
|
2156
|
+
path: `/projects/${proj2.projectId}/base-domain`,
|
|
2157
|
+
body: { api_version: proj2.apiVersion, environment: env },
|
|
2158
|
+
summary: `Point bare hostname \u2192 v${proj2.apiVersion} / ${env}`
|
|
2159
|
+
});
|
|
2160
|
+
spinner.succeed(`Bare hostname now serves ${proj2.projectName} v${proj2.apiVersion} (${env}).`);
|
|
2161
|
+
} catch (err) {
|
|
2162
|
+
spinner.fail("set-base failed.");
|
|
2163
|
+
throw err;
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
|
|
2167
|
+
// src/commands/tenant.ts
|
|
2168
|
+
var import_chalk21 = __toESM(require("chalk"));
|
|
2169
|
+
var import_ora8 = __toESM(require("ora"));
|
|
2170
|
+
async function runTenantList(opts) {
|
|
2171
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2172
|
+
const out = await admin({
|
|
2173
|
+
method: "GET",
|
|
2174
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`,
|
|
2175
|
+
summary: `List tenants for team ${teamName ?? teamId}`
|
|
2176
|
+
});
|
|
2177
|
+
const tenants = out?.tenants ?? [];
|
|
2178
|
+
if (opts.json) {
|
|
2179
|
+
console.log(JSON.stringify(tenants));
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
if (!tenants.length) {
|
|
2183
|
+
console.log(import_chalk21.default.yellow("No tenants."));
|
|
2184
|
+
return;
|
|
2185
|
+
}
|
|
2186
|
+
for (const t of tenants) {
|
|
2187
|
+
const name = typeof t === "string" ? t : t.tenant_name;
|
|
2188
|
+
const display = typeof t === "string" ? "" : import_chalk21.default.dim(` ${t.display_name ?? ""}`);
|
|
2189
|
+
console.log(` ${import_chalk21.default.bold(name)}${display}`);
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
async function runTenantCreate(opts) {
|
|
2193
|
+
if (!opts.name) {
|
|
2194
|
+
console.error(import_chalk21.default.red("--name (display name) is required."));
|
|
2195
|
+
process.exit(1);
|
|
2196
|
+
}
|
|
2197
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2198
|
+
const spinner = (0, import_ora8.default)("Creating tenant...").start();
|
|
2199
|
+
try {
|
|
2200
|
+
const out = await admin({
|
|
2201
|
+
method: "POST",
|
|
2202
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants`,
|
|
2203
|
+
body: { display_name: opts.name, ...opts.slug ? { tenant_name: opts.slug } : {} },
|
|
2204
|
+
summary: `Create tenant "${opts.name}"`
|
|
2205
|
+
});
|
|
2206
|
+
spinner.succeed(`Created tenant ${import_chalk21.default.bold(out?.tenant_name ?? opts.name)}.`);
|
|
2207
|
+
if (opts.json) console.log(JSON.stringify(out));
|
|
2208
|
+
} catch (err) {
|
|
2209
|
+
spinner.fail("Tenant create failed.");
|
|
2210
|
+
throw err;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
async function runTenantAttach(project, opts) {
|
|
2214
|
+
if (!opts.tenant) {
|
|
2215
|
+
console.error(import_chalk21.default.red("--tenant <slug> is required."));
|
|
2216
|
+
process.exit(1);
|
|
2217
|
+
}
|
|
2218
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2219
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2220
|
+
const spinner = (0, import_ora8.default)("Attaching tenant...").start();
|
|
2221
|
+
try {
|
|
2222
|
+
const out = await admin({
|
|
2223
|
+
method: "POST",
|
|
2224
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}/tenants`,
|
|
2225
|
+
body: { tenant_name: opts.tenant, ...opts.authConfig ? { auth_config_id: opts.authConfig } : {} },
|
|
2226
|
+
summary: `Attach tenant ${opts.tenant} \u2192 ${proj2.projectName} v${proj2.apiVersion}`
|
|
2227
|
+
});
|
|
2228
|
+
spinner.succeed(`Attached ${opts.tenant} to ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
2229
|
+
if (opts.json) console.log(JSON.stringify(out));
|
|
2230
|
+
} catch (err) {
|
|
2231
|
+
spinner.fail("Attach failed.");
|
|
2232
|
+
throw err;
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
async function runTenantDelete(slug, opts) {
|
|
2236
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2237
|
+
if (!opts.yes && !opts.json) {
|
|
2238
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2239
|
+
const { confirm } = await inquirer2.prompt([
|
|
2240
|
+
{ type: "confirm", name: "confirm", message: `Permanently delete tenant "${slug}" and everything under it? This cannot be undone.`, default: false }
|
|
2241
|
+
]);
|
|
2242
|
+
if (!confirm) {
|
|
2243
|
+
console.log(import_chalk21.default.dim("Aborted."));
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
const spinner = (0, import_ora8.default)("Deleting tenant...").start();
|
|
2248
|
+
try {
|
|
2249
|
+
await admin({
|
|
2250
|
+
method: "DELETE",
|
|
2251
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(slug)}`,
|
|
2252
|
+
summary: `Delete tenant ${slug} (full cascade)`
|
|
2253
|
+
});
|
|
2254
|
+
spinner.succeed(`Deleted tenant ${slug}.`);
|
|
2255
|
+
} catch (err) {
|
|
2256
|
+
spinner.fail("Tenant delete failed.");
|
|
2257
|
+
throw err;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
async function runTenantCors(opts) {
|
|
2261
|
+
if (!opts.tenant) {
|
|
2262
|
+
console.error(import_chalk21.default.red("--tenant <slug> is required."));
|
|
2263
|
+
process.exit(1);
|
|
2264
|
+
}
|
|
2265
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2266
|
+
const origins = (opts.origins ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
2267
|
+
const cors = origins.length ? { allowed_origins: origins } : null;
|
|
2268
|
+
const spinner = (0, import_ora8.default)("Updating CORS...").start();
|
|
2269
|
+
try {
|
|
2270
|
+
await admin({
|
|
2271
|
+
method: "PUT",
|
|
2272
|
+
path: `/teams/${encodeURIComponent(teamId)}/tenants/${encodeURIComponent(opts.tenant)}/cors`,
|
|
2273
|
+
body: { cors },
|
|
2274
|
+
summary: `Set CORS for tenant ${opts.tenant} \u2192 ${origins.length ? origins.join(", ") : "(cleared)"}`
|
|
2275
|
+
});
|
|
2276
|
+
spinner.succeed(`CORS updated for ${opts.tenant}.`);
|
|
2277
|
+
} catch (err) {
|
|
2278
|
+
spinner.fail("CORS update failed.");
|
|
2279
|
+
throw err;
|
|
2280
|
+
}
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// src/commands/key.ts
|
|
2284
|
+
var import_chalk22 = __toESM(require("chalk"));
|
|
2285
|
+
var import_ora9 = __toESM(require("ora"));
|
|
2286
|
+
async function runKeyList(opts) {
|
|
2287
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2288
|
+
const out = await admin({
|
|
2289
|
+
method: "GET",
|
|
2290
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
2291
|
+
summary: `List developer keys for team ${teamName ?? teamId}`
|
|
2292
|
+
});
|
|
2293
|
+
const keys = out?.keys ?? [];
|
|
2294
|
+
if (opts.json) {
|
|
2295
|
+
console.log(JSON.stringify(keys));
|
|
2296
|
+
return;
|
|
2297
|
+
}
|
|
2298
|
+
if (!keys.length) {
|
|
2299
|
+
console.log(import_chalk22.default.yellow("No developer keys."));
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2302
|
+
for (const k of keys) {
|
|
2303
|
+
console.log(` ${import_chalk22.default.bold(k.key_id ?? k.id)} ${import_chalk22.default.dim(k.description ?? "")} ${import_chalk22.default.dim(k.expires_at ?? "no expiry")}`);
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
async function runKeyMint(opts) {
|
|
2307
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2308
|
+
const body = { role: "consumer-admin" };
|
|
2309
|
+
if (opts.desc) body.description = opts.desc;
|
|
2310
|
+
if (opts.expiresDays) body.expires_in_seconds = Number(opts.expiresDays) * 24 * 60 * 60;
|
|
2311
|
+
const spinner = (0, import_ora9.default)("Minting key...").start();
|
|
2312
|
+
try {
|
|
2313
|
+
const out = await admin({
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys`,
|
|
2316
|
+
body,
|
|
2317
|
+
summary: `Mint a consumer-admin developer key`
|
|
2318
|
+
});
|
|
2319
|
+
spinner.succeed("Key minted.");
|
|
2320
|
+
if (opts.json) {
|
|
2321
|
+
console.log(JSON.stringify(out));
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
console.log(` ${import_chalk22.default.bold("key_id")}: ${out?.key_id}`);
|
|
2325
|
+
console.log(` ${import_chalk22.default.bold("key")}: ${import_chalk22.default.green(out?.key)} ${import_chalk22.default.dim("(shown once \u2014 store it now)")}`);
|
|
2326
|
+
if (out?.expires_at) console.log(` ${import_chalk22.default.dim("expires:")} ${out.expires_at}`);
|
|
2327
|
+
} catch (err) {
|
|
2328
|
+
spinner.fail("Mint failed.");
|
|
2329
|
+
throw err;
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
async function runKeyRevoke(keyId, opts) {
|
|
2333
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2334
|
+
const spinner = (0, import_ora9.default)("Revoking key...").start();
|
|
2335
|
+
try {
|
|
2336
|
+
await admin({
|
|
2337
|
+
method: "DELETE",
|
|
2338
|
+
path: `/teams/${encodeURIComponent(teamId)}/developer-keys/${encodeURIComponent(keyId)}`,
|
|
2339
|
+
summary: `Revoke developer key ${keyId}`
|
|
2340
|
+
});
|
|
2341
|
+
spinner.succeed(`Revoked ${keyId}.`);
|
|
2342
|
+
} catch (err) {
|
|
2343
|
+
spinner.fail("Revoke failed.");
|
|
2344
|
+
throw err;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
// src/commands/spec.ts
|
|
2349
|
+
var fs4 = __toESM(require("fs"));
|
|
2350
|
+
var import_chalk23 = __toESM(require("chalk"));
|
|
2351
|
+
var import_ora10 = __toESM(require("ora"));
|
|
2352
|
+
async function runSpecGet(project, opts) {
|
|
2353
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2354
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2355
|
+
const out = await admin({
|
|
2356
|
+
method: "GET",
|
|
2357
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}/openapi`,
|
|
2358
|
+
summary: `Get OpenAPI spec for ${proj2.projectName} v${proj2.apiVersion}`
|
|
2359
|
+
});
|
|
2360
|
+
console.log(JSON.stringify(out, null, opts.json ? 0 : 2));
|
|
2361
|
+
}
|
|
2362
|
+
async function runSpecSet(project, opts) {
|
|
2363
|
+
if (!opts.file) {
|
|
2364
|
+
console.error(import_chalk23.default.red("--file <path> is required (OpenAPI JSON or YAML)."));
|
|
2365
|
+
process.exit(1);
|
|
2366
|
+
}
|
|
2367
|
+
let specContent;
|
|
2368
|
+
try {
|
|
2369
|
+
specContent = fs4.readFileSync(opts.file, "utf-8");
|
|
2370
|
+
} catch {
|
|
2371
|
+
console.error(import_chalk23.default.red(`Cannot read file: ${opts.file}`));
|
|
1849
2372
|
process.exit(1);
|
|
1850
2373
|
}
|
|
2374
|
+
const { teamId } = await resolveTeam(opts.team);
|
|
2375
|
+
const proj2 = await resolveProject(teamId, project, opts.apiversion);
|
|
2376
|
+
const spinner = (0, import_ora10.default)("Uploading spec...").start();
|
|
2377
|
+
try {
|
|
2378
|
+
const out = await admin({
|
|
2379
|
+
method: "POST",
|
|
2380
|
+
path: `/projects/${proj2.projectId}/${proj2.apiVersion}/refresh-spec`,
|
|
2381
|
+
body: { specContent },
|
|
2382
|
+
summary: `Set OpenAPI spec for ${proj2.projectName} v${proj2.apiVersion} from ${opts.file}`
|
|
2383
|
+
});
|
|
2384
|
+
spinner.succeed(`Spec updated for ${proj2.projectName} v${proj2.apiVersion}.`);
|
|
2385
|
+
if (opts.json) console.log(JSON.stringify(out ?? { ok: true }));
|
|
2386
|
+
} catch (err) {
|
|
2387
|
+
spinner.fail("Spec update failed.");
|
|
2388
|
+
throw err;
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
// src/commands/agent.ts
|
|
2393
|
+
var import_chalk24 = __toESM(require("chalk"));
|
|
2394
|
+
var import_ora11 = __toESM(require("ora"));
|
|
2395
|
+
|
|
2396
|
+
// src/lib/tools.ts
|
|
2397
|
+
async function proj(teamId, name, version2) {
|
|
2398
|
+
return resolveProject(teamId, name, version2);
|
|
2399
|
+
}
|
|
2400
|
+
var TOOLS = [
|
|
2401
|
+
{
|
|
2402
|
+
name: "list_projects",
|
|
2403
|
+
description: "List the proxies (projects) in the active team.",
|
|
2404
|
+
params: {},
|
|
2405
|
+
run: async (_a, { teamId }) => getProjects(teamId)
|
|
2406
|
+
},
|
|
2407
|
+
{
|
|
2408
|
+
name: "set_target",
|
|
2409
|
+
description: "Set a proxy's target URL. Use env to scope to one environment (e.g. prod).",
|
|
2410
|
+
params: { project: "project name or id", url: "target URL", env: "optional environment" },
|
|
2411
|
+
run: async (a, { teamId }) => {
|
|
2412
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2413
|
+
const body = a.env ? { environments: { [a.env]: { target: a.url } } } : { target_url: a.url };
|
|
2414
|
+
return admin({ method: "PATCH", path: `/${p.projectId}/${p.apiVersion}/config`, body, summary: `Set target for ${p.projectName}` });
|
|
2415
|
+
}
|
|
2416
|
+
},
|
|
2417
|
+
{
|
|
2418
|
+
name: "set_throttle",
|
|
2419
|
+
description: "Set per-proxy throttling. rate=req/sec, quota=req/period, period=daily|weekly|monthly.",
|
|
2420
|
+
params: { project: "project name or id", rate: "optional req/sec", quota: "optional req/period", period: "optional daily|weekly|monthly" },
|
|
2421
|
+
run: async (a, { teamId }) => {
|
|
2422
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2423
|
+
const throttling = {};
|
|
2424
|
+
if (a.rate != null) throttling.userRateLimit = Number(a.rate);
|
|
2425
|
+
if (a.quota != null) throttling.proxyQuota = Number(a.quota);
|
|
2426
|
+
if (a.period) throttling.quotaPeriod = a.period;
|
|
2427
|
+
return admin({ method: "PATCH", path: `/${p.projectId}/${p.apiVersion}/config`, body: { throttling }, summary: `Throttle ${p.projectName}` });
|
|
2428
|
+
}
|
|
2429
|
+
},
|
|
2430
|
+
{
|
|
2431
|
+
name: "rename_proxy",
|
|
2432
|
+
description: "Change a proxy's display name.",
|
|
2433
|
+
params: { project: "project name or id", display_name: "new display name" },
|
|
2434
|
+
run: async (a, { teamId }) => {
|
|
2435
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2436
|
+
return admin({ method: "PATCH", path: `/${p.projectId}/${p.apiVersion}/config`, body: { display_name: a.display_name }, summary: `Rename ${p.projectName}` });
|
|
2437
|
+
}
|
|
2438
|
+
},
|
|
2439
|
+
{
|
|
2440
|
+
name: "delete_proxy",
|
|
2441
|
+
description: "Delete a proxy (full cascade). Irreversible \u2014 confirm with the user first.",
|
|
2442
|
+
params: { project: "project name or id", version: "optional api version" },
|
|
2443
|
+
run: async (a, { teamId }) => {
|
|
2444
|
+
const p = await proj(teamId, a.project, a.version);
|
|
2445
|
+
return admin({ method: "DELETE", path: `/${p.projectId}/${p.apiVersion}`, summary: `Delete ${p.projectName} (cascade)` });
|
|
2446
|
+
}
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
name: "list_tenants",
|
|
2450
|
+
description: "List tenants in the active team.",
|
|
2451
|
+
params: {},
|
|
2452
|
+
run: async (_a, { teamId }) => admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/tenants?detail=1`, summary: "List tenants" })
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
name: "create_tenant",
|
|
2456
|
+
description: "Create a tenant (consumer scope) in the active team.",
|
|
2457
|
+
params: { name: "display name", slug: "optional explicit slug" },
|
|
2458
|
+
run: async (a, { teamId }) => admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/tenants`, body: { display_name: a.name, ...a.slug ? { tenant_name: a.slug } : {} }, summary: `Create tenant ${a.name}` })
|
|
2459
|
+
},
|
|
2460
|
+
{
|
|
2461
|
+
name: "attach_tenant",
|
|
2462
|
+
description: "Attach a tenant to a proxy.",
|
|
2463
|
+
params: { project: "project name or id", tenant: "tenant slug" },
|
|
2464
|
+
run: async (a, { teamId }) => {
|
|
2465
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2466
|
+
return admin({ method: "POST", path: `/projects/${p.projectId}/${p.apiVersion}/tenants`, body: { tenant_name: a.tenant }, summary: `Attach ${a.tenant} \u2192 ${p.projectName}` });
|
|
2467
|
+
}
|
|
2468
|
+
},
|
|
2469
|
+
{
|
|
2470
|
+
name: "list_keys",
|
|
2471
|
+
description: "List control-plane developer keys for the active team.",
|
|
2472
|
+
params: {},
|
|
2473
|
+
run: async (_a, { teamId }) => admin({ method: "GET", path: `/teams/${encodeURIComponent(teamId)}/developer-keys`, summary: "List developer keys" })
|
|
2474
|
+
},
|
|
2475
|
+
{
|
|
2476
|
+
name: "mint_key",
|
|
2477
|
+
description: "Mint a consumer-admin developer key (secret returned once).",
|
|
2478
|
+
params: { desc: "optional description", expires_days: "optional expiry in days" },
|
|
2479
|
+
run: async (a, { teamId }) => {
|
|
2480
|
+
const body = { role: "consumer-admin" };
|
|
2481
|
+
if (a.desc) body.description = a.desc;
|
|
2482
|
+
if (a.expires_days) body.expires_in_seconds = Number(a.expires_days) * 86400;
|
|
2483
|
+
return admin({ method: "POST", path: `/teams/${encodeURIComponent(teamId)}/developer-keys`, body, summary: "Mint developer key" });
|
|
2484
|
+
}
|
|
2485
|
+
},
|
|
2486
|
+
{
|
|
2487
|
+
name: "list_domains",
|
|
2488
|
+
description: "List custom domains for a proxy.",
|
|
2489
|
+
params: { project: "project name or id" },
|
|
2490
|
+
run: async (a, { teamId }) => {
|
|
2491
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2492
|
+
return admin({ method: "GET", path: `/projects/${p.projectId}/cf-domains?api_version=${encodeURIComponent(p.apiVersion)}`, summary: `List domains for ${p.projectName}` });
|
|
2493
|
+
}
|
|
2494
|
+
},
|
|
2495
|
+
{
|
|
2496
|
+
name: "set_base_domain",
|
|
2497
|
+
description: "Point the bare hostname of a proxy at a (version, environment).",
|
|
2498
|
+
params: { project: "project name or id", env: "environment (default prod)" },
|
|
2499
|
+
run: async (a, { teamId }) => {
|
|
2500
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2501
|
+
return admin({ method: "PUT", path: `/projects/${p.projectId}/base-domain`, body: { api_version: p.apiVersion, environment: a.env ?? "prod" }, summary: `Base domain \u2192 ${p.projectName}` });
|
|
2502
|
+
}
|
|
2503
|
+
},
|
|
2504
|
+
{
|
|
2505
|
+
name: "get_spec",
|
|
2506
|
+
description: "Fetch the OpenAPI spec of a proxy.",
|
|
2507
|
+
params: { project: "project name or id" },
|
|
2508
|
+
run: async (a, { teamId }) => {
|
|
2509
|
+
const p = await proj(teamId, a.project, a.apiversion);
|
|
2510
|
+
return admin({ method: "GET", path: `/projects/${p.projectId}/${p.apiVersion}/openapi`, summary: `Get spec for ${p.projectName}` });
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
];
|
|
2514
|
+
function toolCatalogue() {
|
|
2515
|
+
return TOOLS.map(({ name, description, params }) => ({ name, description, params }));
|
|
2516
|
+
}
|
|
2517
|
+
function findTool(name) {
|
|
2518
|
+
return TOOLS.find((t) => t.name === name);
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
// src/commands/agent.ts
|
|
2522
|
+
var DASHBOARD_BASE4 = process.env.APIBLAZE_DASHBOARD_BASE || "https://dashboard.apiblaze.com";
|
|
2523
|
+
var MAX_TOOL_STEPS = 6;
|
|
2524
|
+
async function callAgent(messages, teamId) {
|
|
2525
|
+
const token = getAccessToken();
|
|
2526
|
+
const res = await fetch(`${DASHBOARD_BASE4}/api/cli/agent`, {
|
|
2527
|
+
method: "POST",
|
|
2528
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}` },
|
|
2529
|
+
body: JSON.stringify({ messages: messages.slice(-20), tools: toolCatalogue(), team_id: teamId })
|
|
2530
|
+
});
|
|
2531
|
+
let data = null;
|
|
2532
|
+
try {
|
|
2533
|
+
data = await res.json();
|
|
2534
|
+
} catch {
|
|
2535
|
+
}
|
|
2536
|
+
if (!res.ok) throw new ApiError(res.status, data?.error ?? res.statusText, data);
|
|
2537
|
+
return data;
|
|
2538
|
+
}
|
|
2539
|
+
function truncate(value, max = 1500) {
|
|
2540
|
+
let s;
|
|
2541
|
+
try {
|
|
2542
|
+
s = typeof value === "string" ? value : JSON.stringify(value);
|
|
2543
|
+
} catch {
|
|
2544
|
+
s = String(value);
|
|
2545
|
+
}
|
|
2546
|
+
if (s.length <= max) return s;
|
|
2547
|
+
return s.slice(0, max) + `\u2026 [truncated ${s.length - max} chars]`;
|
|
2548
|
+
}
|
|
2549
|
+
function printCost(llm) {
|
|
2550
|
+
const usd = llm.cost > 0 ? `$${llm.cost.toFixed(4)}` : "<$0.0001";
|
|
2551
|
+
console.log(import_chalk24.default.magenta(` \u{1F4B3} ${usd}`) + import_chalk24.default.dim(` (${llm.model}, ${llm.total_tokens} tok)`));
|
|
2552
|
+
}
|
|
2553
|
+
async function runAgent(opts) {
|
|
2554
|
+
requireAuth();
|
|
2555
|
+
const { teamId, teamName } = await resolveTeam(opts.team);
|
|
2556
|
+
const { default: inquirer2 } = await import("inquirer");
|
|
2557
|
+
console.log(import_chalk24.default.bold("APIblaze agent") + import_chalk24.default.dim(` \xB7 team ${teamName ?? teamId}`));
|
|
2558
|
+
console.log(import_chalk24.default.dim('Ask me to create/delete/configure proxies, tenants, keys, domains, specs. Type "exit" to quit.\n'));
|
|
2559
|
+
const history = [];
|
|
2560
|
+
while (true) {
|
|
2561
|
+
const { input } = await inquirer2.prompt([{ type: "input", name: "input", message: import_chalk24.default.cyan("you") + " \u203A" }]);
|
|
2562
|
+
const text = (input ?? "").trim();
|
|
2563
|
+
if (!text) continue;
|
|
2564
|
+
if (["exit", "quit", ":q"].includes(text.toLowerCase())) break;
|
|
2565
|
+
history.push({ role: "user", content: text });
|
|
2566
|
+
for (let step = 0; step < MAX_TOOL_STEPS; step++) {
|
|
2567
|
+
const spinner = (0, import_ora11.default)({ text: "thinking...", color: "magenta" }).start();
|
|
2568
|
+
let resp;
|
|
2569
|
+
try {
|
|
2570
|
+
resp = await callAgent(history, teamId);
|
|
2571
|
+
spinner.stop();
|
|
2572
|
+
} catch (err) {
|
|
2573
|
+
spinner.stop();
|
|
2574
|
+
if (err instanceof ApiError && err.status === 402) {
|
|
2575
|
+
console.log(import_chalk24.default.yellow(" Insufficient credits \u2014 top up to keep using the agent."));
|
|
2576
|
+
break;
|
|
2577
|
+
}
|
|
2578
|
+
throw err;
|
|
2579
|
+
}
|
|
2580
|
+
history.push({ role: "assistant", content: resp.raw });
|
|
2581
|
+
printCost(resp.llm);
|
|
2582
|
+
if (resp.reply) console.log(import_chalk24.default.green("agent") + " \u203A " + resp.reply);
|
|
2583
|
+
if (!resp.action) break;
|
|
2584
|
+
const tool = findTool(resp.action.tool);
|
|
2585
|
+
if (!tool) {
|
|
2586
|
+
history.push({ role: "user", content: `TOOL_RESULT ${resp.action.tool}: error \u2014 unknown tool` });
|
|
2587
|
+
continue;
|
|
2588
|
+
}
|
|
2589
|
+
const runSpinner = (0, import_ora11.default)({ text: `running ${tool.name}...`, color: "cyan" }).start();
|
|
2590
|
+
try {
|
|
2591
|
+
const result = await tool.run(resp.action.args, { teamId });
|
|
2592
|
+
runSpinner.succeed(`${tool.name} \u2713`);
|
|
2593
|
+
history.push({ role: "user", content: `TOOL_RESULT ${tool.name}: ${truncate(result)}` });
|
|
2594
|
+
} catch (err) {
|
|
2595
|
+
runSpinner.fail(`${tool.name} failed`);
|
|
2596
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2597
|
+
history.push({ role: "user", content: `TOOL_RESULT ${tool.name}: error \u2014 ${truncate(msg, 400)}` });
|
|
2598
|
+
}
|
|
2599
|
+
if (step === MAX_TOOL_STEPS - 1) {
|
|
2600
|
+
console.log(import_chalk24.default.dim(" (paused after several steps \u2014 tell me how to continue)"));
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
console.log(import_chalk24.default.dim("\nBye."));
|
|
2605
|
+
}
|
|
2606
|
+
|
|
2607
|
+
// src/index.ts
|
|
2608
|
+
var program = new import_commander.Command();
|
|
2609
|
+
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)");
|
|
2610
|
+
program.hook("preAction", () => {
|
|
2611
|
+
if (program.opts().verbose) setVerbose(true);
|
|
1851
2612
|
});
|
|
1852
|
-
|
|
2613
|
+
process.on("exit", () => renderTrace());
|
|
2614
|
+
function action(fn) {
|
|
2615
|
+
return async (...args) => {
|
|
2616
|
+
try {
|
|
2617
|
+
await fn(...args);
|
|
2618
|
+
} catch (err) {
|
|
2619
|
+
printError(err);
|
|
2620
|
+
process.exit(1);
|
|
2621
|
+
}
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
program.command("login").description("Authenticate with APIblaze").action(async () => {
|
|
1853
2625
|
try {
|
|
1854
|
-
await
|
|
2626
|
+
await runLogin();
|
|
1855
2627
|
} catch (err) {
|
|
1856
2628
|
printError(err);
|
|
1857
2629
|
process.exit(1);
|
|
1858
2630
|
}
|
|
1859
2631
|
});
|
|
1860
|
-
program.command("
|
|
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("--tenant <slug>", "Tenant slug (anonymous create; generated if omitted)").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) => {
|
|
1861
2633
|
try {
|
|
1862
|
-
await
|
|
2634
|
+
await runCreate(opts);
|
|
1863
2635
|
} catch (err) {
|
|
1864
2636
|
printError(err);
|
|
1865
2637
|
process.exit(1);
|
|
1866
2638
|
}
|
|
1867
2639
|
});
|
|
1868
|
-
program.command("
|
|
2640
|
+
var agent = program.command("agent").description("Chat with an assistant that builds and runs your APIs (billed per turn)").option("--team <id|name>", "Team to work in (defaults to your active team)").action(action((opts) => runAgent(opts)));
|
|
2641
|
+
agent.command("authz").description("Chat to design and turn on access rules for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runAuthz(project, apiVersion)));
|
|
2642
|
+
agent.command("openapi").description("Chat to build your API spec from real traffic").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").action(action((project, apiVersion) => runOpenapi(project, apiVersion)));
|
|
2643
|
+
agent.command("mcp").description("Chat to build an MCP server for an API").argument("<project>", "Project name or id").argument("[apiVersion]", "API version (defaults to the project's)").option("--environment <env>", "Environment to publish (default: prod)").action(action((project, apiVersion, opts) => runMcp(project, apiVersion, opts)));
|
|
2644
|
+
program.command("dev").description("Put your localhost behind a public URL (dev tunnel)").argument("[port]", "Local port to tunnel (positional; overrides --port)").option("-p, --port <number>", "Local port to tunnel", "3000").option("-o, --capture-file <path>", "Stream full request/response traffic to a file (JSON lines)").action(async (port, opts) => {
|
|
1869
2645
|
try {
|
|
1870
|
-
|
|
2646
|
+
const resolved = parseInt(port ?? opts.port, 10);
|
|
2647
|
+
if (Number.isNaN(resolved)) {
|
|
2648
|
+
console.error(import_chalk25.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
2649
|
+
process.exit(1);
|
|
2650
|
+
}
|
|
2651
|
+
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
1871
2652
|
} catch (err) {
|
|
1872
2653
|
printError(err);
|
|
1873
2654
|
process.exit(1);
|
|
1874
2655
|
}
|
|
1875
2656
|
});
|
|
1876
|
-
program.command("
|
|
2657
|
+
program.command("logout").description("Sign out \u2014 remove stored credentials from this machine").action(async () => {
|
|
1877
2658
|
try {
|
|
1878
|
-
await
|
|
2659
|
+
await runLogout();
|
|
1879
2660
|
} catch (err) {
|
|
1880
2661
|
printError(err);
|
|
1881
2662
|
process.exit(1);
|
|
1882
2663
|
}
|
|
1883
2664
|
});
|
|
1884
|
-
program.command("
|
|
2665
|
+
program.command("whoami").description("Show the signed-in identity and active team").option("--json", "Output machine-readable JSON").action(async (opts) => {
|
|
1885
2666
|
try {
|
|
1886
|
-
await
|
|
2667
|
+
await runWhoami(opts);
|
|
1887
2668
|
} catch (err) {
|
|
1888
2669
|
printError(err);
|
|
1889
2670
|
process.exit(1);
|
|
1890
2671
|
}
|
|
1891
2672
|
});
|
|
1892
|
-
program.command("
|
|
2673
|
+
program.command("claim").description("Claim an anonymously-created proxy into one of your teams (requires login)").argument("[code]", "Claim code from `create` output / claim URL (prompted if omitted)").option("--team <id|name>", "Team to claim into (defaults to your active team)").option("--json", "Output machine-readable JSON (non-interactive)").action(async (code, opts) => {
|
|
1893
2674
|
try {
|
|
1894
|
-
await
|
|
2675
|
+
await runClaim(code, opts);
|
|
1895
2676
|
} catch (err) {
|
|
1896
2677
|
printError(err);
|
|
1897
2678
|
process.exit(1);
|
|
1898
2679
|
}
|
|
1899
2680
|
});
|
|
1900
|
-
program.command("
|
|
2681
|
+
program.command("team").description("Switch the active team").argument("[team]", "Team name or id to switch to (omit to choose interactively)").action(async (team) => {
|
|
1901
2682
|
try {
|
|
1902
|
-
await
|
|
2683
|
+
await runTeam(team);
|
|
1903
2684
|
} catch (err) {
|
|
1904
2685
|
printError(err);
|
|
1905
2686
|
process.exit(1);
|
|
1906
2687
|
}
|
|
1907
2688
|
});
|
|
1908
|
-
program.command("
|
|
2689
|
+
program.command("projects").description("List the projects in your team").action(async () => {
|
|
1909
2690
|
try {
|
|
1910
|
-
|
|
1911
|
-
if (Number.isNaN(resolved)) {
|
|
1912
|
-
console.error(import_chalk15.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
1913
|
-
process.exit(1);
|
|
1914
|
-
}
|
|
1915
|
-
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
2691
|
+
await runProjects();
|
|
1916
2692
|
} catch (err) {
|
|
1917
2693
|
printError(err);
|
|
1918
2694
|
process.exit(1);
|
|
1919
2695
|
}
|
|
1920
2696
|
});
|
|
2697
|
+
program.command("delete").description("Delete a proxy and everything under it (asks first)").argument("<project>", "Project name or id (see `apiblaze projects`)").argument("[version]", "API version (defaults to the first match)").option("--team <id|name>", "Team the project is in (defaults to active team)").option("-y, --yes", "Skip the confirmation prompt").option("--json", "Output machine-readable JSON").action(action((project, version2, opts) => runDelete(project, version2, opts)));
|
|
2698
|
+
program.command("target").description("Change where a proxy forwards requests").argument("<project>", "Project name or id").requiredOption("--url <url>", "Target URL to forward to").option("--env <env>", "Environment to scope the target to (e.g. prod, dev)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version (defaults to the first match)").option("--json", "Output machine-readable JSON").action(action((project, opts) => runTargetSet(project, opts)));
|
|
2699
|
+
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
|
+
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
|
+
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>", "Tenant to scope the domain to").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
|
+
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
|
+
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
|
+
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)));
|
|
2706
|
+
domain.command("set-base").description("Choose which version/environment your main URL serves").argument("<project>", "Project name or id").option("--env <env>", "Environment (default: prod)").option("--team <id|name>", "Team the project is in").option("--apiversion <version>", "API version").action(action((project, opts) => runDomainSetBase(project, opts)));
|
|
2707
|
+
var tenant = program.command("tenant").description("Manage tenants \u2014 separate groups of your API's users");
|
|
2708
|
+
tenant.command("list").description("List tenants in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantList(opts)));
|
|
2709
|
+
tenant.command("create").description("Create a tenant in your team").requiredOption("--name <display>", "Display name").option("--slug <tenant_name>", "Explicit tenant slug (generated if omitted)").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runTenantCreate(opts)));
|
|
2710
|
+
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
|
+
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
|
+
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 key = program.command("key").description("Create keys to manage your account from scripts or curl");
|
|
2714
|
+
key.command("list").description("List developer keys in your team").option("--team <id|name>", "Team (defaults to active team)").option("--json", "Output machine-readable JSON").action(action((opts) => runKeyList(opts)));
|
|
2715
|
+
key.command("mint").description("Mint a consumer-admin developer 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)));
|
|
2716
|
+
key.command("revoke").description("Revoke a developer key").argument("<keyId>", "Key id (see `key list`)").option("--team <id|name>", "Team (defaults to active team)").action(action((keyId, opts) => runKeyRevoke(keyId, opts)));
|
|
2717
|
+
var spec = program.command("spec").description("View or update a proxy's OpenAPI spec (or build one by chatting: apiblaze agent openapi)");
|
|
2718
|
+
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
|
+
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)));
|
|
1921
2720
|
program.addHelpText(
|
|
1922
2721
|
"after",
|
|
1923
2722
|
`
|
|
1924
2723
|
Examples:
|
|
1925
|
-
#
|
|
1926
|
-
$ npx apiblaze
|
|
2724
|
+
# Just talk to it \u2014 create, configure, inspect your APIs by chatting:
|
|
2725
|
+
$ npx apiblaze agent
|
|
1927
2726
|
|
|
1928
|
-
#
|
|
1929
|
-
$ npx apiblaze create --target https://api.example.com
|
|
1930
|
-
|
|
1931
|
-
# Sign in, then create under your team:
|
|
1932
|
-
$ npx apiblaze login
|
|
1933
|
-
$ npx apiblaze create --name myapi --target https://api.example.com --auth api_key
|
|
2727
|
+
# Make an API in one line (no account needed; prints a claim URL):
|
|
2728
|
+
$ npx apiblaze create --target https://api.example.com
|
|
1934
2729
|
|
|
1935
|
-
#
|
|
1936
|
-
# traffic (full headers + body, secrets masked) until your server is up:
|
|
2730
|
+
# Run your localhost through a public URL:
|
|
1937
2731
|
$ npx apiblaze dev 3000
|
|
1938
|
-
$ npx apiblaze dev 3000 --capture-file traffic.jsonl
|
|
1939
2732
|
|
|
1940
|
-
#
|
|
1941
|
-
$ npx apiblaze
|
|
1942
|
-
$ npx apiblaze
|
|
1943
|
-
$ npx apiblaze
|
|
1944
|
-
$ npx apiblaze
|
|
2733
|
+
# Or drive it yourself (add --verbose to see the equivalent API calls):
|
|
2734
|
+
$ npx apiblaze target myapi --url https://api.example.com
|
|
2735
|
+
$ npx apiblaze throttle myapi --rate 50
|
|
2736
|
+
$ npx apiblaze domain add myapi --domain api.mysite.com
|
|
2737
|
+
$ npx apiblaze delete myapi --verbose
|
|
1945
2738
|
`
|
|
1946
2739
|
);
|
|
1947
2740
|
function printError(err) {
|
|
1948
2741
|
if (err instanceof ApiError) {
|
|
1949
|
-
console.error(
|
|
2742
|
+
console.error(import_chalk25.default.red(`
|
|
1950
2743
|
API error (${err.status}): ${err.message}`));
|
|
1951
2744
|
} else if (err instanceof Error) {
|
|
1952
|
-
console.error(
|
|
2745
|
+
console.error(import_chalk25.default.red(`
|
|
1953
2746
|
Error: ${err.message}`));
|
|
1954
2747
|
} else {
|
|
1955
|
-
console.error(
|
|
2748
|
+
console.error(import_chalk25.default.red("\nUnknown error"));
|
|
1956
2749
|
}
|
|
1957
2750
|
}
|
|
1958
2751
|
program.parse(process.argv);
|