captchakraken 2.6.1 → 2.9.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 (38) hide show
  1. package/README.md +90 -7
  2. package/dist/cli-invocation.d.ts +16 -16
  3. package/dist/cli-invocation.js +33 -21
  4. package/dist/errors.d.ts +1 -1
  5. package/dist/humanize.d.ts +377 -0
  6. package/dist/humanize.js +716 -0
  7. package/dist/index.d.ts +33 -0
  8. package/dist/index.js +44 -1
  9. package/dist/model-name.d.ts +1 -1
  10. package/dist/model-name.js +1 -1
  11. package/dist/playwright-types.d.ts +18 -0
  12. package/dist/solver.d.ts +314 -16
  13. package/dist/solver.js +1208 -278
  14. package/dist/timing.d.ts +48 -0
  15. package/dist/timing.js +103 -0
  16. package/dist/trajectory.d.ts +28 -1
  17. package/dist/trajectory.js +105 -1
  18. package/dist/types.d.ts +113 -2
  19. package/package.json +2 -1
  20. package/python/README.md +43 -0
  21. package/python/pyproject.toml +1 -1
  22. package/python/src/captchakraken/__init__.py +36 -1
  23. package/python/src/captchakraken/cli.py +66 -4
  24. package/python/src/captchakraken/config.py +28 -2
  25. package/python/src/captchakraken/errors.py +24 -0
  26. package/python/src/captchakraken/humanize.py +929 -0
  27. package/python/src/captchakraken/keyframes.py +184 -12
  28. package/python/src/captchakraken/models.json +180 -18
  29. package/python/src/captchakraken/page_solver.py +1132 -321
  30. package/python/src/captchakraken/pinned_model.json +3 -2
  31. package/python/src/captchakraken/planner.py +204 -16
  32. package/python/src/captchakraken/prompts.py +186 -4
  33. package/python/src/captchakraken/server_manager.py +9 -4
  34. package/python/src/captchakraken/solver.py +98 -58
  35. package/python/src/captchakraken/timing.py +8 -3
  36. package/python/src/captchakraken/tool_calls/find_grid.py +357 -20
  37. package/python/src/captchakraken/trajectory.py +117 -0
  38. 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