captchakraken 2.9.0 → 3.1.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 +7 -36
  4. package/dist/cli-invocation.js +8 -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 +131 -708
  28. package/dist/solver.js +1319 -3382
  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 -493
  34. package/dist/watcher.d.ts +5 -78
  35. package/dist/watcher.js +0 -56
  36. package/package.json +11 -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 -883
  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 +46 -484
  56. package/python/src/captchakraken/kinds.py +152 -0
  57. package/python/src/captchakraken/models.json +36 -4
  58. package/python/src/captchakraken/overlay.py +65 -498
  59. package/python/src/captchakraken/page_solver.py +1282 -3101
  60. package/python/src/captchakraken/planner.py +65 -487
  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,46 +1,17 @@
1
- /**
2
- * How the JS client hands a solve request to the bundled Python CLI.
3
- *
4
- * Two rules govern this module, and both exist because the alternative is
5
- * unsafe rather than merely untidy:
6
- *
7
- * 1. The API key travels in the ENVIRONMENT, never in argv and never to stdout.
8
- * Argv is world-readable on Linux — any local user can read
9
- * `/proc/<pid>/cmdline` while a solve runs, and `ps` shows it — and anything
10
- * printed alongside the command lands in CI logs and terminal scrollback.
11
- * An environment block is per-process and not world-readable.
12
- *
13
- * 2. The command is built as an ARGV ARRAY for `execFile`, not a joined string
14
- * for a shell, so a screenshot path containing a space or a quote cannot
15
- * reshape the command.
16
- */
17
- /** Env var the CLI reads the bearer token from. */
1
+ import type { PromptFamily, RetryMode, Vendor } from './kinds.js';
18
2
  export declare const API_KEY_ENV = "CAPTCHA_KRAKEN_API_KEY";
19
- /** Env var carrying the re-ask level for a board the vendor already refused. */
20
3
  export declare const RESAMPLE_LEVEL_ENV = "CAPTCHA_RESAMPLE_LEVEL";
21
- /** Positional placeholder the CLI still accepts in this slot. */
22
4
  export declare const API_PROVIDER = "captchaKrakenApi";
23
5
  export interface SolveInvocation {
24
6
  imagePath: string;
25
- model: string;
26
- puzzleSource: string;
27
- retryMode?: string | null;
7
+ model?: string;
8
+ puzzleSource: Vendor;
9
+ retryMode?: RetryMode | null;
28
10
  textMode?: boolean;
29
- /** One expert of a routed model, or undefined to route by prompt family. */
30
- expert?: string | null;
11
+ expert?: PromptFamily | null;
31
12
  }
32
- /**
33
- * The argv for one solve. No shell, no quoting, and NO CREDENTIAL — the key is
34
- * supplied by `solveEnv` instead.
35
- */
13
+ /** An argv array for execFile, never a shell string. */
36
14
  export declare function buildSolveArgs(invocation: SolveInvocation): string[];
37
- /** Base env plus the credential, when there is one. */
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. */
38
16
  export declare function solveEnv(base: NodeJS.ProcessEnv, apiKey?: string, resampleLevel?: number): NodeJS.ProcessEnv;
39
- /**
40
- * A log line for a solve invocation, with any credential removed.
41
- *
42
- * Belt and braces: `buildSolveArgs` no longer carries the key, but this is the
43
- * function that prints to stdout, so it redacts anything key-shaped regardless
44
- * of how it got into the array.
45
- */
46
17
  export declare function redactCommand(command: string, apiKey?: string): string;
@@ -1,78 +1,37 @@
1
1
  "use strict";
2
- /**
3
- * How the JS client hands a solve request to the bundled Python CLI.
4
- *
5
- * Two rules govern this module, and both exist because the alternative is
6
- * unsafe rather than merely untidy:
7
- *
8
- * 1. The API key travels in the ENVIRONMENT, never in argv and never to stdout.
9
- * Argv is world-readable on Linux — any local user can read
10
- * `/proc/<pid>/cmdline` while a solve runs, and `ps` shows it — and anything
11
- * printed alongside the command lands in CI logs and terminal scrollback.
12
- * An environment block is per-process and not world-readable.
13
- *
14
- * 2. The command is built as an ARGV ARRAY for `execFile`, not a joined string
15
- * for a shell, so a screenshot path containing a space or a quote cannot
16
- * reshape the command.
17
- */
18
2
  Object.defineProperty(exports, "__esModule", { value: true });
19
3
  exports.API_PROVIDER = exports.RESAMPLE_LEVEL_ENV = exports.API_KEY_ENV = void 0;
20
4
  exports.buildSolveArgs = buildSolveArgs;
21
5
  exports.solveEnv = solveEnv;
22
6
  exports.redactCommand = redactCommand;
23
- /** 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.
24
8
  exports.API_KEY_ENV = 'CAPTCHA_KRAKEN_API_KEY';
25
- /** Env var carrying the re-ask level for a board the vendor already refused. */
26
9
  exports.RESAMPLE_LEVEL_ENV = 'CAPTCHA_RESAMPLE_LEVEL';
27
- /** Positional placeholder the CLI still accepts in this slot. */
28
10
  exports.API_PROVIDER = 'captchaKrakenApi';
29
- /**
30
- * The argv for one solve. No shell, no quoting, and NO CREDENTIAL — the key is
31
- * supplied by `solveEnv` instead.
32
- */
11
+ /** An argv array for execFile, never a shell string. */
33
12
  function buildSolveArgs(invocation) {
34
13
  const { imagePath, model, puzzleSource, retryMode, textMode, expert } = invocation;
35
- const args = [
36
- '-m',
37
- 'captchakraken.cli',
38
- imagePath,
39
- model,
40
- exports.API_PROVIDER,
41
- `--puzzle-source=${puzzleSource}`,
42
- ];
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}`);
43
19
  if (retryMode)
44
20
  args.push(`--retry-mode=${retryMode}`);
45
21
  if (textMode)
46
22
  args.push('--text-mode');
47
- // Only when set. An absent flag is what lets the Python side route by prompt
48
- // family, and passing `--expert=` would be an empty choice rather than none.
49
23
  if (expert)
50
24
  args.push(`--expert=${expert}`);
51
25
  return args;
52
26
  }
53
- /** Base env plus the credential, when there is one. */
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. */
54
28
  function solveEnv(base, apiKey, resampleLevel) {
55
29
  const out = apiKey ? { ...base, [exports.API_KEY_ENV]: apiKey } : { ...base };
56
- // How many times THIS board has already been read and refused. The CLI is a
57
- // fresh process per round, so it cannot know; and without it a re-ask is the
58
- // same greedy arithmetic on the same pixels and returns the same answer,
59
- // which is the whole of the "same answer 3 times running" failure.
60
- //
61
- // The LEVEL travels, not the temperature: the schedule lives once, in
62
- // `planner.RESAMPLE_TEMPERATURES`, so the two ports cannot drift apart on a
63
- // number Tier 3 would then average.
64
30
  if (resampleLevel && resampleLevel > 0) {
65
31
  out[exports.RESAMPLE_LEVEL_ENV] = String(resampleLevel);
66
32
  }
67
33
  return out;
68
34
  }
69
- /**
70
- * A log line for a solve invocation, with any credential removed.
71
- *
72
- * Belt and braces: `buildSolveArgs` no longer carries the key, but this is the
73
- * function that prints to stdout, so it redacts anything key-shaped regardless
74
- * of how it got into the array.
75
- */
76
35
  function redactCommand(command, apiKey) {
77
36
  if (!apiKey)
78
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'))