captchakraken 2.9.0 → 3.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 (77) hide show
  1. package/AGENTS.md +112 -0
  2. package/NOTICE +36 -0
  3. package/dist/cli-invocation.d.ts +7 -36
  4. package/dist/cli-invocation.js +8 -49
  5. package/dist/errors.d.ts +7 -27
  6. package/dist/errors.js +6 -25
  7. package/dist/humanize.d.ts +56 -240
  8. package/dist/humanize.js +159 -415
  9. package/dist/index.d.ts +5 -129
  10. package/dist/index.js +4 -98
  11. package/dist/kinds.d.ts +135 -0
  12. package/dist/kinds.js +70 -0
  13. package/dist/limits.d.ts +0 -48
  14. package/dist/limits.js +2 -48
  15. package/dist/model-name.d.ts +4 -19
  16. package/dist/model-name.js +32 -40
  17. package/dist/playwright-types.d.ts +25 -95
  18. package/dist/playwright-types.js +0 -23
  19. package/dist/puppeteer-adapter.d.ts +4 -43
  20. package/dist/puppeteer-adapter.js +22 -58
  21. package/dist/python-command.d.ts +0 -41
  22. package/dist/python-command.js +0 -37
  23. package/dist/selectors.d.ts +54 -0
  24. package/dist/selectors.js +117 -0
  25. package/dist/slide-geometry.d.ts +2 -29
  26. package/dist/slide-geometry.js +18 -44
  27. package/dist/solver.d.ts +131 -708
  28. package/dist/solver.js +1319 -3382
  29. package/dist/timing.d.ts +7 -40
  30. package/dist/timing.js +4 -36
  31. package/dist/token-usage.d.ts +1 -7
  32. package/dist/token-usage.js +10 -95
  33. package/dist/types.d.ts +77 -493
  34. package/dist/watcher.d.ts +5 -78
  35. package/dist/watcher.js +0 -56
  36. package/package.json +11 -4
  37. package/python/AGENTS.md +122 -0
  38. package/python/NOTICE +36 -0
  39. package/python/README.md +1 -2
  40. package/python/examples/README.md +10 -0
  41. package/python/examples/_harness.py +4 -73
  42. package/python/examples/demoHcaptcha.py +0 -13
  43. package/python/examples/demoRecaptcha.py +0 -10
  44. package/python/examples/watch_playwright.py +1 -26
  45. package/python/examples/with_local_gguf.py +28 -0
  46. package/python/examples/with_playwright.py +0 -19
  47. package/python/pyproject.toml +68 -19
  48. package/python/src/captchakraken/__init__.py +37 -45
  49. package/python/src/captchakraken/action_types.py +14 -36
  50. package/python/src/captchakraken/cli.py +262 -883
  51. package/python/src/captchakraken/config.py +60 -146
  52. package/python/src/captchakraken/errors.py +17 -95
  53. package/python/src/captchakraken/humanize.py +174 -597
  54. package/python/src/captchakraken/image_processor.py +16 -235
  55. package/python/src/captchakraken/keyframes.py +46 -484
  56. package/python/src/captchakraken/kinds.py +152 -0
  57. package/python/src/captchakraken/models.json +36 -4
  58. package/python/src/captchakraken/overlay.py +65 -498
  59. package/python/src/captchakraken/page_solver.py +1282 -3101
  60. package/python/src/captchakraken/planner.py +65 -487
  61. package/python/src/captchakraken/prompts.py +45 -261
  62. package/python/src/captchakraken/selectors.py +184 -0
  63. package/python/src/captchakraken/server_manager.py +7 -58
  64. package/python/src/captchakraken/solver.py +128 -522
  65. package/python/src/captchakraken/timing.py +20 -74
  66. package/python/src/captchakraken/tool_calls/board_painted.py +47 -0
  67. package/python/src/captchakraken/tool_calls/find_checkbox.py +20 -30
  68. package/python/src/captchakraken/tool_calls/find_grid.py +186 -1339
  69. package/python/src/captchakraken/tool_calls/track_piece.py +81 -57
  70. package/python/src/captchakraken/updater.py +7 -76
  71. package/python/src/captchakraken/watcher.py +12 -101
  72. package/scripts/copy-python.mjs +10 -10
  73. package/scripts/setup-python.js +1 -21
  74. package/dist/trajectory.d.ts +0 -58
  75. package/dist/trajectory.js +0 -266
  76. package/python/src/captchakraken/tool_calls/move_indicator.py +0 -431
  77. package/python/src/captchakraken/trajectory.py +0 -284
@@ -1,284 +0,0 @@
1
- """
2
- Human-like mouse trajectories for the page driver.
3
-
4
- The Python side of `js/src/trajectory.ts`; the two are one implementation in
5
- two languages, pinned by `test_trajectory_parity.py`.
6
-
7
- The CONTRACT is identical, which is what matters for keeping the two drivers in
8
- step: given a start point, an end point and a sampling frequency, return
9
- `(points, timings)` where `timings[i]` is the cumulative milliseconds from the
10
- start of the gesture at which `points[i]` should be delivered. The caller
11
- (`page_solver`) sleeps against those cumulative timings exactly as the TS driver
12
- does, so movement pacing is driver-independent.
13
-
14
- What makes the output human rather than a lerp:
15
- - a Bezier arc, not a straight line (hand motion bows)
16
- - Fitts's-law duration: distance and precision set the time, not a constant
17
- - an ease-in-out velocity profile, so the cursor accelerates then brakes
18
- - sub-pixel jitter that scales with speed (fast motion is sloppier)
19
- - a small overshoot-and-correct on longer moves, which is the single most
20
- recognisable human tell and the one a linear interpolation never produces
21
-
22
- `generate_swipe` is the SAME contract for a FINGER, and it is a different model
23
- rather than the mouse model with different constants — see its docstring.
24
- """
25
-
26
- from __future__ import annotations
27
-
28
- import math
29
- import random
30
- from typing import List, Sequence, Tuple
31
-
32
- Point = Tuple[float, float]
33
-
34
- # Fitts's law: MT = a + b * log2(distance / width + 1). The constants are the
35
- # usual empirical range for a mouse; `_WIDTH` stands in for target precision.
36
- _FITTS_A_MS = 90.0
37
- _FITTS_B_MS = 105.0
38
- _WIDTH_PX = 28.0
39
-
40
- # Below this, a move is a nudge inside one element: no overshoot, no arc.
41
- _SHORT_MOVE_PX = 24.0
42
- # Overshooting a 30px hop looks like a twitch, not a human.
43
- _OVERSHOOT_MIN_DISTANCE_PX = 220.0
44
-
45
-
46
- def _ease_in_out(t: float) -> float:
47
- """Cubic ease. Real pointer motion is roughly bell-shaped in velocity."""
48
- if t < 0.5:
49
- return 4.0 * t * t * t
50
- return 1.0 - ((-2.0 * t + 2.0) ** 3) / 2.0
51
-
52
-
53
- def _cubic_bezier(p0: Point, p1: Point, p2: Point, p3: Point, t: float) -> Point:
54
- u = 1.0 - t
55
- x = u * u * u * p0[0] + 3 * u * u * t * p1[0] + 3 * u * t * t * p2[0] + t * t * t * p3[0]
56
- y = u * u * u * p0[1] + 3 * u * u * t * p1[1] + 3 * u * t * t * p2[1] + t * t * t * p3[1]
57
- return (x, y)
58
-
59
-
60
- def _control_points(start: Point, end: Point, distance: float) -> Tuple[Point, Point]:
61
- """
62
- Two control points offset PERPENDICULAR to the travel direction, so the path
63
- bows to one side. The sign is random per gesture: always bowing the same way
64
- would itself be a fingerprint.
65
- """
66
- dx, dy = end[0] - start[0], end[1] - start[1]
67
- if distance == 0:
68
- return start, end
69
- # Unit normal to the direction of travel.
70
- nx, ny = -dy / distance, dx / distance
71
- # Bow proportional to distance, but capped — long drags don't arc absurdly.
72
- bow = min(distance * random.uniform(0.06, 0.16), 140.0) * random.choice((-1.0, 1.0))
73
- # Control points at roughly 1/3 and 2/3 along, each pushed off-axis.
74
- c1 = (
75
- start[0] + dx * random.uniform(0.20, 0.40) + nx * bow,
76
- start[1] + dy * random.uniform(0.20, 0.40) + ny * bow,
77
- )
78
- c2 = (
79
- start[0] + dx * random.uniform(0.60, 0.80) + nx * bow * random.uniform(0.4, 0.9),
80
- start[1] + dy * random.uniform(0.60, 0.80) + ny * bow * random.uniform(0.4, 0.9),
81
- )
82
- return c1, c2
83
-
84
-
85
- def generate_trajectory(
86
- target_start: Sequence[float],
87
- target_end: Sequence[float],
88
- frequency: int = 60,
89
- frequency_randomizer: float = 0.12,
90
- ) -> Tuple[List[Point], List[float]]:
91
- """
92
- Returns `(points, timings)`. `timings` is CUMULATIVE milliseconds from the
93
- gesture start — not per-step deltas — because that is what the TS driver's
94
- `tracePath` sleeps against, and the two must pace identically.
95
-
96
- `frequency` is samples per second (the TS driver passes 60).
97
- `frequency_randomizer` jitters each frame's timestamp so the sample interval
98
- is not a metronome; a perfectly periodic mousemove stream is trivially
99
- detectable.
100
- """
101
- start: Point = (float(target_start[0]), float(target_start[1]))
102
- end: Point = (float(target_end[0]), float(target_end[1]))
103
-
104
- dx, dy = end[0] - start[0], end[1] - start[1]
105
- distance = math.hypot(dx, dy)
106
-
107
- if distance < 1e-6:
108
- return [end], [0.0]
109
-
110
- duration_ms = _FITTS_A_MS + _FITTS_B_MS * math.log2(distance / _WIDTH_PX + 1.0)
111
- duration_ms *= random.uniform(0.85, 1.20)
112
-
113
- steps = max(2, int(round(duration_ms / 1000.0 * max(1, frequency))))
114
-
115
- # A short hop is a straight ease with jitter — bowing a 15px move looks worse
116
- # than not bowing it.
117
- if distance < _SHORT_MOVE_PX:
118
- c1, c2 = start, end
119
- else:
120
- c1, c2 = _control_points(start, end, distance)
121
-
122
- # Overshoot: on a long move the hand arrives past the target and corrects.
123
- # Modelled as a longer primary gesture to a point beyond the target, then a
124
- # short settle back — the settle is appended as extra samples below.
125
- overshoot = distance >= _OVERSHOOT_MIN_DISTANCE_PX and random.random() < 0.55
126
- aim: Point = end
127
- if overshoot:
128
- over = random.uniform(0.01, 0.035) * distance
129
- aim = (end[0] + dx / distance * over, end[1] + dy / distance * over)
130
-
131
- points: List[Point] = []
132
- timings: List[float] = []
133
- elapsed = 0.0
134
- per_step = duration_ms / steps
135
-
136
- for i in range(steps + 1):
137
- t = _ease_in_out(i / steps)
138
- px, py = _cubic_bezier(start, c1, c2, aim, t)
139
-
140
- # Jitter scales with instantaneous speed: the faster the cursor is
141
- # moving, the less precisely a human tracks the intended path.
142
- speed = abs(_ease_in_out(min(1.0, (i + 1) / steps)) - _ease_in_out(i / steps))
143
- jitter = min(1.6, 0.25 + speed * steps * 0.5)
144
- px += random.gauss(0.0, jitter * 0.5)
145
- py += random.gauss(0.0, jitter * 0.5)
146
-
147
- points.append((px, py))
148
- timings.append(elapsed)
149
- elapsed += per_step * random.uniform(1.0 - frequency_randomizer, 1.0 + frequency_randomizer)
150
-
151
- if overshoot:
152
- # Correction: a couple of small samples back onto the true target, at the
153
- # slower pace of a deliberate fine adjustment.
154
- settle_steps = random.randint(2, 4)
155
- origin = points[-1]
156
- for i in range(1, settle_steps + 1):
157
- t = i / settle_steps
158
- points.append(
159
- (
160
- origin[0] + (end[0] - origin[0]) * t + random.gauss(0.0, 0.35),
161
- origin[1] + (end[1] - origin[1]) * t + random.gauss(0.0, 0.35),
162
- )
163
- )
164
- timings.append(elapsed)
165
- elapsed += random.uniform(14.0, 30.0)
166
-
167
- # Land exactly on the requested pixel. Everything above is texture; the final
168
- # sample must be the point the caller asked for or clicks drift off-target.
169
- points[-1] = end
170
- return points, timings
171
-
172
-
173
- # ── Touch ───────────────────────────────────────────────────────────────────
174
- #
175
- # Fitts's law holds for direct touch too, but with a slower intercept and a much
176
- # wider effective target: a fingertip contact patch is ~9mm across, which is the
177
- # 44pt / 48dp minimum both platform guidelines are built around. So the same
178
- # distance takes longer AND is aimed less precisely than with a mouse.
179
- _TOUCH_FITTS_A_MS = 160.0
180
- _TOUCH_FITTS_B_MS = 135.0
181
- _TOUCH_WIDTH_PX = 44.0
182
-
183
- # How flick-like the launch is. 0 would be the mouse's symmetric bell; 1 would
184
- # be an instantaneous jump at t=0. See `_ease_touch`.
185
- _TOUCH_LAUNCH = 0.7
186
-
187
- # A finger drags across glass on a short wrist/thumb pivot, so it bows far less
188
- # than a hand moving a mouse across a desk.
189
- _TOUCH_BOW_CAP_PX = 40.0
190
-
191
- # The reported contact point wanders, because it is the CENTROID of a soft patch
192
- # rolling under pressure rather than a rigid sensor. `_TOUCH_WOBBLE_DECAY` is the
193
- # AR(1) coefficient that makes that wander low-frequency: white per-sample noise
194
- # would show up in a spectrum as nothing a finger produces.
195
- _TOUCH_WOBBLE_PX = 0.55
196
- _TOUCH_WOBBLE_DECAY = 0.82
197
-
198
-
199
- def _ease_touch(t: float) -> float:
200
- """Asymmetric ease: a finger leaves fast and brakes late.
201
-
202
- A mouse hand is symmetric (`_ease_in_out` — accelerate, decelerate, equally).
203
- A finger is not: the launch is a flick off the contact point and the arrival
204
- is a brake. Blending the two rather than using a pure ease-out is what keeps
205
- the launch from being an instantaneous jump at t=0, which no digitizer would
206
- ever report.
207
- """
208
- return (1.0 - _TOUCH_LAUNCH) * _ease_in_out(t) + _TOUCH_LAUNCH * (1.0 - (1.0 - t) ** 2.4)
209
-
210
-
211
- def generate_swipe(
212
- target_start: Sequence[float],
213
- target_end: Sequence[float],
214
- frequency: int = 90,
215
- frequency_randomizer: float = 0.10,
216
- ) -> Tuple[List[Point], List[float]]:
217
- """A FINGER travelling from `target_start` to `target_end`, same contract as
218
- `generate_trajectory`: `(points, timings)`, timings cumulative from 0, last
219
- point exactly `target_end`.
220
-
221
- A separate model rather than `generate_trajectory` with other constants,
222
- because the three things that make the mouse output human are each WRONG
223
- here:
224
-
225
- - **No overshoot-and-correct.** That tell is a hand arriving past a target
226
- it cannot see under the cursor. A finger occludes its own target and
227
- commits; the correction, when there is one, is a second gesture.
228
- - **A different velocity profile.** `_ease_touch`, not `_ease_in_out` —
229
- see above.
230
- - **Different jitter.** The mouse model adds white noise scaled by speed
231
- (a hand tracking a path imprecisely). A digitizer instead reports a
232
- wandering centroid, which is low-frequency and roughly speed
233
- INDEPENDENT, so this is a smoothed random walk.
234
-
235
- `frequency` defaults to 90: digitizers sample at 120Hz+, but touchmove is
236
- coalesced to the compositor, so more samples than this buys nothing and
237
- costs a dispatch round-trip each.
238
- """
239
- start: Point = (float(target_start[0]), float(target_start[1]))
240
- end: Point = (float(target_end[0]), float(target_end[1]))
241
-
242
- dx, dy = end[0] - start[0], end[1] - start[1]
243
- distance = math.hypot(dx, dy)
244
-
245
- if distance < 1e-6:
246
- return [end], [0.0]
247
-
248
- duration_ms = _TOUCH_FITTS_A_MS + _TOUCH_FITTS_B_MS * math.log2(
249
- distance / _TOUCH_WIDTH_PX + 1.0
250
- )
251
- duration_ms *= random.uniform(0.85, 1.20)
252
-
253
- steps = max(2, int(round(duration_ms / 1000.0 * max(1, frequency))))
254
-
255
- if distance < _SHORT_MOVE_PX:
256
- c1, c2 = start, end
257
- else:
258
- c1, c2 = _control_points(start, end, distance)
259
- # Pull the mouse model's bow in: `_control_points` is shared, and its
260
- # arc is sized for a hand crossing a desk.
261
- shrink = min(1.0, _TOUCH_BOW_CAP_PX / max(_TOUCH_BOW_CAP_PX, distance * 0.16))
262
- c1 = (start[0] + (c1[0] - start[0]) * shrink, start[1] + (c1[1] - start[1]) * shrink)
263
- c2 = (start[0] + (c2[0] - start[0]) * shrink, start[1] + (c2[1] - start[1]) * shrink)
264
-
265
- points: List[Point] = []
266
- timings: List[float] = []
267
- elapsed = 0.0
268
- per_step = duration_ms / steps
269
- wob_x = wob_y = 0.0
270
-
271
- for i in range(steps + 1):
272
- t = _ease_touch(i / steps)
273
- px, py = _cubic_bezier(start, c1, c2, end, t)
274
-
275
- wob_x = wob_x * _TOUCH_WOBBLE_DECAY + random.gauss(0.0, _TOUCH_WOBBLE_PX)
276
- wob_y = wob_y * _TOUCH_WOBBLE_DECAY + random.gauss(0.0, _TOUCH_WOBBLE_PX)
277
-
278
- points.append((px + wob_x, py + wob_y))
279
- timings.append(elapsed)
280
- elapsed += per_step * random.uniform(1.0 - frequency_randomizer, 1.0 + frequency_randomizer)
281
-
282
- # Land exactly where the caller asked, same reason as the mouse model.
283
- points[-1] = end
284
- return points, timings