{"_id":"@0xuhe/ghost-cursor","_rev":"2-9181b1e88e32fc90568e6644a83d9370","name":"@0xuhe/ghost-cursor","dist-tags":{"latest":"1.4.2-beta"},"versions":{"1.4.2":{"name":"@0xuhe/ghost-cursor","version":"1.4.2","keywords":["bezier-curve","mouse-movement","botting"],"author":{"name":"Xetera"},"license":"ISC","_id":"@0xuhe/ghost-cursor@1.4.2","maintainers":[{"name":"0xuhe","email":"hexu.garfield@gmail.com"}],"homepage":"https://github.com/Xetera/ghost-cursor#readme","bugs":{"url":"https://github.com/Xetera/ghost-cursor/issues"},"dist":{"shasum":"382fe8270f8cd3e2f7bd8ebaad7233f21b3950b5","tarball":"https://registry.npmjs.org/@0xuhe/ghost-cursor/-/ghost-cursor-1.4.2.tgz","fileCount":11,"integrity":"sha512-ZUqeO7k86bAYafNo4998QyfZyS5m2q91pJ+TE7F66Thhq+HHijqOMErx1sO+wJ0uv+VklBfDFOU0XtdhoWjekA==","signatures":[{"sig":"MEQCIDW7D7eU90X+gogCsN70XueiKg7AAmbX1f31QItIht6tAiBxa1vCJwPdApLUs+7pxj+4yhDc/adS7DJEeubqOszYqQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":155614},"jest":{"preset":"jest-puppeteer","verbose":true,"reporters":["default","github-actions"],"transform":{"^.+\\.(t|j)sx?$":"@swc/jest"},"modulePathIgnorePatterns":["./lib","./src/test.ts"]},"main":"lib/spoof.js","types":"lib/spoof.d.ts","exports":{".":{"types":"./lib/spoof.d.ts","default":"./lib/spoof.js"},"./playwright":{"types":"./lib/playwright.d.ts","default":"./lib/playwright.js"}},"gitHead":"709c6ef12055b946221c289c69b88f135a51446d","scripts":{"lint":"ts-standard --fix","test":"jest","build":"tsc -p tsconfig.build.json","debug":"ts-node src/__debug__/browser-debug.ts","test:playwright":"npx playwright test","debug:playwright":"ts-node src/__debug__/browser-debug-playwright.ts"},"_npmUser":{"name":"0xuhe","email":"hexu.garfield@gmail.com"},"repository":{"url":"git+https://github.com/Xetera/ghost-cursor.git","type":"git"},"_npmVersion":"10.9.4","description":"Move your mouse like a human in puppeteer or generate realistic movements on any 2D plane","directories":{},"_nodeVersion":"22.21.1","dependencies":{"debug":"^4.3.4","bezier-js":"^6.1.3","@types/bezier-js":"4"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.4.1","devDependencies":{"jest":"30","husky":"9","ts-node":"^10.9.2","@swc/core":"^1.2.194","@swc/jest":"^0.2.21","puppeteer":"24","playwright":"^1.58.2","typescript":"5","@types/jest":"30","ts-standard":"12","@types/debug":"^4.1.9","jest-puppeteer":"11","@playwright/test":"^1.58.2"},"peerDependencies":{"puppeteer":"*","playwright":"*","playwright-core":"*"},"peerDependenciesMeta":{"puppeteer":{"optional":true},"playwright":{"optional":true},"playwright-core":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ghost-cursor_1.4.2_1771769789603_0.3932444146874301","host":"s3://npm-registry-packages-npm-production"}},"1.4.2-beta":{"name":"@0xuhe/ghost-cursor","version":"1.4.2-beta","description":"Move your mouse like a human in puppeteer or generate realistic movements on any 2D plane","repository":{"type":"git","url":"git+https://github.com/Xetera/ghost-cursor.git"},"main":"lib/spoof.js","types":"lib/spoof.d.ts","exports":{".":{"types":"./lib/spoof.d.ts","default":"./lib/spoof.js"},"./playwright":{"types":"./lib/playwright.d.ts","default":"./lib/playwright.js"}},"scripts":{"build":"tsc -p tsconfig.build.json","debug":"ts-node src/__debug__/browser-debug.ts","debug:playwright":"ts-node src/__debug__/browser-debug-playwright.ts","lint":"ts-standard --fix","test":"jest","test:playwright":"npx playwright test"},"keywords":["bezier-curve","mouse-movement","botting"],"author":{"name":"Xetera"},"license":"ISC","peerDependencies":{"playwright-core":"*","puppeteer":"*"},"peerDependenciesMeta":{"puppeteer":{"optional":true},"playwright-core":{"optional":true}},"dependencies":{"@types/bezier-js":"4","bezier-js":"^6.1.3","debug":"^4.3.4"},"devDependencies":{"@swc/core":"^1.2.194","@swc/jest":"^0.2.21","@types/debug":"^4.1.9","@types/jest":"30","husky":"9","jest":"30","jest-puppeteer":"11","puppeteer":"24","ts-node":"^10.9.2","ts-standard":"12","typescript":"5"},"jest":{"verbose":true,"preset":"jest-puppeteer","modulePathIgnorePatterns":["./lib","./src/test.ts"],"reporters":["default","github-actions"],"transform":{"^.+\\.(t|j)sx?$":"@swc/jest"}},"packageManager":"pnpm@10.4.1","_id":"@0xuhe/ghost-cursor@1.4.2-beta","gitHead":"709c6ef12055b946221c289c69b88f135a51446d","bugs":{"url":"https://github.com/Xetera/ghost-cursor/issues"},"homepage":"https://github.com/Xetera/ghost-cursor#readme","_nodeVersion":"22.21.1","_npmVersion":"10.9.4","dist":{"integrity":"sha512-cNdlNDqXjk/2g1mMgzvHhJDwOKVJvYe8HRtxgHNMxwN7fTTGVjo+Vzw64DgN05OutgezPOpfmm5T9NrWvv8OLQ==","shasum":"a8e25e5c8043ea7fee81d178bce91a7fb54208c5","tarball":"https://registry.npmjs.org/@0xuhe/ghost-cursor/-/ghost-cursor-1.4.2-beta.tgz","fileCount":11,"unpackedSize":157035,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQC54ZKZUu7S7k+MRCNlly+IPItuS5RZEFynzcshsC7CzwIgQFnKWaieoMEnT2wm/BV3Ob44abHZXr9GWsMz6F6anlE="}]},"_npmUser":{"name":"0xuhe","email":"hexu.garfield@gmail.com"},"directories":{},"maintainers":[{"name":"0xuhe","email":"hexu.garfield@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ghost-cursor_1.4.2-beta_1771774301955_0.6007101113720008"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-22T14:16:29.466Z","modified":"2026-02-22T15:31:42.261Z","1.4.2":"2026-02-22T14:16:29.752Z","1.4.2-beta":"2026-02-22T15:31:42.131Z"},"bugs":{"url":"https://github.com/Xetera/ghost-cursor/issues"},"author":{"name":"Xetera"},"license":"ISC","homepage":"https://github.com/Xetera/ghost-cursor#readme","keywords":["bezier-curve","mouse-movement","botting"],"repository":{"type":"git","url":"git+https://github.com/Xetera/ghost-cursor.git"},"description":"Move your mouse like a human in puppeteer or generate realistic movements on any 2D plane","maintainers":[{"name":"0xuhe","email":"hexu.garfield@gmail.com"}],"readme":"# Ghost Cursor\n\n<img src=\"https://media2.giphy.com/media/26ufp2LYURTvL5PRS/giphy.gif\" width=\"100\" align=\"right\">\n\nGenerate realistic, human-like mouse movement data between coordinates or navigate between elements with puppeteer\nlike the definitely-not-robot you are.\n\n> Oh yeah? Could a robot do _**this?**_\n\n## Installation\n\n```sh\nyarn add ghost-cursor\n```\nor with npm\n```sh\nnpm install ghost-cursor\n```\n\n## Usage\nGenerating movement data between 2 coordinates.\n\n```js\nimport { path } from \"ghost-cursor\"\n\nconst from = { x: 100, y: 100 }\nconst to = { x: 600, y: 700 }\n\nconst route = path(from, to)\n\n/**\n * [\n *   { x: 100, y: 100 },\n *   { x: 108.75573501957051, y: 102.83608396351725 },\n *   { x: 117.54686481838543, y: 106.20019239793275 },\n *   { x: 126.3749821408895, y: 110.08364505509256 },\n *   { x: 135.24167973152743, y: 114.47776168684264 }\n *   ... and so on\n * ]\n */\n```\n\nGenerating movement data between 2 coordinates with timestamps.\n```js\nimport { path } from \"ghost-cursor\"\n\nconst from = { x: 100, y: 100 }\nconst to = { x: 600, y: 700 }\n\nconst route = path(from, to, { useTimestamps: true })\n\n/**\n * [\n *   { x: 100, y: 100, timestamp: 1711850430643 },\n *   { x: 114.78071695023473, y: 97.52340709495319, timestamp: 1711850430697 },\n *   { x: 129.1362373468682, y: 96.60141853603243, timestamp: 1711850430749 },\n *   { x: 143.09468422606352, y: 97.18676354029148, timestamp: 1711850430799 },\n *   { x: 156.68418062398405, y: 99.23217132478408, timestamp: 1711850430848 },\n *   ... and so on\n * ]\n */\n```\n\n\nUsage with puppeteer:\n\n```js\nimport { GhostCursor } from \"ghost-cursor\"\nimport puppeteer from \"puppeteer\"\n\nconst run = async (url) => {\n  const selector = \"#sign-up button\"\n  const browser = await puppeteer.launch({ headless: false });\n  const page = await browser.newPage()\n  const cursor = new GhostCursor(page)\n  await page.goto(url)\n  await page.waitForSelector(selector)\n  await cursor.click(selector)\n  // shorthand for\n  // await cursor.move(selector)\n  // await cursor.click()\n}\n```\n\n### Puppeteer-specific behavior\n* `cursor.move()` will automatically overshoot or slightly miss and re-adjust for elements that are too far away\nfrom the cursor's starting point.\n* When moving over objects, a random coordinate that's within the element will be selected instead of\nhovering over the exact center of the element.\n* The speed of the mouse will take the distance and the size of the element you're clicking on into account.\n\n<br>\n\n![ghost-cursor in action](https://cdn.discordapp.com/attachments/418699380833648644/664110683054538772/acc_gen.gif)\n\n> Ghost cursor in action on a form\n\n## Methods\n\n#### `new GhostCursor(page: puppeteer.Page, { start?: Vector, performRandomMoves?: boolean, defaultOptions?: DefaultOptions, visible?: boolean = false }): GhostCursor`\n\nCreates the ghost cursor that contains the action functions described below.\n\n- **page:** Puppeteer `page`.\n- **start (optional):** Cursor start position. Default is `{ x: 0, y: 0 }`.\n- **performRandomMoves (optional):** Initially perform random movements. Default is `false`.\n- **defaultOptions (optional):** Set custom default options for `click`, `move`, `moveTo`, and `randomMove` functions. Default values are described below.\n- **visible (optional):** Make the cursor visible, using `installMouseHelper()`. Default is `false`.\n \n#### `toggleRandomMove(random: boolean): void`\n\nToggles random mouse movements on or off.\n\n#### `click(selector?: string | ElementHandle, options?: ClickOptions): Promise<void>`\n\nSimulates a mouse click at the specified selector or element.\n\n- **selector (optional):** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for clicking. **Extends the `options` of the `move`, `scrollIntoView`, and `getElement` functions (below)**\n  - `hesitate (number):` Delay before initiating the click action in milliseconds. Default is `0`.\n  - `waitForClick (number):` Delay between mousedown and mouseup in milliseconds. Default is `0`.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `2000`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `button (MouseButton):` Mouse button to click. Default is `left`.\n  - `clickCount (number):` Number of times to click the button. Default is `1`.\n\n#### `move(selector: string | ElementHandle, options?: MoveOptions): Promise<void>`\n\nMoves the mouse to the specified selector or element.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for moving. **Extends the `options` of the `scrollIntoView` and `getElement` functions (below)**\n  - `paddingPercentage (number):` Percentage of padding to be added inside the element when determining the target point. Default is `0` (may move to anywhere within the element). `100` will always move to center of element.\n  - `destination (Vector):` Destination to move the cursor to, relative to the top-left corner of the element. If specified, `paddingPercentage` is not used. If not specified (default), destination is random point within the `paddingPercentage`.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.\n  - `maxTries (number):` Maximum number of attempts to mouse-over the element. Default is `10`.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `overshootThreshold (number):` Distance from current location to destination that triggers overshoot to occur. (Below this distance, no overshoot will occur). Default is `500`.\n\n#### `moveTo(destination: Vector, options?: MoveToOptions): Promise<void>`\n\nMoves the mouse to the specified destination point.\n\n- **destination:** An object with `x` and `y` coordinates representing the target position. For example, `{ x: 500, y: 300 }`.\n- **options (optional):** Additional options for moving.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `moveDelay (number):` Delay after moving the mouse in milliseconds. Default is `0`. If `randomizeMoveDelay=true`, delay is randomized from 0 to `moveDelay`.\n  - `randomizeMoveDelay (boolean):` Randomize delay between actions from `0` to `moveDelay`. Default is `true`.\n\n#### `moveBy(delta: Vector, options?: MoveToOptions): Promise<void>`\n\nMoves the mouse by a specified amount.\n\n- **delta:** An object with `x` and `y` coordinates representing the distance to move. For example, `{ x: 10, y: 20 }`.\n- **options (optional):** Additional options for moving. Same as `moveTo` options\n  \n#### `scrollIntoView(selector: string | ElementHandle, options?: ScrollIntoViewOptions) => Promise<void>`\n\nScrolls the element into view. If already in view, no scroll occurs.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options for scrolling. **Extends the `options` of the `getElement` and `scroll` functions (below)**\n  - `scrollSpeed (number):` Scroll speed (when scrolling occurs). 0 to 100. 100 is instant. Default is `100`.\n  - `scrollDelay (number):` Time to wait after scrolling (when scrolling occurs). Default is `200`.\n  - `inViewportMargin (number):` Margin (in px) to add around the element when ensuring it is in the viewport. Default is `0`.\n\n#### `scrollTo: (destination: Partial<Vector> | 'top' | 'bottom' | 'left' | 'right', options?: ScrollOptions) => Promise<void>`\n\nScrolls to the specified destination point.\n\n- **destination:** An object with `x` and `y` coordinates representing the target position. For example, `{ x: 500, y: 300 }`. Can also be `\"top\"` or `\"bottom\"`.\n- **options (optional):** Additional options for scrolling. **Extends the `options` of the `scroll` function (below)**\n\n#### `scroll: (delta: Partial<Vector>, options?: ScrollOptions) => Promise<void>`\n\nScrolls the page the distance set by `delta`.\n\n- **delta:** An object with `x` and `y` coordinates representing the distance to scroll from the current position.\n- **options (optional):** Additional options for scrolling.\n  - `scrollSpeed (number):` Scroll speed. 0 to 100. 100 is instant. Default is `100`.\n  - `scrollDelay (number):` Time to wait after scrolling. Default is `200`.\n\n#### `mouseDown / mouseUp: (options?: MouseButtonOptions) => Promise<void>`\n\nMouse button up or down.\n\n- **options (optional):** Additional options for mouse action.\n  - `button (MouseButton):` Mouse button to click. Default is `left`.\n  - `clickCount (number):` Number of times to click the button. Default is `1`.\n  \n#### `getElement(selector: string | ElementHandle, options?: GetElementOptions) => Promise<void>`\n\nGets the element via a selector. Can use an XPath.\n\n- **selector:** CSS selector or ElementHandle to identify the target element.\n- **options (optional):** Additional options.\n  - `waitForSelector (number):` Time to wait for the selector to appear in milliseconds. Default is to not wait for selector.\n\n#### `getLocation(): Vector`\n\nGet current location of the cursor.\n\n### Other Utility Methods\n\n#### `installMouseHelper(page: Page): Promise<void>`\n\nInstalls a mouse helper on the page, making the pointer visible. Gets executed in the `GhostCursor` initialization when passing `visible=true`. Use for debugging only.\n\n#### `getRandomPagePoint(page: Page): Promise<Vector>`\n\nGets a random point on the browser window.\n\n#### `path(start: Vector, end: Vector | BoundingBox, options?: number | PathOptions): Vector[] | TimedVector[]`\n\nGenerates a set of points for mouse movement between two coordinates.\n\n- **start:** Starting point of the movement.\n- **end:** Ending point (or bounding box) of the movement.\n- **options (optional):** Additional options for generating the path. Can also be a number which will set `spreadOverride`.\n  - `spreadOverride (number):` Override the spread of the generated path.\n  - `moveSpeed (number):` Speed of mouse movement. Default is random.\n  - `useTimestamps (boolean):` Generate timestamps for each point based on the trapezoidal rule.\n\n## How does it work\n\nBezier curves do almost all the work here. They let us create an infinite amount of curves between any 2 points we want\nand they look quite human-like. (At least moreso than alternatives like perlin or simplex noise)\n\n![](https://mamamoo.xetera.dev/😽🤵👲🧦👵.png)\n\nThe magic comes from being able to set multiple points for the curve to go through. This is done by picking\n2 coordinates randomly in a limited area above and under the curve. \n\n<img src=\"https://mamamoo.xetera.dev/🧣👎😠🧟✍.png\" width=\"400\">\n\nHowever, we don't want wonky looking cubic curves when using this method because nobody really moves their mouse\nthat way, so only one side of the line is picked when generating random points.\n\n<img src=\"http://simonwallner.at/ext/fitts/shannon.png\" width=\"250\" align=\"right\">\nWhen calculating how fast the mouse should be moving we use <a href=\"https://en.wikipedia.org/wiki/Fitts%27s_law\">Fitts's Law</a>\nto determine the amount of points we should be returning relative to the width of the element being clicked on and the distance\nbetween the mouse and the object.\n\n## To turn on logging, please set your DEBUG env variable like so:\n\n- OSX: `DEBUG=\"ghost-cursor:*\"`\n- Linux: `DEBUG=\"ghost-cursor:*\"`\n- Windows CMD: `set DEBUG=ghost-cursor:*`\n- Windows PowerShell: `$env:DEBUG = \"ghost-cursor:*\"`\n","readmeFilename":"README.md"}