npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

in-page-bot

v0.1.1

Published

In-page virtual pointer + semantic command scheduler (synthetic events)

Readme

in-page-bot

In-page virtual pointer controller and semantic command scheduler.

Maintains a virtual pointer inside an already-open page, dispatches Pointer / Mouse / Wheel / Keyboard / Input events from semantic commands, supports single-step or serial batch execution, and reports success or failure per step.

Does: in-page synthetic input (dispatchEvent), trajectory moves, click, wheel, typing, serial scheduling.
Does not: screenshots / a11y / business sensing, Chrome extension / MCP / WebSocket bridges, CDP / OS-level real input. Does not claim isTrusted === true.

Live demo: https://in-page-bot.pages.dev — full public API showcase on Cloudflare Pages.

Install

npm install in-page-bot

Local development (before publishing to npm):

npm install /path/to/bot-in-page
# or inside this repo
npm install
npm run build

Zero runtime dependencies. Ships ESM + TypeScript types (.d.ts). Requires a browser environment (document, elementFromPoint, etc.).

Quick start

import { createBot } from 'in-page-bot'

const bot = createBot({
  cursor: { visible: true },
  defaultProfile: 'human',
})

// Single command
await bot.click('#submit')

// Batch (default step interval 300–900ms)
const batch = await bot.run([
  { type: 'click', target: '#email' },
  { type: 'type', text: '[email protected]' },
  { type: 'click', target: '#submit' },
])

if (!batch.ok) {
  console.error(batch.results.find((s) => !s.ok)?.error)
}

Sugar methods (equivalent to run):

await bot.moveTo('#target')
await bot.click('#target')

Create: createBot(options?)

| Option | Type | Default | Description | |--------|------|---------|-------------| | root | Document \| ShadowRoot | document | Query root and cursor mount root | | cursor.visible | boolean | true | Show debug cursor | | cursor.zIndex | number | 2147483647 | Debug cursor z-index | | defaultProfile | 'fast' \| 'human' \| 'slide' | 'human' | Default trajectory profile | | defaultDurationMs | number | 500 | Base move duration; actual run applies ±100ms | | ensureVisible | boolean | true | Smoothly scroll Element/selector into range before pointer movement | | defaultScrollDurationMs | number | 500 | Base smooth-scroll duration; actual run applies ±100ms | | defaultIntervalMs | number \| { min, max } | { min: 300, max: 900 } | Batch step interval | | defaultCharIntervalMs | number \| { min, max } | { min: 40, max: 120 } | Per-character interval for type | | failFast | boolean | true | Stop batch on first ordinary failure |

const bot = createBot({
  root: document,
  cursor: { visible: true, zIndex: 2147483647 },
  defaultProfile: 'human',
  defaultDurationMs: 500,
  ensureVisible: true,
  defaultScrollDurationMs: 500,
  defaultIntervalMs: { min: 300, max: 900 },
  defaultCharIntervalMs: { min: 40, max: 120 },
  failFast: true,
})

Send commands: bot.run

run is the scheduling source of truth:

// Single → StepResult
await bot.run(command: BotCommand): Promise<StepResult>

// Serial batch → BatchResult
await bot.run(
  commands: BotCommand[],
  options?: {
    intervalMs?: number | { min: number; max: number }
    failFast?: boolean
  }
): Promise<BatchResult>

Target type BotTarget

target / from / to may be:

| Form | Example | Notes | |------|---------|-------| | CSS selector | '#btn', '.item' | querySelector inside root; missing → TARGET_NOT_FOUND | | Element | document.querySelector('button')! | Use the node directly | | Viewport point | { x: 120, y: 80 } | clientX / clientY |

Trajectory profile

| Value | Behavior | |-------|----------| | fast | Linear interpolation over duration | | human | Cubic Bézier with random control points | | slide | Same curve style; default duration 2000ms when durationMs omitted |

Commands that move the pointer may set durationMs to override the instance default. Actual duration = configured value ±100ms (clamped to ≥1ms).

For Element/selector targets, pointer-moving commands first smooth-scroll the target into range by default, and only then resolve coordinates and move. Set ensureVisible: false per command (or instance) to opt out; use scrollDurationMs to override the pre-scroll duration.

Scrolling is settled before anything else runs:

  • Scroll containers are temporarily pinned to scroll-behavior: auto (and restored afterwards), so host CSS smooth scrolling cannot keep sliding after the eased scroll finishes.
  • After scrolling, the aim point (target box center) must be inside every scroll container's visible box; otherwise the library performs corrective scroll passes.
  • If the target still cannot be brought into the visible range, the step fails with NOT_INTERACTABLE instead of clicking blind.

The virtual pointer always uses viewport (clientX / clientY) coordinates, like a real mouse: scrolling the page does not drag the pointer along with the document.

Note: Move segments are eased. pointerdown / mouseup / click / key press itself are instantaneous event sequences.

Commands

Implemented

moveTo

Move the virtual pointer to a target.

await bot.run({
  type: 'moveTo',
  target: '#menu',
  profile?: 'fast' | 'human' | 'slide',
  durationMs?: number,
  ensureVisible?: boolean,
  scrollDurationMs?: number,
})

| Field | Required | Description | |-------|----------|-------------| | target | yes | Destination | | profile | no | Override default profile | | durationMs | no | Override default move duration | | ensureVisible | no | Smooth-scroll before moving; inherits instance default true | | scrollDurationMs | no | Override pre-scroll duration |

click

Optionally moveTo first, then dispatch pointerdown → mousedown → pointerup → mouseup → click.

await bot.run({
  type: 'click',
  target?: BotTarget,      // omit → click at current pointer
  profile?: TrajectoryProfile,
  durationMs?: number,     // applies to approach move only
})

await bot.click('#ok')

wheel

Dispatch wheel at the current hit target (pointer does not move).

await bot.run({
  type: 'wheel',
  deltaY: 120,
  deltaX?: number,
})

scrollIntoView

Smooth-scroll a target into view without moving the virtual pointer. It is the external DOM-scroll primitive, is abortable, and does not rely on native instant jumping. Target must be an Element or selector (not a bare point).

await bot.run({
  type: 'scrollIntoView',
  target: '#section',
  durationMs?: number, // default 500ms, actual run ±100ms
})

type

Type into an editable input / textarea / contenteditable, character by character. With target, clicks first.

await bot.run({
  type: 'type',
  text: 'hello',
  target?: BotTarget,
  profile?: TrajectoryProfile,
  durationMs?: number,
  charIntervalMs?: number | { min: number; max: number },
})

| Field | Required | Description | |-------|----------|-------------| | text | yes | Text to type | | target | no | If set, click first; else need focused/hovered editable | | profile / durationMs | no | Approach move only | | charIntervalMs | no | Inter-character delay; default 40–120ms |

Per character: keydown → beforeinput → set value → input → keyup.

key

Dispatch a single key on the focused element (or hover / body).

await bot.run({
  type: 'key',
  key: 'Enter',
  modifiers?: string[], // e.g. 'Control' | 'Shift' | 'Alt' | 'Meta'
})

Backspace attempts to delete one character in an editable field.

wait

Wait only; no DOM events.

await bot.run({ type: 'wait', ms: 500 })

hover

Move to target and stop (no click). Same timing fields as moveTo.

await bot.run({
  type: 'hover',
  target: '#menu',
  profile?: TrajectoryProfile,
  durationMs?: number,
})

dblclick

Optional approach move, then two click rounds; second click has detail: 2, plus dblclick.

await bot.run({
  type: 'dblclick',
  target?: BotTarget,
  profile?: TrajectoryProfile,
  durationMs?: number,
})

clickIntoView

scrollIntoView then click in one command.

await bot.run({
  type: 'clickIntoView',
  target: '#submit',
  profile?: TrajectoryProfile,
  durationMs?: number,
})

drag

Move to frompointerdown → drag trajectory to to (moves with buttons: 1) → pointerup.

await bot.run({
  type: 'drag',
  from: '#handle',
  to: { x: 200, y: 120 },
  profile?: TrajectoryProfile,
  durationMs?: number,
})

Other API

bot.getCursor()
// → { x, y, visible }

bot.setCursorVisible(false)

bot.abort()
// Interrupts the in-flight step (trajectory / wait / char gap / batch).
// Also cancels pending concurrent run() jobs on this instance with ABORTED
// (they never dispatch). Interrupted step: error.code === 'ABORTED'.

Concurrency: On one createBot instance, overlapping run / sugar calls are queued FIFO and run serially.

Result shapes

StepResult (single)

{
  ok: boolean
  commandId: string
  command: string          // e.g. 'click'
  durationMs: number
  cursor: { x: number; y: number }
  error?: { code: string; message: string }
}

BatchResult (batch)

{
  ok: boolean              // true only if every step succeeded
  results: StepResult[]    // executed steps only; skipped after failFast/abort omitted
}

Error codes

| code | Meaning | |--------|---------| | TARGET_NOT_FOUND | Selector matched nothing | | NOT_INTERACTABLE | Not editable / zero size / obscured / no hit, etc. | | DISPATCH_FAILED | Dispatch threw | | ABORTED | Interrupted by abort() (in-flight or still queued) | | INVALID_COMMAND | Bad args or unknown command |

On failure, ok must be false with a stable error.code. No false success.

Full example

import { createBot, ErrorCode } from 'in-page-bot'

const bot = createBot()

const result = await bot.run([
  { type: 'scrollIntoView', target: '#form' },
  { type: 'click', target: '#name' },
  { type: 'type', text: 'Ada', charIntervalMs: { min: 40, max: 100 } },
  { type: 'click', target: '#email' },
  { type: 'type', text: '[email protected]' },
  { type: 'key', key: 'Tab' },
  { type: 'click', target: '#submit' },
  { type: 'wait', ms: 300 },
], {
  intervalMs: { min: 300, max: 900 },
  failFast: true,
})

if (!result.ok) {
  const failed = result.results.find((s) => !s.ok)
  if (failed?.error?.code === ErrorCode.TARGET_NOT_FOUND) {
    console.error('Target missing', failed.error.message)
  }
}

Local demo

Live site: https://in-page-bot.pages.dev

npm install
npm run playground

Opens the English interactive showcase (all public commands, including drag, queue, abort, and ensureVisible scrolling).

Dev scripts

npm run typecheck
npm run lint
npm test
npm run build

Docs

Deeper requirements and principles: docs/. Agent entry: AGENTS.md.


in-page-bot(中文)

页内虚拟键鼠控制器 + 语义命令调度器。

在已打开的页面里维护一台虚拟指针,按语义命令派发 Pointer / Mouse / Wheel / Keyboard / Input 事件,支持单条或批量串行执行,并逐步回报成败。

做什么: 页内合成输入(dispatchEvent)、轨迹移动、点击、滚轮、输入文本、串行调度。
不做什么: 截图 / a11y / 业务感知、Chrome 扩展 / MCP / WebSocket 通道、CDP / OS 真键鼠;不承诺 isTrusted === true

在线演示: https://in-page-bot.pages.dev — Cloudflare Pages 上的全公开 API Showcase。

安装

npm install in-page-bot

本地开发(尚未发布到 npm 时):

npm install /path/to/bot-in-page
# 或在本仓库
npm install
npm run build

运行时 零依赖。产物为 ESM + TypeScript 类型(.d.ts)。需在浏览器环境使用(依赖 document / elementFromPoint 等 DOM API)。

快速开始

import { createBot } from 'in-page-bot'

const bot = createBot({
  cursor: { visible: true },
  defaultProfile: 'human',
})

// 单条
await bot.click('#submit')

// 批量(默认步间隔 300–900ms)
const batch = await bot.run([
  { type: 'click', target: '#email' },
  { type: 'type', text: '[email protected]' },
  { type: 'click', target: '#submit' },
])

if (!batch.ok) {
  console.error(batch.results.find((s) => !s.ok)?.error)
}

糖方法(等价于 run):

await bot.moveTo('#target')
await bot.click('#target')

创建:createBot(options?)

| 选项 | 类型 | 默认 | 说明 | |------|------|------|------| | root | Document \| ShadowRoot | document | selector 查询与挂载根 | | cursor.visible | boolean | true | 是否显示调试光标 | | cursor.zIndex | number | 2147483647 | 调试光标层级 | | defaultProfile | 'fast' \| 'human' \| 'slide' | 'human' | 默认轨迹档位 | | defaultDurationMs | number | 500 | 指针移动基础时长;执行时再 ±100ms | | ensureVisible | boolean | true | 指针移动前先把 Element/selector 缓动滚入可见范围 | | defaultScrollDurationMs | number | 500 | 缓动滚动基础时长;执行时再 ±100ms | | defaultIntervalMs | number \| { min, max } | { min: 300, max: 900 } | 批量命令步间隔 | | defaultCharIntervalMs | number \| { min, max } | { min: 40, max: 120 } | type 字间间隔 | | failFast | boolean | true | 批量遇普通失败是否停止 |

const bot = createBot({
  root: document,
  cursor: { visible: true, zIndex: 2147483647 },
  defaultProfile: 'human',
  defaultDurationMs: 500,
  ensureVisible: true,
  defaultScrollDurationMs: 500,
  defaultIntervalMs: { min: 300, max: 900 },
  defaultCharIntervalMs: { min: 40, max: 120 },
  failFast: true,
})

发送指令:bot.run

调度真源是 run

// 单条 → StepResult
await bot.run(command: BotCommand): Promise<StepResult>

// 批量串行 → BatchResult
await bot.run(
  commands: BotCommand[],
  options?: {
    intervalMs?: number | { min: number; max: number }
    failFast?: boolean
  }
): Promise<BatchResult>

目标类型 BotTarget

命令里的 target / from / to 可以是:

| 形态 | 示例 | 说明 | |------|------|------| | CSS selector | '#btn''.item' | 在 rootquerySelector;找不到 → TARGET_NOT_FOUND | | Element | document.querySelector('button')! | 直接使用该节点 | | 视口坐标 | { x: 120, y: 80 } | clientX / clientY |

轨迹档位 profile

| 值 | 行为 | |----|------| | fast | 按时长直线插值 | | human | 随机控制点三次贝塞尔(人手感) | | slide | 同类曲线;未写 durationMs 时默认更长(2000ms) |

含指针移动的命令可写 durationMs,覆盖实例默认;实际执行 = 配置值 ±100ms(下限 1ms)。

Element/selector 目标默认会在指针运动前先缓动滚入可见范围,滚完再解析坐标并移动。可在实例或单条命令上设 ensureVisible: false 关闭;scrollDurationMs 可覆盖前置滚动时长。

滚动一定先落定,才做后续动作:

  • 滚动期间把滚动容器临时钉为 scroll-behavior: auto(结束后恢复),避免宿主 CSS 平滑滚动在缓动结束后继续滑动。
  • 滚完后瞄点(目标盒中心)必须落在每个滚动容器的可见区内,否则库会追加补偿滚动。
  • 仍无法进入可见范围时,该步返回 NOT_INTERACTABLE,不做盲点击。

虚拟指针始终使用视口坐标clientX / clientY),与真实鼠标一致:页面滚动不会把指针跟着文档一起带走。

注意: 移动段有缓动;pointerdown / mouseup / click / 按键按下本身是瞬时事件序列。

指令一览

已实现

moveTo

把虚拟指针移到目标。

await bot.run({
  type: 'moveTo',
  target: '#menu',
  profile?: 'fast' | 'human' | 'slide',
  durationMs?: number,
  ensureVisible?: boolean,
  scrollDurationMs?: number,
})

| 字段 | 必填 | 说明 | |------|------|------| | target | 是 | 终点 | | profile | 否 | 覆盖默认档位 | | durationMs | 否 | 覆盖默认移动时长 | | ensureVisible | 否 | 移动前缓动滚入;继承实例默认 true | | scrollDurationMs | 否 | 覆盖前置滚动时长 |

click

可选先 moveTo,再派发 pointerdown → mousedown → pointerup → mouseup → click

await bot.run({
  type: 'click',
  target?: BotTarget,      // 省略则点当前指针位置
  profile?: TrajectoryProfile,
  durationMs?: number,     // 仅影响前置移动
})

await bot.click('#ok')

wheel

在当前指针命中处派发 wheel(不移动指针)。

await bot.run({
  type: 'wheel',
  deltaY: 120,
  deltaX?: number,
})

scrollIntoView

把目标缓动滚入可操作区域,但不移动虚拟指针。它是供外部直接使用、可被 abort 的 DOM 真滚动原语,不依赖原生瞬间跳转。目标须为 Element 或 selector(不支持纯坐标)。

await bot.run({
  type: 'scrollIntoView',
  target: '#section',
  durationMs?: number, // 默认 500ms,执行时 ±100ms
})

type

对可编辑 input / textarea / contenteditable 逐字输入。有 target 时先 click 再输入。

await bot.run({
  type: 'type',
  text: 'hello',
  target?: BotTarget,
  profile?: TrajectoryProfile,
  durationMs?: number,
  charIntervalMs?: number | { min: number; max: number },
})

| 字段 | 必填 | 说明 | |------|------|------| | text | 是 | 要输入的文本 | | target | 否 | 有则先 click;无则要求已有焦点/hover 可编辑元素 | | profile / durationMs | 否 | 仅影响前置移动 | | charIntervalMs | 否 | 字间间隔;默认 40–120ms |

每字序列:keydown → beforeinput → 改 value → input → keyup

key

对当前焦点(或 hover / body)派发单键。

await bot.run({
  type: 'key',
  key: 'Enter',
  modifiers?: string[], // 如 'Control' | 'Shift' | 'Alt' | 'Meta'
})

Backspace 会尝试删除可编辑元素中的一个字符。

wait

纯等待,不派发 DOM 事件。

await bot.run({ type: 'wait', ms: 500 })

hover

移到目标并停留(不点击)。时序字段同 moveTo

await bot.run({
  type: 'hover',
  target: '#menu',
  profile?: TrajectoryProfile,
  durationMs?: number,
})

dblclick

可选先 move;两轮 click,第二轮 detail: 2,并补 dblclick

await bot.run({
  type: 'dblclick',
  target?: BotTarget,
  profile?: TrajectoryProfile,
  durationMs?: number,
})

clickIntoView

滚入再点一体:scrollIntoViewclick

await bot.run({
  type: 'clickIntoView',
  target: '#submit',
  profile?: TrajectoryProfile,
  durationMs?: number,
})

drag

移到 frompointerdown → 按住轨迹到 to(move 带 buttons: 1)→ pointerup

await bot.run({
  type: 'drag',
  from: '#handle',
  to: { x: 200, y: 120 },
  profile?: TrajectoryProfile,
  durationMs?: number,
})

其它 API

bot.getCursor()
// → { x, y, visible }

bot.setCursorVisible(false)

bot.abort()
// 打断当前进行中的轨迹 / wait / 字间等待 / 批量队列
// 并清空本实例尚未开始的并发 run 排队任务,回 ABORTED(不派发)
// 中断项 error.code === 'ABORTED'

并发: 同一 createBot 实例上重叠的 run / 糖方法会 FIFO 自动排队 串行执行。

回包形状

StepResult(单条)

{
  ok: boolean
  commandId: string
  command: string          // 如 'click'
  durationMs: number
  cursor: { x: number; y: number }
  error?: { code: string; message: string }
}

BatchResult(批量)

{
  ok: boolean              // 全部成功才为 true
  results: StepResult[]    // 已执行步骤;failFast / abort 后未跑的不出现
}

错误码

| code | 含义 | |--------|------| | TARGET_NOT_FOUND | selector 无匹配 | | NOT_INTERACTABLE | 不可编辑 / 零尺寸 / 被遮挡 / 无命中等 | | DISPATCH_FAILED | 派发异常 | | ABORTED | 被 abort() 打断(进行中或仍在排队) | | INVALID_COMMAND | 参数非法或未知命令 |

失败时 ok 必为 false,并带稳定 error.code;禁止假成功。

完整示例

import { createBot, ErrorCode } from 'in-page-bot'

const bot = createBot()

const result = await bot.run([
  { type: 'scrollIntoView', target: '#form' },
  { type: 'click', target: '#name' },
  { type: 'type', text: 'Ada', charIntervalMs: { min: 40, max: 100 } },
  { type: 'click', target: '#email' },
  { type: 'type', text: '[email protected]' },
  { type: 'key', key: 'Tab' },
  { type: 'click', target: '#submit' },
  { type: 'wait', ms: 300 },
], {
  intervalMs: { min: 300, max: 900 },
  failFast: true,
})

if (!result.ok) {
  const failed = result.results.find((s) => !s.ok)
  if (failed?.error?.code === ErrorCode.TARGET_NOT_FOUND) {
    console.error('目标不存在', failed.error.message)
  }
}

本地演示

在线站点:https://in-page-bot.pages.dev

npm install
npm run playground

打开英文交互 Showcase(覆盖全部公开命令,含 drag、队列、abort、ensureVisible 缓动滚入)。

开发脚本

npm run typecheck
npm run lint
npm test
npm run build

文档

更细的需求与原则见 docs/。Agent 实施入口见 AGENTS.md


Designed by bobliao
Contributor: Cursor