captchakraken 2.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 (37) hide show
  1. package/README.md +54 -0
  2. package/dist/index.d.ts +56 -0
  3. package/dist/index.js +43 -0
  4. package/dist/playwright-types.d.ts +119 -0
  5. package/dist/playwright-types.js +25 -0
  6. package/dist/puppeteer-adapter.d.ts +70 -0
  7. package/dist/puppeteer-adapter.js +96 -0
  8. package/dist/solver.d.ts +264 -0
  9. package/dist/solver.js +1922 -0
  10. package/dist/token-usage.d.ts +15 -0
  11. package/dist/token-usage.js +102 -0
  12. package/dist/types.d.ts +248 -0
  13. package/dist/types.js +2 -0
  14. package/package.json +49 -0
  15. package/python/Dockerfile +41 -0
  16. package/python/README.md +71 -0
  17. package/python/examples/README.md +68 -0
  18. package/python/examples/_harness.py +158 -0
  19. package/python/examples/demoHcaptcha.py +20 -0
  20. package/python/examples/demoRecaptcha.py +17 -0
  21. package/python/pyproject.toml +79 -0
  22. package/python/src/captchakraken/__init__.py +61 -0
  23. package/python/src/captchakraken/action_types.py +56 -0
  24. package/python/src/captchakraken/cli.py +656 -0
  25. package/python/src/captchakraken/config.py +78 -0
  26. package/python/src/captchakraken/image_processor.py +244 -0
  27. package/python/src/captchakraken/overlay.py +520 -0
  28. package/python/src/captchakraken/planner.py +408 -0
  29. package/python/src/captchakraken/planner_types.py +74 -0
  30. package/python/src/captchakraken/server_manager.py +290 -0
  31. package/python/src/captchakraken/solver.py +434 -0
  32. package/python/src/captchakraken/timing.py +42 -0
  33. package/python/src/captchakraken/tool_calls/find_checkbox.py +72 -0
  34. package/python/src/captchakraken/tool_calls/find_grid.py +1762 -0
  35. package/python/src/captchakraken/tool_calls/move_indicator.py +431 -0
  36. package/scripts/copy-python.mjs +29 -0
  37. package/scripts/setup-python.js +104 -0
@@ -0,0 +1,15 @@
1
+ import { TokenUsage } from './types';
2
+ export interface ModelPricing {
3
+ inputPricePerM: number;
4
+ outputPricePerM: number;
5
+ cachedInputPricePerM: number;
6
+ }
7
+ export declare const PRICING: Record<string, ModelPricing>;
8
+ export declare function estimateCost(usage: TokenUsage): number;
9
+ export declare function aggregateTokenUsage(usages: TokenUsage[]): {
10
+ modelName: string;
11
+ inputTokens: number;
12
+ outputTokens: number;
13
+ cachedInputTokens: number;
14
+ estimatedCost: number;
15
+ };
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PRICING = void 0;
4
+ exports.estimateCost = estimateCost;
5
+ exports.aggregateTokenUsage = aggregateTokenUsage;
6
+ exports.PRICING = {
7
+ 'gemini-3-pro-preview': {
8
+ inputPricePerM: 2.00,
9
+ outputPricePerM: 12.00,
10
+ cachedInputPricePerM: 0.20,
11
+ },
12
+ 'gemini-3-flash-preview': {
13
+ inputPricePerM: 0.50,
14
+ outputPricePerM: 3.00,
15
+ cachedInputPricePerM: 0.05,
16
+ },
17
+ 'gemini-2.5-pro': {
18
+ inputPricePerM: 1.25,
19
+ outputPricePerM: 10.00,
20
+ cachedInputPricePerM: 0.125,
21
+ },
22
+ 'gemini-2.5-flash': {
23
+ inputPricePerM: 0.30,
24
+ outputPricePerM: 2.50,
25
+ cachedInputPricePerM: 0.03,
26
+ },
27
+ 'gemini-2.5-flash-preview': {
28
+ inputPricePerM: 0.30,
29
+ outputPricePerM: 2.50,
30
+ cachedInputPricePerM: 0.03,
31
+ },
32
+ 'gemini-2.5-flash-lite': {
33
+ inputPricePerM: 0.10,
34
+ outputPricePerM: 0.40,
35
+ cachedInputPricePerM: 0.01,
36
+ },
37
+ 'gemini-2.5-flash-lite-preview': {
38
+ inputPricePerM: 0.10,
39
+ outputPricePerM: 0.40,
40
+ cachedInputPricePerM: 0.01,
41
+ },
42
+ 'gemini-2.0-flash': {
43
+ inputPricePerM: 0.10,
44
+ outputPricePerM: 0.40,
45
+ cachedInputPricePerM: 0.025,
46
+ },
47
+ 'gemini-2.5-computer-use-preview-10-2025': {
48
+ inputPricePerM: 1.25,
49
+ outputPricePerM: 10.00,
50
+ cachedInputPricePerM: 0.025,
51
+ }
52
+ };
53
+ // The CaptchaKraken CLI (vLLM/OpenAI shape) emits `prompt_tokens` /
54
+ // `completion_tokens`, while older Gemini-path usages used `input_tokens` /
55
+ // `output_tokens`. Normalize both so aggregation never produces NaN.
56
+ function inTok(u) {
57
+ return (u.input_tokens ?? u.prompt_tokens ?? 0);
58
+ }
59
+ function outTok(u) {
60
+ return (u.output_tokens ?? u.completion_tokens ?? 0);
61
+ }
62
+ function cachedTok(u) {
63
+ return (u.cached_input_tokens ?? u.prompt_tokens_details?.cached_tokens ?? 0);
64
+ }
65
+ function estimateCost(usage) {
66
+ const model = usage.model;
67
+ // Default to gemini-2.5-flash pricing if unknown
68
+ const pricing = exports.PRICING[model] || exports.PRICING['gemini-2.5-flash'];
69
+ const inputCost = (inTok(usage) / 1000000) * pricing.inputPricePerM;
70
+ const outputCost = (outTok(usage) / 1000000) * pricing.outputPricePerM;
71
+ const cachedCost = (cachedTok(usage) / 1000000) * pricing.cachedInputPricePerM;
72
+ return inputCost + outputCost + cachedCost;
73
+ }
74
+ function aggregateTokenUsage(usages) {
75
+ if (usages.length === 0) {
76
+ return {
77
+ modelName: 'none',
78
+ inputTokens: 0,
79
+ outputTokens: 0,
80
+ cachedInputTokens: 0,
81
+ estimatedCost: 0,
82
+ };
83
+ }
84
+ const modelName = usages[0].model;
85
+ let inputTokens = 0;
86
+ let outputTokens = 0;
87
+ let cachedInputTokens = 0;
88
+ let estimatedCost = 0;
89
+ for (const usage of usages) {
90
+ inputTokens += inTok(usage);
91
+ outputTokens += outTok(usage);
92
+ cachedInputTokens += cachedTok(usage);
93
+ estimatedCost += estimateCost(usage);
94
+ }
95
+ return {
96
+ modelName,
97
+ inputTokens,
98
+ outputTokens,
99
+ cachedInputTokens,
100
+ estimatedCost,
101
+ };
102
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Lifecycle event emitted by {@link CaptchaKrakenConfig.onStep}. One is fired
3
+ * before any interaction (`stage: 'initial'`) and one after every executed
4
+ * action (click batch, drag, wait, submit) plus per dynamic-grid round, so a
5
+ * caller can record the exact sequence of intermediate stages, time them, and
6
+ * count steps without scraping CAPTCHA_DEBUG dumps.
7
+ */
8
+ export interface SolveStepEvent {
9
+ /** 1-based monotonically increasing step index across the whole solve. */
10
+ index: number;
11
+ /**
12
+ * Coarse stage kind:
13
+ * - `initial` : screenshot taken before any action (baseline)
14
+ * - `click` : after a click (or batch of clicks) was executed
15
+ * - `drag` : after a drag was executed
16
+ * - `wait` : after a CLI-requested wait elapsed
17
+ * - `submit` : after the Verify/Next/Submit button was clicked
18
+ * - `round` : a dynamic reCAPTCHA 3x3 round boundary (pre-solve snapshot)
19
+ */
20
+ stage: 'initial' | 'click' | 'drag' | 'wait' | 'submit' | 'round';
21
+ /** Short human label, e.g. "round-2:clicked 3 tile(s)". */
22
+ label: string;
23
+ /**
24
+ * Absolute path to a PNG screenshot of the captcha element at this step.
25
+ * The file is owned by the callback once emitted — the solver does NOT
26
+ * delete it (copy/move it where you need it). Null if the screenshot failed.
27
+ */
28
+ screenshotPath: string | null;
29
+ /** Detected puzzle vendor, if known. */
30
+ puzzleSource?: 'hcaptcha' | 'recaptcha' | 'unknown';
31
+ /**
32
+ * Which captcha frame this screenshot is of:
33
+ * - `checkbox` : the anchor "I'm not a robot" widget (no puzzle yet)
34
+ * - `challenge` : the open image/grid challenge frame (the real puzzle)
35
+ * - `unknown` : could not be determined from the frame src
36
+ * Recorders that only want the actual solve (not the pre-challenge checkbox
37
+ * clicks) filter to `challenge`.
38
+ */
39
+ frameRole?: 'checkbox' | 'challenge' | 'unknown';
40
+ /** Outer solve-loop attempt this step belongs to. */
41
+ attempt: number;
42
+ /** ms since solve() started. */
43
+ elapsedMs: number;
44
+ /** Free-form per-stage detail (action payload, clicked cells, etc.). */
45
+ meta?: Record<string, any>;
46
+ }
47
+ export interface CaptchaKrakenConfig {
48
+ /**
49
+ * Optional observer fired at each intermediate solve stage. Receives a
50
+ * baseline screenshot before any action and one after every executed action.
51
+ * Use it to capture intermediate-stage screenshots, count steps, and time
52
+ * each phase. The callback may be async; the solver awaits it. Errors thrown
53
+ * by the callback are swallowed (never fail a solve because logging failed).
54
+ *
55
+ * The PNG at `event.screenshotPath` is owned by the callback once emitted —
56
+ * the solver will not delete it.
57
+ */
58
+ onStep?: (event: SolveStepEvent) => void | Promise<void>;
59
+ /**
60
+ * Path to the bundled CaptchaKraken CLI root.
61
+ *
62
+ * Usually you do NOT need to set this. If omitted, the solver will auto-resolve the
63
+ * `python/` directory (the captchakraken package) shipped inside this npm package.
64
+ */
65
+ repoPath?: string;
66
+ /**
67
+ * Command to run python (default: 'python' or 'python3').
68
+ */
69
+ pythonCommand?: string;
70
+ /**
71
+ * vLLM LoRA name to invoke (default: 'captcha' — our bbox-aware LoRA).
72
+ * Override if you've registered a different module with the vLLM server.
73
+ */
74
+ model?: string;
75
+ /**
76
+ * Bearer token for the vLLM server (also picked up from VLLM_API_KEY env).
77
+ */
78
+ apiKey?: string;
79
+ /**
80
+ * Starting mouse position (default: { x: 100, y: 100 }).
81
+ * HIGHLY RECOMMENDED to set this, prevents jumping around of the cursor when solving.
82
+ */
83
+ startingMousePosition?: {
84
+ x: number;
85
+ y: number;
86
+ };
87
+ /**
88
+ * Automatically re-check for newly opened / next-step captchas after each solve
89
+ * attempt (e.g., clicking a checkbox opens an image challenge).
90
+ *
91
+ * Default: 10
92
+ */
93
+ maxSolveLoops?: number;
94
+ /**
95
+ * Delay (ms) after executing actions before re-detecting captchas.
96
+ * Useful to allow challenge frames / new images to appear.
97
+ *
98
+ * Default: 1200
99
+ */
100
+ postSolveDelayMs?: number;
101
+ /**
102
+ * Overall time limit (ms) for the entire solve loop.
103
+ *
104
+ * Default: 120000 (2 minutes)
105
+ */
106
+ overallSolveTimeoutMs?: number;
107
+ /**
108
+ * Poll interval (ms) for the reCAPTCHA grid-cell-load wait — how often the
109
+ * solver re-screenshots the grid while waiting for tiles to stop fading in.
110
+ * Doubles as the inter-frame gap for the settle change-detector, so keep it
111
+ * comfortably above zero.
112
+ *
113
+ * Default: 250
114
+ */
115
+ gridLoadPollIntervalMs?: number;
116
+ /**
117
+ * Overall timeout (ms) for the reCAPTCHA grid-cell-load wait. On timeout the
118
+ * solver proceeds to screenshot anyway (best-effort).
119
+ *
120
+ * Default: 8000
121
+ */
122
+ gridLoadTimeoutMs?: number;
123
+ /**
124
+ * reCAPTCHA 3x3 dynamic puzzles only. After clicking a round of tiles, the
125
+ * max time (ms) to wait for at least one clicked blank/fading tile to finish
126
+ * loading before re-screenshotting and re-solving. On timeout the solver
127
+ * proceeds anyway (best-effort, backstopped by overallSolveTimeoutMs).
128
+ *
129
+ * Default: 6000
130
+ */
131
+ recaptchaDynamicFadeWaitMs?: number;
132
+ /**
133
+ * reCAPTCHA 3x3 dynamic puzzles only. Minimum gap (ms) between the two frames
134
+ * the fade detectors diff, and the poll cadence for waitForAnyClickedTileLoaded
135
+ * / currentLoadingCells. Kept comfortably above zero so two consecutive frames
136
+ * during a slow fade differ enough for the change detector to fire (frames
137
+ * captured back-to-back can look identical mid-fade and read as "loaded").
138
+ *
139
+ * Default: 250
140
+ */
141
+ recaptchaDynamicFadePollMs?: number;
142
+ /**
143
+ * reCAPTCHA 3x3 dynamic puzzles only. Grace window (ms) after clicking during
144
+ * which the solver watches the clicked tiles for the ONSET of a blank/fade
145
+ * before deciding the puzzle is solved. reCAPTCHA keeps a clicked tile
146
+ * SELECTED (showing its old image + a blue badge) for a couple of seconds and
147
+ * only THEN blanks it to swap in a replacement, so the window must comfortably
148
+ * exceed that delay or we submit while the refresh is still pending. If no
149
+ * clicked tile goes blank/changing within this window, the puzzle is treated
150
+ * as solved.
151
+ *
152
+ * Default: 4000
153
+ */
154
+ recaptchaFadeOnsetGraceMs?: number;
155
+ /**
156
+ * reCAPTCHA 3x3 dynamic puzzles only. Cap on the number of
157
+ * click → refresh → re-solve rounds within a single puzzle, independent of
158
+ * maxSolveLoops.
159
+ *
160
+ * Default: 8
161
+ */
162
+ recaptchaMaxDynamicRounds?: number;
163
+ /**
164
+ * reCAPTCHA 3x3 dynamic puzzles only. When true, the solver hovers the mouse
165
+ * over the just-clicked blank/fading tiles (in click order) while waiting for
166
+ * them to reload, mimicking a human. Disable to skip the hover behavior.
167
+ *
168
+ * Default: true
169
+ */
170
+ recaptchaTileHoverEnabled?: boolean;
171
+ /**
172
+ * While the model is generating a solution (the main idle window), drift the
173
+ * cursor over the challenge area with human-like trajectories instead of
174
+ * leaving it frozen. Cancelled the instant the model responds. Set false to
175
+ * keep the cursor still during inference.
176
+ *
177
+ * Default: true
178
+ */
179
+ idleMouseWander?: boolean;
180
+ /**
181
+ * After an action, the max time (ms) to watch for the solve outcome — the
182
+ * vendor's solved signal (checkbox checked / response token) or a freshly
183
+ * rendered next round — before falling back to a full re-detect. Returning as
184
+ * soon as the solved signal appears avoids re-entering the solve pipeline on a
185
+ * challenge frame that is merely animating closed.
186
+ *
187
+ * Default: 4000
188
+ */
189
+ postSolveOutcomeTimeoutMs?: number;
190
+ }
191
+ export interface BoundingBox {
192
+ 0: number;
193
+ 1: number;
194
+ 2: number;
195
+ 3: number;
196
+ }
197
+ export interface ClickAction {
198
+ action: 'click';
199
+ /**
200
+ * One or more normalized [x1, y1, x2, y2] bboxes (0–1 fractions of the
201
+ * screenshot). Each entry produces one click. Emitted by v2 CLI for both
202
+ * grid selections and click-puzzle points.
203
+ */
204
+ target_bounding_boxes?: Array<[number, number, number, number]>;
205
+ /** Legacy v1 fields kept for backwards-compat with older CLI builds. */
206
+ target_number?: number | null;
207
+ target_bounding_box?: [number, number, number, number] | null;
208
+ target_coordinates?: [number, number] | null;
209
+ }
210
+ export interface DragAction {
211
+ action: 'drag';
212
+ source_bounding_box: [number, number, number, number];
213
+ target_bounding_box: [number, number, number, number];
214
+ }
215
+ export interface DoneAction {
216
+ action: 'done';
217
+ }
218
+ export interface WaitAction {
219
+ action: 'wait';
220
+ duration_ms: number;
221
+ }
222
+ export type CaptchaAction = ClickAction | WaitAction | DragAction | DoneAction;
223
+ export type SolverResult = CaptchaAction | CaptchaAction[];
224
+ export interface TokenUsage {
225
+ input_tokens: number;
226
+ output_tokens: number;
227
+ cached_input_tokens?: number;
228
+ model: string;
229
+ }
230
+ export interface CliResponse {
231
+ actions: SolverResult;
232
+ token_usage: TokenUsage[];
233
+ }
234
+ export interface Vector {
235
+ x: number;
236
+ y: number;
237
+ }
238
+ export interface SolveResult {
239
+ isSolved: boolean;
240
+ finalMousePosition: Vector;
241
+ tokenUsage: {
242
+ modelName: string;
243
+ inputTokens: number;
244
+ outputTokens: number;
245
+ cachedInputTokens: number;
246
+ estimatedCost: number;
247
+ };
248
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "captchakraken",
3
+ "version": "2.1.0",
4
+ "description": "Browser-agnostic Playwright/Puppeteer captcha solver. Detects the captcha, reads the grid with a fine-tuned Qwen3.5-9B vision model on vLLM, and clicks through to a token.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "python",
10
+ "scripts"
11
+ ],
12
+ "scripts": {
13
+ "prebuild": "node scripts/copy-python.mjs",
14
+ "build": "tsc",
15
+ "typecheck": "tsc --noEmit -p tsconfig.json",
16
+ "test": "tsc --noEmit -p tsconfig.json",
17
+ "prepublishOnly": "npm run build",
18
+ "postinstall": "node scripts/setup-python.js"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/JWriter20/CaptchaKraken.git",
23
+ "directory": "js"
24
+ },
25
+ "keywords": [
26
+ "captcha",
27
+ "recaptcha",
28
+ "hcaptcha",
29
+ "playwright",
30
+ "puppeteer",
31
+ "automation",
32
+ "vllm",
33
+ "qwen"
34
+ ],
35
+ "author": "Jake Writer",
36
+ "license": "GPL-3.0-or-later",
37
+ "type": "commonjs",
38
+ "bugs": {
39
+ "url": "https://github.com/JWriter20/CaptchaKraken/issues"
40
+ },
41
+ "homepage": "https://github.com/JWriter20/CaptchaKraken#readme",
42
+ "devDependencies": {
43
+ "@types/node": "^24.10.2",
44
+ "typescript": "^5.9.3"
45
+ },
46
+ "dependencies": {
47
+ "cursory-ts": "^1.0.0"
48
+ }
49
+ }
@@ -0,0 +1,41 @@
1
+ # CaptchaKraken vLLM server image.
2
+ #
3
+ # Serves the base model + captcha LoRA over an OpenAI-compatible endpoint on
4
+ # :8000. The image bakes in the `captchakraken[serve]` stack and (optionally)
5
+ # the weights, then hands off to `captchakraken server run`, which execs
6
+ # `vllm serve` assembled entirely from the env-overridable config — so you can
7
+ # repoint it at any base model / adapter without editing this file.
8
+ #
9
+ # docker build -t captchakraken-vllm .
10
+ # docker run --gpus all -p 8000:8000 --ipc=host \
11
+ # -e VLLM_API_KEY=your_key captchakraken-vllm
12
+ FROM pytorch/pytorch:2.7.0-cuda12.8-cudnn9-devel
13
+
14
+ WORKDIR /app
15
+
16
+ RUN apt-get update && apt-get install -y \
17
+ git ninja-build libgl1 libglib2.0-0 wget ffmpeg \
18
+ && rm -rf /var/lib/apt/lists/*
19
+
20
+ # Install the package + serving extra (vllm/torch/transformers/accelerate/hf).
21
+ COPY . /app/captchakraken
22
+ RUN pip install --upgrade pip && pip install "/app/captchakraken[serve]"
23
+
24
+ # Model identity — all overridable at build/run time (model-agnostic image).
25
+ ARG HF_TOKEN
26
+ ENV HF_TOKEN=${HF_TOKEN}
27
+ ENV CAPTCHA_BASE_MODEL="Qwen/Qwen3.5-9B"
28
+ ENV CAPTCHA_LORA_ADAPTER="CaptchaKraken/CaptchaKraken_v1"
29
+ ENV CAPTCHA_LORA_NAME="captcha"
30
+ ENV VLLM_GPU_MEMORY_UTILIZATION="0.80"
31
+ ENV VLLM_PORT=8000
32
+
33
+ # Bake weights into the image (best-effort; falls back to runtime download).
34
+ RUN python3 -c "import os; from huggingface_hub import snapshot_download; \
35
+ token = os.getenv('HF_TOKEN'); \
36
+ snapshot_download(os.getenv('CAPTCHA_BASE_MODEL'), token=token); \
37
+ snapshot_download(os.getenv('CAPTCHA_LORA_ADAPTER'), token=token)" \
38
+ || echo "Warning: model prefetch failed; weights download at first run."
39
+
40
+ EXPOSE 8000
41
+ CMD ["captchakraken", "server", "run"]
@@ -0,0 +1,71 @@
1
+ # captchakraken
2
+
3
+ The Python engine + CLI behind [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken):
4
+ OpenCV grid detection + a fine-tuned **Qwen3.5-9B** vision LoRA served on
5
+ **vLLM**. Given a screenshot of a captcha grid, it locates the tiles and returns
6
+ the click plan. Ships the `captchakraken` command.
7
+
8
+ > For demo videos, accuracy numbers, the browser driver, and the full
9
+ > self-hosting guide, see the main repo
10
+ > **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install captchakraken # client: OpenCV detection + vLLM HTTP planner
16
+ pip install "captchakraken[serve]" # + the serving stack (vLLM/torch) to self-host
17
+ ```
18
+
19
+ The base install is lightweight — everything you need to solve captchas against
20
+ a vLLM server (local or remote). The `[serve]` extra pulls the heavy stack only
21
+ if you want to run the model yourself. The one-command
22
+ [`setup.sh`](https://github.com/JWriter20/CaptchaKraken) installs `[serve]`,
23
+ downloads the weights, and writes an env file for you.
24
+
25
+ ## Hands-off server
26
+
27
+ The vLLM server is managed for you. On your first solve, if the configured
28
+ endpoint is **local** and nothing is listening, a server is started
29
+ automatically and reused. Point `VLLM_BASE_URL` at a server you already run to
30
+ skip local management entirely.
31
+
32
+ ```bash
33
+ captchakraken server start | stop | status | run
34
+ ```
35
+
36
+ ## Usage
37
+
38
+ ```bash
39
+ # Solve an image/video: classify → find_grid → plan. Prints the click actions.
40
+ captchakraken path/to/captcha.png
41
+ captchakraken path/to/captcha.png --puzzle-source hcaptcha
42
+ ```
43
+
44
+ ```python
45
+ from captchakraken import CaptchaSolver
46
+
47
+ solver = CaptchaSolver() # connects to / auto-starts a local vLLM
48
+ actions = solver.solve("captcha.png")
49
+ ```
50
+
51
+ Pure-OpenCV tool subcommands (no model): `find-grid`, `find-checkbox`,
52
+ `detect-selected`, `grid-cell-states`, `find-move`, `find-movable`, and a
53
+ persistent `serve` worker the browser driver polls.
54
+
55
+ ## Configuration (model-agnostic)
56
+
57
+ Everything model-specific lives in `captchakraken.config` and is env-overridable
58
+ — the solver never hard-codes a model.
59
+
60
+ | Variable | Meaning | Default |
61
+ |---|---|---|
62
+ | `VLLM_BASE_URL` | Inference endpoint | `http://localhost:8000/v1` |
63
+ | `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) | `EMPTY` |
64
+ | `CAPTCHA_BASE_MODEL` | Base weights vLLM loads | `Qwen/Qwen3.5-9B` |
65
+ | `CAPTCHA_LORA_ADAPTER` | Captcha adapter (HF id or path) | `CaptchaKraken/CaptchaKraken_v1` |
66
+ | `CAPTCHA_LORA_NAME` | Served adapter name the client requests | `captcha` |
67
+ | `CAPTCHA_KRAKEN_AUTOSTART` | `0` disables local auto-start | `1` |
68
+
69
+ ## License
70
+
71
+ GPL-3.0-or-later.
@@ -0,0 +1,68 @@
1
+ # Examples (Python)
2
+
3
+ Two runnable demos that drive a real stealth browser
4
+ ([camoufox](https://github.com/JWriter20/camoufox)) to a live captcha demo site,
5
+ screenshot the challenge, run the **engine** on it, and print token speed /
6
+ total time / outcome:
7
+
8
+ | File | Site |
9
+ |---|---|
10
+ | `demoRecaptcha.py` | Google reCAPTCHA v2 demo |
11
+ | `demoHcaptcha.py` | hCaptcha demo |
12
+
13
+ > The Python port is the engine (detection + planner). These demos validate the
14
+ > engine + model + server on a real challenge frame. Full click-replay and
15
+ > multi-round verification in a live page are what the TypeScript port
16
+ > (`captchakraken`) does end-to-end.
17
+
18
+ ## Setup
19
+
20
+ ```bash
21
+ cd python
22
+ pip install -e ".[serve]" # engine + serving stack (use ".[]" for a remote server)
23
+ pip install camoufox # example-only dep
24
+ ```
25
+
26
+ ### The camoufox binary (from your fork)
27
+
28
+ Uses the **camoufox binary from the fork's releases**:
29
+ [JWriter20/camoufox → Releases](https://github.com/JWriter20/camoufox/releases).
30
+
31
+ 1. Download the latest release asset for your OS/arch and extract it.
32
+ 2. Point the demo at the extracted `camoufox` executable:
33
+
34
+ ```bash
35
+ export CAMOUFOX_BINARY=/path/to/camoufox/camoufox # your fork binary
36
+ ```
37
+
38
+ If `CAMOUFOX_BINARY` is unset, camoufox falls back to its default binary
39
+ (`python -m camoufox fetch`).
40
+
41
+ ### Point at a model
42
+
43
+ ```bash
44
+ source ../captchakraken.env # VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY
45
+ ```
46
+
47
+ ## Run
48
+
49
+ ```bash
50
+ python examples/demoRecaptcha.py
51
+ python examples/demoHcaptcha.py
52
+ HEADLESS=0 python examples/demoRecaptcha.py # watch the browser
53
+ ```
54
+
55
+ ## Reading the report
56
+
57
+ ```
58
+ result : ✓ engine produced a solution
59
+ click plan: 4 tile(s)/target(s)
60
+ total time : 6.1s (solve: 3.4s)
61
+ tokens : 812 in / 34 out
62
+ gen speed : ~10.0 tok/s
63
+ reason : <only on failure — unsupported puzzle, unreachable server, …>
64
+ ```
65
+
66
+ `gen speed` = model output tokens ÷ solve seconds. Failure reasons the harness
67
+ reports: unreachable vLLM server, an unsupported hCaptcha puzzle (drag/video),
68
+ the challenge iframe never appearing, or the model returning no matching tiles.