captchakraken 2.8.0 → 3.0.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.
Files changed (77) hide show
  1. package/AGENTS.md +112 -0
  2. package/NOTICE +36 -0
  3. package/dist/cli-invocation.d.ts +9 -40
  4. package/dist/cli-invocation.js +16 -49
  5. package/dist/errors.d.ts +7 -27
  6. package/dist/errors.js +6 -25
  7. package/dist/humanize.d.ts +56 -240
  8. package/dist/humanize.js +159 -415
  9. package/dist/index.d.ts +5 -129
  10. package/dist/index.js +4 -98
  11. package/dist/kinds.d.ts +135 -0
  12. package/dist/kinds.js +70 -0
  13. package/dist/limits.d.ts +0 -48
  14. package/dist/limits.js +2 -48
  15. package/dist/model-name.d.ts +4 -19
  16. package/dist/model-name.js +32 -40
  17. package/dist/playwright-types.d.ts +25 -95
  18. package/dist/playwright-types.js +0 -23
  19. package/dist/puppeteer-adapter.d.ts +4 -43
  20. package/dist/puppeteer-adapter.js +22 -58
  21. package/dist/python-command.d.ts +0 -41
  22. package/dist/python-command.js +0 -37
  23. package/dist/selectors.d.ts +54 -0
  24. package/dist/selectors.js +117 -0
  25. package/dist/slide-geometry.d.ts +2 -29
  26. package/dist/slide-geometry.js +18 -44
  27. package/dist/solver.d.ts +112 -539
  28. package/dist/solver.js +1088 -2950
  29. package/dist/timing.d.ts +7 -40
  30. package/dist/timing.js +4 -36
  31. package/dist/token-usage.d.ts +1 -7
  32. package/dist/token-usage.js +10 -95
  33. package/dist/types.d.ts +77 -483
  34. package/dist/watcher.d.ts +5 -78
  35. package/dist/watcher.js +0 -56
  36. package/package.json +12 -4
  37. package/python/AGENTS.md +122 -0
  38. package/python/NOTICE +36 -0
  39. package/python/README.md +1 -2
  40. package/python/examples/README.md +10 -0
  41. package/python/examples/_harness.py +4 -73
  42. package/python/examples/demoHcaptcha.py +0 -13
  43. package/python/examples/demoRecaptcha.py +0 -10
  44. package/python/examples/watch_playwright.py +1 -26
  45. package/python/examples/with_local_gguf.py +28 -0
  46. package/python/examples/with_playwright.py +0 -19
  47. package/python/pyproject.toml +68 -19
  48. package/python/src/captchakraken/__init__.py +37 -45
  49. package/python/src/captchakraken/action_types.py +14 -36
  50. package/python/src/captchakraken/cli.py +262 -879
  51. package/python/src/captchakraken/config.py +60 -146
  52. package/python/src/captchakraken/errors.py +17 -95
  53. package/python/src/captchakraken/humanize.py +174 -597
  54. package/python/src/captchakraken/image_processor.py +16 -235
  55. package/python/src/captchakraken/keyframes.py +69 -434
  56. package/python/src/captchakraken/kinds.py +152 -0
  57. package/python/src/captchakraken/models.json +79 -21
  58. package/python/src/captchakraken/overlay.py +65 -498
  59. package/python/src/captchakraken/page_solver.py +1123 -2564
  60. package/python/src/captchakraken/planner.py +70 -428
  61. package/python/src/captchakraken/prompts.py +45 -261
  62. package/python/src/captchakraken/selectors.py +184 -0
  63. package/python/src/captchakraken/server_manager.py +7 -58
  64. package/python/src/captchakraken/solver.py +128 -522
  65. package/python/src/captchakraken/timing.py +20 -74
  66. package/python/src/captchakraken/tool_calls/board_painted.py +47 -0
  67. package/python/src/captchakraken/tool_calls/find_checkbox.py +20 -30
  68. package/python/src/captchakraken/tool_calls/find_grid.py +186 -1339
  69. package/python/src/captchakraken/tool_calls/track_piece.py +81 -57
  70. package/python/src/captchakraken/updater.py +7 -76
  71. package/python/src/captchakraken/watcher.py +12 -101
  72. package/scripts/copy-python.mjs +10 -10
  73. package/scripts/setup-python.js +1 -21
  74. package/dist/trajectory.d.ts +0 -58
  75. package/dist/trajectory.js +0 -266
  76. package/python/src/captchakraken/tool_calls/move_indicator.py +0 -431
  77. package/python/src/captchakraken/trajectory.py +0 -284
package/AGENTS.md ADDED
@@ -0,0 +1,112 @@
1
+ # captchakraken (npm)
2
+
3
+ The TypeScript **browser driver**. You hand it a Playwright-compatible `Page`;
4
+ it finds the captcha, has a vision model read it, and clicks, drags, slides or
5
+ types through to a token.
6
+
7
+ It is not a browser and it is not the model. It installs no browser — bring your
8
+ own launcher — and every inference goes to a model endpoint you point it at.
9
+
10
+ Repo, guides and the full agent guide:
11
+ <https://github.com/JWriter20/CaptchaKraken>
12
+
13
+ ---
14
+
15
+ ## 1. Point it at a model, first
16
+
17
+ Nothing solves until the driver knows where inference goes. Two ways:
18
+
19
+ | You have | Do this |
20
+ |---|---|
21
+ | No GPU | Use the hosted API — add the account MCP server, `npx -y captchakraken-mcp`, call `sign_in` then `create_api_key` |
22
+ | Your own vLLM server | `export VLLM_BASE_URL=…/v1` and `export CAPTCHA_KRAKEN_API_KEY=…` |
23
+
24
+ `create_api_key` writes the key and endpoint to `~/.captchakraken/credentials`
25
+ (mode 0600) and the driver reads that file by itself, so the MCP path needs **no
26
+ environment variables at all**. The key is never printed into the conversation.
27
+
28
+ ## 2. Install
29
+
30
+ ```bash
31
+ npm install captchakraken
32
+ ```
33
+
34
+ The install runs a `postinstall` that creates a venv inside the package and
35
+ installs the bundled Python engine the driver shells out to. It needs a Python
36
+ 3.10+ interpreter on `PATH`.
37
+
38
+ | Variable | Effect |
39
+ |---|---|
40
+ | `CAPTCHA_KRAKEN_SKIP_PYTHON_SETUP=1` | Skip the bootstrap (you will supply the engine yourself) |
41
+ | `CAPTCHA_KRAKEN_PYTHON=/path/to/python3` | Use this interpreter instead of probing `python3` then `python` |
42
+
43
+ ## 3. Solve
44
+
45
+ ```typescript
46
+ import { chromium } from 'playwright';
47
+ import { CaptchaKrakenSolver } from 'captchakraken';
48
+
49
+ const page = await (await (await chromium.launch()).newContext()).newPage();
50
+ await page.goto('https://www.google.com/recaptcha/api2/demo');
51
+
52
+ await new CaptchaKrakenSolver().solve(page); // detect → solve → click → verify
53
+ ```
54
+
55
+ Any Playwright-compatible launcher works with no adapter (`playwright`,
56
+ `patchright`, `camoufox-js`). Puppeteer needs one wrap:
57
+
58
+ ```typescript
59
+ import { CaptchaKrakenSolver, fromPuppeteer } from 'captchakraken';
60
+ await new CaptchaKrakenSolver().solve(fromPuppeteer(page));
61
+ ```
62
+
63
+ Do not know when a captcha will appear? Install the watcher instead of calling
64
+ `solve()`:
65
+
66
+ ```typescript
67
+ const watcher = solver.watch(page);
68
+ // ... your automation ...
69
+ await watcher.stop();
70
+ ```
71
+
72
+ Touch devices: `new CaptchaKrakenSolver({ humanization: 'mobile' })` emits real
73
+ touch events (needs `hasTouch` on the context, or an Appium/WebdriverIO driver
74
+ via `touchDriver`). `'none'` skips humanisation entirely.
75
+
76
+ ## 4. Errors
77
+
78
+ `solve()` resolves to a `SolveResult` (`isSolved`), and throws when it cannot
79
+ drive the widget at all — no captcha on the page, a puzzle this build does not
80
+ handle, or the round budget exhausted. Only one error class is exported.
81
+
82
+ Refusals from the hosted API arrive as `CaptchaKrakenAPIError`. **Branch on
83
+ `e.code`, never on the message** — the codes are the contract, the wording is
84
+ not. `insufficient_credits`, `missing_api_key`, `invalid_api_key`,
85
+ `rate_limited`, `request_too_large`, `account_suspended` and
86
+ `upstream_unavailable` each name their own fix; `e.resolutionUrl` carries a link
87
+ where one exists. Retrying a `403` that means "no licence" will never succeed.
88
+
89
+ ## Rules for agents
90
+
91
+ - **Never invent a number.** Every accuracy, latency and price figure we publish
92
+ is on one page —
93
+ [docs/benchmarks.md](https://github.com/JWriter20/CaptchaKraken/blob/main/docs/benchmarks.md).
94
+ Quote it as written; do not derive, round, or convert anything, and do not
95
+ read a static-image rate as a promise about a browser run.
96
+ - **Never print an API key** into a transcript, a log, or a commit. Use
97
+ `create_api_key`, which writes it to disk instead. `captchakraken.env` and
98
+ `.env` hold keys and are never committed.
99
+ - **Abyss is what our hosted API serves, and it is never downloadable.** The
100
+ current client names an Abyss expert on every request to our endpoint and is
101
+ answered by Abyss; an older client, or a request that names no model, gets
102
+ Twilight v1.2, because its prompts belong to that generation. Nothing about it
103
+ is gated on the account. Do not suggest weights, a download or a workaround
104
+ for it, and do not quote accuracy figures for it — none are published. Verify
105
+ what is serving with `GET /v1/models`.
106
+ - **Respect the licence.** Building automation with this is fine; selling the
107
+ solve, or shipping it inside a browser or automation product, is not. See
108
+ [LICENSE](./LICENSE) and
109
+ [docs/licensing.md](https://github.com/JWriter20/CaptchaKraken/blob/main/docs/licensing.md).
110
+ - **Changing this package's own code?** Its rules are in
111
+ [CONTRIBUTING.md](https://github.com/JWriter20/CaptchaKraken/blob/main/CONTRIBUTING.md),
112
+ not here.
package/NOTICE ADDED
@@ -0,0 +1,36 @@
1
+ CaptchaKraken
2
+ Copyright (c) 2026 CaptchaKraken LLC. All rights reserved.
3
+
4
+ This product includes third-party software. The notices below are required by
5
+ those components' licences and do not change the terms of the CaptchaKraken
6
+ Source-Available License, which governs everything in this repository that is
7
+ not listed here.
8
+
9
+ ────────────────────────────────────────────────────────────────────────────
10
+ Cursory — mouse trajectory generation
11
+ ────────────────────────────────────────────────────────────────────────────
12
+
13
+ cursory https://github.com/Vinyzu/cursory (Python, PyPI: cursory)
14
+ cursory-js https://github.com/JWriter20/cursory-js (TypeScript, npm: cursory-js)
15
+
16
+ Copyright (c) Vinyzu and the cursory-js contributors.
17
+ Licensed under the GNU Lesser General Public License, version 3 or later
18
+ (LGPL-3.0-or-later). A copy is available at https://www.gnu.org/licenses/lgpl-3.0.html
19
+
20
+ HOW WE USE IT, AND WHY THAT MATTERS. Both are ORDINARY INSTALLED
21
+ DEPENDENCIES — `cursory` is declared in python/pyproject.toml and `cursory-js`
22
+ in js/package.json, and each is resolved by the user's own package manager at
23
+ install time. Neither is vendored into this repository, copied into our
24
+ sources, or bundled into a build artifact: the TypeScript build is `tsc`, which
25
+ leaves `require('cursory-js')` as a runtime import rather than inlining it, and
26
+ `js/scripts/copy-python.mjs` copies only our own `python/` tree.
27
+
28
+ That arrangement is the one the LGPL is written for, and it is what lets this
29
+ product remain under its own licence. It also means a user may replace either
30
+ library with a modified version of their own — reinstall the package, and the
31
+ driver picks it up — which is the freedom section 4 of the LGPL requires be
32
+ preserved. Do not vendor, inline, or bundle either one without taking legal
33
+ advice first; doing so is the change that would put this obligation somewhere
34
+ other than where it now sits.
35
+
36
+ We have not modified either library.
@@ -1,48 +1,17 @@
1
- /**
2
- * How the JS client hands a solve request to the bundled Python CLI.
3
- *
4
- * The API key used to travel as a POSITIONAL ARGUMENT, and the whole command
5
- * line was then echoed:
6
- *
7
- * console.log(`Executing CaptchaKraken CLI: ${command}`);
8
- * // Executing CaptchaKraken CLI: python -m captchakraken.cli "shot.png" \
9
- * // captcha-v12 captchaKrakenApi b8978fa3392... --puzzle-source=hcaptcha
10
- *
11
- * Two leaks from one mistake. Argv is world-readable on Linux — any local user
12
- * can read `/proc/<pid>/cmdline` while the solve runs, and `ps` shows it — and
13
- * the key was additionally written to stdout, so it landed in CI logs, terminal
14
- * scrollback, and anything scraping driver output.
15
- *
16
- * Secrets go through the environment instead, which is per-process and not
17
- * world-readable, and the command is built as an ARGV ARRAY for `execFile`
18
- * rather than a joined string for a shell, so a path with a space or a quote
19
- * cannot reshape the command.
20
- */
21
- /** Env var the CLI reads the bearer token from. */
1
+ import type { PromptFamily, RetryMode, Vendor } from './kinds.js';
22
2
  export declare const API_KEY_ENV = "CAPTCHA_KRAKEN_API_KEY";
23
- /** Historical positional placeholder. The CLI still accepts the slot. */
3
+ export declare const RESAMPLE_LEVEL_ENV = "CAPTCHA_RESAMPLE_LEVEL";
24
4
  export declare const API_PROVIDER = "captchaKrakenApi";
25
5
  export interface SolveInvocation {
26
6
  imagePath: string;
27
- model: string;
28
- puzzleSource: string;
29
- retryMode?: string | null;
7
+ model?: string;
8
+ puzzleSource: Vendor;
9
+ retryMode?: RetryMode | null;
30
10
  textMode?: boolean;
31
- /** One expert of a routed model, or undefined to route by prompt family. */
32
- expert?: string | null;
11
+ expert?: PromptFamily | null;
33
12
  }
34
- /**
35
- * The argv for one solve. No shell, no quoting, and NO CREDENTIAL — the key is
36
- * supplied by `solveEnv` instead.
37
- */
13
+ /** An argv array for execFile, never a shell string. */
38
14
  export declare function buildSolveArgs(invocation: SolveInvocation): string[];
39
- /** Base env plus the credential, when there is one. */
40
- export declare function solveEnv(base: NodeJS.ProcessEnv, apiKey?: string): NodeJS.ProcessEnv;
41
- /**
42
- * A log line for a solve invocation, with any credential removed.
43
- *
44
- * Belt and braces: `buildSolveArgs` no longer carries the key, but this is the
45
- * function that prints to stdout, so it redacts anything key-shaped regardless
46
- * of how it got into the array.
47
- */
15
+ /** The resample LEVEL travels, not a temperature: the schedule lives once in planner.RESAMPLE_TEMPERATURES, and a fresh CLI process cannot know the round count. */
16
+ export declare function solveEnv(base: NodeJS.ProcessEnv, apiKey?: string, resampleLevel?: number): NodeJS.ProcessEnv;
48
17
  export declare function redactCommand(command: string, apiKey?: string): string;
@@ -1,70 +1,37 @@
1
1
  "use strict";
2
- /**
3
- * How the JS client hands a solve request to the bundled Python CLI.
4
- *
5
- * The API key used to travel as a POSITIONAL ARGUMENT, and the whole command
6
- * line was then echoed:
7
- *
8
- * console.log(`Executing CaptchaKraken CLI: ${command}`);
9
- * // Executing CaptchaKraken CLI: python -m captchakraken.cli "shot.png" \
10
- * // captcha-v12 captchaKrakenApi b8978fa3392... --puzzle-source=hcaptcha
11
- *
12
- * Two leaks from one mistake. Argv is world-readable on Linux — any local user
13
- * can read `/proc/<pid>/cmdline` while the solve runs, and `ps` shows it — and
14
- * the key was additionally written to stdout, so it landed in CI logs, terminal
15
- * scrollback, and anything scraping driver output.
16
- *
17
- * Secrets go through the environment instead, which is per-process and not
18
- * world-readable, and the command is built as an ARGV ARRAY for `execFile`
19
- * rather than a joined string for a shell, so a path with a space or a quote
20
- * cannot reshape the command.
21
- */
22
2
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.API_PROVIDER = exports.API_KEY_ENV = void 0;
3
+ exports.API_PROVIDER = exports.RESAMPLE_LEVEL_ENV = exports.API_KEY_ENV = void 0;
24
4
  exports.buildSolveArgs = buildSolveArgs;
25
5
  exports.solveEnv = solveEnv;
26
6
  exports.redactCommand = redactCommand;
27
- /** Env var the CLI reads the bearer token from. */
7
+ // The key travels in env, never argv: argv is world-readable on Linux via /proc/<pid>/cmdline and `ps`. See TRIBAL_KNOWLEDGE.md.
28
8
  exports.API_KEY_ENV = 'CAPTCHA_KRAKEN_API_KEY';
29
- /** Historical positional placeholder. The CLI still accepts the slot. */
9
+ exports.RESAMPLE_LEVEL_ENV = 'CAPTCHA_RESAMPLE_LEVEL';
30
10
  exports.API_PROVIDER = 'captchaKrakenApi';
31
- /**
32
- * The argv for one solve. No shell, no quoting, and NO CREDENTIAL — the key is
33
- * supplied by `solveEnv` instead.
34
- */
11
+ /** An argv array for execFile, never a shell string. */
35
12
  function buildSolveArgs(invocation) {
36
13
  const { imagePath, model, puzzleSource, retryMode, textMode, expert } = invocation;
37
- const args = [
38
- '-m',
39
- 'captchakraken.cli',
40
- imagePath,
41
- model,
42
- exports.API_PROVIDER,
43
- `--puzzle-source=${puzzleSource}`,
44
- ];
14
+ const args = ['-m', 'captchakraken.cli', imagePath];
15
+ // Both positionals or neither: the provider passed alone would bind to `model`.
16
+ if (model)
17
+ args.push(model, exports.API_PROVIDER);
18
+ args.push(`--puzzle-source=${puzzleSource}`);
45
19
  if (retryMode)
46
20
  args.push(`--retry-mode=${retryMode}`);
47
21
  if (textMode)
48
22
  args.push('--text-mode');
49
- // Only when set. An absent flag is what lets the Python side route by prompt
50
- // family, and passing `--expert=` would be an empty choice rather than none.
51
23
  if (expert)
52
24
  args.push(`--expert=${expert}`);
53
25
  return args;
54
26
  }
55
- /** Base env plus the credential, when there is one. */
56
- function solveEnv(base, apiKey) {
57
- if (!apiKey)
58
- return base;
59
- return { ...base, [exports.API_KEY_ENV]: apiKey };
27
+ /** The resample LEVEL travels, not a temperature: the schedule lives once in planner.RESAMPLE_TEMPERATURES, and a fresh CLI process cannot know the round count. */
28
+ function solveEnv(base, apiKey, resampleLevel) {
29
+ const out = apiKey ? { ...base, [exports.API_KEY_ENV]: apiKey } : { ...base };
30
+ if (resampleLevel && resampleLevel > 0) {
31
+ out[exports.RESAMPLE_LEVEL_ENV] = String(resampleLevel);
32
+ }
33
+ return out;
60
34
  }
61
- /**
62
- * A log line for a solve invocation, with any credential removed.
63
- *
64
- * Belt and braces: `buildSolveArgs` no longer carries the key, but this is the
65
- * function that prints to stdout, so it redacts anything key-shaped regardless
66
- * of how it got into the array.
67
- */
68
35
  function redactCommand(command, apiKey) {
69
36
  if (!apiKey)
70
37
  return command;
package/dist/errors.d.ts CHANGED
@@ -1,28 +1,15 @@
1
+ import { ErrorCode } from './kinds.js';
2
+ /** Open on purpose: a code the server adds tomorrow must arrive intact, not be coerced to undefined. */
3
+ export type CaptchaKrakenErrorCode = ErrorCode | (string & {});
1
4
  /**
2
- * Hosted-API refusals, carried across the Python subprocess boundary.
3
- *
4
- * The TypeScript driver does not talk to the model; it shells out to the Python
5
- * CLI, which does. So every hosted-API error arrives here as text on stderr and
6
- * would, without this, be flattened into
7
- * `Failed to execute captcha solver CLI: Command failed with exit code 3` —
8
- * a sentence that tells a camoufox user nothing about the fact that their
9
- * account is out of credits.
10
- *
11
- * The CLI exits 3 and prints `{"error": "...", "ck_error": {...}}`, so the
12
- * wording is decided once, in Python (`captchakraken/errors.py`), and merely
13
- * relayed here. Deliberately NOT re-worded on this side: two copies of the same
14
- * message drift, and the Python one is also what a direct CLI user sees.
5
+ * The Python port words every message once and this side repeats it verbatim: two copies of the same
6
+ * sentence drift. Before this class a customer out of credits read `vLLM 402 Payment Required at ...`.
15
7
  */
16
- /** The machine-readable half. Mirrors the gateway's `GatewayErrorCode`. */
17
- export type CaptchaKrakenErrorCode = 'missing_api_key' | 'invalid_api_key' | 'account_suspended' | 'insufficient_credits' | 'rate_limited' | 'solve_abandoned' | 'unrecognized_prompt' | 'invalid_request' | 'request_too_large' | 'upstream_unavailable' | 'model_not_licensed' | 'model_not_serving' | (string & {});
18
8
  export declare class CaptchaKrakenAPIError extends Error {
19
9
  readonly status: number | undefined;
20
10
  readonly code: CaptchaKrakenErrorCode | undefined;
21
- /** Where to send the user to fix it, when the server named somewhere. */
22
11
  readonly resolutionUrl: string | undefined;
23
- /** Seconds to wait, on `rate_limited`. */
24
12
  readonly retryAfterSeconds: number | undefined;
25
- /** Lets `catch` blocks branch without `instanceof` across module copies. */
26
13
  readonly isCaptchaKrakenAPIError = true;
27
14
  constructor(message: string, fields?: {
28
15
  status?: number;
@@ -32,14 +19,7 @@ export declare class CaptchaKrakenAPIError extends Error {
32
19
  });
33
20
  }
34
21
  /**
35
- * Pull a hosted-API error out of the CLI's stderr, or return null.
36
- *
37
- * Scans line by line rather than parsing the whole buffer: stderr also carries
38
- * timing records and any warning the Python side emitted, so the JSON payload
39
- * is one line among several and `JSON.parse(stderr)` would simply fail.
40
- *
41
- * Returns null for anything that is not recognisably ours, which leaves the
42
- * caller's existing generic handling in place — an unparseable stderr must not
43
- * become a confident but wrong billing message.
22
+ * Line by line, because stderr also carries timing records. Anything unrecognised is null, never a guess:
23
+ * an unparseable stderr must not become a confident but wrong billing message.
44
24
  */
45
25
  export declare function parseApiError(stderr: string): CaptchaKrakenAPIError | null;
package/dist/errors.js CHANGED
@@ -1,26 +1,14 @@
1
1
  "use strict";
2
- /**
3
- * Hosted-API refusals, carried across the Python subprocess boundary.
4
- *
5
- * The TypeScript driver does not talk to the model; it shells out to the Python
6
- * CLI, which does. So every hosted-API error arrives here as text on stderr and
7
- * would, without this, be flattened into
8
- * `Failed to execute captcha solver CLI: Command failed with exit code 3` —
9
- * a sentence that tells a camoufox user nothing about the fact that their
10
- * account is out of credits.
11
- *
12
- * The CLI exits 3 and prints `{"error": "...", "ck_error": {...}}`, so the
13
- * wording is decided once, in Python (`captchakraken/errors.py`), and merely
14
- * relayed here. Deliberately NOT re-worded on this side: two copies of the same
15
- * message drift, and the Python one is also what a direct CLI user sees.
16
- */
17
2
  Object.defineProperty(exports, "__esModule", { value: true });
18
3
  exports.CaptchaKrakenAPIError = void 0;
19
4
  exports.parseApiError = parseApiError;
5
+ /**
6
+ * The Python port words every message once and this side repeats it verbatim: two copies of the same
7
+ * sentence drift. Before this class a customer out of credits read `vLLM 402 Payment Required at ...`.
8
+ */
20
9
  class CaptchaKrakenAPIError extends Error {
21
10
  constructor(message, fields = {}) {
22
11
  super(message);
23
- /** Lets `catch` blocks branch without `instanceof` across module copies. */
24
12
  this.isCaptchaKrakenAPIError = true;
25
13
  this.name = 'CaptchaKrakenAPIError';
26
14
  this.status = fields.status;
@@ -31,15 +19,8 @@ class CaptchaKrakenAPIError extends Error {
31
19
  }
32
20
  exports.CaptchaKrakenAPIError = CaptchaKrakenAPIError;
33
21
  /**
34
- * Pull a hosted-API error out of the CLI's stderr, or return null.
35
- *
36
- * Scans line by line rather than parsing the whole buffer: stderr also carries
37
- * timing records and any warning the Python side emitted, so the JSON payload
38
- * is one line among several and `JSON.parse(stderr)` would simply fail.
39
- *
40
- * Returns null for anything that is not recognisably ours, which leaves the
41
- * caller's existing generic handling in place — an unparseable stderr must not
42
- * become a confident but wrong billing message.
22
+ * Line by line, because stderr also carries timing records. Anything unrecognised is null, never a guess:
23
+ * an unparseable stderr must not become a confident but wrong billing message.
43
24
  */
44
25
  function parseApiError(stderr) {
45
26
  if (!stderr || !stderr.includes('ck_error'))