@hunt-labs/bytekit-cli 0.5.0 → 0.6.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/CHANGELOG.md CHANGED
@@ -8,8 +8,113 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  This file records only the delta between versions. For how to use the package
9
9
  today, see [README.md](./README.md), which remains the authoritative usage guide.
10
10
 
11
+ ## [Unreleased]
12
+
13
+ ## [0.6.0] - 2026-07-29
14
+
15
+ ### Breaking
16
+
17
+ - **`screenshots create --async` now exits `2` in human mode instead of `0`.** The queued 202 is
18
+ not a completed capture — there is no artifact yet — and every other queued path in the CLI
19
+ already signalled that with exit code `2` (`scrape create --async`). A script that relied on
20
+ exit `0` to mean "the screenshot is ready" was reading a false success; under `set -e` such a
21
+ script now stops at the queue step, which is the intended correction. The envelope itself is
22
+ unchanged and still printed. Two carve-outs: **`--json` keeps exit `0`** (the envelope is a
23
+ complete machine-readable payload — identical to `scrape create --async --json`), and
24
+ `--async -o <file>` also exits `2` while still writing no file (it prints the queued envelope
25
+ and skips the download, as before). Poll with `bytekit screenshots get <id>`.
26
+ - **A `--file` per-item key outside a bulk endpoint's own strict schema now fails the whole
27
+ create with a `422`** instead of silently succeeding with that key ignored. In 0.5.x, `--file`
28
+ never read anything but `url` from a JSON line — every other key was dropped client-side
29
+ before the request was ever built. As of 0.6.0 those keys ride to the server (`items` on
30
+ `bulk create` / `scrape bulk create`; inside `urls` on `fetch bulk create`), and `/v1/bulk` /
31
+ `/v1/scrape/bulk` reject any key their `.strict()` per-item schema doesn't recognize. A file
32
+ that worked in 0.5.x purely because its extra keys were quietly discarded may now `422` — see
33
+ each command's `--file` help for the keys actually honored on that endpoint.
34
+ - **`fetch bulk create --file` per-item keys outside `url`/`format`/`country`/`cache_ttl` now
35
+ fail the whole create with a `422`** instead of silently succeeding with that key ignored.
36
+ `/v1/fetch/bulk`'s per-item override schema (inside `urls[]`) is now `.strict()` on the
37
+ gateway (issue #2648), matching the `/v1/bulk` / `/v1/scrape/bulk` posture above. The
38
+ `--file` help text for `fetch bulk create` is now uniform with `bulk create` /
39
+ `scrape bulk create` and no longer enumerates keys — this entry is where the honored set
40
+ (`url`, `format`, `country`, `cache_ttl`) is documented instead.
41
+
42
+ ### Added
43
+
44
+ - **Per-item bulk configuration on all three bulk creates** (`bulk create`, `scrape bulk create`,
45
+ `fetch bulk create`). A `--file` line may now be a JSON object carrying `url` plus per-item
46
+ fields instead of only a bare URL; such a file is sent as `items` on `/v1/bulk` and
47
+ `/v1/scrape/bulk`, and as a heterogeneous `urls` array on `/v1/fetch/bulk` (which has no
48
+ `items` property). A line whose only key is `url` still normalizes to a bare URL string, so a
49
+ file of url-only lines produces a byte-identical request to before. A JSON line with a missing
50
+ or non-string `url` is now rejected locally with a usage error (exit `1`) instead of being sent
51
+ to the server as a URL. (See Breaking, above, for the per-item strict-schema behavior change
52
+ this enables.)
53
+ - `--defaults`, `--webhook-secret` and `--metadata` on all three bulk creates, plus `--custom` on
54
+ `scrape bulk create` only (`/v1/bulk` and `/v1/fetch/bulk` have no top-level `custom`, so the
55
+ flag would be rejected or stripped there). `--defaults` is forwarded verbatim; the server owns
56
+ precedence and unknown-key handling.
57
+ - The full markdown-tuning surface on `fetch create`: `--markdown-query`, `--markdown-links`,
58
+ `--markdown-images`, `--with-links-summary`, `--with-images-summary`, `--markdown-compact`,
59
+ `--markdown-filter-images`, `--markdown-include-media`, `--markdown-include-warnings`,
60
+ `--markdown-include-stats`, and `--custom`.
61
+ - Webhook delivery and processing flags on `sitemap create`: `--webhook-url`, `--webhook-secret`,
62
+ `--cache-ttl`, `--compact`, `--process`, `--metadata`. Webhook-driven sitemap crawls were
63
+ previously unreachable from the shell.
64
+ - `--headers`, `--cookies` and `--metadata` on `screenshots create`.
65
+ - `--metadata` on `monitors create`, and `--metadata` + `--url` on `monitors update`. `--type`
66
+ remains deliberately absent from `update` — it is immutable server-side.
67
+
68
+ ## [0.5.1] - 2026-07-28
69
+
70
+ ### Fixed
71
+
72
+ - **Corrects the scope of the 0.5.0 "stalled body" fix claim.** `screenshots create`/`get -o`
73
+ downloads the `image_url` artifact bytes with a bare `globalThis.fetch` call
74
+ (`lib/download-artifact.ts`) that sat entirely outside the transport seam `sendWithRetry` owns —
75
+ no abort signal, no connection-error taxonomy. The 0.5.0 entry below ("The per-attempt request
76
+ timeout now covers the response body read... a server that answered with headers and then
77
+ stalled its body... hung the CLI forever", #2546) reads as if it covered every response body; it
78
+ only ever covered API-call bodies read through the JSON/raw transports. A stalled artifact host
79
+ (headers, then a body that never completes) still hung the CLI indefinitely until now. The
80
+ download path now shares the same per-attempt abort budget and `ByteKitConnectionError` timeout
81
+ taxonomy as the API transport (`createAttemptBudget` / `toConnectionError`, exported from
82
+ `client.ts` — no second timeout implementation), with the same 120 s per-attempt default and no
83
+ retry (artifact GETs are not the API's 429 domain). Partial-file policy: the response is
84
+ buffered fully in memory before any write, so an aborted download never leaves a
85
+ partial/truncated file at the `-o` destination. **User-visible consequence:** a legitimately
86
+ slow (but not stalled) large artifact download now hard-fails after 120 s instead of waiting
87
+ indefinitely, and there is currently no CLI flag to raise that budget for a single command.
88
+ (#2587)
89
+ - While in the file: the 429-retry path left its (unread) response body undrained before sleeping
90
+ and retrying — undici cannot reclaim the underlying socket for connection-pool reuse until a body
91
+ is fully read. The body is now drained with `response.text()` on that path, bounded by its own
92
+ short (2 s) cap independent of the 120 s per-attempt budget, so a 429 whose body stalls can no
93
+ longer delay the retry by anywhere near the full attempt budget (or, with the budget disabled,
94
+ forever). **Correction to an intermediate `Unreleased` draft of this same fix:** that draft's cap
95
+ only bounded how long the _retry_ waited — it resolved on schedule but never touched the
96
+ abandoned `response.text()` call itself, which kept reading against a live socket indefinitely.
97
+ Because every CLI success path exits via a soft `process.exitCode` (#2544) rather than
98
+ `process.exit()`, that orphaned read kept the whole process alive forever — strictly worse than
99
+ having no drain at all, and on the exact class of hang this ticket exists to eliminate. The cap
100
+ now also **aborts** the request when it fires, so the abandoned read is actually torn down, not
101
+ merely ignored; this holds even when the per-attempt timeout itself is disabled
102
+ (`timeoutMs <= 0`). **User-visible consequence:** a 429 with a stalled body still costs the retry
103
+ at most ~2 s, exactly as before, but the CLI process now reliably exits afterward instead of
104
+ hanging forever regardless of that visible latency. The same discard-drain on the
105
+ artifact-download's non-2xx path is removed outright rather than capped: that path throws
106
+ straight into `process.exit(1)`, so there is no subsequent request in the process for a reclaimed
107
+ socket to serve, and the drain there bought nothing. (#2587)
108
+
11
109
  ## [0.5.0] - 2026-07-24
12
110
 
111
+ ### Note
112
+
113
+ - The `0.3.1` and `0.4.0` versions below document real, tagged changes but were
114
+ never published to npm — the registry's version history jumps straight from
115
+ `0.3.0` to `0.5.0`. Don't go looking for those tarballs; the changes they
116
+ describe ship for the first time in this release.
117
+
13
118
  ### Added
14
119
 
15
120
  - Surface parity round 2 (#2545): every `x-sdk-scope: v0.1` endpoint the SDK
@@ -236,7 +341,11 @@ Additive, lower-risk release — no breaking changes to existing commands.
236
341
  - Initial release of the ByteKit CLI, wrapping the ByteKit SDK as shell
237
342
  subcommands, published under the original `rapidcrawl-cli` name.
238
343
 
344
+ [Unreleased]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
345
+ [0.5.1]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
346
+ [0.5.0]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
239
347
  [0.4.0]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
348
+ [0.3.1]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
240
349
  [0.3.0]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
241
350
  [0.2.2]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
242
351
  [0.2.1]: https://github.com/Hunt-Labs-Inc/ByteKit/tree/main/packages/cli
package/README.md CHANGED
@@ -182,6 +182,11 @@ Both verbs accept `--country <cc>`, `--timeout-ms <n>`, and `--cache-ttl <ttl>`
182
182
  bytekit search "ai web scraping" --limit 10 --country us
183
183
  ```
184
184
 
185
+ `search` also accepts `--type <type>` (`web` (default), `news`, or `images`),
186
+ `--language <code>` (two-letter language code, e.g. `en`, `fr`), and
187
+ `--date-range <window>` (recency filter: `any` (default), `hour`, `day`,
188
+ `week`, `month`, `year`). Each flag is omitted from the request when absent.
189
+
185
190
  ### `screenshots` — capture a page
186
191
 
187
192
  ```bash
@@ -232,7 +237,7 @@ bytekit monitors create --url https://example.com/pricing --interval-type daily
232
237
  bytekit monitors list --limit 25 --status active
233
238
  bytekit monitors update mon_123abc --status paused # pause
234
239
  bytekit monitors update mon_123abc --status active # resume
235
- bytekit monitors captures list mon_123abc
240
+ bytekit monitors captures list mon_123abc --limit 25
236
241
  bytekit monitors delete mon_123abc
237
242
  ```
238
243
 
@@ -240,6 +245,10 @@ bytekit monitors delete mon_123abc
240
245
  (`active`, `processing`, `paused`, `cancelled`, `suspended`) as query filters,
241
246
  each omitted when absent.
242
247
 
248
+ `monitors captures list <id>` accepts `--limit <n>` and `--cursor <cursor>` to
249
+ page through a monitor's capture history — pass the response's `next_cursor`
250
+ back as `--cursor` to fetch the next page. Both flags are omitted when absent.
251
+
243
252
  `monitors update` gains `--status` (`active` | `paused` — pause and resume from
244
253
  the shell), `--change-threshold <pct>` (accepts a float), `--notify-on`
245
254
  (`change`, `every`), `--webhook-secret <secret>`, and three JSON-object flags:
@@ -294,11 +303,24 @@ omitted when absent. `retry <id>` requeues a failed or exhausted delivery.
294
303
 
295
304
  ## Exit codes
296
305
 
297
- | Code | Meaning |
298
- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
299
- | `0` | Success. |
300
- | `1` | Usage error, or an API/connection error. The reason is on stderr, always prefixed `Error: `. |
301
- | `2` | The scrape was accepted but is still **queued**, so there is no content body to emit — `scrape create --async` with `--raw` or a bare `-o` (which writes the bare `sc_` id to stdout), and the same command in human mode. Not a failure: the job was accepted and billed. Poll it with `bytekit scrape get <id>`. |
306
+ | Code | Meaning |
307
+ | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
308
+ | `0` | Success. |
309
+ | `1` | Usage error, or an API/connection error. |
310
+ | `2` | The job was accepted but is still **queued**, so there is no content body or artifact to emit. Applies to `scrape create --async` with `--raw` or a bare `-o` (which writes the bare `sc_` id to stdout) and in human mode, and to `screenshots create --async` in human mode (including with `-o`, which deliberately writes no file). Not a failure: the job was accepted and billed. Poll it with `bytekit scrape get <id>` / `bytekit screenshots get <id>`. **`--json` keeps exit `0` in both cases** — the queued envelope is a complete machine-readable payload. |
311
+
312
+ The stderr message for exit code `1` has two shapes, depending on where the
313
+ error originates:
314
+
315
+ - **App-level errors** (a rejected API call, a connection failure, a missing
316
+ key) are always prefixed `Error: ` (uppercase) — e.g.
317
+ `Error: API key required. Pass --key or set BYTEKIT_API_KEY.`
318
+ - **Commander-level usage errors** (an unknown command, a missing required
319
+ argument, an unrecognized flag) come from the `commander` library itself and
320
+ use its own lowercase `error: ` prefix — e.g. `error: unknown command 'bogus'`.
321
+
322
+ Scripts that grep stderr for a failure reason should match case-insensitively,
323
+ or handle both prefixes.
302
324
 
303
325
  Code `2` is deliberately distinct from both `0` and `1` so a script can branch
304
326
  on "queued" without parsing stderr:
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ByteKit } from '@hunt-labs/bytekit-sdk';
1
+ import { ByteKit, ByteKitConnectionError } from '@hunt-labs/bytekit-sdk';
2
2
  export interface GlobalOptions {
3
3
  key?: string | undefined;
4
4
  baseUrl?: string | undefined;
@@ -22,6 +22,24 @@ export declare const MIN_RETRY_AFTER_MS = 1000;
22
22
  export declare const MAX_RETRY_AFTER_MS = 60000;
23
23
  /** Exactly one retry. A second 429 is terminal — no loop. */
24
24
  export declare const MAX_RETRIES = 1;
25
+ /**
26
+ * Cap on a best-effort, discard-only body drain (#2587 cr2-1). Independent of the per-attempt
27
+ * abort budget: the 429 drain below (`response.text().catch(() => {})`) exists ONLY so undici can
28
+ * return the socket to the connection pool — nothing downstream reads the drained bytes — but
29
+ * binding that read to the FULL per-attempt budget (120 s default, or, under `timeoutMs <= 0`,
30
+ * to no bound at all) let a stalled 429 body burn the whole budget, or hang forever, before a
31
+ * retry that needs nothing from that body. Short enough that a legitimately stalled 429 body costs
32
+ * the retry very little wall-clock time.
33
+ *
34
+ * #2587 cr3-2: past this cap, the read is not merely abandoned — {@link drainBestEffort} actively
35
+ * ABORTS its underlying request. Resolving the race without aborting anything (the earlier
36
+ * cr2-1 shape) let the retry proceed on schedule while the abandoned `response.text()` kept
37
+ * reading against a live socket forever, which — because the CLI exits via a soft
38
+ * `process.exitCode` (#2544) rather than `process.exit()` — kept the whole process alive
39
+ * indefinitely. `.catch`-guarding the read only made it safe to abandon the RESULT; it never made
40
+ * abandoning the underlying READ safe.
41
+ */
42
+ export declare const DRAIN_TIMEOUT_MS = 2000;
25
43
  /**
26
44
  * Default CLIENT-SIDE abort budget for a single fetch attempt, in ms.
27
45
  *
@@ -33,6 +51,49 @@ export declare const MAX_RETRIES = 1;
33
51
  * exists.
34
52
  */
35
53
  export declare const DEFAULT_TIMEOUT_MS = 120000;
54
+ /**
55
+ * A per-attempt abort budget: an `AbortController`, optionally fired by a `setTimeout`.
56
+ *
57
+ * The budget spans the WHOLE attempt — time-to-headers **and** the response body read (#2546).
58
+ * Arming it only until headers arrive left `response.text()` / `response.arrayBuffer()` unbounded,
59
+ * so a server that answered with headers and then stalled its body hung the CLI forever.
60
+ *
61
+ * The controller ALWAYS exists, even when `ms <= 0` — only its own timeout timer is conditional
62
+ * on `ms > 0`. `sendWithRetry`'s 429-drain cap ({@link DRAIN_TIMEOUT_MS}, via the returned
63
+ * `abort()`) needs a live controller to tear down an abandoned body read regardless of whether the
64
+ * OVERALL per-attempt budget is enabled — `ms <= 0` must still mean "no full-attempt timeout", not
65
+ * "no controller to abort a stalled drain with" (#2587 cr3-2: the earlier drain fix abandoned the
66
+ * read entirely rather than aborting it, so the socket — and the event loop — stayed alive
67
+ * indefinitely, worse than the pre-fix baseline it replaced).
68
+ *
69
+ * Deliberately NOT `AbortSignal.timeout` (which the SDK prefers): that schedules on a timer the
70
+ * test runner's fake timers cannot advance, so the transport's timeout would be untestable under
71
+ * `vi.useFakeTimers()`. CLI requests are short-lived and single-shot, so the GC advantage
72
+ * `AbortSignal.timeout` buys the long-running SDK does not apply here. The timer is `unref`'d so a
73
+ * pending budget never keeps the process alive, and `cleanup()` clears it on every exit path.
74
+ *
75
+ * Exported so `lib/download-artifact.ts` can reuse the same budget for the artifact-download path
76
+ * (#2587) rather than growing a second timeout implementation — that path sits entirely outside
77
+ * `sendWithRetry` (it is not a JSON/raw API call), so it would otherwise never inherit this bound.
78
+ */
79
+ export declare function createAttemptBudget(ms: number): {
80
+ signal: AbortSignal;
81
+ cleanup: () => void;
82
+ abort: (reason: unknown) => void;
83
+ };
84
+ /**
85
+ * Map a `fetch` or body-read rejection (no usable HTTP payload was produced) onto the SDK's
86
+ * `ByteKitConnectionError` taxonomy, so a CLI failure carries the same `{name, code, cause}` a
87
+ * direct SDK call would.
88
+ *
89
+ * `timedOut` is true when this attempt's own budget fired the abort, which is the reliable
90
+ * discriminator (undici rejects with the signal's reason — or a wrapper carrying it, see
91
+ * {@link abortNameOf} — whose `name` we also inspect as a fallback for a caller-style AbortError).
92
+ *
93
+ * Exported so `lib/download-artifact.ts` reuses the same taxonomy for the artifact-download path
94
+ * (#2587) instead of hand-rolling a second error mapping.
95
+ */
96
+ export declare function toConnectionError(err: unknown, method: string, path: string, timedOut: boolean): ByteKitConnectionError;
36
97
  /**
37
98
  * Parse a `Retry-After` header into a clamped millisecond delay.
38
99
  *
@@ -48,6 +109,18 @@ export declare function parseRetryAfterMs(raw: string | null): number;
48
109
  * property of the client (see {@link buildClient}) routes all JSON commands through it from one
49
110
  * seam. `/v1/fetch` does NOT ride this transport — it rides `requestRaw` (see
50
111
  * {@link createRetryingRequestRaw}); `/v1/fetch/bulk` is a JSON job envelope and does.
112
+ *
113
+ * DECISION (#2586 dod-4): the 4th `RequestOptions` parameter of the SDK's contract is accepted
114
+ * here — so the signature matches `ByteKit['request']` exactly, closing the gap where a caller-typo
115
+ * of a 5th positional argument would silently no-op instead of a compile error — but deliberately
116
+ * NOT forwarded into `sendWithRetry`. Two reasons: (1) no CLI command constructs a `RequestOptions`
117
+ * today (no `--timeout` flag exists yet, and `signal` has no CLI-level source), so the parameter is
118
+ * always `undefined` in practice; (2) the CLI already owns a per-attempt abort budget with its own
119
+ * semantics (`createAttemptBudget`, `timeoutMs` above) that would need explicit composition with a
120
+ * caller `signal` to honor `RequestOptions` correctly (the SDK's `composeSignals`) — inventing that
121
+ * unexercised composition here, with no caller able to reach it, would be undertested surface for no
122
+ * present behavior change. If a future CLI flag needs per-call overrides, wire it through
123
+ * `sendWithRetry`'s `timeoutMs` (already per-attempt) at that point, with its own tests.
51
124
  */
52
125
  export declare function createRetryingRequest(apiKey: string, baseUrl: string, timeoutMs?: number): ByteKit['request'];
53
126
  /**
@@ -62,8 +135,32 @@ export declare function createRetryingRequest(apiKey: string, baseUrl: string, t
62
135
  * so `FetchResource` builds an ordinary `FetchResult` from it and the CLI never forks fetch
63
136
  * handling away from the SDK. `bytes` is byte-exact (binary-safe); `body` is the best-effort UTF-8
64
137
  * decode, matching the SDK's own semantics.
138
+ *
139
+ * The 4th `RequestOptions` parameter is accepted but not forwarded — see the DECISION note on
140
+ * {@link createRetryingRequest}, which applies here verbatim. `FetchResource.create`/`get` DO thread
141
+ * a `requestOptions` argument through to this seam (unlike every other resource), but no CLI `fetch`
142
+ * command constructs one today (no `--timeout` flag), so it is still always `undefined` in practice.
65
143
  */
66
144
  export declare function createRetryingRequestRaw(apiKey: string, baseUrl: string, timeoutMs?: number): ByteKit['requestRaw'];
145
+ /**
146
+ * Build the CLI's header-carrying JSON transport: the SDK's `requestWithResponse` contract
147
+ * (`{data, headers, status}`, #2547), plus the same bounded 429 retry every other CLI transport
148
+ * gets.
149
+ *
150
+ * Unused by any CLI command today — `screenshots.createWithResponse` / `getWithResponse` and
151
+ * `sitemap.getWithResponse` all exist on the SDK surface but no command builds on them yet. It is
152
+ * still installed (see {@link buildClient}) rather than left un-overridden: the transport-coverage
153
+ * contract test (#2586 dod-3) derives its method list from the real SDK prototype, and leaving a
154
+ * transport method un-overridden is exactly how #2535 shipped broken — the first command that DOES
155
+ * call a `*WithResponse` resource method must not silently ride the bare SDK transport and skip the
156
+ * CLI's retry/timeout/error taxonomy.
157
+ *
158
+ * Same body-read shape as {@link createRetryingRequest} (204/empty → `undefined`, `JSON.parse`
159
+ * otherwise), with `headers` and `status` threaded through from the successful response alongside
160
+ * the parsed `data`. The 4th `RequestOptions` parameter is accepted but not forwarded — see the
161
+ * DECISION note on {@link createRetryingRequest}.
162
+ */
163
+ export declare function createRetryingRequestWithResponse(apiKey: string, baseUrl: string, timeoutMs?: number): ByteKit['requestWithResponse'];
67
164
  /**
68
165
  * Build an authenticated SDK client from global CLI options.
69
166
  *
@@ -72,9 +169,18 @@ export declare function createRetryingRequestRaw(apiKey: string, baseUrl: string
72
169
  * baseUrl: --base-url flag → BYTEKIT_BASE_URL → DEFAULT_BASE_URL.
73
170
  * Exits with a descriptive error when no key is available.
74
171
  *
75
- * The returned client's `request` AND `requestRaw` are both replaced by the retrying transports, so
76
- * every command inherits the 429 retry — JSON commands via `request`, the `/v1/fetch` content
77
- * commands via `requestRaw`. Overriding only `request` is the #2535 defect: the SDK's fetch
78
- * resource rides `requestRaw`, so fetch bypassed the retry and the CLI's error taxonomy entirely.
172
+ * The returned client's `request`, `requestRaw`, AND `requestWithResponse` are all replaced by the
173
+ * retrying transports, so every command inherits the 429 retry — JSON commands via `request`, the
174
+ * `/v1/fetch` content commands via `requestRaw`, and the header-carrying `*WithResponse` surface via
175
+ * `requestWithResponse`. Overriding only `request` is the #2535 defect: the SDK's fetch resource
176
+ * rides `requestRaw`, so fetch bypassed the retry and the CLI's error taxonomy entirely; leaving
177
+ * `requestWithResponse` un-overridden is the same class of defect (#2586) — it is unused by any CLI
178
+ * command TODAY, but a future command built on `screenshots.createWithResponse` /
179
+ * `sitemap.getWithResponse` would otherwise silently ride the bare SDK transport.
180
+ *
181
+ * `baseUrl` is normalized ONCE, here, before it reaches either the SDK constructor or the CLI's own
182
+ * transport builders below — the CLI's transports build their request URL from this local variable
183
+ * directly (they do not call the SDK's own normalized `send()`), so a trailing-slash flag would
184
+ * otherwise double-slash every request the CLI makes (#2586).
79
185
  */
80
186
  export declare function buildClient(opts: GlobalOptions): ByteKit;