@zenrows/cli 1.6.0 → 1.6.1
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 +6 -1
- package/dist/src/cli/commands/crawl.js +31 -12
- package/dist/src/core/config.js +1 -1
- package/dist/src/core/errors.js +5 -2
- package/package.json +1 -1
- package/skills/crawl/SKILL.md +12 -5
- package/skills/trace-debug/SKILL.md +5 -1
package/README.md
CHANGED
|
@@ -203,7 +203,12 @@ run out (default 600). When the time runs out, the command exits 0 and prints
|
|
|
203
203
|
the crawl with status `running` and the hint `zenrows crawl wait <crawl-id>`
|
|
204
204
|
(under `--json`, in `note`); the crawl keeps running. Ctrl-C stops the wait, not the crawl, and exits 130;
|
|
205
205
|
resume with `zenrows crawl wait <crawl-id>`. A crawl that ends `failed` exits 1
|
|
206
|
-
with `CRAWL_FAILED`.
|
|
206
|
+
with `CRAWL_FAILED`. A crawl that ends `stopped` because the account ran out of
|
|
207
|
+
credits or the API key hit a credit cap also exits 1, with the code a refused
|
|
208
|
+
create gets: `CRAWL_INSUFFICIENT_CREDITS` or `CRAWL_KEY_CAP_REACHED`, and the
|
|
209
|
+
API's `insufficient_credits` or `api_key_cap_reached` as `server_code`. Older
|
|
210
|
+
deployments end such a crawl `failed`; it gets the same codes. Its fetched pages
|
|
211
|
+
stay readable.
|
|
207
212
|
|
|
208
213
|
Errors carry the API's code as `server_code`, `crawl_id` on any call about one
|
|
209
214
|
crawl, and `retry_after` on a 429. When the account has reached its limit of
|
|
@@ -15,7 +15,8 @@ import { assertDomainAllowed, assertWithinLimits, loadPolicy } from "../../core/
|
|
|
15
15
|
import { newRunId, writeRun } from "../../core/artifacts.js";
|
|
16
16
|
import { contentIdOf, createCrawl, downloadCrawl, getCrawl, getCrawlContent, isTerminal, listAllResults, listCrawls, stopCrawl, waitForCrawl, } from "../../core/crawl-api.js";
|
|
17
17
|
import { asNumber, asString, parse } from "../command.js";
|
|
18
|
-
import { ToolkitError } from "../../core/errors.js";
|
|
18
|
+
import { ToolkitError, isKeyCapReached, keyCapReached, quotaExhausted } from "../../core/errors.js";
|
|
19
|
+
import { readAccount } from "../../core/agent-account.js";
|
|
19
20
|
import { printError, writeOut, writeOutStream } from "../output.js";
|
|
20
21
|
/** The API's default for `max_pages` (and `max_items`) when the flag is unset. */
|
|
21
22
|
const DEFAULT_MAX_PAGES = 100;
|
|
@@ -415,7 +416,8 @@ async function stopCmd(rest, ctx) {
|
|
|
415
416
|
assertUsable("crawl");
|
|
416
417
|
const apiKey = requireApiKey();
|
|
417
418
|
const s = await stopCrawl(id, { apiKey });
|
|
418
|
-
|
|
419
|
+
// A crawl stopped on credits carries `error`: it had already ended, this call did not stop it.
|
|
420
|
+
const headline = s.status === "stopped" && !s.error ? `Stopped crawl ${id}` : `Nothing to stop: crawl ${id} had already ended`;
|
|
419
421
|
return printCrawl(s, ctx.json || values.json === true, headline);
|
|
420
422
|
}
|
|
421
423
|
/**
|
|
@@ -437,33 +439,50 @@ async function waitUntilEnd(id, opts) {
|
|
|
437
439
|
}
|
|
438
440
|
}
|
|
439
441
|
/**
|
|
440
|
-
* The error for a crawl that ended `failed`, or
|
|
441
|
-
*
|
|
442
|
+
* The error for a crawl that ended `failed`, or `stopped` with an `error` (the
|
|
443
|
+
* account ran out of credits or the API key hit a credit cap), or null for any
|
|
444
|
+
* other state. A crawl the credits ended gets the code and advice a create
|
|
445
|
+
* refused with 402 gets (`CRAWL_INSUFFICIENT_CREDITS`, `CRAWL_KEY_CAP_REACHED`),
|
|
446
|
+
* whether the API reports it `stopped` or, as older deployments do, `failed`.
|
|
447
|
+
* Any other `error.code` picks the next action of a `CRAWL_FAILED`.
|
|
442
448
|
*/
|
|
443
449
|
export function crawlFailure(c) {
|
|
444
|
-
|
|
450
|
+
const creditStop = c.status === "stopped" && !!c.error;
|
|
451
|
+
if (c.status !== "failed" && !creditStop)
|
|
445
452
|
return null;
|
|
446
453
|
const code = c.error?.code;
|
|
454
|
+
const ids = { server_code: code, crawl_id: c.crawl_id };
|
|
455
|
+
const where = `crawl ${c.crawl_id}`;
|
|
456
|
+
const detail = c.error?.detail || undefined;
|
|
457
|
+
const getCmd = `zenrows crawl get ${c.crawl_id}`;
|
|
458
|
+
if (code && isKeyCapReached(code)) {
|
|
459
|
+
const cap = keyCapReached(where, { status: null, detail }).toJSON();
|
|
460
|
+
return new ToolkitError({ ...cap, ...ids, code: "CRAWL_KEY_CAP_REACHED", suggested_commands: [...(cap.suggested_commands ?? []), getCmd] });
|
|
461
|
+
}
|
|
462
|
+
if (code === "insufficient_credits") {
|
|
463
|
+
const acct = readAccount();
|
|
464
|
+
const quota = quotaExhausted(where, acct?.unclaimed ? acct.claimUrl : undefined, { status: null, detail }).toJSON();
|
|
465
|
+
return new ToolkitError({ ...quota, ...ids, code: "CRAWL_INSUFFICIENT_CREDITS", suggested_commands: [...(quota.suggested_commands ?? []), getCmd] });
|
|
466
|
+
}
|
|
447
467
|
const why = [code, c.error?.detail].filter(Boolean).join(": ");
|
|
448
468
|
const next = {
|
|
449
|
-
insufficient_credits: "The account ran out of credits. Check `zenrows usage`, add credits, then start a new crawl.",
|
|
450
469
|
seed_unreachable: "The start URL could not be fetched (the site may be blocking it). Check it with `zenrows fetch <url>`.",
|
|
451
470
|
no_items_found: "Nothing matched. Loosen --include-patterns / --exclude-patterns, raise or drop --depth, or check the start page with `zenrows fetch <url>`.",
|
|
452
471
|
};
|
|
453
472
|
return new ToolkitError({
|
|
454
473
|
code: "CRAWL_FAILED",
|
|
455
|
-
message: `Crawl ${c.crawl_id} failed.`,
|
|
474
|
+
message: creditStop ? `Crawl ${c.crawl_id} stopped before it finished.` : `Crawl ${c.crawl_id} failed.`,
|
|
456
475
|
likely_cause: why || "The crawl ended in status failed without a reason.",
|
|
457
476
|
next_action: (code && next[code]) || "Read the error, fix the cause, then start a new crawl.",
|
|
458
|
-
suggested_commands: [
|
|
459
|
-
|
|
460
|
-
crawl_id: c.crawl_id,
|
|
477
|
+
suggested_commands: [getCmd],
|
|
478
|
+
...ids,
|
|
461
479
|
});
|
|
462
480
|
}
|
|
463
481
|
/**
|
|
464
482
|
* Print a crawl (or a stop answer), structured under --json, and return the exit
|
|
465
|
-
* code: 1 when it ended `failed`,
|
|
466
|
-
*
|
|
483
|
+
* code: 1 when it ended `failed`, or `stopped` because credits ran out or the
|
|
484
|
+
* API key hit a credit cap, else 0. A `stopped` crawl the caller stopped is a
|
|
485
|
+
* deliberate outcome, so it exits 0 but prints as a warning. `extra` carries the results a `get` read,
|
|
467
486
|
* or the note of a wait that ran out.
|
|
468
487
|
*/
|
|
469
488
|
function printCrawl(c, json, headline, extra = {}) {
|
package/dist/src/core/config.js
CHANGED
|
@@ -14,7 +14,7 @@ export const CONFIG_VERSION = "0.1.0";
|
|
|
14
14
|
* entrypoint) so core modules — telemetry, signup provenance — can read it
|
|
15
15
|
* without an import cycle. `VERSION` in `cli/index.ts` re-exports this.
|
|
16
16
|
*/
|
|
17
|
-
export const CLI_VERSION = "1.6.
|
|
17
|
+
export const CLI_VERSION = "1.6.1"; // x-release-please-version
|
|
18
18
|
/** The User-Agent on every request to a Zenrows API. The gateway reads the version from it. */
|
|
19
19
|
export const CLI_USER_AGENT = `zenrows-cli/${CLI_VERSION}`;
|
|
20
20
|
/** Env var to override the Fetch and Extract API base (local/staging testing). */
|
package/dist/src/core/errors.js
CHANGED
|
@@ -45,7 +45,9 @@ export class ToolkitError extends Error {
|
|
|
45
45
|
* (so `claimUrl` is surfaced); for a real logged-in account there is nothing to
|
|
46
46
|
* claim — the step is to add credits / upgrade in the dashboard. `status`/`detail`
|
|
47
47
|
* let the caller record the actual upstream response (402 usage-limit, 429
|
|
48
|
-
* quota, …) instead of a hardcoded status.
|
|
48
|
+
* quota, …) instead of a hardcoded status. `status: null` is for a crawl the
|
|
49
|
+
* credits stopped: its status call succeeded, so there is no HTTP error to cite,
|
|
50
|
+
* only `url` (the crawl).
|
|
49
51
|
*/
|
|
50
52
|
export function quotaExhausted(url, claimUrl, opts = {}) {
|
|
51
53
|
// Say that the allowance comes back. Without it this reads as a permanent paywall,
|
|
@@ -58,10 +60,11 @@ export function quotaExhausted(url, claimUrl, opts = {}) {
|
|
|
58
60
|
? `You are on the Zenrows Free plan. Claim your account to keep your usage and add credits: ${claimUrl}. ${renewLine}`
|
|
59
61
|
: `You are out of Zenrows credits. ${renewLine} To carry on now, add a credit pack (${BILLING_TOPUP_URL} opens the purchase directly) or upgrade your plan: ${PLANS_URL}`;
|
|
60
62
|
const detail = opts.detail ? `${opts.detail.replace(/\.\s*$/, "")}. ` : "";
|
|
63
|
+
const where = opts.status === null ? `Stopped ${url}` : `HTTP ${opts.status ?? 429} for ${url}`;
|
|
61
64
|
return new ToolkitError({
|
|
62
65
|
code: "POLICY_MAX_CREDITS_EXCEEDED",
|
|
63
66
|
message: "Zenrows request quota exhausted.",
|
|
64
|
-
likely_cause: `${detail}
|
|
67
|
+
likely_cause: `${detail}${where}`,
|
|
65
68
|
next_action: claimLine,
|
|
66
69
|
suggested_commands: ["zenrows usage"],
|
|
67
70
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zenrows/cli",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "Open-source CLI, MCP, skills, templates, workflows, recipes, and evals layer for giving AI agents reliable access to protected web data through Zenrows cloud infrastructure.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
package/skills/crawl/SKILL.md
CHANGED
|
@@ -77,8 +77,15 @@ zenrows crawl stop <id> # stop it; kept URLs stay readable
|
|
|
77
77
|
```
|
|
78
78
|
|
|
79
79
|
A crawl ends `completed` (nothing left, or a cap hit: `stop_reason`
|
|
80
|
-
`max_items` / `max_pages`), `stopped`
|
|
81
|
-
|
|
80
|
+
`max_items` / `max_pages`), `stopped` or `failed` (`error.code` and
|
|
81
|
+
`error.detail` say why). `stopped` with `stop_reason` `user` means you stopped
|
|
82
|
+
it. `stopped` with no `stop_reason` and `error.code` `insufficient_credits` or
|
|
83
|
+
`api_key_cap_reached` means the account ran out of credits or the API key hit a
|
|
84
|
+
credit cap; the pages fetched before that stay readable. `failed`, and a
|
|
85
|
+
`stopped` crawl with an `error`, exit non-zero: `CRAWL_INSUFFICIENT_CREDITS` or
|
|
86
|
+
`CRAWL_KEY_CAP_REACHED` when credits ended it (the same codes a refused
|
|
87
|
+
`create` gets, also when an older deployment reports it `failed`), else
|
|
88
|
+
`CRAWL_FAILED`. `server_code` carries the API's `error.code`.
|
|
82
89
|
`stop` on a crawl that already ended prints the status it ended with.
|
|
83
90
|
|
|
84
91
|
With `--json`, `create`, `get`, `wait` and `stop` print
|
|
@@ -112,15 +119,15 @@ The "Retry?" column is for errors that remain after these retries.
|
|
|
112
119
|
| --- | --- | --- |
|
|
113
120
|
| `CRAWL_NOT_ENABLED` | 403 `REQS008`: Crawl is not enabled for this account | no |
|
|
114
121
|
| `AUTH_INVALID` | 401: the API key was rejected | no, log in again |
|
|
115
|
-
| `CRAWL_INSUFFICIENT_CREDITS` | 402
|
|
116
|
-
| `CRAWL_KEY_CAP_REACHED` | 402 `AUTH014` or `api_key_cap_reached`: this API key reached its credit cap | after the cap resets or is raised |
|
|
122
|
+
| `CRAWL_INSUFFICIENT_CREDITS` | 402 on `create`, or a crawl that ended on `insufficient_credits`: the account is out of credits (claim the account or add credits) | after credits renew or are added |
|
|
123
|
+
| `CRAWL_KEY_CAP_REACHED` | 402 `AUTH014` or `api_key_cap_reached` on `create`, or a crawl that ended on `api_key_cap_reached`: this API key reached its credit cap | after the cap resets or is raised |
|
|
117
124
|
| `CRAWL_INVALID_REQUEST` | 400 or 422: fix the request. For `idempotency_key_reused`, send a new key or no key | no, not as is |
|
|
118
125
|
| `CRAWL_REQUEST_IN_FLIGHT` | 409: the same Idempotency-Key is still in flight | yes, once the first request ends |
|
|
119
126
|
| `CRAWL_NOT_FOUND` / `CRAWL_CONTENT_NOT_FOUND` | 404: wrong id, or the page has no content | no |
|
|
120
127
|
| `CRAWL_TOO_MANY_CRAWLS` | 429 `too_many_crawls` on `create`: the account's active-job limit | yes, after `retry_after` |
|
|
121
128
|
| `CRAWL_RATE_LIMITED` | any other 429: requests are being throttled | yes, after `retry_after` |
|
|
122
129
|
| `CRAWL_BUSY` | 503 `crawl_busy` on `stop`: the stop was not saved yet and the crawl still runs | yes, run the stop again after `retry_after` |
|
|
123
|
-
| `CRAWL_FAILED` | the crawl ended `failed`, or another error | 5xx: yes, with backoff |
|
|
130
|
+
| `CRAWL_FAILED` | the crawl ended `failed`, or `stopped` with an error other than credits, or another error | 5xx: yes, with backoff |
|
|
124
131
|
| `BACKEND_UNAVAILABLE` | the API could not be reached | yes |
|
|
125
132
|
|
|
126
133
|
Rules: start small (`--depth 1`, low caps) and check the results before a
|
|
@@ -49,12 +49,16 @@ zenrows trace export <run-id> # JSON for sharing
|
|
|
49
49
|
enabled for this account. Do not retry; ask Zenrows support for access.
|
|
50
50
|
- `CRAWL_INSUFFICIENT_CREDITS` / `CRAWL_KEY_CAP_REACHED` → Crawl's 402: the
|
|
51
51
|
account is out of credits, or this key reached its credit cap (`AUTH014`).
|
|
52
|
+
The same codes come back for a crawl that credits ended (`stopped`, or
|
|
53
|
+
`failed` on older deployments; `server_code` `insufficient_credits` /
|
|
54
|
+
`api_key_cap_reached`); its fetched pages stay readable.
|
|
52
55
|
Same advice as `POLICY_MAX_CREDITS_EXCEEDED` / `KEY_CREDIT_CAP_REACHED`.
|
|
53
56
|
- `CRAWL_TOO_MANY_CRAWLS` → 429 `too_many_crawls`: the account has reached
|
|
54
57
|
its limit of active jobs (3 by default), shared with its Batch jobs.
|
|
55
58
|
Nothing was created; retry after `retry_after` seconds (see
|
|
56
59
|
`zenrows crawl list`).
|
|
57
|
-
- `CRAWL_FAILED` → the crawl ended `failed
|
|
60
|
+
- `CRAWL_FAILED` → the crawl ended `failed`, or `stopped` with an error
|
|
61
|
+
other than credits; `error.code` in
|
|
58
62
|
`zenrows crawl get <id> --json` says why, with `error.detail`. See
|
|
59
63
|
[[crawl]].
|
|
60
64
|
- `PARAM_CONFLICT_AUTO_MANUAL` → drop the managed flags or add `--manual`.
|