# Clicker Moves the real desktop mouse and clicks an element you name with a CSS selector. The extension can see the DOM but can't move the mouse; this script can move the mouse but can't see inside Chrome. They meet at the dashboard API: ``` clicker --POST /api/autobuyer/locate------> "where is button.buy?" extension --POST /api/autobuyer/locate/claim takes the request extension raises the Chrome window, brings the tab to the front, scrolls the element to centre, measures it extension --POST /api/autobuyer/locate/result desktop x,y clicker --GET /api/autobuyer/locate?id=---> reads the answer clicker moves the mouse, clicks ``` ## Setup ```bash pip install -r requirements.txt ``` On **macOS** you must grant Accessibility permission to whatever runs the script (Terminal, iTerm, VS Code) — System Settings → Privacy & Security → Accessibility. Without it `pyautogui` moves nothing and fails silently. ## Use The AutoBuyer switch on the dashboard is the master arm: the script refuses to run while it's off. ```bash # Measure only — no mouse movement. Start here. python clicker.py locate "button.buy" # Move the cursor to the target but don't press. python clicker.py click "button.buy" --dry-run # Actually click. python clicker.py click "button.buy" # Pin it to a specific tab, and pick the 3rd match. python clicker.py click ".trade-btn" --index 2 --url "https://tradeify.co/*" ``` | Flag | Meaning | |---|---| | `--url` | Chrome match pattern for the tab. Without it, the active tab is used. | | `--index` | Which match, when the selector hits several (default 0). | | `--api` | Dashboard URL (default `http://localhost:3000`). | | `--timeout` | Seconds to wait for the extension (default 20). | | `--scale` | CSS-to-desktop pixel ratio. Auto-detected; override if clicks land off. | | `--dry-run` | Move the cursor, don't press. | | `--force` | Click even when something is covering the element. | | `--robotic` | Straight-line move and instant click, skipping the motion model. | | `--seed` | Seed the motion RNG so a run replays identically (debugging). | | `--no-activate` | Don't raise the browser first. The click may then be swallowed. | ## Window focus A click on a window that isn't focused is consumed by the window manager *activating* that window — it never reaches the control underneath. That's why an automated click against a background Chrome appears to do nothing the first time and work the second: the first click only brought Chrome forward. The extension calls `chrome.windows.update({focused: true})`, but that only orders windows **within** Chrome. If the frontmost *application* is your terminal — which it is, since that's where you launched this — Chrome is still in the background. So `focus.py` raises the browser application itself immediately before the press, then confirms it actually came forward before committing to the click. If it can't verify, it refuses rather than firing a click that would be eaten (exit code 3). On macOS this uses `NSWorkspace` via pyobjc, which needs no Automation permission — activating an app is not scripting it. Without pyobjc it falls back to `osascript`, which does prompt for Automation permission the first time. ## Cursor motion `humanize.py` moves the pointer the way a hand does rather than teleporting: - a curved (cubic Bézier) path instead of a straight line, bowing to one side - eased velocity — accelerate out, coast, decelerate in - sub-pixel tremor that decays near the target, so the landing stays exact - long throws (>260px) sometimes overshoot slightly and pull back - a 60–170ms dwell after arriving, before the press - the button held down 55–120ms rather than an instant down/up This is about reliability as much as appearance. Plenty of web controls only arm once they have actually been hovered — dropdowns, tooltip-gated buttons, custom widgets — and a cursor that arrives and presses in the same tick can outrun the page's own `mousemove` handlers. The dwell is what lets those catch up. Note `pyautogui.PAUSE` is set to 0 on import: it otherwise sleeps 0.1s after *every* call, which would add tens of seconds across a stepped path. Exit codes: `0` ok, `1` error, `2` capture switch off, `3` refused to click (covered element, or coordinates off-screen). ## Safety - **Failsafe**: slam the cursor into a screen corner to abort mid-run. - **Covered elements**: before reporting, the extension checks `document.elementFromPoint()` at the target centre. If a cookie banner or modal is on top, the click is refused rather than sent into the overlay — `--force` overrides. - **Off-screen check**: coordinates outside the display bounds are refused, which catches a Chrome window on a second monitor or partly off the edge. - The element is scrolled to the centre of the viewport before measuring, so a target below the fold is handled rather than mis-clicked. ## Known limits - **Latency** is bounded by the extension's poll interval (default 3s), so a click takes a few seconds to fire. Drop the interval in the extension popup if that matters. - **Multi-monitor**: coordinates come from `window.screenX/screenY`, which are relative to the primary display's origin. A Chrome window on a secondary monitor usually still works, but verify with `locate` before trusting `click`. - **Display scaling**: the scale factor is inferred by comparing the screen width the OS reports against the one the browser reports. On macOS and unscaled Windows this is 1:1. If clicks land at a consistent offset, set `--scale` explicitly. - The measurement and the click are separate moments. If the page moves the element in between (a re-render, a late-loading banner), the click lands where it *was*.