@openbat/cli 0.1.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 ADDED
@@ -0,0 +1,292 @@
1
+ # @openbat/cli
2
+
3
+ Command-line tool for managing OpenBat chatbots end-to-end — read
4
+ analytics + conversations, manage settings + keys + webhooks + workflows
5
+ + reports, run experiments, and help install the SDK in a target app.
6
+
7
+ > **Companion docs**: [`@openbat/mcp`](../mcp/README.md) (same surface
8
+ > over MCP), [`lib/openbat-tools/`](../../lib/openbat-tools/README.md)
9
+ > (tool registry that powers both), and the
10
+ > [A-Z test guide](../../docs/agent-surface-testing.md).
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ npm i -g @openbat/cli
16
+ # or run on demand without installing:
17
+ npx @openbat/cli --help
18
+ ```
19
+
20
+ ---
21
+
22
+ ## Authentication — four key kinds
23
+
24
+ The CLI accepts any read-capable OpenBat key. Pick the smallest scope
25
+ that works:
26
+
27
+ | Prefix | Kind | Scope | What you can do |
28
+ |---|---|---|---|
29
+ | `ob_read_*` | read | one chatbot, **read-only** | Listing, analytics, conversations, export |
30
+ | `ob_admin_*` | admin | one chatbot, **read+write** | All of read, plus webhooks / workflows / reports / settings / mint read keys |
31
+ | `ob_pat_*` | PAT | one user across multiple chatbots and orgs | All of admin, plus `chatbots create/delete`, `org`, mint admin keys, multi-chatbot inventory |
32
+ | `ob_live_*` | ingest | (rejected by the CLI) | SDK-only — capture endpoint. Never use here. |
33
+
34
+ PATs also carry a sub-scope on the row (`read` or `admin`). A read-scope
35
+ PAT can list across all your chatbots but mutate nothing.
36
+
37
+ Generate keys in the dashboard:
38
+ - **Read key** → `Settings → API Keys → Generate Read key`
39
+ - **Admin key** → `Settings → API Keys → Generate Admin key`
40
+ - **PAT** → `Settings → Personal Access Tokens`
41
+
42
+ Each plaintext is shown exactly once.
43
+
44
+ ### Save the key
45
+
46
+ ```bash
47
+ # Recommended — stdin keeps the plaintext out of shell history:
48
+ echo "ob_pat_..." | openbat config set-key --from-stdin
49
+ ```
50
+
51
+ Stored at `~/.openbatrc` with mode `0600`. The CLI refuses to load if
52
+ the perms are looser than that.
53
+
54
+ **Auth resolution order** (each falls through to the next):
55
+
56
+ 1. `--api-key <key>` flag — convenient but leaks into shell history;
57
+ discouraged.
58
+ 2. `$OPENBAT_API_KEY` env var — best for CI.
59
+ 3. `~/.openbatrc` — persistent local default.
60
+
61
+ If you accidentally try to set an ingest key (`ob_live_*`), the CLI
62
+ rejects it with a helpful error.
63
+
64
+ ---
65
+
66
+ ## Command tree
67
+
68
+ ```
69
+ openbat
70
+ ├── config
71
+ │ ├── set-key Store / replace the key in ~/.openbatrc
72
+ │ ├── set-url <baseUrl> Override the API base URL
73
+ │ └── show Print the resolved config (key prefix only)
74
+
75
+ ├── auth [any kind]
76
+ │ ├── whoami Show kind, chatbots in scope, orgs (PAT only)
77
+ │ └── audit-log [--days] Recent api_audit_log entries (stub today)
78
+
79
+ ├── org [pat]
80
+ │ ├── list List user's orgs
81
+ │ ├── show Active org + members
82
+ │ ├── rename --id ORG --name "..." Owner only
83
+ │ ├── members list --id ORG
84
+ │ ├── members invite --id ORG --email --role member|admin
85
+ │ ├── members set-role --id ORG --member M --role admin|member
86
+ │ ├── members remove --id ORG --member M
87
+ │ └── invitations list --id ORG
88
+
89
+ ├── chatbots [any kind]
90
+ │ ├── list Every chatbot in scope
91
+ │ ├── create --name --website [--docs-url] [--mcp-url] [pat] mint chatbot + ingest key
92
+ │ └── delete <id> [admin/pat] cascade delete
93
+
94
+ ├── chatbot (legacy alias)
95
+ │ └── info [any kind] current chatbot row
96
+
97
+ ├── conversations [any kind]
98
+ │ ├── list [--days N] [--from ISO] [--to ISO] [--limit N]
99
+ │ └── show <id>
100
+
101
+ ├── users [any kind]
102
+ │ └── list --chatbot ID [--days] [--search] External users + health
103
+
104
+ ├── settings [admin or pat]
105
+ │ ├── update --chatbot ID [--description] [--website-url] [--language]
106
+ │ └── keys
107
+ │ ├── list-admin --chatbot ID
108
+ │ ├── rotate-ingest --chatbot ID New ob_live_* (shown once)
109
+ │ ├── generate-read --chatbot ID New ob_read_* (shown once)
110
+ │ ├── generate-admin --chatbot ID --name N [--expires-in-days] [pat] new ob_admin_*
111
+ │ └── revoke-admin --chatbot ID --key KEYID [pat] revoke
112
+
113
+ ├── webhooks [admin or pat]
114
+ │ ├── list --chatbot ID
115
+ │ ├── create --chatbot ID --name --url --type discord|slack|custom Returns signing secret (once)
116
+ │ └── delete --chatbot ID --webhook WHID
117
+
118
+ ├── workflows [admin or pat]
119
+ │ ├── list --chatbot ID
120
+ │ └── create --chatbot ID --name --template T --trigger-value V --webhook WHID [--message TPL]
121
+ │ Templates: flag-to-webhook | outcome-to-webhook | sentiment-drop-to-webhook
122
+
123
+ ├── reports [admin or pat]
124
+ │ ├── list --chatbot ID
125
+ │ └── create --chatbot ID [--name "..."] Returns org-private dashboard URL
126
+
127
+ ├── analysis [admin or pat]
128
+ │ ├── list --chatbot ID [--type] [--pending]
129
+ │ └── add --chatbot ID --type intent|flag|assistant_outcome|assistant_issue
130
+ │ --name SLUG --display-name "..." --description "..."
131
+
132
+ ├── analytics [any kind]
133
+ │ ├── overview
134
+ │ └── sentiment [--days N]
135
+
136
+ ├── export --format json|csv [--out FILE] [any kind] streaming export
137
+
138
+ └── sdk [any kind]
139
+ ├── install-instructions [--framework next|node|vercel-ai-sdk] [--chatbot ID]
140
+ └── verify --chatbot ID [--timeout N] Polls until first event
141
+ ```
142
+
143
+ Every command supports `--json` for raw output (default for non-TTY
144
+ stdout, so piping into `jq` always works).
145
+
146
+ ---
147
+
148
+ ## Write commands — what to expect
149
+
150
+ Mutating commands follow a consistent output convention so scripts
151
+ and humans can both consume them cleanly:
152
+
153
+ - **Plaintext secrets go to stderr**, inside a "shown ONCE" banner:
154
+
155
+ ```
156
+ ────────────────────────────────────────────────────────────
157
+ Webhook signing secret (shown ONCE — store this now)
158
+
159
+ whsec_abcdef…
160
+ ────────────────────────────────────────────────────────────
161
+ ```
162
+
163
+ - **Structured response goes to stdout** without the plaintext, so
164
+ `... | jq` keeps working:
165
+
166
+ ```bash
167
+ openbat webhooks create --chatbot $CB --name foo --url ... --type slack > webhook.json
168
+ jq .id webhook.json
169
+ ```
170
+
171
+ - **Errors go to stderr with a non-zero exit code.** API key plaintext
172
+ is automatically redacted from any error message (`ob_admin_<16 chars>…<hidden>`).
173
+
174
+ - **HTTP 401 vs 403**: a 401 means "key invalid / expired / wrong kind
175
+ for this endpoint" — usually fixed by `openbat auth whoami` + a fresh
176
+ key. A 403 means "key valid but lacks permission" — e.g. a read-scope
177
+ PAT trying to mutate, or a member trying to do an owner-only action.
178
+
179
+ - **HTTP 429** includes a `Retry-After` header. The CLI surfaces it in
180
+ the error message.
181
+
182
+ ---
183
+
184
+ ## Quickstart — chatbot zero to first event
185
+
186
+ End-to-end in under 5 minutes. The full A-Z (including SDK install in a
187
+ real Next.js app) lives in
188
+ [`docs/agent-surface-testing.md`](../../docs/agent-surface-testing.md).
189
+
190
+ ```bash
191
+ # 0. Configure a PAT (you mint this in the dashboard).
192
+ echo "ob_pat_..." | openbat config set-key --from-stdin
193
+
194
+ # 1. Verify scope.
195
+ openbat auth whoami
196
+
197
+ # 2. Create a chatbot.
198
+ openbat chatbots create --name "My Bot" --website https://example.com
199
+ # stderr: Ingest API key (shown ONCE)
200
+ # stdout: { chatbot: { id, ... }, dashboardUrl }
201
+ CB=<chatbot id from stdout>
202
+
203
+ # 3. Mint an admin key so we can manage it without the PAT.
204
+ openbat settings keys generate-admin --chatbot $CB --name "dev" --expires-in-days 30
205
+ # stderr: ob_admin_... (shown ONCE)
206
+
207
+ # 4. Add a webhook + a workflow that fires it on a flag.
208
+ openbat webhooks create --chatbot $CB --name "ops" \
209
+ --url https://hooks.slack.com/services/T.../B.../X --type slack
210
+ # stdout: { id: WH_ID, ... }
211
+
212
+ openbat analysis add --chatbot $CB --type flag --name billing_issue \
213
+ --display-name "Billing Issue" --description "Customer raises a billing concern"
214
+
215
+ openbat workflows create --chatbot $CB \
216
+ --name "billing → slack" \
217
+ --template flag-to-webhook \
218
+ --trigger-value billing_issue \
219
+ --webhook $WH_ID
220
+
221
+ # 5. Help an agent install the SDK in a target app.
222
+ openbat sdk install-instructions --framework next --chatbot $CB
223
+ # Markdown to stdout — agent (or you) follows the steps.
224
+
225
+ # 6. After SDK is wired up + a real chat sent, verify ingestion:
226
+ openbat sdk verify --chatbot $CB --timeout 60
227
+ # Exits 0 on first event, 2 on timeout.
228
+
229
+ # 7. Read your data.
230
+ openbat conversations list --days 7
231
+ openbat analytics overview
232
+ ```
233
+
234
+ ---
235
+
236
+ ## Override the API base URL
237
+
238
+ ```bash
239
+ openbat config set-url https://staging.openbat.dev
240
+ # or per-invocation:
241
+ openbat --base-url http://localhost:3000 auth whoami
242
+ # or via env:
243
+ OPENBAT_BASE_URL=http://localhost:3000 openbat auth whoami
244
+ ```
245
+
246
+ The CLI **refuses non-HTTPS base URLs** unless they point at `localhost`
247
+ or `127.0.0.1`. There is no `--insecure` escape hatch.
248
+
249
+ ---
250
+
251
+ ## Security properties
252
+
253
+ - API key plaintext never lands in any error message — auto-redacted to
254
+ `ob_<kind>_<first 16>…<hidden>`.
255
+ - HTTPS-only base URL (localhost exception only).
256
+ - `~/.openbatrc` enforced at `mode 0600`; the loader refuses looser perms.
257
+ - Mint commands print plaintext to stderr only, with a "shown ONCE"
258
+ banner. Pipe stderr to `/dev/null` if you don't want it on screen
259
+ (you'll lose the secret).
260
+ - Every authenticated call lands in `api_audit_log` server-side
261
+ (migration 036) — operators can answer "who did what when" via
262
+ Supabase Studio.
263
+
264
+ ## Rate limits
265
+
266
+ Per-tool buckets keyed by credential. The strict ones to know about:
267
+
268
+ | Operation | Limit |
269
+ |---|---|
270
+ | Create chatbot | 5 per hour per PAT |
271
+ | Mint or rotate any key | 10 per hour per credential |
272
+ | Create backtest | 10 per hour per PAT |
273
+ | Invite org member | 20 per hour per PAT |
274
+ | Chat with an AI report | 30 per minute per credential |
275
+ | Generic write | 60 per minute per credential |
276
+ | Generic read | 600 per minute per credential |
277
+ | Export | 30 per hour per credential |
278
+
279
+ 429 responses include `Retry-After` (seconds).
280
+
281
+ ---
282
+
283
+ ## See also
284
+
285
+ - [`@openbat/mcp`](../mcp/README.md) — the same surface to Claude /
286
+ Cursor / any MCP client.
287
+ - [`@openbat/sdk`](../sdk/README.md) — capture conversations from your
288
+ app (uses the ingest key only).
289
+ - [`lib/openbat-tools/`](../../lib/openbat-tools/README.md) — the
290
+ registry that powers every CLI command + MCP tool + v1 route.
291
+ - [A-Z test guide](../../docs/agent-surface-testing.md) — clone the
292
+ repo and exercise every key kind, CLI, MCP, SDK in ~30 minutes.
package/bin/openbat ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // Thin shim — loads the CommonJS entry compiled by tsup.
3
+ require("../dist/index.js");
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minimal fetch wrapper used by both the CLI and the MCP server.
3
+ *
4
+ * Two design properties worth preserving:
5
+ *
6
+ * • **HTTPS-only.** The constructor refuses non-HTTPS base URLs unless
7
+ * they point at localhost. There is no `--insecure` escape hatch —
8
+ * if you need to test against a self-signed cert, fix the cert.
9
+ *
10
+ * • **Key redaction.** The client never emits the plaintext API key in
11
+ * any error message. If a request fails, only the response status
12
+ * and the server's generic error string are exposed.
13
+ */
14
+ type ApiClientOptions = {
15
+ baseUrl: string;
16
+ apiKey: string;
17
+ };
18
+ declare class ApiClient {
19
+ #private;
20
+ readonly baseUrl: string;
21
+ constructor(opts: ApiClientOptions);
22
+ /**
23
+ * Issue a GET against `path` (must begin with `/`). Returns the parsed
24
+ * JSON on 200. Throws an Error whose message is safe to print (key
25
+ * redacted, status included).
26
+ */
27
+ get<T = unknown>(path: string): Promise<T>;
28
+ /** Issue a POST with a JSON body. */
29
+ post<T = unknown>(path: string, body?: unknown): Promise<T>;
30
+ /** Issue a PATCH with a JSON body. */
31
+ patch<T = unknown>(path: string, body?: unknown): Promise<T>;
32
+ /** Issue a DELETE. Body is rarely used; we still allow it. */
33
+ delete<T = unknown>(path: string, body?: unknown): Promise<T>;
34
+ /** Pass-through for streaming endpoints (export). Returns the raw body. */
35
+ getRaw(path: string): Promise<{
36
+ body: ReadableStream<Uint8Array>;
37
+ contentType: string;
38
+ }>;
39
+ }
40
+
41
+ export { ApiClient, type ApiClientOptions };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minimal fetch wrapper used by both the CLI and the MCP server.
3
+ *
4
+ * Two design properties worth preserving:
5
+ *
6
+ * • **HTTPS-only.** The constructor refuses non-HTTPS base URLs unless
7
+ * they point at localhost. There is no `--insecure` escape hatch —
8
+ * if you need to test against a self-signed cert, fix the cert.
9
+ *
10
+ * • **Key redaction.** The client never emits the plaintext API key in
11
+ * any error message. If a request fails, only the response status
12
+ * and the server's generic error string are exposed.
13
+ */
14
+ type ApiClientOptions = {
15
+ baseUrl: string;
16
+ apiKey: string;
17
+ };
18
+ declare class ApiClient {
19
+ #private;
20
+ readonly baseUrl: string;
21
+ constructor(opts: ApiClientOptions);
22
+ /**
23
+ * Issue a GET against `path` (must begin with `/`). Returns the parsed
24
+ * JSON on 200. Throws an Error whose message is safe to print (key
25
+ * redacted, status included).
26
+ */
27
+ get<T = unknown>(path: string): Promise<T>;
28
+ /** Issue a POST with a JSON body. */
29
+ post<T = unknown>(path: string, body?: unknown): Promise<T>;
30
+ /** Issue a PATCH with a JSON body. */
31
+ patch<T = unknown>(path: string, body?: unknown): Promise<T>;
32
+ /** Issue a DELETE. Body is rarely used; we still allow it. */
33
+ delete<T = unknown>(path: string, body?: unknown): Promise<T>;
34
+ /** Pass-through for streaming endpoints (export). Returns the raw body. */
35
+ getRaw(path: string): Promise<{
36
+ body: ReadableStream<Uint8Array>;
37
+ contentType: string;
38
+ }>;
39
+ }
40
+
41
+ export { ApiClient, type ApiClientOptions };
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
22
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
23
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
25
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
26
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
+
28
+ // src/api-client.ts
29
+ var api_client_exports = {};
30
+ __export(api_client_exports, {
31
+ ApiClient: () => ApiClient
32
+ });
33
+ module.exports = __toCommonJS(api_client_exports);
34
+ var import_node_url = require("url");
35
+ var KEY_REGEX = /ob_(?:live|read|admin|pat)_[0-9a-f]{32}/g;
36
+ function redact(s) {
37
+ return s.replace(KEY_REGEX, (k) => `${k.slice(0, 16)}\u2026<hidden>`);
38
+ }
39
+ function assertHttpsOrLocalhost(baseUrl) {
40
+ let url;
41
+ try {
42
+ url = new import_node_url.URL(baseUrl);
43
+ } catch {
44
+ throw new Error(`Invalid base URL: ${redact(baseUrl)}`);
45
+ }
46
+ if (url.protocol === "https:") return;
47
+ if (url.protocol === "http:" && (url.hostname === "localhost" || url.hostname === "127.0.0.1")) {
48
+ return;
49
+ }
50
+ throw new Error(
51
+ "Refusing to use a non-HTTPS base URL. localhost / 127.0.0.1 are allowed for dev."
52
+ );
53
+ }
54
+ var _apiKey, _ApiClient_instances, mutate_fn;
55
+ var ApiClient = class {
56
+ constructor(opts) {
57
+ __privateAdd(this, _ApiClient_instances);
58
+ __privateAdd(this, _apiKey);
59
+ assertHttpsOrLocalhost(opts.baseUrl);
60
+ this.baseUrl = opts.baseUrl.replace(/\/$/, "");
61
+ __privateSet(this, _apiKey, opts.apiKey);
62
+ }
63
+ /**
64
+ * Issue a GET against `path` (must begin with `/`). Returns the parsed
65
+ * JSON on 200. Throws an Error whose message is safe to print (key
66
+ * redacted, status included).
67
+ */
68
+ async get(path) {
69
+ const url = `${this.baseUrl}${path}`;
70
+ let res;
71
+ try {
72
+ res = await fetch(url, {
73
+ method: "GET",
74
+ headers: {
75
+ "x-openbat-key": __privateGet(this, _apiKey),
76
+ accept: "application/json"
77
+ }
78
+ });
79
+ } catch (err) {
80
+ const msg = err instanceof Error ? err.message : String(err);
81
+ throw new Error(`Request to ${redact(url)} failed: ${redact(msg)}`);
82
+ }
83
+ return parseResponse(res, url);
84
+ }
85
+ /** Issue a POST with a JSON body. */
86
+ async post(path, body) {
87
+ return __privateMethod(this, _ApiClient_instances, mutate_fn).call(this, "POST", path, body);
88
+ }
89
+ /** Issue a PATCH with a JSON body. */
90
+ async patch(path, body) {
91
+ return __privateMethod(this, _ApiClient_instances, mutate_fn).call(this, "PATCH", path, body);
92
+ }
93
+ /** Issue a DELETE. Body is rarely used; we still allow it. */
94
+ async delete(path, body) {
95
+ return __privateMethod(this, _ApiClient_instances, mutate_fn).call(this, "DELETE", path, body);
96
+ }
97
+ /** Pass-through for streaming endpoints (export). Returns the raw body. */
98
+ async getRaw(path) {
99
+ const url = `${this.baseUrl}${path}`;
100
+ const res = await fetch(url, {
101
+ method: "GET",
102
+ headers: {
103
+ "x-openbat-key": __privateGet(this, _apiKey)
104
+ }
105
+ });
106
+ if (!res.ok || !res.body) {
107
+ const errText = await res.text().catch(() => "");
108
+ throw new Error(
109
+ `GET ${redact(url)} \u2192 ${res.status} ${res.statusText}${errText ? `: ${redact(errText.slice(0, 200))}` : ""}`
110
+ );
111
+ }
112
+ return {
113
+ body: res.body,
114
+ contentType: res.headers.get("content-type") ?? "application/octet-stream"
115
+ };
116
+ }
117
+ };
118
+ _apiKey = new WeakMap();
119
+ _ApiClient_instances = new WeakSet();
120
+ mutate_fn = async function(method, path, body) {
121
+ const url = `${this.baseUrl}${path}`;
122
+ let res;
123
+ try {
124
+ res = await fetch(url, {
125
+ method,
126
+ headers: {
127
+ "x-openbat-key": __privateGet(this, _apiKey),
128
+ "content-type": "application/json",
129
+ accept: "application/json"
130
+ },
131
+ body: body === void 0 ? void 0 : JSON.stringify(body)
132
+ });
133
+ } catch (err) {
134
+ const msg = err instanceof Error ? err.message : String(err);
135
+ throw new Error(`Request to ${redact(url)} failed: ${redact(msg)}`);
136
+ }
137
+ return parseResponse(res, url);
138
+ };
139
+ async function parseResponse(res, url) {
140
+ if (res.status === 401) {
141
+ throw new Error(
142
+ "Unauthorized. The API key was rejected (invalid, wrong kind for this endpoint, expired, or revoked)."
143
+ );
144
+ }
145
+ if (res.status === 403) {
146
+ throw new Error(
147
+ "Forbidden. The credential is valid but lacks permission for this operation (e.g. read-scope PAT can't mutate)."
148
+ );
149
+ }
150
+ if (res.status === 429) {
151
+ const retry = res.headers.get("retry-after");
152
+ throw new Error(
153
+ `Rate limited.${retry ? ` Retry after ${retry}s.` : ""}`
154
+ );
155
+ }
156
+ if (!res.ok) {
157
+ let errBody = null;
158
+ try {
159
+ errBody = await res.json();
160
+ } catch {
161
+ }
162
+ throw new Error(
163
+ `GET ${redact(url)} \u2192 ${res.status} ${res.statusText}${errBody?.error ? `: ${redact(errBody.error)}` : ""}`
164
+ );
165
+ }
166
+ try {
167
+ return await res.json();
168
+ } catch {
169
+ throw new Error(`Response from ${redact(url)} was not valid JSON`);
170
+ }
171
+ }
172
+ // Annotate the CommonJS export names for ESM import in node:
173
+ 0 && (module.exports = {
174
+ ApiClient
175
+ });
@@ -0,0 +1,6 @@
1
+ import {
2
+ ApiClient
3
+ } from "./chunk-CRJZM45P.mjs";
4
+ export {
5
+ ApiClient
6
+ };