captchakraken 2.2.0__tar.gz → 2.3.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 (51) hide show
  1. {captchakraken-2.2.0 → captchakraken-2.3.0}/.gitignore +4 -1
  2. captchakraken-2.3.0/LICENSE +105 -0
  3. captchakraken-2.3.0/PKG-INFO +210 -0
  4. {captchakraken-2.2.0 → captchakraken-2.3.0}/README.md +3 -1
  5. {captchakraken-2.2.0 → captchakraken-2.3.0}/pyproject.toml +6 -2
  6. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/__init__.py +13 -1
  7. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/cli.py +63 -0
  8. captchakraken-2.3.0/src/captchakraken/config.py +160 -0
  9. captchakraken-2.3.0/src/captchakraken/page_solver.py +1579 -0
  10. captchakraken-2.3.0/src/captchakraken/pinned_model.json +28 -0
  11. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/planner.py +248 -17
  12. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/server_manager.py +3 -2
  13. captchakraken-2.3.0/src/captchakraken/trajectory.py +172 -0
  14. captchakraken-2.3.0/src/captchakraken/updater.py +168 -0
  15. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_checkbox_captcha.py +8 -0
  16. captchakraken-2.3.0/tests/test_credentials_file.py +87 -0
  17. captchakraken-2.3.0/tests/test_fetch_command.py +91 -0
  18. captchakraken-2.3.0/tests/test_freshness_check.py +85 -0
  19. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_object_detection.py +8 -0
  20. captchakraken-2.3.0/tests/test_page_solver.py +563 -0
  21. captchakraken-2.3.0/tests/test_pinned_model.py +86 -0
  22. captchakraken-2.3.0/tests/test_pixel_parser.py +76 -0
  23. captchakraken-2.3.0/tests/test_routing_headers.py +104 -0
  24. captchakraken-2.3.0/tests/test_solver_contract.py +230 -0
  25. captchakraken-2.2.0/PKG-INFO +0 -102
  26. captchakraken-2.2.0/src/captchakraken/config.py +0 -78
  27. {captchakraken-2.2.0 → captchakraken-2.3.0}/Dockerfile +0 -0
  28. {captchakraken-2.2.0 → captchakraken-2.3.0}/examples/README.md +0 -0
  29. {captchakraken-2.2.0 → captchakraken-2.3.0}/examples/_harness.py +0 -0
  30. {captchakraken-2.2.0 → captchakraken-2.3.0}/examples/demoHcaptcha.py +0 -0
  31. {captchakraken-2.2.0 → captchakraken-2.3.0}/examples/demoRecaptcha.py +0 -0
  32. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/action_types.py +0 -0
  33. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/image_processor.py +0 -0
  34. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/overlay.py +0 -0
  35. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/planner_types.py +0 -0
  36. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/solver.py +0 -0
  37. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/timing.py +0 -0
  38. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/tool_calls/find_checkbox.py +0 -0
  39. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/tool_calls/find_grid.py +0 -0
  40. {captchakraken-2.2.0 → captchakraken-2.3.0}/src/captchakraken/tool_calls/move_indicator.py +0 -0
  41. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/conftest.py +0 -0
  42. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/grid_diag.py +0 -0
  43. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/grid_fp.py +0 -0
  44. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/grid_regression.py +0 -0
  45. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/grid_trace_reject.py +0 -0
  46. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_find_grid_corpus.py +0 -0
  47. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_grid_detection.py +0 -0
  48. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_grid_detection_ci.py +0 -0
  49. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_move_indicator.py +0 -0
  50. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_selected_fields.py +0 -0
  51. {captchakraken-2.2.0 → captchakraken-2.3.0}/tests/test_solver.py +0 -0
@@ -41,4 +41,7 @@ record_demos*.log
41
41
  run_full_record.out
42
42
  run_full_record.sh
43
43
  *_video_attempt_*.png
44
- captcha_videos/
44
+ captcha_videos/
45
+ # Personal VSCode workspace files (machine-specific; belong at a project
46
+ # root, not in the tree). One strayed into python/tests/.
47
+ *.code-workspace
@@ -0,0 +1,105 @@
1
+ CaptchaKraken Source-Available License v1.0
2
+ Copyright (c) 2026 JobHarvest / JWriter20. All rights reserved.
3
+
4
+ This license governs use of the CaptchaKraken software in this repository and of
5
+ the associated machine-learning model weights and LoRA adapters published by the
6
+ copyright holder (collectively, the "Software"). By using the Software you agree
7
+ to these terms. If you do not agree, do not use the Software.
8
+
9
+ ================================================================================
10
+ 1. GRANT
11
+ ================================================================================
12
+ Subject to the restrictions below, you are granted a worldwide, royalty-free,
13
+ non-exclusive license to use, copy, modify, and distribute the Software, and to
14
+ incorporate it into your own products and services.
15
+
16
+ ================================================================================
17
+ 2. PERMITTED USES
18
+ ================================================================================
19
+ You MAY use the Software for:
20
+
21
+ (a) Personal, non-commercial use, research, and education.
22
+
23
+ (b) Commercial use INSIDE a larger product or service that delivers
24
+ substantial value BEYOND captcha solving itself — that is, where captcha
25
+ solving is an internal, enabling component rather than the product.
26
+
27
+ Illustrative (non-exhaustive) examples of PERMITTED commercial use:
28
+ - Web scrapers and data-collection pipelines.
29
+ - Stealth / anti-detection browsers and browser automation frameworks
30
+ that use the Software as one internal capability.
31
+ - QA, testing, and accessibility tooling.
32
+ - Any application where the captcha solve is a means to an end the user
33
+ is actually paying for.
34
+
35
+ ================================================================================
36
+ 3. PROHIBITED USES
37
+ ================================================================================
38
+ You MAY NOT, without a separate written commercial agreement from the copyright
39
+ holder:
40
+
41
+ (a) Sell, resell, rent, or otherwise offer for a fee any captcha-SOLVING
42
+ service, product, or API whose primary value is solving captchas,
43
+ where that service is powered by the Software or its model outputs.
44
+
45
+ (b) Distribute "thin wrappers" around the Software whose primary purpose is
46
+ captcha solving — including but not limited to browser extensions,
47
+ hosted endpoints, SaaS products, or CLIs that simply expose the
48
+ Software's solving capability to end users.
49
+
50
+ (c) Expose, proxy, or relay the responses or outputs of the Software's LoRA
51
+ models (e.g. bounding boxes, tile selections, click plans) through a paid
52
+ or public API as a captcha-solving service.
53
+
54
+ In short: you may BUILD WITH this Software, but you may not SELL THE SOLVE.
55
+
56
+ ================================================================================
57
+ 4. MODEL OUTPUTS
58
+ ================================================================================
59
+ Outputs produced by the model weights/adapters are subject to Sections 2 and 3
60
+ to the same extent as the Software itself. Reselling or API-relaying those
61
+ outputs as a captcha-solving service is prohibited under Section 3(c).
62
+
63
+ ================================================================================
64
+ 5. REDISTRIBUTION
65
+ ================================================================================
66
+ If you redistribute the Software (modified or not), you must retain this license
67
+ and this notice, and you must not remove or alter the restrictions in Section 3.
68
+
69
+ ================================================================================
70
+ 6. TRADEMARKS
71
+ ================================================================================
72
+ This license does not grant permission to use the trade names, trademarks, or
73
+ product names of the copyright holder, except as required to describe the origin
74
+ of the Software.
75
+
76
+ ================================================================================
77
+ 7. NO WARRANTY
78
+ ================================================================================
79
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
80
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
81
+ FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
82
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
83
+ IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
84
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
85
+
86
+ ================================================================================
87
+ 8. RESPONSIBLE USE
88
+ ================================================================================
89
+ You are responsible for ensuring your use of the Software complies with all
90
+ applicable laws, the terms of service of any site you interact with, and
91
+ applicable anti-fraud and computer-access statutes. The copyright holder does
92
+ not endorse using the Software to violate any third party's terms or rights.
93
+
94
+ ================================================================================
95
+ 9. COMMERCIAL LICENSING
96
+ ================================================================================
97
+ For uses prohibited under Section 3 — including selling captcha-solving as a
98
+ service — contact the copyright holder to discuss a separate commercial license.
99
+
100
+ --------------------------------------------------------------------------------
101
+ This text is a license, not legal advice. If the boundary between a permitted
102
+ "value-add" product and a prohibited "thin wrapper" is unclear for your use
103
+ case, ask by opening an issue or messaging the maintainer on GitHub:
104
+ https://github.com/JWriter20/CaptchaKraken
105
+ --------------------------------------------------------------------------------
@@ -0,0 +1,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: captchakraken
3
+ Version: 2.3.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: CaptchaKraken Source-Available License v1.0
9
+ Copyright (c) 2026 JobHarvest / JWriter20. All rights reserved.
10
+
11
+ This license governs use of the CaptchaKraken software in this repository and of
12
+ the associated machine-learning model weights and LoRA adapters published by the
13
+ copyright holder (collectively, the "Software"). By using the Software you agree
14
+ to these terms. If you do not agree, do not use the Software.
15
+
16
+ ================================================================================
17
+ 1. GRANT
18
+ ================================================================================
19
+ Subject to the restrictions below, you are granted a worldwide, royalty-free,
20
+ non-exclusive license to use, copy, modify, and distribute the Software, and to
21
+ incorporate it into your own products and services.
22
+
23
+ ================================================================================
24
+ 2. PERMITTED USES
25
+ ================================================================================
26
+ You MAY use the Software for:
27
+
28
+ (a) Personal, non-commercial use, research, and education.
29
+
30
+ (b) Commercial use INSIDE a larger product or service that delivers
31
+ substantial value BEYOND captcha solving itself — that is, where captcha
32
+ solving is an internal, enabling component rather than the product.
33
+
34
+ Illustrative (non-exhaustive) examples of PERMITTED commercial use:
35
+ - Web scrapers and data-collection pipelines.
36
+ - Stealth / anti-detection browsers and browser automation frameworks
37
+ that use the Software as one internal capability.
38
+ - QA, testing, and accessibility tooling.
39
+ - Any application where the captcha solve is a means to an end the user
40
+ is actually paying for.
41
+
42
+ ================================================================================
43
+ 3. PROHIBITED USES
44
+ ================================================================================
45
+ You MAY NOT, without a separate written commercial agreement from the copyright
46
+ holder:
47
+
48
+ (a) Sell, resell, rent, or otherwise offer for a fee any captcha-SOLVING
49
+ service, product, or API whose primary value is solving captchas,
50
+ where that service is powered by the Software or its model outputs.
51
+
52
+ (b) Distribute "thin wrappers" around the Software whose primary purpose is
53
+ captcha solving — including but not limited to browser extensions,
54
+ hosted endpoints, SaaS products, or CLIs that simply expose the
55
+ Software's solving capability to end users.
56
+
57
+ (c) Expose, proxy, or relay the responses or outputs of the Software's LoRA
58
+ models (e.g. bounding boxes, tile selections, click plans) through a paid
59
+ or public API as a captcha-solving service.
60
+
61
+ In short: you may BUILD WITH this Software, but you may not SELL THE SOLVE.
62
+
63
+ ================================================================================
64
+ 4. MODEL OUTPUTS
65
+ ================================================================================
66
+ Outputs produced by the model weights/adapters are subject to Sections 2 and 3
67
+ to the same extent as the Software itself. Reselling or API-relaying those
68
+ outputs as a captcha-solving service is prohibited under Section 3(c).
69
+
70
+ ================================================================================
71
+ 5. REDISTRIBUTION
72
+ ================================================================================
73
+ If you redistribute the Software (modified or not), you must retain this license
74
+ and this notice, and you must not remove or alter the restrictions in Section 3.
75
+
76
+ ================================================================================
77
+ 6. TRADEMARKS
78
+ ================================================================================
79
+ This license does not grant permission to use the trade names, trademarks, or
80
+ product names of the copyright holder, except as required to describe the origin
81
+ of the Software.
82
+
83
+ ================================================================================
84
+ 7. NO WARRANTY
85
+ ================================================================================
86
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
88
+ FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
89
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER
90
+ IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
91
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
92
+
93
+ ================================================================================
94
+ 8. RESPONSIBLE USE
95
+ ================================================================================
96
+ You are responsible for ensuring your use of the Software complies with all
97
+ applicable laws, the terms of service of any site you interact with, and
98
+ applicable anti-fraud and computer-access statutes. The copyright holder does
99
+ not endorse using the Software to violate any third party's terms or rights.
100
+
101
+ ================================================================================
102
+ 9. COMMERCIAL LICENSING
103
+ ================================================================================
104
+ For uses prohibited under Section 3 — including selling captcha-solving as a
105
+ service — contact the copyright holder to discuss a separate commercial license.
106
+
107
+ --------------------------------------------------------------------------------
108
+ This text is a license, not legal advice. If the boundary between a permitted
109
+ "value-add" product and a prohibited "thin wrapper" is unclear for your use
110
+ case, ask by opening an issue or messaging the maintainer on GitHub:
111
+ https://github.com/JWriter20/CaptchaKraken
112
+ --------------------------------------------------------------------------------
113
+ License-File: LICENSE
114
+ Keywords: automation,captcha,computer-vision,hcaptcha,qwen,recaptcha,vllm
115
+ Classifier: License :: Other/Proprietary License
116
+ Classifier: Operating System :: OS Independent
117
+ Classifier: Programming Language :: Python :: 3
118
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
119
+ Requires-Python: >=3.10
120
+ Requires-Dist: numpy>=1.24.0
121
+ Requires-Dist: opencv-python-headless>=4.10.0
122
+ Requires-Dist: pillow>=10.0.0
123
+ Requires-Dist: pydantic>=2.0.0
124
+ Requires-Dist: python-dotenv>=1.0.0
125
+ Requires-Dist: requests>=2.31.0
126
+ Provides-Extra: dev
127
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
128
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
129
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
130
+ Provides-Extra: serve
131
+ Requires-Dist: accelerate>=0.27.0; extra == 'serve'
132
+ Requires-Dist: huggingface-hub>=0.23.0; extra == 'serve'
133
+ Requires-Dist: torch>=2.0.0; extra == 'serve'
134
+ Requires-Dist: transformers>=4.40.0; extra == 'serve'
135
+ Requires-Dist: vllm>=0.6.3; extra == 'serve'
136
+ Description-Content-Type: text/markdown
137
+
138
+ # captchakraken
139
+
140
+ The Python engine + CLI behind [CaptchaKraken](https://github.com/JWriter20/CaptchaKraken):
141
+ OpenCV grid detection + a fine-tuned **Qwen3.5-9B** vision LoRA served on
142
+ **vLLM**. Given a screenshot of a captcha grid, it locates the tiles and returns
143
+ the click plan. Ships the `captchakraken` command.
144
+
145
+ > For demo videos, accuracy numbers, the browser driver, and the full
146
+ > self-hosting guide, see the main repo
147
+ > **[CaptchaKraken](https://github.com/JWriter20/CaptchaKraken)**.
148
+
149
+ ## Install
150
+
151
+ ```bash
152
+ pip install captchakraken # client: OpenCV detection + vLLM HTTP planner
153
+ pip install "captchakraken[serve]" # + the serving stack (vLLM/torch) to self-host
154
+ ```
155
+
156
+ The base install is lightweight — everything you need to solve captchas against
157
+ a vLLM server (local or remote). The `[serve]` extra pulls the heavy stack only
158
+ if you want to run the model yourself. The one-command
159
+ [`setup.sh`](https://github.com/JWriter20/CaptchaKraken) installs `[serve]`,
160
+ downloads the weights, and writes an env file for you.
161
+
162
+ ## Hands-off server
163
+
164
+ The vLLM server is managed for you. On your first solve, if the configured
165
+ endpoint is **local** and nothing is listening, a server is started
166
+ automatically and reused. Point `VLLM_BASE_URL` at a server you already run to
167
+ skip local management entirely.
168
+
169
+ ```bash
170
+ captchakraken server start | stop | status | run
171
+ ```
172
+
173
+ ## Usage
174
+
175
+ ```bash
176
+ # Solve an image/video: classify → find_grid → plan. Prints the click actions.
177
+ captchakraken path/to/captcha.png
178
+ captchakraken path/to/captcha.png --puzzle-source hcaptcha
179
+ ```
180
+
181
+ ```python
182
+ from captchakraken import CaptchaSolver
183
+
184
+ solver = CaptchaSolver() # connects to / auto-starts a local vLLM
185
+ actions = solver.solve("captcha.png")
186
+ ```
187
+
188
+ Pure-OpenCV tool subcommands (no model): `find-grid`, `find-checkbox`,
189
+ `detect-selected`, `grid-cell-states`, `find-move`, `find-movable`, and a
190
+ persistent `serve` worker the browser driver polls.
191
+
192
+ ## Configuration (model-agnostic)
193
+
194
+ Everything model-specific lives in `captchakraken.config` and is env-overridable
195
+ — the solver never hard-codes a model.
196
+
197
+ | Variable | Meaning | Default |
198
+ |---|---|---|
199
+ | `VLLM_BASE_URL` | Inference endpoint | `http://localhost:8000/v1` |
200
+ | `CAPTCHA_KRAKEN_API_KEY` | Bearer token (`VLLM_API_KEY` also accepted) | `EMPTY` |
201
+ | `CAPTCHA_BASE_MODEL` | Base weights vLLM loads | `Qwen/Qwen3.5-9B` |
202
+ | `CAPTCHA_LORA_ADAPTER` | Captcha adapter (HF id or path) | `CaptchaKraken/CaptchaKraken_v1` |
203
+ | `CAPTCHA_LORA_NAME` | Served adapter name the client requests | `captcha` |
204
+ | `CAPTCHA_KRAKEN_AUTOSTART` | `0` disables local auto-start | `1` |
205
+
206
+ ## License
207
+
208
+ **CaptchaKraken Source-Available License v1.0** — see [LICENSE](./LICENSE).
209
+ Build *with* it (scrapers, stealth browsers, QA tooling); you may **not sell the
210
+ solve itself** or ship a thin wrapper (browser extension, hosted solving API).
@@ -68,4 +68,6 @@ Everything model-specific lives in `captchakraken.config` and is env-overridable
68
68
 
69
69
  ## License
70
70
 
71
- GPL-3.0-or-later.
71
+ **CaptchaKraken Source-Available License v1.0** — see [LICENSE](./LICENSE).
72
+ Build *with* it (scrapers, stealth browsers, QA tooling); you may **not sell the
73
+ solve itself** or ship a thin wrapper (browser extension, hosted solving API).
@@ -4,17 +4,21 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "captchakraken"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "Self-hosted captcha solver: OpenCV grid detection + a fine-tuned Qwen3.5-9B vision LoRA served on vLLM."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
11
- license = { text = "GPL-3.0-or-later" }
11
+ # Source-available, NOT GPL. The CaptchaKraken Source-Available License permits
12
+ # commercial use as an internal component but prohibits selling the solve itself
13
+ # or thin-wrapper resale — see LICENSE. (GPL-3.0 would wrongly permit reselling.)
14
+ license = { file = "LICENSE" }
12
15
  authors = [{ name = "Jake Writer" }]
13
16
  keywords = ["captcha", "recaptcha", "hcaptcha", "vllm", "qwen", "computer-vision", "automation"]
14
17
  classifiers = [
15
18
  "Programming Language :: Python :: 3",
16
19
  "Operating System :: OS Independent",
17
20
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
21
+ "License :: Other/Proprietary License",
18
22
  ]
19
23
 
20
24
  # Core = the lightweight CLIENT: OpenCV grid detection + the HTTP planner that
@@ -40,14 +40,26 @@ from .overlay import add_overlays_to_image
40
40
  try: # pragma: no cover - exercised only when the serving stack is installed
41
41
  from .planner import ActionPlanner
42
42
  from .solver import CaptchaSolver, solve_captcha
43
+
44
+ # The page driver sits behind the same guard: it imports `solver`, so it has
45
+ # the same dependency floor. It needs NO browser package of its own — the
46
+ # caller supplies the Playwright-compatible page (see page_solver's module
47
+ # docstring on why we duck-type rather than import one).
48
+ from .page_solver import PageSolver, SolveResult, solve_captcha_on_page
43
49
  except ModuleNotFoundError:
44
50
  ActionPlanner = None # type: ignore[assignment,misc]
45
51
  CaptchaSolver = None # type: ignore[assignment,misc]
46
52
  solve_captcha = None # type: ignore[assignment]
53
+ PageSolver = None # type: ignore[assignment,misc]
54
+ SolveResult = None # type: ignore[assignment,misc]
55
+ solve_captcha_on_page = None # type: ignore[assignment]
47
56
 
48
57
  __all__ = [
49
58
  "CaptchaSolver",
50
59
  "solve_captcha",
60
+ "PageSolver",
61
+ "SolveResult",
62
+ "solve_captcha_on_page",
51
63
  "ActionPlanner",
52
64
  "ImageProcessor",
53
65
  "CaptchaAction",
@@ -58,4 +70,4 @@ __all__ = [
58
70
  "add_overlays_to_image",
59
71
  ]
60
72
 
61
- __version__ = "2.0.0"
73
+ __version__ = "2.3.0"
@@ -42,11 +42,18 @@ Modes:
42
42
  these — a local server auto-starts on the first solve (disable with
43
43
  CAPTCHA_KRAKEN_AUTOSTART=0). Point VLLM_BASE_URL at your own server to
44
44
  skip local management entirely.
45
+
46
+ captchakraken fetch [--weights-only|--engine-only] [--no-restart] [--dry-run]
47
+ Update in one step: pull the latest CaptchaKraken model from the HF org
48
+ (https://huggingface.co/CaptchaKraken) AND upgrade the vLLM serving
49
+ stack, then restart a running local server so it takes effect. Use this
50
+ to get new model revisions + engine fixes without re-running setup.sh.
45
51
  """
46
52
 
47
53
  import argparse
48
54
  import json
49
55
  import os
56
+ import subprocess
50
57
  import sys
51
58
 
52
59
  from .solver import CaptchaSolver, UnsupportedCaptchaError
@@ -372,6 +379,7 @@ def _handle_serve() -> bool:
372
379
  find-grid {image} -> grid_boxes | null
373
380
  grid-cell-states {a, b} -> states | {grid: null}
374
381
  grid-cell-states-fixed {a, b, grid_boxes} -> states
382
+ check-movement {a, b, threshold?} -> {has_movement: bool}
375
383
 
376
384
  Unknown cmd / malformed line -> an {ok:false} response (the process keeps
377
385
  running). EOF on stdin ends the loop cleanly. All heavy detection delegates
@@ -396,6 +404,15 @@ def _handle_serve() -> bool:
396
404
  if not grid_boxes:
397
405
  raise ValueError("empty grid_boxes")
398
406
  return _compute_grid_cell_states(req["a"], req["b"], grid_boxes)
407
+ if cmd == "check-movement":
408
+ # Freshness check for the JS solver: did the captcha frame change
409
+ # (tiles faded in / refreshed) between the screenshot we sent the
410
+ # model and now? Same primitive the one-shot `check-movement` uses.
411
+ from .image_processor import ImageProcessor
412
+
413
+ threshold = float(req.get("threshold", 0.005))
414
+ moved = ImageProcessor.detect_movement(req["a"], req["b"], threshold)
415
+ return {"has_movement": bool(moved)}
399
416
  raise ValueError(f"unknown cmd: {cmd!r}")
400
417
 
401
418
  # Signal readiness so the JS side knows imports are done before it polls.
@@ -560,9 +577,55 @@ def _handle_server_commands() -> bool:
560
577
  return True
561
578
 
562
579
 
580
+ def _handle_fetch() -> bool:
581
+ """`captchakraken fetch` — pull the latest published weights from the HF org
582
+ and refresh the vLLM serving stack, restarting a running local server so the
583
+ update takes effect. See updater.fetch for the full behavior."""
584
+ if len(sys.argv) <= 1 or sys.argv[1] not in {"fetch", "update"}:
585
+ return False
586
+
587
+ flags = set(sys.argv[2:])
588
+ unknown = flags - {"--weights-only", "--engine-only", "--no-restart", "--dry-run"}
589
+ if unknown:
590
+ print(
591
+ json.dumps({"error": f"unknown fetch flag(s): {sorted(unknown)} "
592
+ "(use --weights-only|--engine-only|--no-restart|--dry-run)"}),
593
+ file=sys.stderr,
594
+ )
595
+ sys.exit(2)
596
+ if "--weights-only" in flags and "--engine-only" in flags:
597
+ print(json.dumps({"error": "--weights-only and --engine-only are mutually exclusive"}),
598
+ file=sys.stderr)
599
+ sys.exit(2)
600
+
601
+ from . import updater
602
+
603
+ weights = "--engine-only" not in flags
604
+ engine = "--weights-only" not in flags
605
+ try:
606
+ result = updater.fetch(
607
+ weights=weights,
608
+ engine=engine,
609
+ restart="--no-restart" not in flags,
610
+ dry_run="--dry-run" in flags,
611
+ )
612
+ print(json.dumps(result))
613
+ return True
614
+ except subprocess.CalledProcessError as e:
615
+ print(json.dumps({"error": f"fetch step failed (exit {e.returncode}): "
616
+ f"{' '.join(e.cmd) if isinstance(e.cmd, list) else e.cmd}"}),
617
+ file=sys.stderr)
618
+ sys.exit(1)
619
+ except Exception as e:
620
+ print(json.dumps({"error": str(e)}), file=sys.stderr)
621
+ sys.exit(1)
622
+
623
+
563
624
  def main():
564
625
  if _handle_server_commands():
565
626
  return
627
+ if _handle_fetch():
628
+ return
566
629
  if _handle_movement_commands():
567
630
  return
568
631
  if _handle_move_commands():
@@ -0,0 +1,160 @@
1
+ """
2
+ Central, model-agnostic configuration for CaptchaKraken.
3
+
4
+ Every model-specific value lives here and is overridable via environment
5
+ variables, so swapping the underlying model never requires touching the solver,
6
+ planner, or CLI. The defaults point at the published unified captcha LoRA, but
7
+ nothing in the codebase hard-codes a model name outside this module.
8
+
9
+ The two variables MOST users ever set:
10
+ VLLM_BASE_URL where inference requests go (local or a remote server)
11
+ CAPTCHA_KRAKEN_API_KEY bearer token for that server
12
+
13
+ Advanced / self-hosting overrides (only needed to change WHICH model is served):
14
+ CAPTCHA_BASE_MODEL base weights vLLM loads (HF id or local path)
15
+ CAPTCHA_LORA_ADAPTER LoRA applied on top (HF id or local path)
16
+ CAPTCHA_LORA_REVISION adapter git revision to serve
17
+ CAPTCHA_LORA_NAME served adapter name the client asks for as `model`
18
+
19
+ Defaults for the model-identity values come from `pinned_model.json` (see
20
+ `pinned()`), which records the model + prompt pair this release was validated
21
+ against.
22
+ VLLM_PORT, VLLM_GPU_MEMORY_UTILIZATION, VLLM_MAX_MODEL_LEN, VLLM_MAX_LORA_RANK
23
+ CAPTCHA_KRAKEN_AUTOSTART=0 disable auto-starting a local server on first use
24
+ """
25
+
26
+ import json
27
+ import os
28
+ from functools import lru_cache
29
+ from pathlib import Path
30
+ from typing import Any, Dict
31
+
32
+ _PINNED_MODEL_PATH = Path(__file__).with_name("pinned_model.json")
33
+
34
+
35
+ @lru_cache(maxsize=1)
36
+ def pinned() -> Dict[str, Any]:
37
+ """The pinned model manifest shipped with this release.
38
+
39
+ Defines the (base model, adapter, serving prompts) triple this version was
40
+ validated against. Env vars still override every value below — the manifest
41
+ supplies the DEFAULT and gives CI something to assert against, so an adapter
42
+ or prompt change is a reviewable diff instead of a silent drift.
43
+ """
44
+ with _PINNED_MODEL_PATH.open(encoding="utf-8") as f:
45
+ return json.load(f)
46
+
47
+
48
+ # ── Inference endpoint ──────────────────────────────────────────────────────
49
+ def base_url() -> str:
50
+ """OpenAI-compatible endpoint, e.g. http://localhost:8000/v1."""
51
+ return os.getenv("VLLM_BASE_URL", f"http://localhost:{port()}/v1")
52
+
53
+
54
+ def state_dir() -> Path:
55
+ """Shared state dir for the pidfile, lockfile, server log, and credentials."""
56
+ return Path(os.getenv("CAPTCHA_KRAKEN_STATE_DIR", str(Path.home() / ".captchakraken")))
57
+
58
+
59
+ def credentials_path() -> Path:
60
+ return state_dir() / "credentials"
61
+
62
+
63
+ def _key_from_credentials_file() -> str:
64
+ """Key written by the MCP server's signup flow (or a future `login` command).
65
+
66
+ Hosted-API onboarding writes the key to a 0600 file rather than returning it
67
+ through the agent transcript, so it never lands in an LLM context window.
68
+ Reading it here is what lets a solve authenticate with NO env vars set.
69
+
70
+ Deliberately tolerant of a bare token, an env-file line, or surrounding
71
+ quotes: the file is written by a *separate* tool, and a format mismatch here
72
+ would surface as a baffling 401 rather than an obvious parse error.
73
+ Returns "" whenever the file is missing, unreadable, or has no usable line.
74
+ """
75
+ try:
76
+ text = credentials_path().read_text(encoding="utf-8")
77
+ except OSError:
78
+ return ""
79
+
80
+ for line in text.splitlines():
81
+ line = line.strip()
82
+ if not line or line.startswith("#"):
83
+ continue
84
+ if "=" in line:
85
+ name, _, value = line.partition("=")
86
+ # An env-file with unrelated keys must not yield a bogus token.
87
+ if name.strip() not in {"CAPTCHA_KRAKEN_API_KEY", "VLLM_API_KEY"}:
88
+ continue
89
+ line = value.strip()
90
+ return line.strip("'\"")
91
+
92
+ return ""
93
+
94
+
95
+ def api_key() -> str:
96
+ """Bearer token, in precedence order.
97
+
98
+ Env first so an explicit override always wins, then the credentials file so
99
+ the MCP/hosted path needs no env plumbing at all, then "EMPTY" (which leaves
100
+ a self-hosted local vLLM open, as before).
101
+ """
102
+ return (
103
+ os.getenv("CAPTCHA_KRAKEN_API_KEY")
104
+ or os.getenv("VLLM_API_KEY")
105
+ or _key_from_credentials_file()
106
+ or "EMPTY"
107
+ )
108
+
109
+
110
+ # ── Model identity (all overridable — the CLI itself is model-agnostic) ──────
111
+ def base_model() -> str:
112
+ return os.getenv("CAPTCHA_BASE_MODEL", pinned()["base_model"])
113
+
114
+
115
+ def lora_adapter() -> str:
116
+ """HF repo id or local path of the captcha adapter served on top of the base."""
117
+ return os.getenv("CAPTCHA_LORA_ADAPTER", pinned()["lora_adapter"])
118
+
119
+
120
+ def lora_revision() -> str:
121
+ """Git revision of the adapter repo to serve — pinning this is what makes
122
+ 'the adapter this release was tested against' a reproducible statement."""
123
+ return os.getenv("CAPTCHA_LORA_REVISION", pinned()["lora_revision"])
124
+
125
+
126
+ def lora_name() -> str:
127
+ """The served LoRA name the client sends as the `model` field."""
128
+ return os.getenv("CAPTCHA_LORA_NAME", pinned()["lora_name"])
129
+
130
+
131
+ # ── Local vLLM server knobs ─────────────────────────────────────────────────
132
+ def port() -> int:
133
+ return int(os.getenv("VLLM_PORT", "8000"))
134
+
135
+
136
+ def gpu_memory_utilization() -> float:
137
+ return float(os.getenv("VLLM_GPU_MEMORY_UTILIZATION", "0.80"))
138
+
139
+
140
+ def max_model_len() -> int:
141
+ return int(os.getenv("VLLM_MAX_MODEL_LEN", "65536"))
142
+
143
+
144
+ def max_lora_rank() -> int:
145
+ return int(os.getenv("VLLM_MAX_LORA_RANK", "64"))
146
+
147
+
148
+ def autostart_enabled() -> bool:
149
+ """Whether to auto-launch a local server on first use (default on)."""
150
+ return os.getenv("CAPTCHA_KRAKEN_AUTOSTART", "1") != "0"
151
+
152
+
153
+ def extra_serve_args() -> list:
154
+ """Extra flags appended verbatim to `vllm serve` (space-separated).
155
+
156
+ Escape hatch for serving tweaks the config doesn't model directly — e.g.
157
+ `--enforce-eager` or a smaller `--max-num-seqs` to squeeze onto a tight GPU.
158
+ """
159
+ raw = os.getenv("VLLM_EXTRA_ARGS", "").strip()
160
+ return raw.split() if raw else []