captchakraken 2.0.0__tar.gz

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 (36) hide show
  1. captchakraken-2.0.0/.gitignore +44 -0
  2. captchakraken-2.0.0/Dockerfile +41 -0
  3. captchakraken-2.0.0/PKG-INFO +102 -0
  4. captchakraken-2.0.0/README.md +71 -0
  5. captchakraken-2.0.0/examples/README.md +68 -0
  6. captchakraken-2.0.0/examples/_harness.py +158 -0
  7. captchakraken-2.0.0/examples/demoHcaptcha.py +20 -0
  8. captchakraken-2.0.0/examples/demoRecaptcha.py +17 -0
  9. captchakraken-2.0.0/pyproject.toml +79 -0
  10. captchakraken-2.0.0/src/captchakraken/__init__.py +61 -0
  11. captchakraken-2.0.0/src/captchakraken/action_types.py +56 -0
  12. captchakraken-2.0.0/src/captchakraken/cli.py +656 -0
  13. captchakraken-2.0.0/src/captchakraken/config.py +78 -0
  14. captchakraken-2.0.0/src/captchakraken/image_processor.py +244 -0
  15. captchakraken-2.0.0/src/captchakraken/overlay.py +520 -0
  16. captchakraken-2.0.0/src/captchakraken/planner.py +408 -0
  17. captchakraken-2.0.0/src/captchakraken/planner_types.py +74 -0
  18. captchakraken-2.0.0/src/captchakraken/server_manager.py +290 -0
  19. captchakraken-2.0.0/src/captchakraken/solver.py +434 -0
  20. captchakraken-2.0.0/src/captchakraken/timing.py +42 -0
  21. captchakraken-2.0.0/src/captchakraken/tool_calls/find_checkbox.py +72 -0
  22. captchakraken-2.0.0/src/captchakraken/tool_calls/find_grid.py +1762 -0
  23. captchakraken-2.0.0/src/captchakraken/tool_calls/move_indicator.py +431 -0
  24. captchakraken-2.0.0/tests/conftest.py +25 -0
  25. captchakraken-2.0.0/tests/grid_diag.py +203 -0
  26. captchakraken-2.0.0/tests/grid_fp.py +55 -0
  27. captchakraken-2.0.0/tests/grid_regression.py +85 -0
  28. captchakraken-2.0.0/tests/grid_trace_reject.py +92 -0
  29. captchakraken-2.0.0/tests/test_checkbox_captcha.py +92 -0
  30. captchakraken-2.0.0/tests/test_find_grid_corpus.py +213 -0
  31. captchakraken-2.0.0/tests/test_grid_detection.py +91 -0
  32. captchakraken-2.0.0/tests/test_grid_detection_ci.py +98 -0
  33. captchakraken-2.0.0/tests/test_move_indicator.py +174 -0
  34. captchakraken-2.0.0/tests/test_object_detection.py +142 -0
  35. captchakraken-2.0.0/tests/test_selected_fields.py +122 -0
  36. captchakraken-2.0.0/tests/test_solver.py +208 -0
@@ -0,0 +1,44 @@
1
+ node_modules
2
+ dist
3
+ *.log
4
+ .DS_Store
5
+ .env
6
+ .npmrc
7
+ test-results
8
+ playwright-report
9
+ debug_runs/
10
+ test_logs/
11
+
12
+ # generated by setup.sh
13
+ captchakraken.env
14
+
15
+ # python port
16
+ python/.venv
17
+ python/build/
18
+ python/dist/
19
+ **/__pycache__/
20
+ *.egg-info/
21
+ .pytest_cache/
22
+ .ruff_cache/
23
+
24
+ # js port: the python engine is copied in at build time from the sibling
25
+ # repo-root python/ (single source of truth) — never commit the copy.
26
+ js/python/
27
+ js/dist/
28
+ js/node_modules/
29
+
30
+ # test-run artifacts
31
+ hcaptcha_5x_attempt_*.png
32
+ hcaptcha_5x_summary.json
33
+ captcha_video_summary.json
34
+ server_log.txt
35
+
36
+ # demo recorder (tests/record_demos.spec.ts) transients. Curated clips that the
37
+ # README embeds live in docs/demos/ (committed); everything else is throwaway.
38
+ record_demos_summary.json
39
+ record_demos_*_summary.json
40
+ record_demos*.log
41
+ run_full_record.out
42
+ run_full_record.sh
43
+ *_video_attempt_*.png
44
+ captcha_videos/
@@ -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,102 @@
1
+ Metadata-Version: 2.4
2
+ Name: captchakraken
3
+ Version: 2.0.0
4
+ Summary: Self-hosted captcha solver: OpenCV grid detection + a fine-tuned Qwen3.5-9B vision LoRA served on vLLM.
5
+ Project-URL: Homepage, https://github.com/JWriter20/CaptchaKraken
6
+ Project-URL: Issues, https://github.com/JWriter20/CaptchaKraken/issues
7
+ Author: Jake Writer
8
+ License: GPL-3.0-or-later
9
+ Keywords: automation,captcha,computer-vision,hcaptcha,qwen,recaptcha,vllm
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Requires-Python: >=3.10
14
+ Requires-Dist: numpy>=1.24.0
15
+ Requires-Dist: opencv-python-headless>=4.10.0
16
+ Requires-Dist: pillow>=10.0.0
17
+ Requires-Dist: pydantic>=2.0.0
18
+ Requires-Dist: python-dotenv>=1.0.0
19
+ Requires-Dist: requests>=2.31.0
20
+ Provides-Extra: dev
21
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
22
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
23
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
24
+ Provides-Extra: serve
25
+ Requires-Dist: accelerate>=0.27.0; extra == 'serve'
26
+ Requires-Dist: huggingface-hub>=0.23.0; extra == 'serve'
27
+ Requires-Dist: torch>=2.0.0; extra == 'serve'
28
+ Requires-Dist: transformers>=4.40.0; extra == 'serve'
29
+ Requires-Dist: vllm>=0.6.3; extra == 'serve'
30
+ Description-Content-Type: text/markdown
31
+
32
+ # captchakraken
33
+
34
+ The Python engine + CLI behind [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken):
35
+ OpenCV grid detection + a fine-tuned **Qwen3.5-9B** vision LoRA served on
36
+ **vLLM**. Given a screenshot of a captcha grid, it locates the tiles and returns
37
+ the click plan. Ships the `captchakraken` command.
38
+
39
+ > For demo videos, accuracy numbers, the browser driver, and the full
40
+ > self-hosting guide, see the main repo
41
+ > **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
42
+
43
+ ## Install
44
+
45
+ ```bash
46
+ pip install captchakraken # client: OpenCV detection + vLLM HTTP planner
47
+ pip install "captchakraken[serve]" # + the serving stack (vLLM/torch) to self-host
48
+ ```
49
+
50
+ The base install is lightweight — everything you need to solve captchas against
51
+ a vLLM server (local or remote). The `[serve]` extra pulls the heavy stack only
52
+ if you want to run the model yourself. The one-command
53
+ [`setup.sh`](https://github.com/JWriter20/CaptchaKraken) installs `[serve]`,
54
+ downloads the weights, and writes an env file for you.
55
+
56
+ ## Hands-off server
57
+
58
+ The vLLM server is managed for you. On your first solve, if the configured
59
+ endpoint is **local** and nothing is listening, a server is started
60
+ automatically and reused. Point `VLLM_BASE_URL` at a server you already run to
61
+ skip local management entirely.
62
+
63
+ ```bash
64
+ captchakraken server start | stop | status | run
65
+ ```
66
+
67
+ ## Usage
68
+
69
+ ```bash
70
+ # Solve an image/video: classify → find_grid → plan. Prints the click actions.
71
+ captchakraken path/to/captcha.png
72
+ captchakraken path/to/captcha.png --puzzle-source hcaptcha
73
+ ```
74
+
75
+ ```python
76
+ from captchakraken import CaptchaSolver
77
+
78
+ solver = CaptchaSolver() # connects to / auto-starts a local vLLM
79
+ actions = solver.solve("captcha.png")
80
+ ```
81
+
82
+ Pure-OpenCV tool subcommands (no model): `find-grid`, `find-checkbox`,
83
+ `detect-selected`, `grid-cell-states`, `find-move`, `find-movable`, and a
84
+ persistent `serve` worker the browser driver polls.
85
+
86
+ ## Configuration (model-agnostic)
87
+
88
+ Everything model-specific lives in `captchakraken.config` and is env-overridable
89
+ — the solver never hard-codes a model.
90
+
91
+ | Variable | Meaning | Default |
92
+ |---|---|---|
93
+ | `VLLM_BASE_URL` | Inference endpoint | `http://localhost:8000/v1` |
94
+ | `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) | `EMPTY` |
95
+ | `CAPTCHA_BASE_MODEL` | Base weights vLLM loads | `Qwen/Qwen3.5-9B` |
96
+ | `CAPTCHA_LORA_ADAPTER` | Captcha adapter (HF id or path) | `CaptchaKraken/CaptchaKraken_v1` |
97
+ | `CAPTCHA_LORA_NAME` | Served adapter name the client requests | `captcha` |
98
+ | `CAPTCHA_KRAKEN_AUTOSTART` | `0` disables local auto-start | `1` |
99
+
100
+ ## License
101
+
102
+ GPL-3.0-or-later.
@@ -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.
@@ -0,0 +1,158 @@
1
+ """
2
+ Shared runner for the CaptchaKraken Python demos.
3
+
4
+ The Python port is the *engine* (OpenCV detection + the vLLM planner). These demos
5
+ launch a real stealth browser (camoufox, using the binary from your fork —
6
+ JWriter20/camoufox releases; see README.md), navigate to a captcha demo site,
7
+ open the challenge, screenshot it, and run the engine on that frame. They report
8
+ token-generation speed, total time, and whether the engine produced a valid
9
+ solution — plus a best-effort reason when it didn't.
10
+
11
+ Note: full click-replay + multi-round verification in a live page is what the
12
+ TypeScript port (`captchakraken`) does end-to-end. Here we validate the
13
+ engine + model + server on a real challenge frame.
14
+ """
15
+
16
+ import os
17
+ import tempfile
18
+ import time
19
+ from dataclasses import dataclass
20
+
21
+ from camoufox.sync_api import Camoufox
22
+
23
+ from captchakraken import CaptchaSolver
24
+ from captchakraken.action_types import ClickAction
25
+ from captchakraken.solver import UnsupportedCaptchaError
26
+
27
+
28
+ @dataclass
29
+ class DemoSpec:
30
+ name: str
31
+ url: str
32
+ vendor: str # "recaptcha" | "hcaptcha"
33
+
34
+
35
+ def _launch_kwargs() -> dict:
36
+ kw = dict(headless=os.getenv("HEADLESS", "1") != "0", humanize=True, geoip=False)
37
+ # Point camoufox at YOUR fork's binary. If unset, camoufox uses its default
38
+ # cached binary (`python -m camoufox fetch`).
39
+ binary = os.getenv("CAMOUFOX_BINARY") or os.getenv("CAMOUFOX_EXECUTABLE_PATH")
40
+ if binary:
41
+ kw["executable_path"] = binary
42
+ return kw
43
+
44
+
45
+ def _capture_challenge(page, spec: DemoSpec) -> str:
46
+ """Reach the challenge iframe and screenshot it to a temp PNG. Returns the path.
47
+
48
+ Raises RuntimeError with a human message if the challenge never appears.
49
+ """
50
+ out = tempfile.NamedTemporaryFile(suffix=".png", delete=False).name
51
+
52
+ if spec.vendor == "recaptcha":
53
+ anchor = page.wait_for_selector('iframe[src*="/recaptcha/api2/anchor"]', timeout=30_000)
54
+ frame = anchor.content_frame()
55
+ frame.click("#recaptcha-anchor", timeout=15_000)
56
+ challenge = page.wait_for_selector('iframe[src*="/recaptcha/api2/bframe"]', timeout=30_000)
57
+ # Let the tiles paint.
58
+ page.wait_for_timeout(2500)
59
+ challenge.screenshot(path=out)
60
+ return out
61
+
62
+ if spec.vendor == "hcaptcha":
63
+ box = page.wait_for_selector('iframe[src*="hcaptcha.com"][src*="checkbox"], iframe[title*="checkbox"]',
64
+ timeout=30_000)
65
+ box.content_frame().click("#checkbox, div[role=checkbox]", timeout=15_000)
66
+ challenge = page.wait_for_selector('iframe[title*="hCaptcha challenge"], iframe[src*="hcaptcha.com"][src*="frame=challenge"]',
67
+ timeout=30_000)
68
+ page.wait_for_timeout(2500)
69
+ challenge.screenshot(path=out)
70
+ return out
71
+
72
+ raise RuntimeError(f"unknown vendor {spec.vendor!r}")
73
+
74
+
75
+ def _tokens_per_sec(solver: CaptchaSolver, elapsed_s: float) -> tuple[int, int, float]:
76
+ """(input_tokens, output_tokens, tokens/sec) from the planner's usage log."""
77
+ inp = out = 0
78
+ for u in getattr(solver.planner, "token_usage", []) or []:
79
+ inp += int(u.get("prompt_tokens", 0) or 0)
80
+ out += int(u.get("completion_tokens", 0) or 0)
81
+ tps = out / elapsed_s if out and elapsed_s > 0 else 0.0
82
+ return inp, out, tps
83
+
84
+
85
+ def _explain(vendor: str, err: Exception) -> str:
86
+ msg = str(err).lower()
87
+ if isinstance(err, UnsupportedCaptchaError) or "cannot solve" in msg or "unsupported" in msg:
88
+ if vendor == "hcaptcha":
89
+ return ("hCaptcha served a non-grid challenge (drag / video / choose-the-card). "
90
+ "The engine handles image grids + checkboxes only — re-run to try for a grid.")
91
+ return "Not a supported grid/checkbox challenge — re-run to try again."
92
+ if "vllm" in msg or "connection" in msg or "refused" in msg or "max retries" in msg:
93
+ return ("Could not reach the vLLM server — is it up and is VLLM_BASE_URL correct? "
94
+ "(A local server auto-starts only if captchakraken[serve] is installed.)")
95
+ if "timeout" in msg or "wait_for_selector" in msg:
96
+ return "The challenge iframe never appeared (slow network, blocked widget, or the checkbox auto-passed)."
97
+ return str(err) or "Unknown failure."
98
+
99
+
100
+ def run_demo(spec: DemoSpec) -> None:
101
+ t0 = time.time()
102
+ ok = False
103
+ reason = None
104
+ inp = out = 0
105
+ tps = 0.0
106
+ solve_s = 0.0
107
+ plan_desc = "-"
108
+
109
+ try:
110
+ with Camoufox(**_launch_kwargs()) as browser:
111
+ page = browser.new_page()
112
+ page.goto(spec.url, wait_until="domcontentloaded", timeout=60_000)
113
+ shot = _capture_challenge(page, spec)
114
+
115
+ solver = CaptchaSolver()
116
+ s0 = time.time()
117
+ try:
118
+ actions = solver.solve(shot, puzzle_source=spec.vendor)
119
+ finally:
120
+ solve_s = time.time() - s0
121
+ inp, out, tps = _tokens_per_sec(solver, solve_s)
122
+
123
+ acts = actions if isinstance(actions, list) else [actions]
124
+ clicks = [a for a in acts if isinstance(a, ClickAction) and (a.target_bounding_boxes or [])]
125
+ n_tiles = sum(len(a.target_bounding_boxes or []) for a in clicks)
126
+ if n_tiles > 0:
127
+ ok = True
128
+ plan_desc = f"click plan: {n_tiles} tile(s)/target(s)"
129
+ else:
130
+ plan_desc = f"actions: {[type(a).__name__ for a in acts]}"
131
+ reason = ("The model returned no tiles to click — either none matched the prompt, "
132
+ "or the challenge frame wasn't a clean grid. Re-run to try again.")
133
+ except Exception as err: # noqa: BLE001 — demo: report, don't traceback
134
+ reason = _explain(spec.vendor, err)
135
+
136
+ _report(spec, ok=ok, total_s=time.time() - t0, solve_s=solve_s,
137
+ inp=inp, out=out, tps=tps, plan=plan_desc, reason=reason)
138
+
139
+
140
+ def _fmt(s: float) -> str:
141
+ return f"{s:.1f}s" if s >= 1 else f"{int(s * 1000)}ms"
142
+
143
+
144
+ def _report(spec, *, ok, total_s, solve_s, inp, out, tps, plan, reason):
145
+ line = "─" * 52
146
+ print(f"\n{line}")
147
+ print(f" CaptchaKraken demo (engine) — {spec.name}")
148
+ print(f" {spec.url}")
149
+ print(line)
150
+ print(f" result : {'✓ engine produced a solution' if ok else '✗ no solution'}")
151
+ print(f" {plan}")
152
+ print(f" total time : {_fmt(total_s)} (solve: {_fmt(solve_s)})")
153
+ print(f" tokens : {inp} in / {out} out")
154
+ print(f" gen speed : {f'~{tps:.1f} tok/s' if tps > 0 else 'n/a'}")
155
+ if reason:
156
+ print(f" reason : {reason}")
157
+ print(f"{line}\n")
158
+ raise SystemExit(0 if ok else 1)
@@ -0,0 +1,20 @@
1
+ """
2
+ Engine demo: run CaptchaKraken on the standard hCaptcha demo page.
3
+
4
+ pip install -e ".[serve]" # engine + serving stack (or [.] against a remote server)
5
+ pip install camoufox && python -m camoufox fetch # or set CAMOUFOX_BINARY to your fork binary
6
+ source ../captchakraken.env # VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY
7
+ python examples/demoHcaptcha.py
8
+
9
+ Note: hCaptcha randomly serves non-grid puzzles (drag / video / choose-the-card),
10
+ which the engine does not handle yet — the report says so; re-run for a grid.
11
+ """
12
+
13
+ from _harness import DemoSpec, run_demo
14
+
15
+ if __name__ == "__main__":
16
+ run_demo(DemoSpec(
17
+ name="hCaptcha",
18
+ url="https://accounts.hcaptcha.com/demo",
19
+ vendor="hcaptcha",
20
+ ))
@@ -0,0 +1,17 @@
1
+ """
2
+ Engine demo: run CaptchaKraken on Google's standard reCAPTCHA v2 demo page.
3
+
4
+ pip install -e ".[serve]" # engine + serving stack (or [.] against a remote server)
5
+ pip install camoufox && python -m camoufox fetch # or set CAMOUFOX_BINARY to your fork binary
6
+ source ../captchakraken.env # VLLM_BASE_URL + CAPTCHA_KRAKEN_API_KEY
7
+ python examples/demoRecaptcha.py
8
+ """
9
+
10
+ from _harness import DemoSpec, run_demo
11
+
12
+ if __name__ == "__main__":
13
+ run_demo(DemoSpec(
14
+ name="reCAPTCHA v2",
15
+ url="https://www.google.com/recaptcha/api2/demo",
16
+ vendor="recaptcha",
17
+ ))
@@ -0,0 +1,79 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "captchakraken"
7
+ version = "2.0.0"
8
+ description = "Self-hosted captcha solver: OpenCV grid detection + a fine-tuned Qwen3.5-9B vision LoRA served on vLLM."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "GPL-3.0-or-later" }
12
+ authors = [{ name = "Jake Writer" }]
13
+ keywords = ["captcha", "recaptcha", "hcaptcha", "vllm", "qwen", "computer-vision", "automation"]
14
+ classifiers = [
15
+ "Programming Language :: Python :: 3",
16
+ "Operating System :: OS Independent",
17
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
18
+ ]
19
+
20
+ # Core = the lightweight CLIENT: OpenCV grid detection + the HTTP planner that
21
+ # talks to a vLLM server (local or remote). This is all you need to SOLVE
22
+ # captchas against an existing endpoint. The heavy serving stack (vllm/torch)
23
+ # is deliberately NOT here — see the `serve` extra.
24
+ dependencies = [
25
+ "pydantic>=2.0.0",
26
+ "pillow>=10.0.0",
27
+ "numpy>=1.24.0",
28
+ "opencv-python-headless>=4.10.0",
29
+ "requests>=2.31.0",
30
+ "python-dotenv>=1.0.0",
31
+ ]
32
+
33
+ [project.optional-dependencies]
34
+ # Self-hosting: install this to RUN a local vLLM server (the setup script does
35
+ # `pip install "captchakraken[serve]"`). Not needed to call a remote server.
36
+ serve = [
37
+ "vllm>=0.6.3",
38
+ "torch>=2.0.0",
39
+ "transformers>=4.40.0",
40
+ "accelerate>=0.27.0",
41
+ "huggingface_hub>=0.23.0",
42
+ ]
43
+ dev = [
44
+ "pytest>=7.0.0",
45
+ "mypy>=1.0.0",
46
+ "ruff>=0.1.0",
47
+ ]
48
+
49
+ [project.scripts]
50
+ captchakraken = "captchakraken.cli:main"
51
+
52
+ [project.urls]
53
+ Homepage = "https://github.com/JWriter20/CaptchaKraken"
54
+ Issues = "https://github.com/JWriter20/CaptchaKraken/issues"
55
+
56
+ [tool.hatch.build.targets.wheel]
57
+ packages = ["src/captchakraken"]
58
+
59
+ [tool.pytest.ini_options]
60
+ testpaths = ["tests"]
61
+ pythonpath = ["src"]
62
+
63
+ [tool.mypy]
64
+ python_version = "3.10"
65
+ warn_return_any = false
66
+ warn_unused_configs = true
67
+ disallow_untyped_defs = false
68
+ check_untyped_defs = false
69
+ ignore_missing_imports = true
70
+ no_implicit_optional = false
71
+ exclude = ["build/", "dist/"]
72
+
73
+ [tool.ruff]
74
+ line-length = 120
75
+ target-version = "py310"
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E", "F", "W", "I"]
79
+ ignore = ["E501"]
@@ -0,0 +1,61 @@
1
+ """
2
+ CaptchaKraken — OpenCV grid detection + a fine-tuned Qwen3.5-9B vision LoRA
3
+ served on vLLM.
4
+
5
+ Usage:
6
+ from captchakraken import CaptchaSolver
7
+ solver = CaptchaSolver() # auto-starts / connects to a local vLLM server
8
+ actions = solver.solve("captcha.png")
9
+
10
+ Model/endpoint defaults live in `captchakraken.config` and are fully
11
+ env-overridable (VLLM_BASE_URL, CAPTCHA_LORA_ADAPTER, …); the solver itself is
12
+ model-agnostic. The legacy v1 stack (SAM3 grounding, multi-provider planner,
13
+ detect/segment/drag-refine) lives on the `v1-old-architecture` branch.
14
+ """
15
+
16
+ from pathlib import Path
17
+
18
+ try: # pragma: no cover
19
+ from dotenv import load_dotenv
20
+
21
+ project_root = Path(__file__).resolve().parent.parent
22
+ load_dotenv(project_root / ".env")
23
+ except Exception:
24
+ pass
25
+
26
+ from .action_types import (
27
+ CaptchaAction,
28
+ ClickAction,
29
+ DragAction,
30
+ TypeAction,
31
+ WaitAction,
32
+ )
33
+ from .image_processor import ImageProcessor
34
+ from .overlay import add_overlays_to_image
35
+
36
+ # The planner (requests) and solver (torch/vllm/transformers) pull in the heavy
37
+ # serving stack. Keep them optional so leaf modules — e.g. tool_calls.find_grid,
38
+ # which needs only cv2 + numpy + pillow — can be imported in a minimal env (CI's
39
+ # hermetic grid-detection test) without the full GPU dependency set installed.
40
+ try: # pragma: no cover - exercised only when the serving stack is installed
41
+ from .planner import ActionPlanner
42
+ from .solver import CaptchaSolver, solve_captcha
43
+ except ModuleNotFoundError:
44
+ ActionPlanner = None # type: ignore[assignment,misc]
45
+ CaptchaSolver = None # type: ignore[assignment,misc]
46
+ solve_captcha = None # type: ignore[assignment]
47
+
48
+ __all__ = [
49
+ "CaptchaSolver",
50
+ "solve_captcha",
51
+ "ActionPlanner",
52
+ "ImageProcessor",
53
+ "CaptchaAction",
54
+ "ClickAction",
55
+ "DragAction",
56
+ "TypeAction",
57
+ "WaitAction",
58
+ "add_overlays_to_image",
59
+ ]
60
+
61
+ __version__ = "2.0.0"