apiblaze 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -7
- package/dist/index.js +861 -31
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,23 +44,79 @@ npx apiblaze dev 3000 --capture-file traffic.jsonl
|
|
|
44
44
|
## Help
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
apiblaze --help
|
|
48
|
-
apiblaze help create
|
|
49
|
-
apiblaze help
|
|
47
|
+
apiblaze --help # all commands
|
|
48
|
+
apiblaze help create # help for one command
|
|
49
|
+
apiblaze domain --help # subcommands of a group (domain/tenant/key/spec)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Add `--verbose` (or `-v`) to **any** command to print the exact series of API
|
|
53
|
+
calls it makes — as copy-pasteable `curl` you could run yourself with your own
|
|
54
|
+
login token, plus the underlying admin-api leaf each one drives:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
apiblaze throttle myapi --rate 50 --verbose
|
|
50
58
|
```
|
|
51
59
|
|
|
52
60
|
## Commands
|
|
53
61
|
|
|
62
|
+
### Account & basics
|
|
63
|
+
|
|
54
64
|
| Command | Description |
|
|
55
65
|
|---|---|
|
|
56
66
|
| `apiblaze login` | Authenticate with your APIblaze account |
|
|
67
|
+
| `apiblaze logout` | Sign out and remove stored credentials |
|
|
68
|
+
| `apiblaze whoami` | Show the signed-in identity and active team |
|
|
69
|
+
| `apiblaze team [team]` | Switch the active team |
|
|
70
|
+
| `apiblaze projects` | List your team's proxies |
|
|
57
71
|
| `apiblaze create [options]` | Create a new API proxy (anonymous if not logged in) |
|
|
58
72
|
| `apiblaze claim [code]` | Claim an anonymously-created proxy into your team |
|
|
59
|
-
| `apiblaze projects` | List your team projects |
|
|
60
73
|
| `apiblaze dev [port]` | Start a dev tunnel for your localhost projects |
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
74
|
+
|
|
75
|
+
### Manage a proxy
|
|
76
|
+
|
|
77
|
+
| Command | Description |
|
|
78
|
+
|---|---|
|
|
79
|
+
| `apiblaze delete <project> [version]` | Delete a proxy (full cascade) — shows impact, then confirms |
|
|
80
|
+
| `apiblaze target <project> --url <u> [--env <e>]` | Set the target URL (per-environment with `--env`, else project-level) |
|
|
81
|
+
| `apiblaze throttle <project> [--rate n] [--quota n] [--period daily\|weekly\|monthly]` | Set rate limits + quota |
|
|
82
|
+
| `apiblaze rename <project> --display-name <name>` | Change the display name |
|
|
83
|
+
| `apiblaze spec get <project>` | Print the current OpenAPI document |
|
|
84
|
+
| `apiblaze spec set <project> --file <path>` | Replace the OpenAPI spec from a local file |
|
|
85
|
+
|
|
86
|
+
### Custom domains
|
|
87
|
+
|
|
88
|
+
| Command | Description |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `apiblaze domain add <project> --domain <host>` | Register a custom hostname (prints DNS records; does not poll) |
|
|
91
|
+
| `apiblaze domain list <project>` | List custom domains |
|
|
92
|
+
| `apiblaze domain status <project> --id <domainId>` | Check a domain's validation status |
|
|
93
|
+
| `apiblaze domain rm <project> --id <domainId>` | Remove a custom domain |
|
|
94
|
+
| `apiblaze domain set-base <project> [--env <e>]` | Point the bare `{project}` hostname at a (version, env) |
|
|
95
|
+
|
|
96
|
+
### Tenants & keys
|
|
97
|
+
|
|
98
|
+
| Command | Description |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `apiblaze tenant list` | List tenants in your team |
|
|
101
|
+
| `apiblaze tenant create --name <display> [--slug <s>]` | Create a tenant |
|
|
102
|
+
| `apiblaze tenant attach <project> --tenant <slug>` | Attach a tenant to a proxy |
|
|
103
|
+
| `apiblaze tenant cors --tenant <slug> --origins <a,b>` | Set the CORS allow-list for a tenant |
|
|
104
|
+
| `apiblaze tenant delete <slug>` | Delete a tenant (full cascade) |
|
|
105
|
+
| `apiblaze key list` | List control-plane developer keys |
|
|
106
|
+
| `apiblaze key mint [--desc <text>] [--expires-days <n>]` | Mint a consumer-admin key (secret shown once) |
|
|
107
|
+
| `apiblaze key revoke <keyId>` | Revoke a developer key |
|
|
108
|
+
|
|
109
|
+
### Design assistants (chat)
|
|
110
|
+
|
|
111
|
+
| Command | Description |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `apiblaze agent` | Chat with a producer assistant that can create/manage your proxies, tenants, keys, domains, and specs. Billed per turn (cost shown). |
|
|
114
|
+
| `apiblaze authz <project>` | Design API authorization interactively, then publish + enable it |
|
|
115
|
+
| `apiblaze openapi <project>` | Design your OpenAPI spec from captured traffic, then publish it |
|
|
116
|
+
| `apiblaze mcp <project>` | Design an MCP server from the spec + traffic, then publish it |
|
|
117
|
+
|
|
118
|
+
> Most management commands accept `--team <id\|name>` (defaults to your active team),
|
|
119
|
+
> `--apiversion <version>`, and `--json` (machine-readable output for scripts).
|
|
64
120
|
|
|
65
121
|
## How it works
|
|
66
122
|
|
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.0";
|
|
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.",
|
|
@@ -1792,15 +1792,16 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
1792
1792
|
const projectId = match.projectId;
|
|
1793
1793
|
const apiVersion = apiVersionArg || match.apiVersion;
|
|
1794
1794
|
const environment = opts.environment || "prod";
|
|
1795
|
+
const mcpHost = match.tenant ? `${projectId}-${match.tenant}` : projectId;
|
|
1795
1796
|
async function publish(ctx) {
|
|
1796
|
-
const
|
|
1797
|
-
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 });
|
|
1798
1799
|
if (pub.status >= 400) {
|
|
1799
1800
|
ctx.log(import_chalk14.default.red(` Publish failed (${pub.status}): ${pub.data?.error ?? ""}
|
|
1800
1801
|
`));
|
|
1801
1802
|
return;
|
|
1802
1803
|
}
|
|
1803
|
-
ctx.log(import_chalk14.default.green(` \u2713 Published MCP server \u2014 ${
|
|
1804
|
+
ctx.log(import_chalk14.default.green(` \u2713 Published MCP server \u2014 ${mcpHost}.mcp.apiblaze.com/${apiVersion}/${environment}.
|
|
1804
1805
|
`));
|
|
1805
1806
|
}
|
|
1806
1807
|
await runAgentChatRepl({
|
|
@@ -1810,20 +1811,816 @@ async function runMcp(projectArg, apiVersionArg, opts) {
|
|
|
1810
1811
|
buildBody: () => ({ environment, included_sample_ids: [] }),
|
|
1811
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.",
|
|
1812
1813
|
summarizeProposal: (data) => {
|
|
1813
|
-
const
|
|
1814
|
-
const tools = Array.isArray(
|
|
1814
|
+
const spec2 = data.proposal;
|
|
1815
|
+
const tools = Array.isArray(spec2.tools) ? spec2.tools : [];
|
|
1815
1816
|
const names = tools.slice(0, 12).map((t) => t.name ?? "(unnamed)").join(", ");
|
|
1816
1817
|
return `Catalogue ready: ${tools.length} tool(s)${names ? ` \u2014 ${names}${tools.length > 12 ? ", \u2026" : ""}` : ""}.`;
|
|
1817
1818
|
},
|
|
1818
1819
|
commands: [
|
|
1819
|
-
{ name: "publish", describe: `to ${
|
|
1820
|
+
{ name: "publish", describe: `to ${mcpHost}.mcp.apiblaze.com`, needsProposal: true, run: publish }
|
|
1820
1821
|
]
|
|
1821
1822
|
});
|
|
1822
1823
|
}
|
|
1823
1824
|
|
|
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;
|
|
1882
|
+
try {
|
|
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();
|
|
1968
|
+
} catch (err) {
|
|
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."));
|
|
2033
|
+
process.exit(1);
|
|
2034
|
+
}
|
|
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();
|
|
2075
|
+
try {
|
|
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}`));
|
|
2090
|
+
} catch (err) {
|
|
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`)."));
|
|
2117
|
+
process.exit(1);
|
|
2118
|
+
}
|
|
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();
|
|
2136
|
+
try {
|
|
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.");
|
|
2143
|
+
} catch (err) {
|
|
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}`));
|
|
2372
|
+
process.exit(1);
|
|
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
|
+
|
|
1824
2607
|
// src/index.ts
|
|
1825
2608
|
var program = new import_commander.Command();
|
|
1826
|
-
program.name("apiblaze").description("APIblaze CLI \u2014 create & manage API proxies and run dev tunnels").version(version);
|
|
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);
|
|
2612
|
+
});
|
|
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
|
+
}
|
|
1827
2624
|
program.command("login").description("Authenticate with APIblaze").action(async () => {
|
|
1828
2625
|
try {
|
|
1829
2626
|
await runLogin();
|
|
@@ -1908,7 +2705,7 @@ program.command("dev").description("Start a dev tunnel for your localhost projec
|
|
|
1908
2705
|
try {
|
|
1909
2706
|
const resolved = parseInt(port ?? opts.port, 10);
|
|
1910
2707
|
if (Number.isNaN(resolved)) {
|
|
1911
|
-
console.error(
|
|
2708
|
+
console.error(import_chalk25.default.red(`Invalid port: ${port ?? opts.port}`));
|
|
1912
2709
|
process.exit(1);
|
|
1913
2710
|
}
|
|
1914
2711
|
await runDev({ port: resolved, captureFile: opts.captureFile });
|
|
@@ -1917,6 +2714,30 @@ program.command("dev").description("Start a dev tunnel for your localhost projec
|
|
|
1917
2714
|
process.exit(1);
|
|
1918
2715
|
}
|
|
1919
2716
|
});
|
|
2717
|
+
program.command("delete").description("Delete a proxy (full cascade) \u2014 shows impact, then confirms").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)));
|
|
2718
|
+
program.command("target").description("Set a proxy's target URL (per-environment with --env, else project-level)").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)));
|
|
2719
|
+
program.command("throttle").description("Set per-proxy throttling (rate limits + quota)").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)));
|
|
2720
|
+
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)));
|
|
2721
|
+
var domain = program.command("domain").description("Manage custom domains for a proxy");
|
|
2722
|
+
domain.command("add").description("Register a custom hostname (prints DNS records; does not poll)").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)));
|
|
2723
|
+
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)));
|
|
2724
|
+
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)));
|
|
2725
|
+
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)));
|
|
2726
|
+
domain.command("set-base").description("Point the bare {project} hostname at a (version, env)").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)));
|
|
2727
|
+
var tenant = program.command("tenant").description("Manage tenants (consumer scopes) for your team/proxies");
|
|
2728
|
+
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)));
|
|
2729
|
+
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)));
|
|
2730
|
+
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)));
|
|
2731
|
+
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)));
|
|
2732
|
+
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)));
|
|
2733
|
+
var key = program.command("key").description("Manage control-plane developer keys (consumer-admin) for curl-based management");
|
|
2734
|
+
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)));
|
|
2735
|
+
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)));
|
|
2736
|
+
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)));
|
|
2737
|
+
var spec = program.command("spec").description("Inspect / regenerate a proxy OpenAPI spec (author it conversationally with `apiblaze openapi`)");
|
|
2738
|
+
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)));
|
|
2739
|
+
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)));
|
|
2740
|
+
program.command("agent").description("Chat with a producer assistant that can create/manage your proxies, tenants, keys, domains, and specs (billed per turn)").option("--team <id|name>", "Team to operate in (defaults to active team)").action(action((opts) => runAgent(opts)));
|
|
1920
2741
|
program.addHelpText(
|
|
1921
2742
|
"after",
|
|
1922
2743
|
`
|
|
@@ -1936,22 +2757,31 @@ Examples:
|
|
|
1936
2757
|
$ npx apiblaze dev 3000
|
|
1937
2758
|
$ npx apiblaze dev 3000 --capture-file traffic.jsonl
|
|
1938
2759
|
|
|
1939
|
-
# Manage:
|
|
1940
|
-
$ npx apiblaze whoami
|
|
2760
|
+
# Manage your proxies (require login; --verbose prints the equivalent API calls):
|
|
1941
2761
|
$ npx apiblaze projects
|
|
1942
|
-
$ npx apiblaze
|
|
1943
|
-
$ npx apiblaze
|
|
2762
|
+
$ npx apiblaze target myapi --url https://api.example.com --env prod
|
|
2763
|
+
$ npx apiblaze throttle myapi --rate 50 --quota 100000 --period daily
|
|
2764
|
+
$ npx apiblaze rename myapi --display-name "My API"
|
|
2765
|
+
$ npx apiblaze domain add myapi --domain api.mysite.com
|
|
2766
|
+
$ npx apiblaze tenant create --name "Acme" && npx apiblaze tenant attach myapi --tenant acme
|
|
2767
|
+
$ npx apiblaze key mint --desc "ci key"
|
|
2768
|
+
$ npx apiblaze spec set myapi --file ./openapi.json
|
|
2769
|
+
$ npx apiblaze delete myapi --verbose
|
|
2770
|
+
$ npx apiblaze whoami | team | logout
|
|
2771
|
+
|
|
2772
|
+
# Or just chat (billed per turn):
|
|
2773
|
+
$ npx apiblaze agent
|
|
1944
2774
|
`
|
|
1945
2775
|
);
|
|
1946
2776
|
function printError(err) {
|
|
1947
2777
|
if (err instanceof ApiError) {
|
|
1948
|
-
console.error(
|
|
2778
|
+
console.error(import_chalk25.default.red(`
|
|
1949
2779
|
API error (${err.status}): ${err.message}`));
|
|
1950
2780
|
} else if (err instanceof Error) {
|
|
1951
|
-
console.error(
|
|
2781
|
+
console.error(import_chalk25.default.red(`
|
|
1952
2782
|
Error: ${err.message}`));
|
|
1953
2783
|
} else {
|
|
1954
|
-
console.error(
|
|
2784
|
+
console.error(import_chalk25.default.red("\nUnknown error"));
|
|
1955
2785
|
}
|
|
1956
2786
|
}
|
|
1957
2787
|
program.parse(process.argv);
|