nekoro-browser 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. nekoro_browser-0.1.0/.claude/skills/nekoro-browser/SKILL.md +52 -0
  2. nekoro_browser-0.1.0/.gitignore +25 -0
  3. nekoro_browser-0.1.0/AGENTS.md +46 -0
  4. nekoro_browser-0.1.0/LICENSE +21 -0
  5. nekoro_browser-0.1.0/PKG-INFO +255 -0
  6. nekoro_browser-0.1.0/README.md +230 -0
  7. nekoro_browser-0.1.0/README.zh-CN.md +214 -0
  8. nekoro_browser-0.1.0/SKILL.md +244 -0
  9. nekoro_browser-0.1.0/domain-skills/README.md +125 -0
  10. nekoro_browser-0.1.0/extension/background.js +1098 -0
  11. nekoro_browser-0.1.0/extension/icons/icon-128.png +0 -0
  12. nekoro_browser-0.1.0/extension/icons/icon-16.png +0 -0
  13. nekoro_browser-0.1.0/extension/icons/icon-48.png +0 -0
  14. nekoro_browser-0.1.0/extension/icons/icon-512.png +0 -0
  15. nekoro_browser-0.1.0/extension/icons/logo.svg +39 -0
  16. nekoro_browser-0.1.0/extension/keepalive.js +34 -0
  17. nekoro_browser-0.1.0/extension/manifest.json +40 -0
  18. nekoro_browser-0.1.0/extension/options.html +41 -0
  19. nekoro_browser-0.1.0/extension/options.js +25 -0
  20. nekoro_browser-0.1.0/pyproject.toml +66 -0
  21. nekoro_browser-0.1.0/src/nekoro_browser/__init__.py +3 -0
  22. nekoro_browser-0.1.0/src/nekoro_browser/agent_helpers.py +11 -0
  23. nekoro_browser-0.1.0/src/nekoro_browser/auth.py +62 -0
  24. nekoro_browser-0.1.0/src/nekoro_browser/bridge.py +448 -0
  25. nekoro_browser-0.1.0/src/nekoro_browser/cli.py +387 -0
  26. nekoro_browser-0.1.0/src/nekoro_browser/config.py +70 -0
  27. nekoro_browser-0.1.0/src/nekoro_browser/daemon.py +234 -0
  28. nekoro_browser-0.1.0/src/nekoro_browser/helpers.py +784 -0
  29. nekoro_browser-0.1.0/src/nekoro_browser/lifecycle.py +219 -0
  30. nekoro_browser-0.1.0/src/nekoro_browser/mcp_server.py +290 -0
  31. nekoro_browser-0.1.0/src/nekoro_browser/paths.py +36 -0
  32. nekoro_browser-0.1.0/src/nekoro_browser/site_notes.py +188 -0
  33. nekoro_browser-0.1.0/tests/test_auth.py +118 -0
  34. nekoro_browser-0.1.0/tests/test_cli_timeout.py +74 -0
  35. nekoro_browser-0.1.0/tests/test_close_tab.py +71 -0
  36. nekoro_browser-0.1.0/tests/test_daemon.py +92 -0
  37. nekoro_browser-0.1.0/tests/test_dialog.py +99 -0
  38. nekoro_browser-0.1.0/tests/test_fill_input.py +80 -0
  39. nekoro_browser-0.1.0/tests/test_js_values.py +126 -0
  40. nekoro_browser-0.1.0/tests/test_keepalive.py +74 -0
  41. nekoro_browser-0.1.0/tests/test_lifecycle.py +143 -0
  42. nekoro_browser-0.1.0/tests/test_mcp.py +283 -0
  43. nekoro_browser-0.1.0/tests/test_navigate.py +69 -0
  44. nekoro_browser-0.1.0/tests/test_netidle.py +72 -0
  45. nekoro_browser-0.1.0/tests/test_new_tab.py +99 -0
  46. nekoro_browser-0.1.0/tests/test_paths.py +68 -0
  47. nekoro_browser-0.1.0/tests/test_pipelining.py +88 -0
  48. nekoro_browser-0.1.0/tests/test_port_config.py +118 -0
  49. nekoro_browser-0.1.0/tests/test_press_key.py +102 -0
  50. nekoro_browser-0.1.0/tests/test_reattach.py +63 -0
  51. nekoro_browser-0.1.0/tests/test_reload_ext.py +77 -0
  52. nekoro_browser-0.1.0/tests/test_setup.py +151 -0
  53. nekoro_browser-0.1.0/tests/test_site_notes.py +207 -0
  54. nekoro_browser-0.1.0/tests/test_stop.py +93 -0
  55. nekoro_browser-0.1.0/tests/test_upload.py +123 -0
  56. nekoro_browser-0.1.0/tests/test_ws_transport.py +230 -0
@@ -0,0 +1,52 @@
1
+ ---
2
+ name: nekoro-browser
3
+ description: 浏览器自动化——打开网页、搜索、点击、截图、执行 JS、填表、上传文件、处理对话框。通过 Chrome 扩展的 chrome.debugger API 操控用户日常浏览器,保留登录态,不开调试端口。触发词:"浏览器"、"打开网页"、"搜索"、"截图"、"点击"、"填表"、"上传文件"、"自动化操作"。
4
+ allowed-tools: Bash(nekoro-browser:*) Bash(python:*) Read Edit Write
5
+ ---
6
+
7
+ # nekoro-browser
8
+
9
+ 通过 Chrome 扩展 + 持久 WebSocket 操控用户日常 Chrome 的 CLI 工具。不需要
10
+ `--remote-debugging-port`(Chrome 136 起该方式无法接默认 profile),保留真实登录态。
11
+
12
+ **完整命令参考、故障排查、领域技能见仓库根目录 [`SKILL.md`](../../../SKILL.md)——先读那份,本文件只补 Claude Code 场景的要点。**
13
+
14
+ ## 前置条件
15
+
16
+ ```bash
17
+ pip install -e . # 或 uv pip install -e .,注册 nekoro-browser 命令
18
+ ```
19
+
20
+ 加载 Chrome 扩展:`chrome://extensions` → 开发者模式 → 加载已解压的扩展程序 → 选
21
+ `extension/` 目录(`nekoro-browser --extension-path` 打印绝对路径)。未打包扩展会被
22
+ Chrome 更新/重启后自动停用,`--doctor` 说 SW 不响应时先去那页确认它还开着。
23
+
24
+ ## 快速开始
25
+
26
+ ```bash
27
+ # 终端 1:启动 daemon(前台,保持打开)
28
+ nekoro-browser
29
+
30
+ # 终端 2:验证
31
+ nekoro-browser --doctor
32
+ echo "page_info()" | nekoro-browser
33
+ ```
34
+
35
+ daemon 默认监听 `127.0.0.1:28417`(选此端口是为了不与同类工具 `@jackwener/opencli` 的
36
+ 19825 撞车,若你机器上也装了 OpenCLI,两者可共存但同一时刻按需只启一个 daemon)。
37
+
38
+ 改端口:Python 侧 `nekoro-browser --port 30500` 或环境变量 `NEKORO_PORT`,
39
+ 扩展侧在扩展详情页的「扩展程序选项」里设成同一个。客户端不用重复传参——
40
+ daemon 把实际端口写进 `<数据目录>/port`,管道模式自己会读。
41
+
42
+ ## 每次调用前
43
+
44
+ 检查 daemon 是否存活(`nekoro-browser --doctor`),死了 `nekoro-browser --reload-ext`
45
+ 或重启;扩展 service worker 偶尔需要在 `chrome://extensions` 手动重载(尤其 Chrome
46
+ 刚重开时)。
47
+
48
+ ## 自愈
49
+
50
+ 缺少函数时编辑 `src/nekoro_browser/agent_helpers.py` 添加——**只有这个文件**每次
51
+ `/exec` 前会自动 reload,改完立即生效、无需重启 daemon。改 `helpers.py` 本身需要重启
52
+ daemon 才生效(daemon 启动时导入一次)。
@@ -0,0 +1,25 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ *.egg
8
+
9
+ # Virtual environments
10
+ .venv/
11
+ venv/
12
+ # 库不锁依赖版本(本来就零依赖),锁文件只是本地噪音
13
+ # 注意:gitignore 不支持行尾注释,注释必须独占一行,否则整行都会被当成 pattern
14
+ uv.lock
15
+
16
+ # IDE
17
+ .vscode/
18
+ .idea/
19
+
20
+ # OS
21
+ .DS_Store
22
+ Thumbs.db
23
+
24
+ # 本地一次性脚本(作者自用,不进仓库;目录本身也不跟踪)
25
+ scripts/
@@ -0,0 +1,46 @@
1
+ nekoro-browser is a CLI that lets an agent drive the user's daily Chrome via a MV3
2
+ extension (`chrome.debugger` API) + a local daemon, keeping the real login state —
3
+ no `--remote-debugging-port` (Chrome 136+ blocks that on the default profile anyway).
4
+
5
+ # Code priorities
6
+ - Clarity
7
+ - Precision
8
+ - Low verbosity
9
+ - Never fabricate success — helpers return `{"ok": false, "error": ...}` on failure,
10
+ not a silently-wrong `{"ok": true}`
11
+
12
+ # Overview
13
+ Two halves, one wire (HTTP + WebSocket, same port):
14
+ - `extension/` — MV3 extension. `background.js` is the service worker: WS transport to
15
+ the daemon, `chrome.debugger` attach/CDP dispatch, dialog auto-handling, tab lifecycle.
16
+ `keepalive.js` is a content script that gives the service worker an independent wake
17
+ vector (MV3 workers get evicted; a plain WS/alarm keep-alive alone isn't reliable).
18
+ - `src/nekoro_browser/` — the daemon + CLI.
19
+ - `daemon.py` — long-lived middleman process between the extension and the agent's `-c` code
20
+ - `bridge.py` — the WS/HTTP transport
21
+ - `lifecycle.py` — pid file, process fingerprint, stale-daemon self-heal
22
+ - `helpers.py` — CDP wrapper functions auto-imported into `-c` scripts, each a thin
23
+ (≤10 line) wrapper over one CDP capability
24
+ - `cli.py` — the `nekoro-browser` command
25
+ - `mcp_server.py` — the `nekoro-browser-mcp` command: stdio JSON-RPC MCP server for
26
+ clients that don't read skill files (Cursor / Cline / Claude Desktop). Tools are
27
+ reflected off `helpers.py` via `inspect.signature`, then forwarded to the same
28
+ daemon `/exec` endpoint the CLI uses — no second execution path to keep in sync.
29
+
30
+ `SKILL.md` tells agents how to use the CLI and lists every helper. `README.md` covers
31
+ install + quick start for humans.
32
+
33
+ An agent operating nekoro-browser edits two places:
34
+ - `src/nekoro_browser/agent_helpers.py` — task-specific browser helpers the agent adds
35
+ at runtime; hot-reloaded via `reload_agent_helpers()`, no daemon restart needed
36
+ - `domain-skills/` — Markdown notes on specific sites, written and read by the agent.
37
+ Empty by default and never imported; workflows go in `agent_helpers.py`.
38
+
39
+ # Testing
40
+ `tests/*.py` are stdlib-style, not pytest: `assert` + a final `print("ALL OK")`, run via
41
+ `uv run python tests/test_X.py`. Extension JS has no unit-test surface — verify syntax
42
+ with `node --check`, behavior needs a live Chrome.
43
+
44
+ # Contributing
45
+ Consider what is really needed. Prefer the smallest diff that fixes the bug. Don't add
46
+ speculative config/flags for scenarios that aren't happening yet.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Nekoro
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,255 @@
1
+ Metadata-Version: 2.4
2
+ Name: nekoro-browser
3
+ Version: 0.1.0
4
+ Summary: Lightweight browser automation CLI + MCP server driving your everyday Chrome via the extension chrome.debugger API — keeps your login state, no --remote-debugging-port
5
+ Project-URL: Homepage, https://github.com/zeshuochen/nekoro-browser
6
+ Project-URL: Repository, https://github.com/zeshuochen/nekoro-browser
7
+ Project-URL: Issues, https://github.com/zeshuochen/nekoro-browser/issues
8
+ Project-URL: Documentation, https://github.com/zeshuochen/nekoro-browser/blob/master/SKILL.md
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,browser-automation,cdp,chrome,chrome-devtools-protocol,llm,mcp,model-context-protocol,playwright-alternative
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: MacOS :: MacOS X
16
+ Classifier: Operating System :: Microsoft :: Windows
17
+ Classifier: Operating System :: POSIX :: Linux
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: Browsers
22
+ Classifier: Topic :: Software Development :: Testing
23
+ Requires-Python: >=3.12
24
+ Description-Content-Type: text/markdown
25
+
26
+ <p align="center">
27
+ <img src="extension/icons/icon-128.png" width="80" alt="nekoro-browser">
28
+ </p>
29
+
30
+ <h1 align="center">nekoro-browser</h1>
31
+
32
+ <p align="center">
33
+ <a href="https://github.com/zeshuochen/nekoro-browser/actions/workflows/tests.yml"><img src="https://github.com/zeshuochen/nekoro-browser/actions/workflows/tests.yml/badge.svg" alt="tests"></a>
34
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+"></a>
35
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
36
+ <a href="#mcp-cursor--cline--claude-desktop"><img src="https://img.shields.io/badge/MCP-supported-8A2BE2" alt="MCP supported"></a>
37
+ </p>
38
+
39
+ <p align="center">
40
+ Lightweight browser automation CLI + MCP server. Drives your everyday Chrome through an extension — <b>keeps your login state</b>, <b>no debug port</b>, <b>no banners</b>.<br>
41
+ <sub><a href="README.zh-CN.md">中文</a></sub>
42
+ </p>
43
+
44
+ ---
45
+
46
+ ## Why Not `--remote-debugging-port`?
47
+
48
+ Since Chrome 136, `--remote-debugging-port` / `--remote-debugging-pipe` **refuse the default profile** — you must point Chrome at a non-default `--user-data-dir`, i.e. a clean instance with none of your logins. An extension's `chrome.debugger` is not subject to that restriction, which is why nekoro goes through an extension.
49
+
50
+ | | CDP WebSocket | playwright-cli | opencli | **nekoro-browser** |
51
+ |------|:--:|:--:|:--:|:--:|
52
+ | Approach | `--remote-debugging-port` | Playwright extension | OpenCLI extension | Custom extension + persistent WebSocket |
53
+ | Install | one flag | `npm i -g` (~200MB) | npm / desktop app | `pip install` (stdlib only, zero deps) |
54
+ | Login state | ❌ fresh instance | ✅ | ✅ | ✅ |
55
+ | Modify the extension | — | Edit Playwright source | Edit OpenCLI source | ✅ right in this repo |
56
+ | Self-healing | ❌ | ❌ | ❌ | ✅ Agent edits helpers at runtime |
57
+ | MCP | ❌ | ✅ (separate `@playwright/mcp`) | ❌ | ✅ built in, 45 tools via `nekoro-browser-mcp` |
58
+
59
+ ## Quick Start
60
+
61
+ **1 — Install** (Python 3.12+, zero third-party dependencies)
62
+
63
+ ```bash
64
+ git clone https://github.com/zeshuochen/nekoro-browser
65
+ cd nekoro-browser
66
+ pip install -e .
67
+ ```
68
+
69
+ **2 — Load the extension**
70
+
71
+ ```bash
72
+ nekoro-browser setup
73
+ ```
74
+
75
+ `setup` copies the extension directory to your clipboard and then waits — up to three
76
+ minutes — until the extension actually connects, so you find out it worked instead of
77
+ guessing. Meanwhile you do the part Chrome reserves for humans: open `chrome://extensions/`,
78
+ turn on **Developer mode**, click **Load unpacked**, paste the directory.
79
+
80
+ **3 — Start the daemon** — give it its own terminal and **leave it open**; it runs in the
81
+ foreground and closing that window stops it
82
+
83
+ ```bash
84
+ nekoro-browser
85
+ ```
86
+
87
+ **4 — Drive the browser** from anywhere else
88
+
89
+ ```bash
90
+ echo "page_info()" | nekoro-browser
91
+ # → {"ok": true, "result": {"title": "...", "url": "..."}}
92
+ ```
93
+
94
+ That's it. If step 4 says the daemon isn't running, or a command times out, run
95
+ `nekoro-browser --doctor` — it checks the daemon, the extension and the service worker
96
+ separately and tells you which one is down.
97
+
98
+ ## Examples
99
+
100
+ Send a multi-step flow in one shot with a heredoc. Every helper is a top-level `await`:
101
+
102
+ ```bash
103
+ nekoro-browser <<'PY'
104
+ await new_tab("https://example.com")
105
+ print((await page_info())["title"]) # Example Domain
106
+ print((await get_markdown(max_chars=200))["result"])
107
+ print((await state(max_items=3))["result"]) # indexed interactive elements, model-ready
108
+ await close_tab()
109
+ PY
110
+ ```
111
+
112
+ `state()` numbers the elements and `click_index(n)` clicks by number — the model never has to guess a CSS selector:
113
+
114
+ ```bash
115
+ nekoro-browser <<'PY'
116
+ await navigate("https://github.com/search?q=browser+automation&type=repositories")
117
+ await wait_for_load()
118
+ print((await state(max_items=40))["result"])
119
+ await click_index(12)
120
+ PY
121
+ ```
122
+
123
+ All helpers are documented in [SKILL.md](SKILL.md).
124
+
125
+ ## MCP (Cursor / Cline / Claude Desktop)
126
+
127
+ Every function in `helpers.py` is reflected into an MCP tool (45 today) — no glue code:
128
+
129
+ ```json
130
+ {
131
+ "mcpServers": {
132
+ "nekoro-browser": {
133
+ "command": "nekoro-browser-mcp"
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ The daemon still has to be running in another terminal (`nekoro-browser`) — the MCP server just forwards tool calls to it over the same authenticated path as `echo ... | nekoro-browser`. Two escape hatches ship as tools: `cdp` (raw CDP command) and `exec_python` (arbitrary Python in the daemon namespace — a whole multi-step flow in one round trip).
140
+
141
+ Screenshots come back as image content so clients can render them. A helper's own failure (`{"ok": false}`) is surfaced as `isError` rather than being dressed up as success.
142
+
143
+ ## API
144
+
145
+ | Category | Commands |
146
+ |----------|----------|
147
+ | Navigation | `navigate(url)`, `new_tab(url)`, `list_tabs()`, `switch_tab(id)`, `close_tab(id)` |
148
+ | Page info | `page_info()`, `page_html()`, `page_text()`, `get_markdown()`, `state()` |
149
+ | JavaScript | `js(code)`, `cdp(method, **p)`, `cdp_batch(*cmds)` |
150
+ | Interaction | `click_selector(sel)`, `click_index(n)`, `click_at_xy(x,y)`, `type_text(t)`, `fill_input(sel,t)`, `press_key(k)`, `upload_file(sel,path)` |
151
+ | Dialogs | `dialog_off()`, `get_last_dialog()` |
152
+ | Waiting | `wait_for_load()`, `wait_selector(sel)`, `wait_for_network_idle()`, `sleep(s)` |
153
+ | Screenshots | `capture_screenshot()`, `capture_screenshot("jpeg", 90)` |
154
+
155
+ ## Architecture
156
+
157
+ ```
158
+ Chrome extension (background.js) —— chrome.debugger / CDP
159
+ ↕ persistent WebSocket
160
+ Python daemon (127.0.0.1:28417)
161
+ ↕ HTTP /exec (token auth)
162
+ CLI (nekoro-browser) · MCP server (nekoro-browser-mcp)
163
+ ```
164
+
165
+ `helpers.py` (46 thin wrappers) → CDP commands, each ≤10 lines, none of them aware of any particular website.
166
+
167
+ `lifecycle.py` manages the daemon: pid file + process fingerprint (avoids killing a reused pid), self-heal on stale daemon (CDP probe fails → auto cleanup and restart), localhost requests bypass the system proxy.
168
+
169
+ The extension is hardened against MV3 service worker eviction: a `content_scripts` heartbeat (an independent wake vector living in the page, reconnects and wakes the SW even after it's killed) + `onStartup` (reconnects instantly on Chrome cold start) + reattaches the last-driven tab after a restart instead of drifting to a blank tab.
170
+
171
+ ## CLI
172
+
173
+ | Command | What it does |
174
+ |---------|---------------|
175
+ | `nekoro-browser` | Start the daemon (foreground) |
176
+ | `nekoro-browser setup` | Guided install: extension path + opens chrome://extensions + waits for it to connect |
177
+ | `nekoro-browser --doctor` | End-to-end diagnostic (daemon + extension + SW all alive?) |
178
+ | `nekoro-browser --stop` | Stop the daemon |
179
+ | `nekoro-browser --restart` | Stop and restart (foreground) |
180
+ | `nekoro-browser --reload-ext` | Reload the extension's service worker — run before a batch job for a clean state |
181
+ | `nekoro-browser --extension-path` | Print the extension directory (for "Load unpacked") |
182
+ | `nekoro-browser --port N` | Run the daemon on port N (default 28417) |
183
+ | `nekoro-browser -c "code"` | Run one snippet, print the result |
184
+ | `nekoro-browser --timeout N` | Seconds to allow a snippet (default 120 — page loads are slow) |
185
+ | `echo "code" \| nekoro-browser` | Pipe mode (daemon must already be running) |
186
+
187
+ ## Configuration
188
+
189
+ The daemon listens on **28417** by default. To change it:
190
+
191
+ | Side | How |
192
+ |------|-----|
193
+ | Python (daemon + CLI + MCP) | `nekoro-browser --port 30500`, or set `NEKORO_PORT=30500` |
194
+ | Extension | Extension details → **Extension options** → set the port → Save (reconnects immediately, no reload) |
195
+
196
+ Both sides must agree. Clients don't need the flag repeated: the daemon records its
197
+ actual port in `<data dir>/port`, so a plain `echo ... | nekoro-browser` finds a daemon
198
+ running on a non-default port. Precedence is `--port` > `NEKORO_PORT` > that file > default.
199
+
200
+ ## Self-Healing
201
+
202
+ `src/nekoro_browser/agent_helpers.py` is editable at runtime and reloaded on every `/exec`. When an agent hits a gap, it appends the missing function there — effective on the next call, no daemon restart, no extension reload.
203
+
204
+ `domain-skills/` is where site knowledge goes (page structure, selectors, gotchas) — Markdown only, and empty by default since everyone automates different sites. Write a workflow against your notes, drop it into `agent_helpers.py`, same convention (`daemon` as first argument). See [`domain-skills/README.md`](domain-skills/README.md).
205
+
206
+ ## Platform Support
207
+
208
+ | Platform | Status |
209
+ |----------|--------|
210
+ | Windows | Primary development platform, exercised end to end |
211
+ | Linux / macOS | The code has the branches (XDG dirs, `chmod 600` token, `/proc` and `ps` liveness probes) and CI runs the unit tests on all three, **but the full "Chrome + extension" loop has never been run on a real macOS/Linux box** — reports welcome |
212
+
213
+ ## Known Limitations
214
+
215
+ - **Unpacked extensions get disabled by Chrome.** An extension installed via "Load unpacked" may be switched off automatically after a Chrome update or restart, or hidden behind the "Disable developer mode extensions" prompt. When `--doctor` reports Extension/SW not responding, re-enable it in `chrome://extensions/` first. This project is **not published to the Chrome Web Store**, so the limitation is not going away soon.
216
+ - **Service worker keepalive is not 100%.** MV3 eviction timing is Chrome's call. The heartbeat + `onStartup` + reattach cover the vast majority of cases, but unattended long-running cron jobs should still health-check with `--doctor` and retry.
217
+ - **One active tab at a time.** Tabs can be listed and switched (`list_tabs` / `switch_tab`), but commands always go to the current active tab — there are no parallel sessions.
218
+ - **The MCP server handles requests serially.** During a `wait_selector(timeout=90)` every other request on that connection (including `ping`) queues behind it. Open separate client connections if you need concurrency.
219
+
220
+ ## Troubleshooting
221
+
222
+ | Symptom | Cause | Fix |
223
+ |---------|-------|-----|
224
+ | `Daemon not running` | Daemon not started | Run `nekoro-browser` in terminal 1 |
225
+ | CDP timeout | Extension not connected / service worker asleep | `nekoro-browser --doctor` to diagnose; try `--reload-ext` or manually reload in `chrome://extensions` |
226
+ | Extension disabled by Chrome | Unpacked extension + Chrome update | Re-enable it in `chrome://extensions/`, then re-run `--doctor` |
227
+ | Page unchanged | Extension not attached to tab | Open a regular (non-chrome://) page, restart daemon |
228
+ | Port in use | Stale process | Kill the process on port 28417, or just run `nekoro-browser --stop` |
229
+
230
+ ## Security
231
+
232
+ The daemon listens on `127.0.0.1` and `/exec` runs arbitrary Python, so the transport is guarded:
233
+
234
+ - **CLI / MCP → daemon** (`/exec`, `/raw`): a per-session token is written to a user-private file (`%LOCALAPPDATA%\nekoro-browser\token`, `chmod 600` on POSIX). Clients read it and send `X-Nekoro-Token`; missing/wrong token → `403`. Web pages and remote hosts can't read local files, so they can't obtain it. `/ping` stays open.
235
+ - **Extension → daemon** (`/ws`): the handshake `Origin` must be `chrome-extension://…`; a web page's `WebSocket` to localhost carries its own origin and is rejected.
236
+
237
+ Same-user local processes can read the token file — that boundary matches the OS user account, as with browser-harness's `chmod 600`.
238
+
239
+ ## Feedback
240
+
241
+ Hit a problem, or missing a helper you need? Open an
242
+ [issue](https://github.com/zeshuochen/nekoro-browser/issues).
243
+ For bugs, include the output of `nekoro-browser --doctor`, your Chrome version and OS — saves a round trip.
244
+
245
+ PRs welcome. Run the tests first: `for f in tests/test_*.py; do python "$f"; done` (CI runs them on all three platforms too).
246
+
247
+ ---
248
+
249
+ ## Acknowledgments
250
+
251
+ Core architecture derived from:
252
+
253
+ - **[browser-harness](https://github.com/browser-use/browser-harness)** — thin-wrapper philosophy (each function is a CDP alias, ≤10 lines), pipe mode, self-healing `agent_helpers.py`, domain-skills directory structure, `cdp()` raw access
254
+ - **[browser-act](https://github.com/browser-act/skills)** — `state()` indexed element tree, `*[N]` change markers, `waitSelector()` state polling, `getMarkdown()` page extraction
255
+ - **[Playwright](https://github.com/microsoft/playwright)** — CDP `Input.dispatchMouseEvent` real mouse events (`isTrusted:true`), extension + daemon dual-path architecture
@@ -0,0 +1,230 @@
1
+ <p align="center">
2
+ <img src="extension/icons/icon-128.png" width="80" alt="nekoro-browser">
3
+ </p>
4
+
5
+ <h1 align="center">nekoro-browser</h1>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/zeshuochen/nekoro-browser/actions/workflows/tests.yml"><img src="https://github.com/zeshuochen/nekoro-browser/actions/workflows/tests.yml/badge.svg" alt="tests"></a>
9
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="Python 3.12+"></a>
10
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
11
+ <a href="#mcp-cursor--cline--claude-desktop"><img src="https://img.shields.io/badge/MCP-supported-8A2BE2" alt="MCP supported"></a>
12
+ </p>
13
+
14
+ <p align="center">
15
+ Lightweight browser automation CLI + MCP server. Drives your everyday Chrome through an extension — <b>keeps your login state</b>, <b>no debug port</b>, <b>no banners</b>.<br>
16
+ <sub><a href="README.zh-CN.md">中文</a></sub>
17
+ </p>
18
+
19
+ ---
20
+
21
+ ## Why Not `--remote-debugging-port`?
22
+
23
+ Since Chrome 136, `--remote-debugging-port` / `--remote-debugging-pipe` **refuse the default profile** — you must point Chrome at a non-default `--user-data-dir`, i.e. a clean instance with none of your logins. An extension's `chrome.debugger` is not subject to that restriction, which is why nekoro goes through an extension.
24
+
25
+ | | CDP WebSocket | playwright-cli | opencli | **nekoro-browser** |
26
+ |------|:--:|:--:|:--:|:--:|
27
+ | Approach | `--remote-debugging-port` | Playwright extension | OpenCLI extension | Custom extension + persistent WebSocket |
28
+ | Install | one flag | `npm i -g` (~200MB) | npm / desktop app | `pip install` (stdlib only, zero deps) |
29
+ | Login state | ❌ fresh instance | ✅ | ✅ | ✅ |
30
+ | Modify the extension | — | Edit Playwright source | Edit OpenCLI source | ✅ right in this repo |
31
+ | Self-healing | ❌ | ❌ | ❌ | ✅ Agent edits helpers at runtime |
32
+ | MCP | ❌ | ✅ (separate `@playwright/mcp`) | ❌ | ✅ built in, 45 tools via `nekoro-browser-mcp` |
33
+
34
+ ## Quick Start
35
+
36
+ **1 — Install** (Python 3.12+, zero third-party dependencies)
37
+
38
+ ```bash
39
+ git clone https://github.com/zeshuochen/nekoro-browser
40
+ cd nekoro-browser
41
+ pip install -e .
42
+ ```
43
+
44
+ **2 — Load the extension**
45
+
46
+ ```bash
47
+ nekoro-browser setup
48
+ ```
49
+
50
+ `setup` copies the extension directory to your clipboard and then waits — up to three
51
+ minutes — until the extension actually connects, so you find out it worked instead of
52
+ guessing. Meanwhile you do the part Chrome reserves for humans: open `chrome://extensions/`,
53
+ turn on **Developer mode**, click **Load unpacked**, paste the directory.
54
+
55
+ **3 — Start the daemon** — give it its own terminal and **leave it open**; it runs in the
56
+ foreground and closing that window stops it
57
+
58
+ ```bash
59
+ nekoro-browser
60
+ ```
61
+
62
+ **4 — Drive the browser** from anywhere else
63
+
64
+ ```bash
65
+ echo "page_info()" | nekoro-browser
66
+ # → {"ok": true, "result": {"title": "...", "url": "..."}}
67
+ ```
68
+
69
+ That's it. If step 4 says the daemon isn't running, or a command times out, run
70
+ `nekoro-browser --doctor` — it checks the daemon, the extension and the service worker
71
+ separately and tells you which one is down.
72
+
73
+ ## Examples
74
+
75
+ Send a multi-step flow in one shot with a heredoc. Every helper is a top-level `await`:
76
+
77
+ ```bash
78
+ nekoro-browser <<'PY'
79
+ await new_tab("https://example.com")
80
+ print((await page_info())["title"]) # Example Domain
81
+ print((await get_markdown(max_chars=200))["result"])
82
+ print((await state(max_items=3))["result"]) # indexed interactive elements, model-ready
83
+ await close_tab()
84
+ PY
85
+ ```
86
+
87
+ `state()` numbers the elements and `click_index(n)` clicks by number — the model never has to guess a CSS selector:
88
+
89
+ ```bash
90
+ nekoro-browser <<'PY'
91
+ await navigate("https://github.com/search?q=browser+automation&type=repositories")
92
+ await wait_for_load()
93
+ print((await state(max_items=40))["result"])
94
+ await click_index(12)
95
+ PY
96
+ ```
97
+
98
+ All helpers are documented in [SKILL.md](SKILL.md).
99
+
100
+ ## MCP (Cursor / Cline / Claude Desktop)
101
+
102
+ Every function in `helpers.py` is reflected into an MCP tool (45 today) — no glue code:
103
+
104
+ ```json
105
+ {
106
+ "mcpServers": {
107
+ "nekoro-browser": {
108
+ "command": "nekoro-browser-mcp"
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ The daemon still has to be running in another terminal (`nekoro-browser`) — the MCP server just forwards tool calls to it over the same authenticated path as `echo ... | nekoro-browser`. Two escape hatches ship as tools: `cdp` (raw CDP command) and `exec_python` (arbitrary Python in the daemon namespace — a whole multi-step flow in one round trip).
115
+
116
+ Screenshots come back as image content so clients can render them. A helper's own failure (`{"ok": false}`) is surfaced as `isError` rather than being dressed up as success.
117
+
118
+ ## API
119
+
120
+ | Category | Commands |
121
+ |----------|----------|
122
+ | Navigation | `navigate(url)`, `new_tab(url)`, `list_tabs()`, `switch_tab(id)`, `close_tab(id)` |
123
+ | Page info | `page_info()`, `page_html()`, `page_text()`, `get_markdown()`, `state()` |
124
+ | JavaScript | `js(code)`, `cdp(method, **p)`, `cdp_batch(*cmds)` |
125
+ | Interaction | `click_selector(sel)`, `click_index(n)`, `click_at_xy(x,y)`, `type_text(t)`, `fill_input(sel,t)`, `press_key(k)`, `upload_file(sel,path)` |
126
+ | Dialogs | `dialog_off()`, `get_last_dialog()` |
127
+ | Waiting | `wait_for_load()`, `wait_selector(sel)`, `wait_for_network_idle()`, `sleep(s)` |
128
+ | Screenshots | `capture_screenshot()`, `capture_screenshot("jpeg", 90)` |
129
+
130
+ ## Architecture
131
+
132
+ ```
133
+ Chrome extension (background.js) —— chrome.debugger / CDP
134
+ ↕ persistent WebSocket
135
+ Python daemon (127.0.0.1:28417)
136
+ ↕ HTTP /exec (token auth)
137
+ CLI (nekoro-browser) · MCP server (nekoro-browser-mcp)
138
+ ```
139
+
140
+ `helpers.py` (46 thin wrappers) → CDP commands, each ≤10 lines, none of them aware of any particular website.
141
+
142
+ `lifecycle.py` manages the daemon: pid file + process fingerprint (avoids killing a reused pid), self-heal on stale daemon (CDP probe fails → auto cleanup and restart), localhost requests bypass the system proxy.
143
+
144
+ The extension is hardened against MV3 service worker eviction: a `content_scripts` heartbeat (an independent wake vector living in the page, reconnects and wakes the SW even after it's killed) + `onStartup` (reconnects instantly on Chrome cold start) + reattaches the last-driven tab after a restart instead of drifting to a blank tab.
145
+
146
+ ## CLI
147
+
148
+ | Command | What it does |
149
+ |---------|---------------|
150
+ | `nekoro-browser` | Start the daemon (foreground) |
151
+ | `nekoro-browser setup` | Guided install: extension path + opens chrome://extensions + waits for it to connect |
152
+ | `nekoro-browser --doctor` | End-to-end diagnostic (daemon + extension + SW all alive?) |
153
+ | `nekoro-browser --stop` | Stop the daemon |
154
+ | `nekoro-browser --restart` | Stop and restart (foreground) |
155
+ | `nekoro-browser --reload-ext` | Reload the extension's service worker — run before a batch job for a clean state |
156
+ | `nekoro-browser --extension-path` | Print the extension directory (for "Load unpacked") |
157
+ | `nekoro-browser --port N` | Run the daemon on port N (default 28417) |
158
+ | `nekoro-browser -c "code"` | Run one snippet, print the result |
159
+ | `nekoro-browser --timeout N` | Seconds to allow a snippet (default 120 — page loads are slow) |
160
+ | `echo "code" \| nekoro-browser` | Pipe mode (daemon must already be running) |
161
+
162
+ ## Configuration
163
+
164
+ The daemon listens on **28417** by default. To change it:
165
+
166
+ | Side | How |
167
+ |------|-----|
168
+ | Python (daemon + CLI + MCP) | `nekoro-browser --port 30500`, or set `NEKORO_PORT=30500` |
169
+ | Extension | Extension details → **Extension options** → set the port → Save (reconnects immediately, no reload) |
170
+
171
+ Both sides must agree. Clients don't need the flag repeated: the daemon records its
172
+ actual port in `<data dir>/port`, so a plain `echo ... | nekoro-browser` finds a daemon
173
+ running on a non-default port. Precedence is `--port` > `NEKORO_PORT` > that file > default.
174
+
175
+ ## Self-Healing
176
+
177
+ `src/nekoro_browser/agent_helpers.py` is editable at runtime and reloaded on every `/exec`. When an agent hits a gap, it appends the missing function there — effective on the next call, no daemon restart, no extension reload.
178
+
179
+ `domain-skills/` is where site knowledge goes (page structure, selectors, gotchas) — Markdown only, and empty by default since everyone automates different sites. Write a workflow against your notes, drop it into `agent_helpers.py`, same convention (`daemon` as first argument). See [`domain-skills/README.md`](domain-skills/README.md).
180
+
181
+ ## Platform Support
182
+
183
+ | Platform | Status |
184
+ |----------|--------|
185
+ | Windows | Primary development platform, exercised end to end |
186
+ | Linux / macOS | The code has the branches (XDG dirs, `chmod 600` token, `/proc` and `ps` liveness probes) and CI runs the unit tests on all three, **but the full "Chrome + extension" loop has never been run on a real macOS/Linux box** — reports welcome |
187
+
188
+ ## Known Limitations
189
+
190
+ - **Unpacked extensions get disabled by Chrome.** An extension installed via "Load unpacked" may be switched off automatically after a Chrome update or restart, or hidden behind the "Disable developer mode extensions" prompt. When `--doctor` reports Extension/SW not responding, re-enable it in `chrome://extensions/` first. This project is **not published to the Chrome Web Store**, so the limitation is not going away soon.
191
+ - **Service worker keepalive is not 100%.** MV3 eviction timing is Chrome's call. The heartbeat + `onStartup` + reattach cover the vast majority of cases, but unattended long-running cron jobs should still health-check with `--doctor` and retry.
192
+ - **One active tab at a time.** Tabs can be listed and switched (`list_tabs` / `switch_tab`), but commands always go to the current active tab — there are no parallel sessions.
193
+ - **The MCP server handles requests serially.** During a `wait_selector(timeout=90)` every other request on that connection (including `ping`) queues behind it. Open separate client connections if you need concurrency.
194
+
195
+ ## Troubleshooting
196
+
197
+ | Symptom | Cause | Fix |
198
+ |---------|-------|-----|
199
+ | `Daemon not running` | Daemon not started | Run `nekoro-browser` in terminal 1 |
200
+ | CDP timeout | Extension not connected / service worker asleep | `nekoro-browser --doctor` to diagnose; try `--reload-ext` or manually reload in `chrome://extensions` |
201
+ | Extension disabled by Chrome | Unpacked extension + Chrome update | Re-enable it in `chrome://extensions/`, then re-run `--doctor` |
202
+ | Page unchanged | Extension not attached to tab | Open a regular (non-chrome://) page, restart daemon |
203
+ | Port in use | Stale process | Kill the process on port 28417, or just run `nekoro-browser --stop` |
204
+
205
+ ## Security
206
+
207
+ The daemon listens on `127.0.0.1` and `/exec` runs arbitrary Python, so the transport is guarded:
208
+
209
+ - **CLI / MCP → daemon** (`/exec`, `/raw`): a per-session token is written to a user-private file (`%LOCALAPPDATA%\nekoro-browser\token`, `chmod 600` on POSIX). Clients read it and send `X-Nekoro-Token`; missing/wrong token → `403`. Web pages and remote hosts can't read local files, so they can't obtain it. `/ping` stays open.
210
+ - **Extension → daemon** (`/ws`): the handshake `Origin` must be `chrome-extension://…`; a web page's `WebSocket` to localhost carries its own origin and is rejected.
211
+
212
+ Same-user local processes can read the token file — that boundary matches the OS user account, as with browser-harness's `chmod 600`.
213
+
214
+ ## Feedback
215
+
216
+ Hit a problem, or missing a helper you need? Open an
217
+ [issue](https://github.com/zeshuochen/nekoro-browser/issues).
218
+ For bugs, include the output of `nekoro-browser --doctor`, your Chrome version and OS — saves a round trip.
219
+
220
+ PRs welcome. Run the tests first: `for f in tests/test_*.py; do python "$f"; done` (CI runs them on all three platforms too).
221
+
222
+ ---
223
+
224
+ ## Acknowledgments
225
+
226
+ Core architecture derived from:
227
+
228
+ - **[browser-harness](https://github.com/browser-use/browser-harness)** — thin-wrapper philosophy (each function is a CDP alias, ≤10 lines), pipe mode, self-healing `agent_helpers.py`, domain-skills directory structure, `cdp()` raw access
229
+ - **[browser-act](https://github.com/browser-act/skills)** — `state()` indexed element tree, `*[N]` change markers, `waitSelector()` state polling, `getMarkdown()` page extraction
230
+ - **[Playwright](https://github.com/microsoft/playwright)** — CDP `Input.dispatchMouseEvent` real mouse events (`isTrusted:true`), extension + daemon dual-path architecture