1f4459985aadd995e21b8156508fe3c8924c7d2d
11
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
1f4459985a |
Extension to 0.11.2
background.js changed with the widget-targeting fix, so the version has to move with it: the AutoBuyer page now shows the version each capture was sent by, and an unchanged number is how you tell a reload did not take. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
dfa706c7b7 |
Target the captcha widget the open modal is actually showing
Second reset still failed: token solved, callback reported as fired, confirm button never enabled. The button enabling is pure client-side state — the site's callback storing the token — so if it stays dark the LIVE callback never ran, whatever was reported. Picking the newest client was still a guess. There is a precise link. reCAPTCHA numbers its response fields by widget id: the first widget gets #g-recaptcha-response, the second #g-recaptcha-response-1. The field the open modal is showing therefore names the client holding its callback. Use that, fall back to newest-first when there is no id to go on. The run log now prints which field carried the token and which widgets were on the page, so the next failure is diagnosable from the log instead of by hypothesis: `filled 1 response field(s) ['g-recaptcha-response-1'], fired grecaptcha callback (widget 1)` / `page had widgets ['0','1'], live one looked like 1`. Also surfaces the extension version on the AutoBuyer page. The extension has always stamped it into every capture and nothing ever read it, so the README's claim that a version mismatch shows there was no way to check. Chrome runs the old service worker until an unpacked extension is reloaded by hand, which is exactly the state that makes a deployed fix look like a broken one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
2d53639c82 |
Hand the token to the live captcha widget, not a dead one
First live run reset one account, then failed on the second: the confirm button never enabled and the step reported it covered by a bare `div`. reCAPTCHA adds a client to ___grecaptcha_cfg.clients per rendered widget and removes none, so the second modal leaves two — and the callback walk fired the first it found, the one belonging to the modal that already closed. The token goes to a widget nobody is listening to, the live modal never hears it was solved, and its button stays dark. Iteration 1 worked only because there was one client to find. Now the newest wins: hook widgets are walked in reverse, clients sorted by descending key, and the count of candidates is reported so `2 widgets on the page, used the newest` says plainly when a page is stacking stale ones. Also from that run: - coveredBy printed `div` and nothing else, which diagnoses nothing. It only ever reported tag and id, and modal backdrops and captcha overlays carry neither. It now names classes too. - A covered target gets 8s to clear before the step fails. Most things over a button are on their way out — a backdrop mid-fade, an overlay closing. This does not weaken the check: still covered at the deadline still fails, and nothing is clicked while anything is on top of it. - The reset list's scroll container is confirmed as a class, so the class-or-id hedge is trimmed. The run reported it as `dashboard_body MuiBox-root mui-0`. Extension to 0.11.1 so the dashboard can confirm the reload took. Runner VERSION deliberately unchanged — the step vocabulary is the same, and a bump would raise the mismatch banner for a change that does not need it. The runner does still need restarting to pick up the covered-retry. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
159389be35 |
Solve captchas through the 2captcha API, not its extension
The two Tradeify automations clicked the 2captcha *extension's* solve button and waited for it to go green. That needed a second extension installed and signed in, put a real mouse click on a button whose markup is 2captcha's to change, and gave a run no way to tell "still solving" from "not installed". Replaced with one `solveCaptcha` step. No mouse: the extension reads the widget's sitekey out of the page, the dashboard buys a token from 2captcha, and the extension puts it in the response field and fires the page's own callback. captcha-hook.js is the part that earns its keep. A widget rendered programmatically puts nothing in the DOM — no sitekey, and no callback. Most pages only enable submit from that callback, so filling the hidden field alone leaves the page looking unsolved. The hook wraps turnstile/grecaptcha/hcaptcha `render` at document_start to hold on to what the page passed. Solves go through 2captcha's residential proxy. Its API rejects host names in proxyAddress, so na.proxy.2captcha.com is resolved to an IP first (cached); the gateway was confirmed to work addressed that way. Covers reCAPTCHA v2 (incl. invisible and Enterprise), hCaptcha and Turnstile. NOT reCAPTCHA v3: a v3 page carries an anchor iframe with k=, so it is detected as v2 and would be solved with the wrong task type. Everything else unsupported fails loudly instead. The API key and proxy credentials are hardcoded in the captcha route on request. Unlike the sqlite database, that file is in the repo, so they travel with it. TWOCAPTCHA_API_KEY / TWOCAPTCHA_PROXY and the settings rows still override; neither has a field on the Settings page. Runner VERSION and RUNNER_EXPECTED_VERSION both to 0.17.0 — they are a pair, and a mismatch shows as a red "restart your runner" banner that blames the wrong process. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
25289cb9b3 |
Let the master dashboard drive this instance; drop the AutoBuyer switch
Three changes that ride together because they touch the same surface. A control link to the master. lib/master-link.ts dials out to <master>/ws/instance and holds it open, so the dashboard can trigger a trade or queue an AutoBuyer run here. Commands call the same functions the local pages call — runTrade, startScheduler, queueRun — so nothing reaches the broker by a path the UI does not also use. Reports prefer the socket and fall back to the existing POST, which keeps older masters working unchanged. startMasterLink is idempotent, and that matters more than it looks: getClients() is lazy and starts the link, so the first command re-enters it from inside a command the live socket is waiting to ack. Tearing the socket down there loses the ack and reports a trade that actually executed as a failure — which invites pressing the button twice. queueRun is lifted out of the automations route so the socket and HTTP share one copy of the input clamping that decides how many times a purchase fires. Page capture loses its switch and gains a scope. It now runs every tick, but only against the active tab of the last-focused window that matches host_permissions — so it follows the page in front of you and stops when you switch away, where before it would fall back to any firm tab left open in the background. autobuyer_enabled is gone entirely. The AutoBuyer is ready whenever the runner is; a run happens because someone asked for one. That does mean a queued run executes as soon as clicker/runner.py claims it, with no second gate — stopping the runner is now the way to make a machine idle. Extension to 0.10.0; it needs a manual reload at chrome://extensions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3cc7ddcc5c |
Add scrollToLoad and an absent variant of waitFor
scrollToLoad walks a progressively-loading list to the bottom before the steps that act on its items run. Stopping is two-part: no new matches appeared AND the container was already pinned to the bottom — counting alone stops early on a slow fetch. Hitting the scroll cap is reported rather than passed off as done, so a later step never works quietly on a partial list. The scrolling element is usually not the window. Lists like this live in a div with its own overflow, and scrolling the document does nothing at all, so the step walks up from a matched item to the ancestor that actually scrolls — overflow allows it and there is more content than fits — with containerSelector to name one outright when the guess is wrong. Verified against a page whose document also scrolls, which is the case that tells the two apart: it found the inner div and pulled 12 items up to 60 in 7 scrolls. waitFor gains `absent`, for waiting on something to go rather than arrive — a modal closing after a reset. It only accepts a genuine "selector matched nothing"; an unreachable extension looks the same from a distance and would otherwise satisfy the gate for the wrong reason, sending the next iteration into a page that still has the modal open. The locate queue carries a free-form options blob now, so a new kind of request stops meaning a new column each time. Also fixes a missing comma in the reset flow that broke the build. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
3ac9fe060f |
Raise the browser the extension reported, and add a diagnostics script
focus.py raised the first browser in its list that happened to be running. On a machine with both Chrome and Edge installed that is a coin flip, and losing it is silent: coordinates measured from a tab in one browser, the click delivered into a window of the other. It presents as selectors failing for no reason. A VPS with both installed hit exactly this. The extension now reports which browser is hosting it, and that travels with the measurement, so the clicker raises the browser the coordinates actually came from. Asking for a browser that is not running now fails honestly instead of quietly raising a different one, and the verification step rejects the wrong browser coming forward. Chromium, Opera and Vivaldi are recognised alongside Chrome, Edge and Brave, on both platforms. diagnose.py answers the question a remote desktop makes hard: whether the mouse is really moving or the viewer simply is not drawing it. It moves the cursor and reads the position back from the OS, so the answer does not depend on anything being rendered, and it reports DPI mode, screen size, whether this is an RDP session, and whether the browser can be raised at all. Nothing is clicked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
65a1103cda |
Derive capture scope from host_permissions, drop the target URL setting
The popup's target URL pattern was doing two jobs. As a fallback for locate requests it is now dead — every step carries its firm's pattern. As the thing deciding which tab gets scraped it was still load-bearing: without it, capture falls back to whichever tab is active, which means scraping a banking or mail tab and storing it in the dashboard's database. So the setting goes, but the scoping moves to the manifest rather than disappearing. host_permissions already lists exactly the hosts this extension is allowed to read; capture now queries those (minus localhost, which is the dashboard mirroring its own output back). The two cannot drift apart, and adding a firm — which means adding its host to the manifest anyway — scopes capture without a second place to remember. With more than one firm open, capture prefers the active tab over the first match, so it follows attention rather than tab order. That case could not arise while a single pattern matched one site. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
22db6eae8d |
Add repeat blocks, per-run inputs, skipIfNotFound, and orphaned-run recovery
Repeat. A `repeat` block runs its steps several times, with the count either fixed in the config or taken from an input the user sets on the dashboard. The block is unrolled in resolveSteps before the runner sees it, so the runner needs no loop, the run's total step count stays honest, and every iteration appears in the log as its own line — a failure on the third purchase reads as "(3/5)" rather than as an indistinguishable repeat of the first. Counts are clamped server-side against the automation's declared min/max, and expansion is capped at 400 steps and three levels of nesting. Each iteration can be a purchase, so the number is not taken on trust from the client, and the confirmation dialog names it before anything runs. skipIfNotFound on a click or type step tolerates an element that is not on the page — a cookie banner, a modal that only sometimes appears. Only absence is tolerated. That distinction needed a new NotFoundError: previously a missing element, an unreachable dashboard, a missing tab and a covered button all surfaced as the same DashboardError, and skipping that whole class would mean a step quietly passing while the extension was down. Orphaned runs are now reaped. Only one run executes at a time, so a run left in 'running' when its runner went away blocked every future run — restarting the daemon mid-run deadlocked the queue, which is exactly what happened. The heartbeat decides: a runner that is gone, or up and reporting idle, is not driving that run whatever the status column says. Gated on the busy flag rather than elapsed time alone, since a run sitting in a waitFor gate or a sign-in wait can legitimately go minutes without progress. Lucid Trading is scaffolded with no automations yet. One match pattern covers both its hosts — `*.` matches the apex as well as subdomains, confirmed against a live tab. Its signed-out pattern is `//lucidtrading.com/` rather than `lucidtrading.com/dashboard`: the leading slashes anchor it to the start of the host, and without them the substring also matches dash.lucidtrading.com, which would abort every step while properly signed in. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
b748f95372 |
Add automation framework, typing, and runner for the autobuyer
Turns the autobuyer from a page scraper into something that acts. A dashboard button queues a run; a desktop process executes it against the real browser. lib/automations.ts — automations are declarative step lists nested inside the firm whose site they drive. Steps are click / type / wait / navigate, and they inherit the firm's tab pattern and URL, so one firm's automation can't act on another's tab. Adding a button means adding an entry here; the page renders buttons from the API and the runner receives steps from the server, so neither needs editing. Runs key on firm:automation — every firm will plausibly have its own "buy-accounts", and a bare id would resolve to the wrong one. clicker/runner.py — the daemon behind the buttons. Claims a queued run, works through the steps, reports each one back for the page's live log. Only one run executes at a time: two processes driving one physical mouse would interleave clicks. Heartbeats on its own thread, because a step can block for tens of seconds and folding the beat into the main loop would show the runner as offline in the middle of the run it was executing. clicker/actions.py — one implementation of the safety checks, shared by the CLI and the runner. Refuses to act when the element is covered by an overlay, when coordinates fall off-screen, when the browser can't be confirmed frontmost, or (for type) when the target isn't an editable field. Typing: uneven human cadence, and the field is read back afterwards and compared against what was typed — a field that never took focus fails silently and looks identical to success otherwise. Non-ASCII is rejected because pyautogui skips those characters without complaint, and newlines because Enter may submit the form. Typos are deliberately not simulated: a mistyped digit in a trading form is a real loss, and the correction is the part that can go wrong. Extension: opens the firm's page when no tab matches, navigates to a specific page for a navigate step (skipped when already there, so page state survives), and retries the locate while a freshly loaded React app mounts — `complete` only means the document loaded. Staleness reporting, after it cost three debugging rounds: Chrome doesn't reload an unpacked extension and Python doesn't reload a running process, so both now report their version. A stale runner gets a red banner naming both versions and the automation buttons are disabled, rather than failing mid-run on a step type it predates. Scale detection is now conservative: a raw OS/browser width ratio is only trusted when it lands on a real scaling factor. On this multi-monitor desktop the previous logic would have silently halved every coordinate. Verified end to end against the live browser: navigate, locate, and a real click (run #12, all three steps). API round-trips, claim-once semantics, run cancellation, the heartbeat online/offline lifecycle, motion geometry and timing, focus activation, and typing verification all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
54221bbc0c |
Add autobuyer page capture, browser extension, and desktop clicker
Builds the pipeline the autobuyer needs: see the page, find an element, click it. extension/ — MV3 Chromium extension. Polls /api/autobuyer/status and, while on, scrapes the target tab's HTML and posts it back. Also serves locate requests: focuses the window, scrolls the element into view, and reports its position. host_permissions is scoped to tradeify plus localhost so it cannot read other sites — an empty target pattern would otherwise capture whatever tab happened to be active, including banking or mail. app/api/autobuyer/ — status toggle, capture store, and the locate request queue. CORS is open because the extension's origin changes every time an unpacked extension is reloaded. app/autobuyer/page.tsx — ON switch, source view (default) and a rendered view. The render uses sandbox="allow-scripts" without allow-same-origin: the page's own JS is needed because sites ship content at opacity:0 and fade it in, but the frame must not reach the dashboard's same-origin API routes, which serve firm credentials. clicker/ — Python CLI. Asks the extension where a selector is, adds the element rect to the window's screen position and the browser chrome height to get desktop coordinates, then clicks with a human motion model (curved path, eased velocity, occasional overshoot, dwell before press). Raises the browser application first, since macOS consumes a click on an unfocused window rather than delivering it. Refuses to click when the element is covered by an overlay, when the coordinates fall off-screen, or when the browser cannot be confirmed frontmost. Verified: API round-trips, capture pruning, locate claim-once semantics, motion geometry and timing, and focus activation — the last two against stubs, since pyautogui and pyobjc are not installed here. NOT verified end to end: Chrome is still running a stale build of the extension, so a locate request has never completed against a real page and no real click has been sent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |