@hunt-labs/bytekit-cli 0.2.2 → 0.5.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.
@@ -0,0 +1,81 @@
1
+ import { Command } from 'commander';
2
+ import { buildClient } from '../client.js';
3
+ import { printResult, handleError } from '../output.js';
4
+ /**
5
+ * The `usage` command group (#2545) — the `x-sdk-scope: v0.1` usage endpoints, previously curl-only.
6
+ *
7
+ * `daily` / `by-endpoint` accept the same three optional query flags (`--from`, `--to`,
8
+ * `--api-key`). Each maps 1:1 to its snake_case query param and is written ONLY when present; when
9
+ * NO flag is passed the SDK method is invoked with ZERO arguments (the `monitors list` pattern), so
10
+ * the bare `/v1/usage/daily` URL is serialized unchanged.
11
+ */
12
+ export function makeUsageCommand(getGlobal) {
13
+ const cmd = new Command('usage').description('Account usage and billing-period reporting');
14
+ cmd
15
+ .command('get')
16
+ .description('Get current billing-period usage summary')
17
+ .action(async () => {
18
+ const g = getGlobal();
19
+ const client = buildClient(g);
20
+ try {
21
+ const result = await client.usage.get();
22
+ printResult(result, g.json);
23
+ }
24
+ catch (err) {
25
+ handleError(err);
26
+ }
27
+ });
28
+ cmd
29
+ .command('daily')
30
+ .description('Get daily usage breakdown (default last 30 days; max 366)')
31
+ .option('--from <date>', 'Start date YYYY-MM-DD (omitted → server default, 30 days ago)')
32
+ .option('--to <date>', 'End date YYYY-MM-DD (omitted → server default, today)')
33
+ .option('--api-key <ak_id>', 'Filter by API key prefixed ID (omitted → all keys)')
34
+ .action(async (opts) => {
35
+ const g = getGlobal();
36
+ const client = buildClient(g);
37
+ const params = {};
38
+ if (opts.from !== undefined)
39
+ params.from = opts.from;
40
+ if (opts.to !== undefined)
41
+ params.to = opts.to;
42
+ if (opts.apiKey !== undefined)
43
+ params.api_key = opts.apiKey;
44
+ try {
45
+ const result = Object.keys(params).length > 0
46
+ ? await client.usage.daily(params)
47
+ : await client.usage.daily();
48
+ printResult(result, g.json);
49
+ }
50
+ catch (err) {
51
+ handleError(err);
52
+ }
53
+ });
54
+ cmd
55
+ .command('by-endpoint')
56
+ .description('Get per-endpoint bandwidth breakdown (default last 30 days; max 366)')
57
+ .option('--from <date>', 'Start date YYYY-MM-DD (omitted → server default, 30 days ago)')
58
+ .option('--to <date>', 'End date YYYY-MM-DD (omitted → server default, today)')
59
+ .option('--api-key <ak_id>', 'Filter by API key prefixed ID (omitted → all keys)')
60
+ .action(async (opts) => {
61
+ const g = getGlobal();
62
+ const client = buildClient(g);
63
+ const params = {};
64
+ if (opts.from !== undefined)
65
+ params.from = opts.from;
66
+ if (opts.to !== undefined)
67
+ params.to = opts.to;
68
+ if (opts.apiKey !== undefined)
69
+ params.api_key = opts.apiKey;
70
+ try {
71
+ const result = Object.keys(params).length > 0
72
+ ? await client.usage.byEndpoint(params)
73
+ : await client.usage.byEndpoint();
74
+ printResult(result, g.json);
75
+ }
76
+ catch (err) {
77
+ handleError(err);
78
+ }
79
+ });
80
+ return cmd;
81
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from 'commander';
2
+ import { type GlobalOptions } from '../client.js';
3
+ /**
4
+ * The `webhook-deliveries` command group (#2545) — inspect and requeue webhook delivery attempts.
5
+ *
6
+ * `list` flags map 1:1 to their query params and are omitted-when-absent; a bare `list` calls the
7
+ * SDK with ZERO args so the plain `/v1/webhook-deliveries` URL is serialized unchanged.
8
+ */
9
+ export declare function makeWebhookDeliveriesCommand(getGlobal: () => GlobalOptions & {
10
+ json: boolean;
11
+ }): Command;
@@ -0,0 +1,59 @@
1
+ import { Command } from 'commander';
2
+ import { buildClient } from '../client.js';
3
+ import { printResult, handleError } from '../output.js';
4
+ import { parseEnum, parseIntFlag } from '../lib/parse-flags.js';
5
+ /** The `listWebhookDeliveries` `status` query enum (DISTINCT from the monitor status enum). */
6
+ const DELIVERY_STATUSES = ['pending', 'delivered', 'failed', 'exhausted'];
7
+ /**
8
+ * The `webhook-deliveries` command group (#2545) — inspect and requeue webhook delivery attempts.
9
+ *
10
+ * `list` flags map 1:1 to their query params and are omitted-when-absent; a bare `list` calls the
11
+ * SDK with ZERO args so the plain `/v1/webhook-deliveries` URL is serialized unchanged.
12
+ */
13
+ export function makeWebhookDeliveriesCommand(getGlobal) {
14
+ const cmd = new Command('webhook-deliveries').description('Webhook delivery operations');
15
+ cmd
16
+ .command('list')
17
+ .description('List webhook deliveries')
18
+ .option('--limit <n>', 'Maximum number of deliveries to return', (v) => parseIntFlag('--limit', v))
19
+ .option('--cursor <cursor>', 'Pagination cursor from a previous page')
20
+ .option('--status <status>', `Filter by delivery status: ${DELIVERY_STATUSES.join(', ')}`, (v) => parseEnum('--status', DELIVERY_STATUSES, v))
21
+ .option('--since <iso>', 'Only deliveries created after this ISO date-time')
22
+ .action(async (opts) => {
23
+ const g = getGlobal();
24
+ const client = buildClient(g);
25
+ const params = {};
26
+ if (opts.limit !== undefined)
27
+ params.limit = opts.limit;
28
+ if (opts.cursor !== undefined)
29
+ params.cursor = opts.cursor;
30
+ if (opts.status !== undefined)
31
+ params.status = opts.status;
32
+ if (opts.since !== undefined)
33
+ params.since = opts.since;
34
+ try {
35
+ const result = Object.keys(params).length > 0
36
+ ? await client.webhooks.list(params)
37
+ : await client.webhooks.list();
38
+ printResult(result, g.json);
39
+ }
40
+ catch (err) {
41
+ handleError(err);
42
+ }
43
+ });
44
+ cmd
45
+ .command('retry <id>')
46
+ .description('Requeue a failed or exhausted webhook delivery')
47
+ .action(async (id) => {
48
+ const g = getGlobal();
49
+ const client = buildClient(g);
50
+ try {
51
+ const result = await client.webhooks.retry(id);
52
+ printResult(result, g.json);
53
+ }
54
+ catch (err) {
55
+ handleError(err);
56
+ }
57
+ });
58
+ return cmd;
59
+ }
package/dist/index.js CHANGED
@@ -11,6 +11,8 @@ import { makeMonitorsCommand } from './commands/monitors.js';
11
11
  import { makeSitemapCommand } from './commands/sitemap.js';
12
12
  import { makeSearchCommand } from './commands/search.js';
13
13
  import { makeAccountCommand } from './commands/account.js';
14
+ import { makeUsageCommand } from './commands/usage.js';
15
+ import { makeWebhookDeliveriesCommand } from './commands/webhook-deliveries.js';
14
16
  // Resolve version from package.json at runtime (supports compiled output).
15
17
  const require = createRequire(import.meta.url);
16
18
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
@@ -44,6 +46,8 @@ export function buildProgram() {
44
46
  program.addCommand(makeSitemapCommand(getGlobal));
45
47
  program.addCommand(makeSearchCommand(getGlobal));
46
48
  program.addCommand(makeAccountCommand(getGlobal));
49
+ program.addCommand(makeUsageCommand(getGlobal));
50
+ program.addCommand(makeWebhookDeliveriesCommand(getGlobal));
47
51
  return program;
48
52
  }
49
53
  /** Resolve symlinks and `..` segments, falling back to the raw path if it cannot be resolved. */
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Download a screenshot artifact from its `image_url` and write the raw bytes to `dest`.
3
+ *
4
+ * - `dest === '-'` writes the bytes to stdout (the conventional dash-is-stdout convention),
5
+ * never with `console.log` — binary must not be coerced or newline-appended.
6
+ * - any other `dest` is a file path written verbatim.
7
+ *
8
+ * `globalThis.fetch` is resolved at CALL time (never captured at module/construction load) so a
9
+ * test that stubs `globalThis.fetch` in a `beforeEach` — after this module was imported — still
10
+ * sees its stub (G-049). A non-2xx response throws with the HTTP status so `handleError` renders a
11
+ * clean one-liner and exits 1.
12
+ */
13
+ export declare function downloadArtifact(url: string, dest: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import { writeFileSync } from 'node:fs';
2
+ /**
3
+ * Download a screenshot artifact from its `image_url` and write the raw bytes to `dest`.
4
+ *
5
+ * - `dest === '-'` writes the bytes to stdout (the conventional dash-is-stdout convention),
6
+ * never with `console.log` — binary must not be coerced or newline-appended.
7
+ * - any other `dest` is a file path written verbatim.
8
+ *
9
+ * `globalThis.fetch` is resolved at CALL time (never captured at module/construction load) so a
10
+ * test that stubs `globalThis.fetch` in a `beforeEach` — after this module was imported — still
11
+ * sees its stub (G-049). A non-2xx response throws with the HTTP status so `handleError` renders a
12
+ * clean one-liner and exits 1.
13
+ */
14
+ export async function downloadArtifact(url, dest) {
15
+ const res = await globalThis.fetch(url);
16
+ if (!res.ok) {
17
+ throw new Error(`artifact download failed: HTTP ${res.status}`);
18
+ }
19
+ const bytes = Buffer.from(await res.arrayBuffer());
20
+ if (dest === '-') {
21
+ process.stdout.write(bytes);
22
+ return;
23
+ }
24
+ writeFileSync(dest, bytes);
25
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Shared commander argParser helpers for CLI flag values (#2480 cr-3). One home for the parser
3
+ * family that scrape.ts, screenshots.ts, and monitors.ts previously each held private copies of —
4
+ * every off-spec value is rejected locally, at parse time, with an actionable message, before any
5
+ * HTTP request is issued.
6
+ */
7
+ /**
8
+ * Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
9
+ * `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
10
+ * of a server 422 or a NaN on the wire.
11
+ */
12
+ export declare function parseIntFlag(flag: string, value: string): number;
13
+ /** Reject an off-spec enum value at parse time with the allowed set in the message. */
14
+ export declare function parseEnum<T extends string>(flag: string, allowed: readonly T[], value: string): T;
15
+ /** Split a comma-separated flag value into a trimmed, non-empty string array. */
16
+ export declare function parseCsv(value: string): string[];
17
+ /**
18
+ * Parse a finite decimal flag value (#2545). Unlike {@link parseIntFlag}, the `/^\d+(\.\d+)?$/`
19
+ * test accepts a fractional value such as `2.5` — required for spec `type: number` fields like
20
+ * `monitors change_threshold` (default 5, range 0–100) that `parseIntFlag`'s `/^\d+$/` would reject
21
+ * at parse time. Rejects `abc`, ``, `-5`, `1.` and `1e2` with the flag named in the message.
22
+ */
23
+ export declare function parseNumberFlag(flag: string, value: string): number;
24
+ /**
25
+ * Parse a `cache_ttl` flag value (#2545). Mirrors the gateway validator's own format grammar
26
+ * (`^(0|\d+[hd])$`, `packages/gateway/src/validators/shared.ts`) locally so an off-grammar value
27
+ * (`30m`, `soon`) is rejected at parse time with an actionable message instead of a server 422.
28
+ * The string is returned verbatim — the validator accepts the string `'0'`, and the range cap
29
+ * (7d/168h for scrape+fetch) stays a server-side concern.
30
+ */
31
+ export declare function parseCacheTtl(flag: string, value: string): string;
32
+ /**
33
+ * Parse a JSON-object- or JSON-array-valued flag (#2545). The ONLY `JSON.parse` call site for CLI
34
+ * flag values — every JSON-shaped flag (`--cookies`, `--headers`, `--custom`,
35
+ * `--webhook-headers`, `--options`, `--scrape-options`) routes through here, so command modules
36
+ * carry no raw `JSON.parse` (grep-gated). Rejects malformed JSON AND a value whose top-level shape
37
+ * does not match `expect`, with a copy-pasteable example in the message (E5).
38
+ */
39
+ export declare function parseJsonFlag(flag: string, value: string, expect: 'object' | 'array'): unknown;
40
+ /**
41
+ * Commander collector for a repeatable, CSV-aware `--format` flag (#2545). Each occurrence is
42
+ * CSV-split and appended to the accumulator, so `--format raw_html --format markdown` and
43
+ * `--format raw_html,markdown` both yield `['raw_html','markdown']`. Commander seeds `prev` with
44
+ * the option default (or the previous return) on each occurrence.
45
+ */
46
+ export declare function collectFormats(value: string, prev: string[] | undefined): string[];
@@ -0,0 +1,90 @@
1
+ import { InvalidArgumentError } from 'commander';
2
+ /**
3
+ * Shared commander argParser helpers for CLI flag values (#2480 cr-3). One home for the parser
4
+ * family that scrape.ts, screenshots.ts, and monitors.ts previously each held private copies of —
5
+ * every off-spec value is rejected locally, at parse time, with an actionable message, before any
6
+ * HTTP request is issued.
7
+ */
8
+ /**
9
+ * Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
10
+ * `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
11
+ * of a server 422 or a NaN on the wire.
12
+ */
13
+ export function parseIntFlag(flag, value) {
14
+ if (!/^\d+$/.test(value)) {
15
+ throw new InvalidArgumentError(`${flag} must be a whole number.`);
16
+ }
17
+ return Number(value);
18
+ }
19
+ /** Reject an off-spec enum value at parse time with the allowed set in the message. */
20
+ export function parseEnum(flag, allowed, value) {
21
+ if (!allowed.includes(value)) {
22
+ throw new InvalidArgumentError(`${flag} must be one of: ${allowed.join(', ')}.`);
23
+ }
24
+ return value;
25
+ }
26
+ /** Split a comma-separated flag value into a trimmed, non-empty string array. */
27
+ export function parseCsv(value) {
28
+ return value
29
+ .split(',')
30
+ .map((s) => s.trim())
31
+ .filter((s) => s.length > 0);
32
+ }
33
+ /**
34
+ * Parse a finite decimal flag value (#2545). Unlike {@link parseIntFlag}, the `/^\d+(\.\d+)?$/`
35
+ * test accepts a fractional value such as `2.5` — required for spec `type: number` fields like
36
+ * `monitors change_threshold` (default 5, range 0–100) that `parseIntFlag`'s `/^\d+$/` would reject
37
+ * at parse time. Rejects `abc`, ``, `-5`, `1.` and `1e2` with the flag named in the message.
38
+ */
39
+ export function parseNumberFlag(flag, value) {
40
+ if (!/^\d+(\.\d+)?$/.test(value)) {
41
+ throw new InvalidArgumentError(`${flag} must be a number.`);
42
+ }
43
+ return Number(value);
44
+ }
45
+ /**
46
+ * Parse a `cache_ttl` flag value (#2545). Mirrors the gateway validator's own format grammar
47
+ * (`^(0|\d+[hd])$`, `packages/gateway/src/validators/shared.ts`) locally so an off-grammar value
48
+ * (`30m`, `soon`) is rejected at parse time with an actionable message instead of a server 422.
49
+ * The string is returned verbatim — the validator accepts the string `'0'`, and the range cap
50
+ * (7d/168h for scrape+fetch) stays a server-side concern.
51
+ */
52
+ export function parseCacheTtl(flag, value) {
53
+ if (!/^(0|\d+[hd])$/.test(value)) {
54
+ throw new InvalidArgumentError(`${flag} must be 0, Nh (hours), or Nd (days) — e.g. 48h, 2d, 0.`);
55
+ }
56
+ return value;
57
+ }
58
+ /**
59
+ * Parse a JSON-object- or JSON-array-valued flag (#2545). The ONLY `JSON.parse` call site for CLI
60
+ * flag values — every JSON-shaped flag (`--cookies`, `--headers`, `--custom`,
61
+ * `--webhook-headers`, `--options`, `--scrape-options`) routes through here, so command modules
62
+ * carry no raw `JSON.parse` (grep-gated). Rejects malformed JSON AND a value whose top-level shape
63
+ * does not match `expect`, with a copy-pasteable example in the message (E5).
64
+ */
65
+ export function parseJsonFlag(flag, value, expect) {
66
+ const example = expect === 'array' ? `[{"name":"a","value":"b"}]` : `{"X-Header":"value"}`;
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(value);
70
+ }
71
+ catch {
72
+ throw new InvalidArgumentError(`${flag} must be valid JSON (a ${expect}), e.g. '${example}'.`);
73
+ }
74
+ const matches = expect === 'array'
75
+ ? Array.isArray(parsed)
76
+ : typeof parsed === 'object' && parsed !== null && !Array.isArray(parsed);
77
+ if (!matches) {
78
+ throw new InvalidArgumentError(`${flag} must be a JSON ${expect}, e.g. '${example}'.`);
79
+ }
80
+ return parsed;
81
+ }
82
+ /**
83
+ * Commander collector for a repeatable, CSV-aware `--format` flag (#2545). Each occurrence is
84
+ * CSV-split and appended to the accumulator, so `--format raw_html --format markdown` and
85
+ * `--format raw_html,markdown` both yield `['raw_html','markdown']`. Commander seeds `prev` with
86
+ * the option default (or the previous return) on each occurrence.
87
+ */
88
+ export function collectFormats(value, prev) {
89
+ return [...(prev ?? []), ...parseCsv(value)];
90
+ }
package/dist/output.d.ts CHANGED
@@ -22,6 +22,24 @@ export interface OutputFlags {
22
22
  raw?: boolean | undefined;
23
23
  output?: string | undefined;
24
24
  }
25
+ /**
26
+ * Serialize a value as the CLI's `--json` payload text. The single serializer EVERY `--json`
27
+ * branch in this module goes through, so no branch can regress to a raw `JSON.stringify`.
28
+ *
29
+ * `JSON.stringify` returns the VALUE `undefined` — not a string — for `undefined`, functions and
30
+ * symbols. Handed to `console.log` that prints the literal text `undefined`, which is NOT valid
31
+ * JSON and breaks every `| jq` pipeline; handed to `writeFileSync` it throws
32
+ * `ERR_INVALID_ARG_TYPE`. A `204 No Content` response resolves to `undefined` in the SDK
33
+ * (`monitors.delete` is typed `Promise<void>`), so this is the ORDINARY shape of
34
+ * `bytekit monitors delete <id> --json`, not an exotic edge case (#2544).
35
+ *
36
+ * The representation is JSON `null` — the canonical JSON spelling of "no content". Deliberately
37
+ * NOT a synthesized envelope such as `{"status":"deleted"}`: that is a shape the API never
38
+ * returned, and it would have to be re-invented (and kept consistent) per command. `null` is
39
+ * uniform across every 204-returning path, and `… --json | jq .` prints `null` and exits 0.
40
+ * The contract is documented in README (`Exit codes and output contracts`) and CHANGELOG.
41
+ */
42
+ export declare function toJsonText(data: unknown): string;
25
43
  /**
26
44
  * Resolve the output mode from the flags, and enforce the `--json` / `--raw` exclusion.
27
45
  *
@@ -56,6 +74,40 @@ export declare function resolveOutputMode(flags: OutputFlags): OutputMode;
56
74
  * not text, so `--raw` can never print `[object Object]` or a format the caller did not ask for.
57
75
  */
58
76
  export declare function extractRawBody(response: unknown, requestedFormat?: string): string;
77
+ /**
78
+ * Exit code for `--raw`/`-o` against a 202 queued (async) scrape.
79
+ *
80
+ * Deliberately NOT 0 (a full raw-body success) and NOT 1 (a usage/API error): the job WAS accepted
81
+ * and billed, there is simply no content body to emit yet. A distinct code lets a caller branch on
82
+ * "queued" without parsing stderr.
83
+ */
84
+ export declare const QUEUED_RAW_EXIT_CODE = 2;
85
+ /** A 202 ScrapeQueuedEnvelope: `status: 'queued'` plus a stable `sc_` id (openapi.yaml). */
86
+ interface QueuedEnvelope {
87
+ status: 'queued';
88
+ id: string;
89
+ }
90
+ /**
91
+ * Handle `--raw`/`-o` on a 202 queued envelope.
92
+ *
93
+ * The user asked for a content body that does not exist yet — but the job was accepted, so losing
94
+ * the handle (the old behaviour: throw `--raw: response contains no formats`, exit 1) is the real
95
+ * defect. Instead: emit the `sc_` id to STDOUT so `$(bytekit scrape create … --raw)` still captures
96
+ * a usable handle, print a human note to STDERR explaining it is queued and how to poll, and signal
97
+ * {@link QUEUED_RAW_EXIT_CODE} — distinguishable from both success and error.
98
+ *
99
+ * The exit code is a SOFT `process.exitCode`, never a hard `process.exit` (#2544). Node documents
100
+ * `process.stdout` as ASYNCHRONOUS when it points at a pipe: a write that cannot be handed to the
101
+ * kernel in one go is queued in userspace and drained on a later event-loop turn, and
102
+ * `process.exit()` runs before any of those turns and DISCARDS the queue. That is exactly the
103
+ * `sc=$(bytekit scrape create … --async --raw)` case this branch exists to serve — the id would be
104
+ * truncated away whenever stdout is backlogged. Setting `process.exitCode` instead lets the process
105
+ * end naturally once stdout has drained, with the same observable exit code.
106
+ *
107
+ * Returns `void` (not `never`): callers MUST `return` after invoking it, since execution now
108
+ * continues past this function.
109
+ */
110
+ export declare function emitQueuedRaw(env: QueuedEnvelope): void;
59
111
  /**
60
112
  * Emit a raw body to the target.
61
113
  *
@@ -78,6 +130,27 @@ export declare function emitRaw(body: string, target: OutputTarget): void;
78
130
  * `--json | jq` pipelines stay byte-identical.
79
131
  */
80
132
  export declare function emitResult(result: unknown, mode: OutputMode, target: OutputTarget, requestedFormat?: string): void;
133
+ /**
134
+ * Collect the `X-Fetch-*` response headers of a {@link FetchResult} into a Title-Cased object —
135
+ * the documented `--json` payload for a fetch command.
136
+ */
137
+ export declare function collectFetchMetadata(headers: Headers): Record<string, string>;
138
+ /**
139
+ * Render a `/v1/fetch` content result.
140
+ *
141
+ * The SDK's fetch resource resolves a {@link FetchResult} whose `bytes` ARE the fetched content and
142
+ * whose `headers` carry the `X-Fetch-*` metadata.
143
+ *
144
+ * - default — write the raw body BYTES verbatim (`process.stdout.write`, never `console.log`: no
145
+ * coercion, no appended newline, binary-safe). `bytes` is used rather than the `body` string
146
+ * because the string is a lossy UTF-8 decode for true binary passthrough (images, PDFs).
147
+ * - `--json` — print the `X-Fetch-*` metadata object instead of the body.
148
+ * - `target.file` — write the same bytes to that path instead of stdout, byte-for-byte.
149
+ *
150
+ * Falls back to {@link printResult} for any result that is not a fetch result (defensive — e.g. a
151
+ * bulk-fetch envelope routed here by mistake), so no path can silently emit nothing.
152
+ */
153
+ export declare function emitFetchResult(result: unknown, jsonMode: boolean, target?: OutputTarget): void;
81
154
  /**
82
155
  * Print a result to stdout.
83
156
  *
@@ -86,5 +159,20 @@ export declare function emitResult(result: unknown, mode: OutputMode, target: Ou
86
159
  export declare function printResult(data: unknown, jsonMode: boolean): void;
87
160
  /**
88
161
  * Handle a CLI command error: print to stderr and exit 1.
162
+ *
163
+ * The message always begins `Error: ` (a prefix scripts grep on) followed by the error message and
164
+ * an optional parenthesised suffix built from the two diagnostic axes the ByteKit errors carry:
165
+ *
166
+ * - a `ByteKitError` from the transport carries both a numeric `status` and an API `code`
167
+ * (`rate_limited`, `invalid_url`, …) → `Error: <message> (<code>, status: <n>)`;
168
+ * - a `ByteKitConnectionError` (client-side timeout / abort / socket failure) carries a `code`
169
+ * (`timeout` | `aborted` | `connection`) but NO status → `Error: <message> (<code>)`;
170
+ * - a plain error with neither → `Error: <message>`.
171
+ *
172
+ * The `code` axis is read only off the two ByteKit error classes (see {@link errorCode}) — a plain
173
+ * error carrying an unrelated string `.code` (a Node errno error) gets NO code suffix. When a
174
+ * status is present but no code (a hand-built error), the suffix is `(status: <n>)` with no empty
175
+ * placeholder — never `(, status: <n>)`.
89
176
  */
90
177
  export declare function handleError(err: unknown): never;
178
+ export {};