captchakraken 2.6.1 → 2.8.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.
- package/README.md +90 -7
- package/dist/cli-invocation.d.ts +2 -0
- package/dist/cli-invocation.js +5 -1
- package/dist/errors.d.ts +1 -1
- package/dist/humanize.d.ts +377 -0
- package/dist/humanize.js +716 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +44 -1
- package/dist/playwright-types.d.ts +18 -0
- package/dist/solver.d.ts +112 -5
- package/dist/solver.js +527 -174
- package/dist/timing.d.ts +48 -0
- package/dist/timing.js +103 -0
- package/dist/trajectory.d.ts +27 -0
- package/dist/trajectory.js +104 -0
- package/dist/types.d.ts +99 -0
- package/package.json +1 -1
- package/python/README.md +43 -0
- package/python/pyproject.toml +1 -1
- package/python/src/captchakraken/__init__.py +36 -1
- package/python/src/captchakraken/cli.py +62 -4
- package/python/src/captchakraken/config.py +28 -2
- package/python/src/captchakraken/errors.py +24 -0
- package/python/src/captchakraken/humanize.py +929 -0
- package/python/src/captchakraken/keyframes.py +99 -0
- package/python/src/captchakraken/models.json +149 -13
- package/python/src/captchakraken/page_solver.py +476 -270
- package/python/src/captchakraken/pinned_model.json +3 -2
- package/python/src/captchakraken/planner.py +118 -9
- package/python/src/captchakraken/prompts.py +182 -0
- package/python/src/captchakraken/server_manager.py +9 -4
- package/python/src/captchakraken/solver.py +97 -57
- package/python/src/captchakraken/timing.py +8 -3
- package/python/src/captchakraken/tool_calls/find_grid.py +357 -20
- package/python/src/captchakraken/trajectory.py +117 -0
- package/python/src/captchakraken/updater.py +62 -1
|
@@ -75,6 +75,53 @@ def _pip_upgrade_cmd() -> "list[str]":
|
|
|
75
75
|
return [sys.executable, "-m", "pip", "install", "--upgrade", *ENGINE_PACKAGES]
|
|
76
76
|
|
|
77
77
|
|
|
78
|
+
class LicensedModelError(RuntimeError):
|
|
79
|
+
"""Raised when `fetch` is pointed at weights that are not downloadable."""
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _refuse_licensed(repo_id: str) -> None:
|
|
83
|
+
"""A licensed model has no Hub repo. Say so, instead of 404ing at the Hub.
|
|
84
|
+
|
|
85
|
+
Without this the failure is `RepositoryNotFoundError` from huggingface_hub,
|
|
86
|
+
which reads as "you are not logged in" or "typo" — so the next thing a
|
|
87
|
+
self-hoster does is hunt for a token that will never exist. It is also the
|
|
88
|
+
one place a licensed model's name can plausibly be typed by accident:
|
|
89
|
+
CAPTCHA_LORA_ADAPTER takes any string and `fetch` hands it straight to the
|
|
90
|
+
Hub.
|
|
91
|
+
|
|
92
|
+
A `private` model is NOT refused here, and that asymmetry is the point.
|
|
93
|
+
Its repo exists; the 401 an unauthorised puller gets is the true answer,
|
|
94
|
+
and pre-empting it with a refusal would stop the holder of an authorised
|
|
95
|
+
token from fetching weights they are entitled to. `_auth_hint` covers the
|
|
96
|
+
part that IS worth saying in advance.
|
|
97
|
+
"""
|
|
98
|
+
from . import prompts
|
|
99
|
+
|
|
100
|
+
if not prompts.is_licensed(repo_id):
|
|
101
|
+
return
|
|
102
|
+
raise LicensedModelError(
|
|
103
|
+
f"{repo_id} is a LICENSED model: its weights are not published and "
|
|
104
|
+
"there is nothing at that Hub id to download.\n"
|
|
105
|
+
" Reach it through the hosted API (https://api.captchakraken.com), "
|
|
106
|
+
"which needs no weights at all,\n"
|
|
107
|
+
" or ask us about a self-hosting licence: https://captchakraken.com/contact\n"
|
|
108
|
+
" To fetch a downloadable model instead, unset CAPTCHA_LORA_ADAPTER "
|
|
109
|
+
"or point it at a published one."
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _needs_auth(*repo_ids: "str | None") -> "list[str]":
|
|
114
|
+
"""Which of these repos are registered `availability: private`.
|
|
115
|
+
|
|
116
|
+
Reported by `plan()` so `--dry-run` says "this one needs a token" BEFORE
|
|
117
|
+
the download, rather than leaving a 401 to be read as a typo. Same reason
|
|
118
|
+
the licensed refusal lives in `plan()` and not at the download call.
|
|
119
|
+
"""
|
|
120
|
+
from . import prompts
|
|
121
|
+
|
|
122
|
+
return [r for r in repo_ids if r and prompts.requires_auth(r)]
|
|
123
|
+
|
|
124
|
+
|
|
78
125
|
def plan(
|
|
79
126
|
*,
|
|
80
127
|
weights: bool = True,
|
|
@@ -84,11 +131,21 @@ def plan(
|
|
|
84
131
|
lora: "str | None" = None,
|
|
85
132
|
) -> dict:
|
|
86
133
|
"""Assemble the fetch plan WITHOUT running anything. Pure + side-effect-free
|
|
87
|
-
so `--dry-run` and the tests can assert exactly what a real run would do.
|
|
134
|
+
so `--dry-run` and the tests can assert exactly what a real run would do.
|
|
135
|
+
|
|
136
|
+
Raises `LicensedModelError` rather than planning a download that cannot
|
|
137
|
+
succeed. Refusing HERE and not at the download call is deliberate: `plan()`
|
|
138
|
+
is what `--dry-run` prints, so the refusal is visible before anyone runs the
|
|
139
|
+
real thing, and there is exactly one place to keep it correct.
|
|
140
|
+
"""
|
|
88
141
|
base_model = base or config.base_model()
|
|
89
142
|
lora_adapter = lora or config.lora_adapter()
|
|
90
143
|
base_url = config.base_url()
|
|
91
144
|
|
|
145
|
+
if weights:
|
|
146
|
+
_refuse_licensed(lora_adapter)
|
|
147
|
+
_refuse_licensed(base_model)
|
|
148
|
+
|
|
92
149
|
repos = [lora_adapter, base_model] if weights else []
|
|
93
150
|
return {
|
|
94
151
|
"weights": weights,
|
|
@@ -98,6 +155,7 @@ def plan(
|
|
|
98
155
|
"lora_adapter": lora_adapter,
|
|
99
156
|
"hf_org": "https://huggingface.co/CaptchaKraken",
|
|
100
157
|
"downloads": [_download_cmd(r) for r in repos],
|
|
158
|
+
"needs_auth": _needs_auth(*repos),
|
|
101
159
|
"engine_upgrade": _pip_upgrade_cmd() if engine else None,
|
|
102
160
|
"server": {"base_url": base_url, "local": _is_local(base_url)},
|
|
103
161
|
}
|
|
@@ -139,6 +197,9 @@ def fetch(
|
|
|
139
197
|
|
|
140
198
|
if weights:
|
|
141
199
|
_log(f"Pulling latest weights from {p['hf_org']} (+ base) …")
|
|
200
|
+
for repo in p["needs_auth"]:
|
|
201
|
+
_log(f"{repo} is a PRIVATE repo — this needs a HuggingFace token "
|
|
202
|
+
"authorised on it (`hf auth login`, or HF_TOKEN).")
|
|
142
203
|
for cmd in p["downloads"]:
|
|
143
204
|
_run(cmd)
|
|
144
205
|
|