@hunt-labs/bytekit-cli 0.9.3 → 0.10.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 +21 -0
- package/README.md +5 -2
- package/dist/commands/bulk.js +19 -4
- package/dist/commands/fetch.js +18 -3
- package/dist/commands/scrape.js +20 -2
- package/dist/lib/parse-flags.d.ts +9 -1
- package/dist/lib/parse-flags.js +11 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,27 @@ today, see [README.md](./README.md), which remains the authoritative usage guide
|
|
|
10
10
|
|
|
11
11
|
## [Unreleased]
|
|
12
12
|
|
|
13
|
+
## [0.10.0] - 2026-09-14
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `--solve-challenge` on `scrape create` (#4449). It sends `solve_challenge: true`, which asks
|
|
18
|
+
the scrape to attempt to clear a Cloudflare challenge; success is not guaranteed. It is billed
|
|
19
|
+
as a normal browser render. Without the flag the request body is unchanged and carries no
|
|
20
|
+
`solve_challenge` key.
|
|
21
|
+
|
|
22
|
+
## [0.9.4] - 2026-09-10
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- `--cursor`, `--limit` and `--status` on the three bulk recovery commands (#3717):
|
|
27
|
+
`bulk screenshots list <id>`, `scrape bulk get <id>` and `fetch bulk get <id>`.
|
|
28
|
+
They forward the cursor pagination the API already exposed, so a job whose webhook
|
|
29
|
+
manifest came back incomplete can be enumerated page by page from the CLI.
|
|
30
|
+
`--limit` is bounded 1-500 and `--status` accepts only the documented result
|
|
31
|
+
statuses; both are rejected locally before a request is made. No new command and no
|
|
32
|
+
`--all` flag — the existing commands gained options, nothing else moved.
|
|
33
|
+
|
|
13
34
|
## [0.9.3] - 2026-09-03
|
|
14
35
|
|
|
15
36
|
### Changed
|
package/README.md
CHANGED
|
@@ -129,6 +129,7 @@ materialized):
|
|
|
129
129
|
| `--include-tags <csv>` | Comma-separated HTML tags to keep. |
|
|
130
130
|
| `--exclude-tags <csv>` | Comma-separated HTML tags to drop. |
|
|
131
131
|
| `--mobile` | Render with a mobile viewport/user-agent. |
|
|
132
|
+
| `--solve-challenge` | Attempt to clear a Cloudflare challenge; success is not guaranteed. Billed as a normal browser render. |
|
|
132
133
|
| `--async` | Queue the scrape and return a job envelope immediately. |
|
|
133
134
|
| `--webhook-url <url>` | Webhook notified when an async scrape completes. |
|
|
134
135
|
|
|
@@ -276,8 +277,10 @@ bytekit bulk screenshots list "$bulk_id"
|
|
|
276
277
|
`urls.txt` is newline-delimited: one plain URL (or `{"url":"https://..."}`) per
|
|
277
278
|
line. A line whose only key is `url` is sent as a bare URL string; any line with
|
|
278
279
|
additional keys switches that entry to the per-item shape. `bulk cancel <id>`
|
|
279
|
-
stops an in-flight job
|
|
280
|
-
|
|
280
|
+
stops an in-flight job. Recover another result page with `--cursor <cursor>`,
|
|
281
|
+
bound it with `--limit <n>`, or filter it with `--status <status>` on
|
|
282
|
+
`bulk screenshots list <id>`. The same recovery options are available on
|
|
283
|
+
`scrape bulk get <id>` and `fetch bulk get <id>`.
|
|
281
284
|
|
|
282
285
|
The three bulk-job creators — `bulk create`, `scrape bulk create` and
|
|
283
286
|
`fetch bulk create` — share one flag set:
|
package/dist/commands/bulk.js
CHANGED
|
@@ -2,19 +2,34 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { buildClient } from '../client.js';
|
|
3
3
|
import { printResult, handleError } from '../output.js';
|
|
4
4
|
import { parseBulkLines, toBulkItems } from '../lib/url-lines.js';
|
|
5
|
-
import { parseJsonFlag } from '../lib/parse-flags.js';
|
|
5
|
+
import { parseEnum, parseIntFlag, parseJsonFlag } from '../lib/parse-flags.js';
|
|
6
|
+
const BULK_RESULT_STATUSES = ['pending', 'processing', 'completed', 'failed', 'cancelled'];
|
|
7
|
+
// Mirrors the `limit` query schema (`minimum: 1, maximum: 500`) on this job's recovery route in
|
|
8
|
+
// docs/api/openapi.yaml, so the range the `--limit` help text advertises is enforced locally.
|
|
9
|
+
const PAGE_LIMIT_BOUNDS = { min: 1, max: 500 };
|
|
6
10
|
export function makeBulkCommand(getGlobal) {
|
|
7
11
|
const cmd = new Command('bulk').description('Bulk screenshot operations');
|
|
8
12
|
const screenshotsCmd = new Command('screenshots').description('Bulk screenshot sub-resources');
|
|
9
13
|
screenshotsCmd
|
|
10
14
|
.command('list <id>')
|
|
11
|
-
.description('List
|
|
12
|
-
.
|
|
15
|
+
.description('List items in a mixed bulk job')
|
|
16
|
+
.option('--cursor <cursor>', 'Continue from a cursor returned by the preceding page')
|
|
17
|
+
.option('--limit <n>', 'Maximum items per type to return (1-500)', (v) => parseIntFlag('--limit', v, PAGE_LIMIT_BOUNDS))
|
|
18
|
+
.option('--status <status>', `Filter items by status: ${BULK_RESULT_STATUSES.join(', ')}`, (v) => parseEnum('--status', BULK_RESULT_STATUSES, v))
|
|
19
|
+
.action(async (id, opts) => {
|
|
13
20
|
const g = getGlobal();
|
|
14
21
|
try {
|
|
15
22
|
// Inside the try: buildClient() throws on a missing key (#2686 item 4) and the throw must reach handleError(). See its doc comment in client.ts.
|
|
16
23
|
const client = buildClient(g);
|
|
17
|
-
const
|
|
24
|
+
const bulkId = id;
|
|
25
|
+
const pageParams = {
|
|
26
|
+
...(opts.cursor !== undefined ? { cursor: opts.cursor } : {}),
|
|
27
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
28
|
+
...(opts.status !== undefined ? { status: opts.status } : {}),
|
|
29
|
+
};
|
|
30
|
+
const result = Object.keys(pageParams).length === 0
|
|
31
|
+
? await client.bulk.screenshots.list(bulkId)
|
|
32
|
+
: await client.bulk.screenshots.list(bulkId, pageParams);
|
|
18
33
|
printResult(result, g.json);
|
|
19
34
|
}
|
|
20
35
|
catch (err) {
|
package/dist/commands/fetch.js
CHANGED
|
@@ -2,7 +2,11 @@ import { Command } from 'commander';
|
|
|
2
2
|
import { buildClient } from '../client.js';
|
|
3
3
|
import { printResult, emitFetchResult, handleError } from '../output.js';
|
|
4
4
|
import { parseBulkLines } from '../lib/url-lines.js';
|
|
5
|
-
import { parseIntFlag, parseCacheTtl, parseJsonFlag } from '../lib/parse-flags.js';
|
|
5
|
+
import { parseIntFlag, parseCacheTtl, parseJsonFlag, parseEnum } from '../lib/parse-flags.js';
|
|
6
|
+
const BULK_RESULT_STATUSES = ['pending', 'processing', 'completed', 'failed', 'cancelled'];
|
|
7
|
+
// Mirrors the `limit` query schema (`minimum: 1, maximum: 500`) on this job's recovery route in
|
|
8
|
+
// docs/api/openapi.yaml, so the range the `--limit` help text advertises is enforced locally.
|
|
9
|
+
const PAGE_LIMIT_BOUNDS = { min: 1, max: 500 };
|
|
6
10
|
export function makeFetchCommand(getGlobal) {
|
|
7
11
|
const cmd = new Command('fetch').description('Fetch URL content operations');
|
|
8
12
|
const bulkCmd = new Command('bulk').description('Bulk fetch operations');
|
|
@@ -44,12 +48,23 @@ export function makeFetchCommand(getGlobal) {
|
|
|
44
48
|
bulkCmd
|
|
45
49
|
.command('get <id>')
|
|
46
50
|
.description('Get a bulk fetch job')
|
|
47
|
-
.
|
|
51
|
+
.option('--cursor <cursor>', 'Continue from a cursor returned by the preceding page')
|
|
52
|
+
.option('--limit <n>', 'Maximum items to return (1-500)', (v) => parseIntFlag('--limit', v, PAGE_LIMIT_BOUNDS))
|
|
53
|
+
.option('--status <status>', `Filter items by status: ${BULK_RESULT_STATUSES.join(', ')}`, (v) => parseEnum('--status', BULK_RESULT_STATUSES, v))
|
|
54
|
+
.action(async (id, opts) => {
|
|
48
55
|
const g = getGlobal();
|
|
49
56
|
try {
|
|
50
57
|
// Inside the try: buildClient() throws on a missing key (#2686 item 4) and the throw must reach handleError(). See its doc comment in client.ts.
|
|
51
58
|
const client = buildClient(g);
|
|
52
|
-
const
|
|
59
|
+
const bulkId = id;
|
|
60
|
+
const pageParams = {
|
|
61
|
+
...(opts.cursor !== undefined ? { cursor: opts.cursor } : {}),
|
|
62
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
63
|
+
...(opts.status !== undefined ? { status: opts.status } : {}),
|
|
64
|
+
};
|
|
65
|
+
const result = Object.keys(pageParams).length === 0
|
|
66
|
+
? await client.fetch.bulk.get(bulkId)
|
|
67
|
+
: await client.fetch.bulk.get(bulkId, pageParams);
|
|
53
68
|
printResult(result, g.json);
|
|
54
69
|
}
|
|
55
70
|
catch (err) {
|
package/dist/commands/scrape.js
CHANGED
|
@@ -11,6 +11,10 @@ const SCRAPE_EVENTS = ['queued', 'completed', 'failed'];
|
|
|
11
11
|
const TOKEN_ENCODINGS = ['cl100k_base', 'o200k_base'];
|
|
12
12
|
/** The `markdown_links` / `markdown_images` render-mode enum. */
|
|
13
13
|
const MARKDOWN_RENDER_MODES = ['inline', 'references', 'none', 'text'];
|
|
14
|
+
const BULK_RESULT_STATUSES = ['pending', 'processing', 'completed', 'failed', 'cancelled'];
|
|
15
|
+
// Mirrors the `limit` query schema (`minimum: 1, maximum: 500`) on this job's recovery route in
|
|
16
|
+
// docs/api/openapi.yaml, so the range the `--limit` help text advertises is enforced locally.
|
|
17
|
+
const PAGE_LIMIT_BOUNDS = { min: 1, max: 500 };
|
|
14
18
|
/**
|
|
15
19
|
* Exit code when a queued (202 async) envelope reaches a non-JSON output mode.
|
|
16
20
|
*
|
|
@@ -83,12 +87,23 @@ export function makeScrapeCommand(getGlobal) {
|
|
|
83
87
|
bulkCmd
|
|
84
88
|
.command('get <id>')
|
|
85
89
|
.description('Get a bulk scrape job')
|
|
86
|
-
.
|
|
90
|
+
.option('--cursor <cursor>', 'Continue from a cursor returned by the preceding page')
|
|
91
|
+
.option('--limit <n>', 'Maximum items to return (1-500)', (v) => parseIntFlag('--limit', v, PAGE_LIMIT_BOUNDS))
|
|
92
|
+
.option('--status <status>', `Filter items by status: ${BULK_RESULT_STATUSES.join(', ')}`, (v) => parseEnum('--status', BULK_RESULT_STATUSES, v))
|
|
93
|
+
.action(async (id, opts) => {
|
|
87
94
|
const g = getGlobal();
|
|
88
95
|
try {
|
|
89
96
|
// Inside the try: buildClient() throws on a missing key (#2686 item 4) and the throw must reach handleError(). See its doc comment in client.ts.
|
|
90
97
|
const client = buildClient(g);
|
|
91
|
-
const
|
|
98
|
+
const bulkId = id;
|
|
99
|
+
const pageParams = {
|
|
100
|
+
...(opts.cursor !== undefined ? { cursor: opts.cursor } : {}),
|
|
101
|
+
...(opts.limit !== undefined ? { limit: opts.limit } : {}),
|
|
102
|
+
...(opts.status !== undefined ? { status: opts.status } : {}),
|
|
103
|
+
};
|
|
104
|
+
const result = Object.keys(pageParams).length === 0
|
|
105
|
+
? await client.scrape.bulk.get(bulkId)
|
|
106
|
+
: await client.scrape.bulk.get(bulkId, pageParams);
|
|
92
107
|
printResult(result, g.json);
|
|
93
108
|
}
|
|
94
109
|
catch (err) {
|
|
@@ -139,6 +154,7 @@ export function makeScrapeCommand(getGlobal) {
|
|
|
139
154
|
.option('--include-tags <csv>', 'Comma-separated HTML tags to keep (e.g. article,main)')
|
|
140
155
|
.option('--exclude-tags <csv>', 'Comma-separated HTML tags to drop (e.g. nav,footer)')
|
|
141
156
|
.option('--mobile', 'Render with a mobile viewport/user-agent')
|
|
157
|
+
.option('--solve-challenge', 'Attempt to clear a Cloudflare challenge; success is not guaranteed. Billed as a normal browser render.')
|
|
142
158
|
.option('--raw', 'Print the requested format body raw, with no JSON wrapper')
|
|
143
159
|
.option('-o, --output <file>', 'Write the output to the given file path (implies --raw unless --json is set)')
|
|
144
160
|
.action(async (opts) => {
|
|
@@ -206,6 +222,8 @@ export function makeScrapeCommand(getGlobal) {
|
|
|
206
222
|
body['exclude_tags'] = parseCsv(opts.excludeTags);
|
|
207
223
|
if (opts.mobile === true)
|
|
208
224
|
body['mobile'] = true;
|
|
225
|
+
if (opts.solveChallenge === true)
|
|
226
|
+
body['solve_challenge'] = true;
|
|
209
227
|
try {
|
|
210
228
|
// Inside the try: buildClient() throws on a missing key (#2686 item 4) and the throw must reach handleError(). See its doc comment in client.ts.
|
|
211
229
|
const client = buildClient(g);
|
|
@@ -8,8 +8,16 @@
|
|
|
8
8
|
* Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
|
|
9
9
|
* `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
|
|
10
10
|
* of a server 422 or a NaN on the wire.
|
|
11
|
+
*
|
|
12
|
+
* `bounds` extends that guarantee to flags whose spec schema declares a range (#3717): a
|
|
13
|
+
* well-formed integer outside `[min, max]` is just as certain to 422 as `abc` is, so it is
|
|
14
|
+
* rejected here rather than spending a round trip to be told so. Pass it wherever the flag's help
|
|
15
|
+
* text states a range; omit it where the spec declares none.
|
|
11
16
|
*/
|
|
12
|
-
export declare function parseIntFlag(flag: string, value: string
|
|
17
|
+
export declare function parseIntFlag(flag: string, value: string, bounds?: {
|
|
18
|
+
min: number;
|
|
19
|
+
max: number;
|
|
20
|
+
}): number;
|
|
13
21
|
/** Reject an off-spec enum value at parse time with the allowed set in the message. */
|
|
14
22
|
export declare function parseEnum<T extends string>(flag: string, allowed: readonly T[], value: string): T;
|
|
15
23
|
/** Split a comma-separated flag value into a trimmed, non-empty string array. */
|
package/dist/lib/parse-flags.js
CHANGED
|
@@ -9,12 +9,21 @@ import { InvalidArgumentError } from 'commander';
|
|
|
9
9
|
* Parse a whole-number flag value. The strict `/^\d+$/` test deterministically rejects `abc`, ``,
|
|
10
10
|
* `1.5`, `-5`, `1e2`, so an off-spec value fails at parse time with an actionable message instead
|
|
11
11
|
* of a server 422 or a NaN on the wire.
|
|
12
|
+
*
|
|
13
|
+
* `bounds` extends that guarantee to flags whose spec schema declares a range (#3717): a
|
|
14
|
+
* well-formed integer outside `[min, max]` is just as certain to 422 as `abc` is, so it is
|
|
15
|
+
* rejected here rather than spending a round trip to be told so. Pass it wherever the flag's help
|
|
16
|
+
* text states a range; omit it where the spec declares none.
|
|
12
17
|
*/
|
|
13
|
-
export function parseIntFlag(flag, value) {
|
|
18
|
+
export function parseIntFlag(flag, value, bounds) {
|
|
14
19
|
if (!/^\d+$/.test(value)) {
|
|
15
20
|
throw new InvalidArgumentError(`${flag} must be a whole number.`);
|
|
16
21
|
}
|
|
17
|
-
|
|
22
|
+
const parsed = Number(value);
|
|
23
|
+
if (bounds && (parsed < bounds.min || parsed > bounds.max)) {
|
|
24
|
+
throw new InvalidArgumentError(`${flag} must be between ${bounds.min} and ${bounds.max}.`);
|
|
25
|
+
}
|
|
26
|
+
return parsed;
|
|
18
27
|
}
|
|
19
28
|
/** Reject an off-spec enum value at parse time with the allowed set in the message. */
|
|
20
29
|
export function parseEnum(flag, allowed, value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hunt-labs/bytekit-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "ByteKit CLI — wraps @hunt-labs/bytekit-sdk as shell subcommands",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
],
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"commander": "^12.0.0",
|
|
44
|
-
"@hunt-labs/bytekit-sdk": "^0.
|
|
44
|
+
"@hunt-labs/bytekit-sdk": "^0.11.0"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
47
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc",
|