[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-1844":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":8,"htmlUrl":8,"language":9,"languages":8,"totalLinesOfCode":8,"stars":10,"forks":11,"watchers":12,"openIssues":13,"contributorsCount":14,"subscribersCount":14,"size":14,"stars1d":14,"stars7d":13,"stars30d":15,"stars90d":14,"forks30d":14,"starsTrendScore":14,"compositeScore":16,"rankGlobal":8,"rankLanguage":8,"license":17,"archived":18,"fork":18,"defaultBranch":19,"hasWiki":20,"hasPages":18,"topics":21,"createdAt":8,"pushedAt":8,"updatedAt":22,"readmeContent":23,"aiSummary":24,"trendingCount":14,"starSnapshotCount":14,"syncStatus":15,"lastSyncTime":25,"discoverSource":26},1844,"bunwv","NatiCha\u002Fbunwv","NatiCha",null,"TypeScript",159,3,156,1,0,2,1.81,"MIT License",false,"main",true,[],"2026-06-12 02:00:33","# bunwv\n\n[![npm](https:\u002F\u002Fimg.shields.io\u002Fnpm\u002Fv\u002F@naticha\u002Fbunwv)](https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002F@naticha\u002Fbunwv)\n[![platform](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fplatform-macOS%20%7C%20Linux%20%7C%20Windows-lightgrey)](https:\u002F\u002Fgithub.com\u002Fnaticha\u002Fbunwv)\n[![Bun](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fbun-%3E%3D1.3.14-orange)](https:\u002F\u002Fbun.sh)\n[![License: MIT](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FLicense-MIT-blue.svg)](LICENSE)\n\n![bunwv demo](https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fc09a565a-0031-4698-9e09-6c7c9c222da9)\n\nHeadless browser automation CLI for [Bun](https:\u002F\u002Fbun.sh), powered by `Bun.WebView`. Cross-platform: WebKit on macOS (default, zero dependencies), Chrome on macOS\u002FLinux\u002FWindows.\n\nA persistent daemon keeps a browser instance alive so page state — DOM, modals, forms, auth, cookies — survives across commands. Designed **agent-first**: every action verb is silent on success, errors are JSON on stderr with stable exit codes, and event\u002Fconsole buffers are cursor-pulled. Built for AI coding assistants (Claude Code, Cursor, etc.) driving the browser through discrete tool calls.\n\n## Install\n\n```bash\nbun install -g @naticha\u002Fbunwv\n```\n\nRequires Bun v1.3.14+. On macOS, uses the native WebKit engine by default (zero dependencies). On Linux and Windows, automatically uses Chrome\u002FChromium (must be installed).\n\n### AI Coding Assistant Skill\n\n```bash\nbunx skills add naticha\u002Fbunwv\n# or\nnpx skills add naticha\u002Fbunwv\n```\n\nOr install directly in Claude Code:\n\n```\n\u002Fplugin marketplace add naticha\u002Fbunwv\n\u002Fplugin install bunwv@bunwv\n```\n\nThis installs the skill file that teaches AI assistants how to use bunwv for browser testing.\n\n## Quick Start\n\n```bash\nbunwv start                                  # start the daemon\nbunwv navigate http:\u002F\u002Flocalhost:3000         # go to a page\nbunwv screenshot                             # writes \u002Ftmp\u002Fbunwv-screenshot-\u003Csession>.jpg (JPEG @ q80), prints the path\nbunwv screenshot --max-width 1024            # bound the longest side; aspect preserved\nbunwv screenshot --metadata                  # {\"width\",\"height\",\"format\"} instead of pixels\nbunwv click --selector \"button.submit\"       # click an element (auto-waits)\nbunwv type \"hello world\"                     # type into focused element\nbunwv evaluate \"document.title\"              # run JS in the page, JSON-literal result\nbunwv close                                  # stop the daemon\n```\n\n## Agent-first contract\n\n- **Successful action verbs print nothing on stdout and exit 0.** `click`, `type`, `navigate`, `press`, `scroll`, `scroll-to`, `clear`, `submit`, `resize`, `back`\u002F`forward`\u002F`reload`, `close`, `exists`, `wait-for`, `wait-for-gone`, `cdp-subscribe`, `cdp-unsubscribe` are all silent. Read verbs (`status`, `evaluate`, `events`, `console`, `cdp`, `cdp-subscriptions`, `screenshot`, `sessions`) print their result.\n- **Stable exit codes**: `0` ok, `1` generic, `2` usage, `3` timeout, `4` element-not-found, `5` daemon-unreachable, `6` batch-partial.\n- **Errors are JSON on stderr**: `{ok:false, error, exitCode}`. Branch on the exit code, not stderr text.\n- **Error-level console auto-surfaces.** If the page logs `console.error`\u002F`console.warn` while a verb runs, `{\"console\":[…]}` is written to stderr alongside the verb's response.\n- **`--json` global flag** wraps any command's output as `{ok, data?, error?, exitCode}`.\n- **Flexible flags**: `--flag value`, `--flag=value`, repeated flags (e.g. `--mod Shift --mod Control`), and flags before or after the command all work.\n- **`BUNWV_SESSION` env var** replaces `--session \u003Cname>` when set.\n\n## Commands\n\n### Session\n\n| Command | Description |\n|---|---|\n| `start [--width N] [--height N] [--data-store PATH] [--idle-timeout ms] [--backend webkit\\|chrome] [--chrome-path PATH] [--chrome-argv '[json]'] [--chrome-url \u003Cws>] [--chrome-stdout inherit\\|ignore] [--chrome-stderr inherit\\|ignore] [--webkit-stdout inherit\\|ignore] [--webkit-stderr inherit\\|ignore] [--url \u003Cinitial>]` | Start the daemon (default 1920x1080, 30min idle timeout) |\n| `close [--all]` | Stop this session, or every running session with `--all` |\n| `status` | Terse: `\u003Curl> \\| \u003Ctitle> \\| \u003Cidle\\|loading> \\| pending=\u003Cn>`. `--json` for `loading`, `pendingEvents`, `cursor`, `cdpSubscriptions` |\n| `sessions` | List all running sessions |\n\n### Navigation\n\n| Command | Description |\n|---|---|\n| `navigate \u003Curl>` | Navigate to a URL (silent) |\n| `back` \u002F `forward` \u002F `reload` | History + refresh (silent) |\n\n### Interaction\n\n| Command | Description |\n|---|---|\n| `click --selector \u003Ccss>` | Click an element by CSS selector (auto-waits for actionability, `isTrusted: true`) |\n| `click --text \u003Ctext>` | Click an element by visible text. `--text-match exact\\|contains\\|regex` (default: trimmed contains) |\n| `click --at \u003Cx,y>` | Click at coordinates (no actionability wait) |\n| `click ... [--button left\\|right\\|middle] [--count 1\\|2\\|3] [--mod Shift] [--mod Control] [--mod Alt] [--mod Meta] [--timeout ms]` | Modifiers, mouse button, click count, actionability timeout |\n| `exists \u003Cselector>` | Silent probe. Exit 0 present, 4 missing |\n| `type \u003Ctext>` | Type text into the focused element |\n| `press \u003Ckey> [--mod Shift] [--mod Control] ...` | Press a key with optional modifiers (case-sensitive per Bun.WebView) |\n| `clear \u003Cselector>` | Clear an input\u002Ftextarea (React-compatible native setter) |\n| `submit [--form \u003Csel>] [--button \u003Ctext>]` | Submit a form via `requestSubmit()` (React-compatible) |\n| `scroll \u003Cdx> \u003Cdy>` | Scroll by wheel event |\n| `scroll-to \u003Cselector> [--block start\\|center\\|end\\|nearest] [--timeout ms]` | Scroll element into view |\n\n### Inspection\n\n| Command | Description |\n|---|---|\n| `screenshot [--format png\\|jpeg\\|webp\\|avif\\|heic] [--quality 0-100] [--max-width N] [--max-height N] [--placeholder \\| --metadata] [--encoding blob\\|buffer\\|base64\\|shmem] [--out \u003Cpath>\\|-]` | Capture the viewport. **Default: JPEG @ q80, writes `\u002Ftmp\u002Fbunwv-screenshot-\u003Csession>.jpg`** and prints the path. `--max-width`\u002F`--max-height` cap dimensions (aspect preserved, never upscales). `--placeholder` emits a blur-up data URL; `--metadata` emits `{width,height,format}` JSON. AVIF\u002FHEIC encode is Apple-Silicon-only |\n| `image \u003Cinput> [--out \u003Cpath>\\|-] [--format ...] [--quality N] [--resize WxH \\| --max-width N \\| --max-height N] [--rotate 90\\|180\\|270] [--flip] [--flop] [--metadata] [--placeholder]` | Transform a local image via `Bun.Image` — no daemon required. Output format inferred from `--out` extension; defaults to `jpeg`. Default `--out` is the input with the new extension |\n| `evaluate \u003Cexpr>` | Evaluate JS in the page. Always prints the JSON-literal result (auto-wraps statements in an IIFE) |\n| `console [--clear] [--since \u003Cseq>]` | Captured page console output. Terse: `\u003Cseq> [\u003Clevel>] \u003Cmessage>`. `\\n`\u002F`\\r` escaped. `--json` for raw messages + cursor |\n| `events [--since \u003Cseq>]` | Navigation events + subscribed CDP events since the cursor. 1000 entries \u002F 10 MB LRU cap |\n| `cdp \u003Cmethod> [--params '{}']` | Raw Chrome DevTools Protocol call (Chrome backend only) |\n| `cdp-subscribe \u003CCDP.event> [\u003CCDP.event> ...]` | Subscribe one or more CDP events into the `events` buffer |\n| `cdp-unsubscribe \u003CCDP.event> [\u003CCDP.event> ...]` | Unsubscribe |\n| `cdp-subscriptions` | List active subscriptions |\n| `resize \u003Cw> \u003Ch>` | Resize the viewport |\n\n### Waiting\n\n| Command | Description |\n|---|---|\n| `wait-for \u003Cselector>` | Wait until element appears (default 10s) |\n| `wait-for --url \u003Csubstring>` \u002F `--title \u003Csubstring>` | Wait for URL or title to contain a substring |\n| `wait-for-gone \u003Cselector> \\| --url \u003Csubstr> \\| --title \u003Csubstr>` | Symmetric removal wait |\n| `wait-for ... [--timeout ms]` | Override the 10s default |\n\n### Batch\n\n| Command | Description |\n|---|---|\n| `batch [--file \u003Cpath>] [--keep-going]` | Read NDJSON from stdin (or a file), each line a JSON array of args. Runs all lines in one Bun process, emits one NDJSON envelope per command. Outer flags like `--session` inherit into each line |\n\nAll commands accept `--json`, `--session \u003Cname>` (or `BUNWV_SESSION` env var), and the flexible flag syntax.\n\n## Sessions\n\nSessions are named and isolated. Each runs its own daemon on a separate Unix socket. Sockets and PID files are `chmod 0600`, so other local users can't drive your session.\n\n```bash\nbunwv start                          # \"default\" session\nbunwv start --session staging        # separate \"staging\" session\nBUNWV_SESSION=staging bunwv navigate http:\u002F\u002Fstaging:3000\nbunwv sessions                       # list running sessions\nbunwv close --session staging        # stop one session\nbunwv close --all                    # stop every running session\n```\n\n**Auto-shutdown** — daemons exit after 30 minutes of inactivity. Override with `--idle-timeout`:\n\n```bash\nbunwv start --idle-timeout 3600000   # 1 hour\nbunwv start --idle-timeout 0         # never\n```\n\n**Reuse detection** — starting an existing session reports its current state and exits 0:\n\n```\n$ bunwv start\nReusing existing session \"default\" (PID: 12345)\n  URL:   http:\u002F\u002Flocalhost:3000\u002Fdashboard\n```\n\n**Persistent auth** — use `--data-store` to preserve cookies\u002FlocalStorage across daemon restarts:\n\n```bash\nbunwv start --data-store .\u002Fbunwv-session\n```\n\n## Working with React\n\nTwo commands are specifically designed for React apps:\n\n**`clear`** — clears input fields using the native value setter and dispatches React-compatible events. Keyboard-based clearing (`Cmd+A`, `Backspace`) does not reliably update React state.\n\n```bash\nbunwv clear \"input[name='email']\"\nbunwv click --selector \"input[name='email']\"\nbunwv type \"new-value@example.com\"\n```\n\n**`submit`** — submits forms via `form.requestSubmit()`, which properly triggers React form handlers. JS `.click()` produces `isTrusted: false` events that many React forms ignore.\n\n```bash\nbunwv submit --button \"Save Changes\"\nbunwv wait-for-gone \"[role='dialog']\"\n```\n\n## Console Capture\n\nPage `console.log`, `console.error`, etc. are captured into a cursor-based ring buffer (1000 entries). `console.error`\u002F`console.warn` entries that fire **during a verb** are auto-surfaced to that verb's stderr as `{\"console\":[…]}` — the agent sees failures without a second call.\n\nPull the buffer explicitly:\n\n```bash\nbunwv console                        # terse: \"\u003Cseq> [\u003Clevel>] \u003Cmessage>\"\nbunwv console --clear                # print, then clear\nbunwv console --since 42             # only entries with seq > 42\nbunwv --json console                 # {messages, cursor, truncated?, oldest?}\n```\n\nAdvance `--since` using the max `seq` you saw (first field of each line). Use `--json` when you need raw multi-line messages or the truncation signal.\n\n## Events & CDP\n\n`onNavigated`, `onNavigationFailed`, and any subscribed CDP events land in a shared ring buffer (1000 entries \u002F 10 MB LRU):\n\n```bash\nbunwv events --since 0               # full buffer\nbunwv events --since 42              # new events only\n```\n\nIf the buffer evicted older entries, the response includes `\"truncated\":true,\"oldest\":\u003Cseq>`.\n\n### Chrome backend & CDP\n\nmacOS defaults to WebKit; Linux\u002FWindows auto-use Chrome. Override on any platform:\n\n```bash\nbunwv start --backend chrome\nbunwv start --backend webkit                                # macOS only\nbunwv start --chrome-path \u002Fpath\u002Fto\u002Fchromium\nbunwv start --chrome-argv '[\"--headless=new\"]'              # extra flags\nbunwv start --chrome-url ws:\u002F\u002F127.0.0.1:9222\u002Fdevtools\u002F...   # attach to a running Chrome\n```\n\nRaw CDP calls and subscriptions (Chrome only):\n\n```bash\nbunwv cdp \"Page.getLayoutMetrics\"\nbunwv cdp \"Runtime.evaluate\" --params '{\"expression\": \"1+1\"}'\n\nbunwv cdp \"Network.enable\"\nbunwv cdp-subscribe Network.responseReceived Network.requestWillBeSent\nbunwv navigate https:\u002F\u002Fexample.com\nbunwv events --since 0\nbunwv cdp-unsubscribe Network.responseReceived Network.requestWillBeSent\n```\n\n## Batch mode\n\n`bunwv batch` runs many commands in a single Bun process, eliminating per-command startup cost. Each stdin line is a JSON array of args; each response is an NDJSON envelope on stdout.\n\n```bash\ncat \u003C\u003C'EOF' | bunwv batch --session staging --keep-going\n[\"navigate\",\"http:\u002F\u002Flocalhost:3000\u002Flogin\"]\n[\"click\",\"--selector\",\"input[name='email']\"]\n[\"type\",\"me@example.com\"]\n[\"press\",\"Tab\"]\n[\"type\",\"hunter2\"]\n[\"submit\",\"--button\",\"Sign In\"]\n[\"wait-for\",\"--url\",\"\u002Fdashboard\"]\n[\"screenshot\"]\nEOF\n```\n\n`--keep-going` runs the full list even if one line fails; the process exits `6` (batch-partial) on any failure, `0` on full success. Without `--keep-going`, batch stops at the first failure and returns that line's exit code.\n\n## How It Works\n\n```\n┌──────────┐     Unix Socket      ┌───────────────┐    Bun.WebView    ┌──────────────┐\n│  bunwv   │ ──── HTTP POST ────▶ │    daemon     │ ─────── API ────▶ │ WebKit macOS │\n│   CLI    │ ◀─── JSON\u002Fbytes ──── │ (background)  │ ◀──────────────── │ Chrome Linux │\n└──────────┘  \u002Ftmp\u002Fbunwv-*.sock   └───────────────┘                   │   \u002F Windows  │\n                                                                      └──────────────┘\n```\n\n- The daemon spawns on `bunwv start` and listens on a Unix socket (owner-only, `chmod 0600`).\n- Each CLI command sends one HTTP request to the daemon and exits — no long-lived connections.\n- The daemon owns a single `Bun.WebView` instance.\n- All selector\u002Fcoordinate input is dispatched as **native events** (`isTrusted: true`); selector-based methods auto-wait for actionability (attached, visible, stable, unobscured).\n- Navigation and CDP events are buffered with monotonic `seq` cursors so agents can poll for what's new since their last turn.\n\n## AI Assistant Integration\n\nbunwv is designed for AI coding assistants that can't see a browser. The typical workflow:\n\n1. **Navigate** to a page\n2. **Screenshot** — the assistant Reads the PNG to \"see\" the page\n3. **Decide** what to do based on the screenshot\n4. **Act** — `click`, `type`, `submit`\n5. **Wait** — `wait-for` a selector, URL, or title change\n6. **Screenshot** again to verify\n7. **Repeat**\n\nThe Claude Code skill (`skills\u002Fbunwv\u002FSKILL.md`) documents these patterns end-to-end, including batch mode, React form handling, error recovery via exit codes, and cursor-based event\u002Fconsole polling.\n\n## License\n\nMIT\n","bunwv 是一个基于 Bun 的无头浏览器自动化命令行工具，利用 `Bun.WebView` 实现跨平台的 WebKit 和 Chrome 浏览器控制。其核心功能包括持久化守护进程保持浏览器实例活跃，使得页面状态（如 DOM、模态框、表单、认证信息和 Cookies）在多次命令间得以保留。该工具设计为“代理优先”，成功操作时静默执行，错误以 JSON 格式输出至 stderr，并提供稳定的退出码。事件和控制台缓冲区可通过光标拉取访问。bunwv 适用于需要通过离散工具调用来驱动浏览器的人工智能编码助手场景，例如 Claude Code 或 Cursor 等，同时也适合于需要高效稳定浏览器自动化的开发测试环境。","2026-06-11 02:46:24","CREATED_QUERY"]