apiblaze 0.3.8 → 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.
Files changed (3) hide show
  1. package/README.md +63 -7
  2. package/dist/index.js +858 -29
  3. 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 dev
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
- | `apiblaze team [team]` | Switch the active team |
62
- | `apiblaze whoami` | Show the signed-in identity and active team |
63
- | `apiblaze logout` | Sign out and remove stored credentials |
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 key of __getOwnPropNames(from))
12
- if (!__hasOwnProp.call(to, key) && key !== except)
13
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
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 import_chalk15 = __toESM(require("chalk"));
28
+ var import_chalk25 = __toESM(require("chalk"));
29
29
 
30
30
  // package.json
31
- var version = "0.3.8";
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 key of Object.keys(headers)) {
590
- if (!STRIP_HEADERS.has(key.toLowerCase())) out[key] = headers[key];
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, key) => {
706
- if (!STRIP_HEADERS.has(key.toLowerCase())) headers[key] = value;
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 key = result.api_keys?.dev ?? Object.values(result.api_keys ?? {})[0];
826
- if (key) {
827
- console.log(` ${import_chalk4.default.dim("API key (dev):")} ${import_chalk4.default.bold.green(key)}`);
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 tenant = match.tenant || projectId;
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(tenant)}`, "POST", proposal.model);
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 ${tenant} \xB7 discuss what authorization fits this API, then make it official.`,
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 spec = ctx.proposal;
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 spec = data.proposal;
1815
- const tools = Array.isArray(spec.tools) ? spec.tools : [];
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,9 +1822,805 @@ async function runMcp(projectArg, apiVersionArg, opts) {
1822
1822
  });
1823
1823
  }
1824
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
+
1825
2607
  // src/index.ts
1826
2608
  var program = new import_commander.Command();
1827
- 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
+ }
1828
2624
  program.command("login").description("Authenticate with APIblaze").action(async () => {
1829
2625
  try {
1830
2626
  await runLogin();
@@ -1909,7 +2705,7 @@ program.command("dev").description("Start a dev tunnel for your localhost projec
1909
2705
  try {
1910
2706
  const resolved = parseInt(port ?? opts.port, 10);
1911
2707
  if (Number.isNaN(resolved)) {
1912
- console.error(import_chalk15.default.red(`Invalid port: ${port ?? opts.port}`));
2708
+ console.error(import_chalk25.default.red(`Invalid port: ${port ?? opts.port}`));
1913
2709
  process.exit(1);
1914
2710
  }
1915
2711
  await runDev({ port: resolved, captureFile: opts.captureFile });
@@ -1918,6 +2714,30 @@ program.command("dev").description("Start a dev tunnel for your localhost projec
1918
2714
  process.exit(1);
1919
2715
  }
1920
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)));
1921
2741
  program.addHelpText(
1922
2742
  "after",
1923
2743
  `
@@ -1937,22 +2757,31 @@ Examples:
1937
2757
  $ npx apiblaze dev 3000
1938
2758
  $ npx apiblaze dev 3000 --capture-file traffic.jsonl
1939
2759
 
1940
- # Manage:
1941
- $ npx apiblaze whoami
2760
+ # Manage your proxies (require login; --verbose prints the equivalent API calls):
1942
2761
  $ npx apiblaze projects
1943
- $ npx apiblaze team
1944
- $ npx apiblaze logout
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
1945
2774
  `
1946
2775
  );
1947
2776
  function printError(err) {
1948
2777
  if (err instanceof ApiError) {
1949
- console.error(import_chalk15.default.red(`
2778
+ console.error(import_chalk25.default.red(`
1950
2779
  API error (${err.status}): ${err.message}`));
1951
2780
  } else if (err instanceof Error) {
1952
- console.error(import_chalk15.default.red(`
2781
+ console.error(import_chalk25.default.red(`
1953
2782
  Error: ${err.message}`));
1954
2783
  } else {
1955
- console.error(import_chalk15.default.red("\nUnknown error"));
2784
+ console.error(import_chalk25.default.red("\nUnknown error"));
1956
2785
  }
1957
2786
  }
1958
2787
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apiblaze",
3
- "version": "0.3.8",
3
+ "version": "0.4.0",
4
4
  "description": "Dev tunnel CLI for APIblaze — route localhost projects through your APIblaze endpoints",
5
5
  "keywords": [
6
6
  "apiblaze",