open-browser-setup 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +65 -0
- package/bin/setup.js +5 -0
- package/package.json +43 -0
- package/skill/SKILL.md +267 -0
- package/src/clients.js +138 -0
- package/src/extension.js +23 -0
- package/src/index.js +115 -0
- package/src/packages.js +38 -0
- package/src/skill.js +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 samonjoat
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Open Browser Setup
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/open-browser-setup)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
Interactive installer for the Open Browser ecosystem. One command to set up everything you need to control your real Chrome browser with AI agents.
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx open-browser-setup
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The installer walks you through:
|
|
15
|
+
|
|
16
|
+
1. **Package installation** — MCP server, CLI tool, or both
|
|
17
|
+
2. **Skill installation** — Claude Code `/open-browser` skill for the CLI
|
|
18
|
+
3. **MCP client configuration** — auto-configures Claude Code, Cursor, Windsurf, Codex, OpenCode, or Pi
|
|
19
|
+
4. **Chrome extension** — opens the Chrome Web Store for the Open Browser Bridge extension
|
|
20
|
+
|
|
21
|
+
## What gets installed
|
|
22
|
+
|
|
23
|
+
### MCP Server (`open-browser-mcp`)
|
|
24
|
+
|
|
25
|
+
58 browser automation tools exposed via the Model Context Protocol. Works with any MCP-compatible client (Claude Code, Cursor, Windsurf, etc.).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm install -g open-browser-mcp
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### CLI Tool (`open-browser-cli`)
|
|
32
|
+
|
|
33
|
+
Direct terminal commands for browser control. Token-efficient alternative to MCP — no tool registration overhead.
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm install -g open-browser-cli
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Claude Code Skill
|
|
40
|
+
|
|
41
|
+
The `/open-browser` skill loads on demand in Claude Code, giving the agent full browser control without consuming context window tokens for tool schemas.
|
|
42
|
+
|
|
43
|
+
### Chrome Extension
|
|
44
|
+
|
|
45
|
+
The [Open Browser Bridge](https://chromewebstore.google.com/detail/open-browser-bridge) Chrome extension connects the MCP server or CLI to your real browser via WebSocket + CDP.
|
|
46
|
+
|
|
47
|
+
## Supported MCP Clients
|
|
48
|
+
|
|
49
|
+
| Client | Config File | Auto-configured |
|
|
50
|
+
|--------|-------------|:-:|
|
|
51
|
+
| Claude Code | `~/.claude/settings.json` | Yes |
|
|
52
|
+
| Cursor | `~/.cursor/mcp.json` | Yes |
|
|
53
|
+
| Windsurf | `~/.codeium/windsurf/mcp_config.json` | Yes |
|
|
54
|
+
| Codex | `~/.codex/config.toml` | Yes |
|
|
55
|
+
| OpenCode | `~/.config/opencode/opencode.json` | Yes |
|
|
56
|
+
| Pi (omp) | `~/.omp/mcp.json` | Yes |
|
|
57
|
+
|
|
58
|
+
## Requirements
|
|
59
|
+
|
|
60
|
+
- Node.js >= 18
|
|
61
|
+
- Chrome (any recent version)
|
|
62
|
+
|
|
63
|
+
## License
|
|
64
|
+
|
|
65
|
+
MIT
|
package/bin/setup.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "open-browser-setup",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Interactive installer for Open Browser — MCP server, CLI tool, skill, and Chrome extension setup",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"open-browser-setup": "bin/setup.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"skill/",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"open-browser",
|
|
18
|
+
"mcp",
|
|
19
|
+
"browser",
|
|
20
|
+
"chrome",
|
|
21
|
+
"automation",
|
|
22
|
+
"setup",
|
|
23
|
+
"installer",
|
|
24
|
+
"cli",
|
|
25
|
+
"ai",
|
|
26
|
+
"agent",
|
|
27
|
+
"skill"
|
|
28
|
+
],
|
|
29
|
+
"author": "samonjoat <samuelodira@gmail.com>",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/samonjoat/open-browser-setup.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/samonjoat/open-browser-setup#readme",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@clack/prompts": "^0.11.0",
|
|
41
|
+
"picocolors": "^1.1.0"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: open-browser
|
|
3
|
+
description: >
|
|
4
|
+
Control the user's real Chrome browser from the terminal via the open-browser CLI.
|
|
5
|
+
A token-efficient alternative to open-browser-mcp — loads on demand instead of
|
|
6
|
+
registering 58 MCP tools in context. Use this skill whenever the user wants to:
|
|
7
|
+
browse a website, open a URL, navigate to a page, take a screenshot of a website,
|
|
8
|
+
interact with a web page, fill out a form online, check or test a website, read or
|
|
9
|
+
scrape web content, automate browser tasks, click something in Chrome, verify page
|
|
10
|
+
content, test a web UI, or do anything involving a real browser. Also triggers on
|
|
11
|
+
/open-browser or /browser. Prefer this over launching Puppeteer, Playwright, or any
|
|
12
|
+
sandboxed browser — this uses the user's actual Chrome profile with their cookies,
|
|
13
|
+
sessions, extensions, and bookmarks.
|
|
14
|
+
allowed-tools:
|
|
15
|
+
- Bash
|
|
16
|
+
- Read
|
|
17
|
+
user-invocable: true
|
|
18
|
+
argument-hint: "describe what you want to do in the browser"
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# Open Browser
|
|
22
|
+
|
|
23
|
+
Control the user's real Chrome browser via the `open-browser` CLI. Every command runs as a shell call and exits — the browser state persists through the Chrome extension between commands.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- `open-browser-cli` installed globally (`npm install -g open-browser-cli`)
|
|
28
|
+
- The Open Browser Bridge Chrome extension loaded in Chrome (shared with open-browser-mcp)
|
|
29
|
+
- The open-browser-mcp server must NOT be running (they share the same WebSocket port)
|
|
30
|
+
|
|
31
|
+
## Core Workflow
|
|
32
|
+
|
|
33
|
+
Every browser session follows this pattern:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
1. open-browser session ← required first step, creates tab group + CDP
|
|
37
|
+
2. open-browser navigate <url> ← go to a page
|
|
38
|
+
3. open-browser snapshot --json ← see interactive elements with @eN refs
|
|
39
|
+
4. open-browser click @e5 ← interact using refs from snapshot
|
|
40
|
+
5. open-browser verify text "…" ← confirm the result
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Always start with `session`. Always use `snapshot --json` to discover elements before interacting. The `@eN` refs (like `@e1`, `@e5`, `@e12`) are how you target elements for click, type, select, and other interactions.
|
|
44
|
+
|
|
45
|
+
## Using --json
|
|
46
|
+
|
|
47
|
+
Add `--json` to any command when you need to parse the output programmatically. Without it, output is human-readable text. Use `--json` for snapshot, read, markdown, find, network, cookies, tabs, and screenshot commands. For simple action commands (click, type, key, navigate), text output is usually fine.
|
|
48
|
+
|
|
49
|
+
## Command Reference
|
|
50
|
+
|
|
51
|
+
### Session & Navigation
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
open-browser session # REQUIRED first step
|
|
55
|
+
open-browser navigate <url> # go to URL (--wait networkIdle for SPAs)
|
|
56
|
+
open-browser navigate <url> --wait networkIdle # wait for all network to settle
|
|
57
|
+
open-browser close # close active tab
|
|
58
|
+
open-browser close --tab-id 123 # close specific tab
|
|
59
|
+
open-browser back # browser back
|
|
60
|
+
open-browser forward # browser forward
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
External domains trigger a permission popup in Chrome — the user must approve. This is expected behavior. The navigate command has a 5-minute timeout to allow for this.
|
|
64
|
+
|
|
65
|
+
### Reading & Inspection
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
open-browser snapshot --json # primary tool — get all interactive elements
|
|
69
|
+
open-browser read --json # full page text content
|
|
70
|
+
open-browser read --format html --json # raw HTML source
|
|
71
|
+
open-browser markdown --json # clean markdown, boilerplate removed
|
|
72
|
+
open-browser markdown --full --json # include nav/footer/sidebar
|
|
73
|
+
open-browser find "div.error" --json # search by CSS selector
|
|
74
|
+
open-browser find "Welcome back" --json # search by text
|
|
75
|
+
open-browser js "document.title" --json # execute JavaScript
|
|
76
|
+
open-browser console --json # read console messages
|
|
77
|
+
open-browser console --pattern "error" --json # filter console by pattern
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The snapshot returns elements like:
|
|
81
|
+
```
|
|
82
|
+
@e1 link "Home" href="/home"
|
|
83
|
+
@e2 textbox "" placeholder="Search..."
|
|
84
|
+
@e5 button "Sign In"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use these `@eN` refs in click, type, hover, select, and drag commands.
|
|
88
|
+
|
|
89
|
+
### Input & Interaction
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
open-browser click @e5 # click element
|
|
93
|
+
open-browser click @e5 --button right # right-click
|
|
94
|
+
open-browser click @e5 --count 2 # double-click
|
|
95
|
+
open-browser type @e2 "hello world" # type into input (clears first)
|
|
96
|
+
open-browser type @e2 "search term" --slowly # char-by-char for autocomplete
|
|
97
|
+
open-browser key Enter # press key
|
|
98
|
+
open-browser key Tab # tab to next field
|
|
99
|
+
open-browser key "Ctrl+A" # select all
|
|
100
|
+
open-browser hover @e3 # hover to reveal menus
|
|
101
|
+
open-browser select @e4 "Option B" # select dropdown value
|
|
102
|
+
open-browser drag @e1 @e5 # drag element to target
|
|
103
|
+
open-browser upload @e3 /path/to/file.pdf # upload file
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Verification
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
open-browser verify text "Welcome back" # PASS/FAIL — is text on page?
|
|
110
|
+
open-browser verify element "#submit-btn" # PASS/FAIL — is element visible?
|
|
111
|
+
open-browser verify list "Item A" "Item B" # per-item PASS/FAIL
|
|
112
|
+
open-browser verify value "#email" "user@example.com" # check input value
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Screenshots & Media
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
open-browser screenshot --json # viewport screenshot
|
|
119
|
+
open-browser screenshot --full-page --json # full scrollable page
|
|
120
|
+
open-browser screenshot --filename "login.png" --json # custom name
|
|
121
|
+
open-browser pdf --json # save page as PDF
|
|
122
|
+
open-browser resize 1920 1080 # resize window
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Screenshots save to `.open-browser-mcp/` in the current directory. Use the Read tool to view screenshot files afterward.
|
|
126
|
+
|
|
127
|
+
### Tabs
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
open-browser tabs --json # list all tabs (id, title, url)
|
|
131
|
+
open-browser tab create https://example.com # open new tab
|
|
132
|
+
open-browser tab switch 12345 # switch to tab by ID
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Waiting & Scrolling
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
open-browser wait --time 2000 # wait 2 seconds
|
|
139
|
+
open-browser wait --selector ".loaded" # wait for element to appear
|
|
140
|
+
open-browser wait-text "Loading complete" --timeout 10000 # poll for text
|
|
141
|
+
open-browser scroll --down 500 # scroll down 500px
|
|
142
|
+
open-browser scroll --up 300 # scroll up
|
|
143
|
+
open-browser scroll --ref @e15 # scroll element into view
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Network & Cookies
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
open-browser network --json # captured network requests
|
|
150
|
+
open-browser network --url-pattern "api" --json # filter by URL
|
|
151
|
+
open-browser cookies --json # read cookies
|
|
152
|
+
open-browser set-cookie session_id abc123 .example.com # set cookie
|
|
153
|
+
open-browser clear-storage # clear all storage
|
|
154
|
+
open-browser block-urls "*ads*" "*.tracker.com*" # block URL patterns
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Device Emulation
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
open-browser emulate # default iPhone (375x812)
|
|
161
|
+
open-browser emulate --width 768 --height 1024 --no-mobile # tablet
|
|
162
|
+
open-browser emulate --network "Slow 3G" # throttle network
|
|
163
|
+
open-browser geolocation 37.7749 -122.4194 # set GPS (San Francisco)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Batch Operations
|
|
167
|
+
|
|
168
|
+
For multi-step interactions in a single call — faster than individual commands:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
open-browser batch '[{"action":"click","ref":"@e1"},{"action":"type","ref":"@e2","text":"hello"},{"action":"press_key","key":"Enter"}]'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Supported actions: `click`, `type`, `select_option`, `press_key`, `wait`, `scroll`, `mouse_click_xy`.
|
|
175
|
+
|
|
176
|
+
### Advanced
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
# Captcha handling
|
|
180
|
+
open-browser solve-captcha # detect and click captcha checkbox
|
|
181
|
+
open-browser captcha-tiles --json # analyze tile grid after checkbox
|
|
182
|
+
open-browser batch-click @t1 @t4 @t7 --human-like # click tiles
|
|
183
|
+
|
|
184
|
+
# DevTools
|
|
185
|
+
open-browser a11y-tree --json # accessibility tree
|
|
186
|
+
open-browser audit-a11y --json # accessibility audit
|
|
187
|
+
open-browser memory --json # JS heap, DOM nodes, layout
|
|
188
|
+
open-browser locator @e5 --json # generate CSS/XPath selectors
|
|
189
|
+
open-browser trace start # start performance trace
|
|
190
|
+
open-browser trace stop --json # stop and get results
|
|
191
|
+
|
|
192
|
+
# Script injection (persists across navigations)
|
|
193
|
+
open-browser inject "window.__TEST = true"
|
|
194
|
+
open-browser inject --remove <identifier>
|
|
195
|
+
|
|
196
|
+
# Screen recording
|
|
197
|
+
open-browser screencast start --interval 500 # record at 2 FPS
|
|
198
|
+
open-browser screencast stop # save frames to disk
|
|
199
|
+
|
|
200
|
+
# JS dialogs
|
|
201
|
+
open-browser dialog accept # click OK on alert/confirm
|
|
202
|
+
open-browser dialog dismiss # click Cancel
|
|
203
|
+
open-browser dialog accept --text "response" # answer prompt dialog
|
|
204
|
+
|
|
205
|
+
# Domain permissions
|
|
206
|
+
open-browser domain-mode allow_all # skip domain popups
|
|
207
|
+
open-browser domains allow github.com stackoverflow.com # pre-approve
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Common Patterns
|
|
211
|
+
|
|
212
|
+
### Fill and submit a form
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
open-browser session
|
|
216
|
+
open-browser navigate "https://example.com/login"
|
|
217
|
+
open-browser snapshot --json
|
|
218
|
+
# Find the username and password fields from snapshot output
|
|
219
|
+
open-browser type @e2 "username"
|
|
220
|
+
open-browser type @e5 "password"
|
|
221
|
+
open-browser click @e8 # submit button
|
|
222
|
+
open-browser verify text "Welcome"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Read article content
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
open-browser session
|
|
229
|
+
open-browser navigate "https://example.com/article"
|
|
230
|
+
open-browser markdown --json
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Test responsive layout
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
open-browser session
|
|
237
|
+
open-browser navigate "https://example.com"
|
|
238
|
+
open-browser screenshot --filename "desktop.png" --json
|
|
239
|
+
open-browser emulate --width 375 --height 812
|
|
240
|
+
open-browser screenshot --filename "mobile.png" --json
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Multi-step with batch
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
open-browser session
|
|
247
|
+
open-browser navigate "https://example.com/form"
|
|
248
|
+
open-browser snapshot --json
|
|
249
|
+
open-browser batch '[{"action":"type","ref":"@e1","text":"John"},{"action":"type","ref":"@e2","text":"john@example.com"},{"action":"click","ref":"@e5"}]'
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Handling
|
|
253
|
+
|
|
254
|
+
If a command fails, the error message is printed to stderr. Common issues:
|
|
255
|
+
|
|
256
|
+
- **"Chrome extension is not connected"** — The extension isn't loaded or Chrome isn't open
|
|
257
|
+
- **"Chrome extension did not connect within 10s"** — Extension is loaded but WebSocket port may be in use by the MCP server
|
|
258
|
+
- **"Request timed out"** — Page took too long to respond; try `--wait networkIdle` or increase `--timeout`
|
|
259
|
+
- **"EADDRINUSE"** — Port 9877 is in use (likely by open-browser-mcp); stop the MCP server first
|
|
260
|
+
|
|
261
|
+
## Important Notes
|
|
262
|
+
|
|
263
|
+
- The CLI process exits after each command — state persists in the browser via the extension
|
|
264
|
+
- Only one WebSocket client at a time: either the CLI or the MCP server, not both
|
|
265
|
+
- Screenshots save to `.open-browser-mcp/` in the working directory
|
|
266
|
+
- Domain permission popups appear in Chrome for external sites — this is a security feature
|
|
267
|
+
- Relative URLs resolve to `http://localhost:9876` (the built-in test server from open-browser-mcp)
|
package/src/clients.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { spinner } from '@clack/prompts';
|
|
5
|
+
|
|
6
|
+
const HOME = homedir();
|
|
7
|
+
|
|
8
|
+
const CLIENTS = {
|
|
9
|
+
'Claude Code': {
|
|
10
|
+
configPath: join(HOME, '.claude', 'settings.json'),
|
|
11
|
+
detect: () => existsSync(join(HOME, '.claude')),
|
|
12
|
+
write: (config) => {
|
|
13
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
14
|
+
config.mcpServers.browser = { command: 'open-browser-mcp' };
|
|
15
|
+
return config;
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
'Cursor': {
|
|
19
|
+
configPath: join(HOME, '.cursor', 'mcp.json'),
|
|
20
|
+
detect: () => existsSync(join(HOME, '.cursor')),
|
|
21
|
+
write: (config) => {
|
|
22
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
23
|
+
config.mcpServers.browser = { command: 'open-browser-mcp' };
|
|
24
|
+
return config;
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
'Windsurf': {
|
|
28
|
+
configPath: join(HOME, '.codeium', 'windsurf', 'mcp_config.json'),
|
|
29
|
+
detect: () => existsSync(join(HOME, '.codeium', 'windsurf')),
|
|
30
|
+
write: (config) => {
|
|
31
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
32
|
+
config.mcpServers.browser = { command: 'open-browser-mcp' };
|
|
33
|
+
return config;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
'Codex': {
|
|
37
|
+
configPath: join(HOME, '.codex', 'config.toml'),
|
|
38
|
+
detect: () => existsSync(join(HOME, '.codex')),
|
|
39
|
+
write: null // TOML handled separately
|
|
40
|
+
},
|
|
41
|
+
'OpenCode': {
|
|
42
|
+
configPath: join(HOME, '.config', 'opencode', 'opencode.json'),
|
|
43
|
+
detect: () => existsSync(join(HOME, '.config', 'opencode')),
|
|
44
|
+
write: (config) => {
|
|
45
|
+
if (!config.mcp) config.mcp = {};
|
|
46
|
+
config.mcp.browser = { type: 'local', command: ['open-browser-mcp'], enabled: true };
|
|
47
|
+
return config;
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
'Pi (omp)': {
|
|
51
|
+
configPath: join(HOME, '.omp', 'mcp.json'),
|
|
52
|
+
detect: () => existsSync(join(HOME, '.omp')),
|
|
53
|
+
write: (config) => {
|
|
54
|
+
if (!config.mcpServers) config.mcpServers = {};
|
|
55
|
+
config.mcpServers.browser = { command: 'open-browser-mcp' };
|
|
56
|
+
return config;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detect which MCP clients are installed.
|
|
63
|
+
*/
|
|
64
|
+
export function detectClients() {
|
|
65
|
+
const detected = [];
|
|
66
|
+
for (const [name, client] of Object.entries(CLIENTS)) {
|
|
67
|
+
if (client.detect()) detected.push(name);
|
|
68
|
+
}
|
|
69
|
+
return detected;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all client names.
|
|
74
|
+
*/
|
|
75
|
+
export function getAllClients() {
|
|
76
|
+
return Object.keys(CLIENTS);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Configure an MCP client with the open-browser-mcp server.
|
|
81
|
+
*/
|
|
82
|
+
export async function configureClient(clientName) {
|
|
83
|
+
const client = CLIENTS[clientName];
|
|
84
|
+
if (!client) throw new Error(`Unknown client: ${clientName}`);
|
|
85
|
+
|
|
86
|
+
const s = spinner();
|
|
87
|
+
s.start(`Configuring ${clientName}...`);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Codex uses TOML
|
|
91
|
+
if (clientName === 'Codex') {
|
|
92
|
+
return configureCodex(client.configPath, s);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// All others use JSON
|
|
96
|
+
const configDir = dirname(client.configPath);
|
|
97
|
+
mkdirSync(configDir, { recursive: true });
|
|
98
|
+
|
|
99
|
+
let config = {};
|
|
100
|
+
if (existsSync(client.configPath)) {
|
|
101
|
+
try {
|
|
102
|
+
config = JSON.parse(readFileSync(client.configPath, 'utf8'));
|
|
103
|
+
} catch {
|
|
104
|
+
config = {};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
config = client.write(config);
|
|
109
|
+
writeFileSync(client.configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
110
|
+
s.stop(`${clientName} configured at ${client.configPath}`);
|
|
111
|
+
return true;
|
|
112
|
+
} catch (err) {
|
|
113
|
+
s.stop(`Failed to configure ${clientName}`);
|
|
114
|
+
throw err;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function configureCodex(configPath, s) {
|
|
119
|
+
const configDir = dirname(configPath);
|
|
120
|
+
mkdirSync(configDir, { recursive: true });
|
|
121
|
+
|
|
122
|
+
let content = '';
|
|
123
|
+
if (existsSync(configPath)) {
|
|
124
|
+
content = readFileSync(configPath, 'utf8');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if already configured
|
|
128
|
+
if (content.includes('[mcp_servers.browser]')) {
|
|
129
|
+
s.stop('Codex — already configured');
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Append TOML section
|
|
134
|
+
const tomlEntry = `\n[mcp_servers.browser]\ncommand = "open-browser-mcp"\nargs = []\n`;
|
|
135
|
+
writeFileSync(configPath, content + tomlEntry, 'utf8');
|
|
136
|
+
s.stop(`Codex configured at ${configPath}`);
|
|
137
|
+
return true;
|
|
138
|
+
}
|
package/src/extension.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { platform } from 'os';
|
|
3
|
+
|
|
4
|
+
const STORE_URL = 'https://chromewebstore.google.com/detail/open-browser-bridge';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Open the Chrome Web Store page for the extension.
|
|
8
|
+
*/
|
|
9
|
+
export function openExtensionStore() {
|
|
10
|
+
const os = platform();
|
|
11
|
+
try {
|
|
12
|
+
if (os === 'win32') {
|
|
13
|
+
execSync(`start "" "${STORE_URL}"`, { stdio: 'pipe', shell: true });
|
|
14
|
+
} else if (os === 'darwin') {
|
|
15
|
+
execSync(`open "${STORE_URL}"`, { stdio: 'pipe' });
|
|
16
|
+
} else {
|
|
17
|
+
execSync(`xdg-open "${STORE_URL}"`, { stdio: 'pipe' });
|
|
18
|
+
}
|
|
19
|
+
return true;
|
|
20
|
+
} catch {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { installPackages } from './packages.js';
|
|
4
|
+
import { installSkill, isSkillInstalled } from './skill.js';
|
|
5
|
+
import { detectClients, getAllClients, configureClient } from './clients.js';
|
|
6
|
+
import { openExtensionStore } from './extension.js';
|
|
7
|
+
|
|
8
|
+
export async function run() {
|
|
9
|
+
p.intro(pc.bgCyan(pc.black(' Open Browser Setup ')));
|
|
10
|
+
|
|
11
|
+
// Step 1: Choose what to install
|
|
12
|
+
const installChoice = await p.select({
|
|
13
|
+
message: 'What would you like to install?',
|
|
14
|
+
options: [
|
|
15
|
+
{ value: 'both', label: 'Both (recommended)', hint: 'MCP server + CLI tool' },
|
|
16
|
+
{ value: 'mcp', label: 'MCP Server', hint: 'open-browser-mcp — 58 tools via MCP protocol' },
|
|
17
|
+
{ value: 'cli', label: 'CLI Tool', hint: 'open-browser-cli — terminal commands + Claude Code skill' }
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
if (p.isCancel(installChoice)) {
|
|
22
|
+
p.cancel('Setup cancelled.');
|
|
23
|
+
process.exit(0);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Step 2: Install packages
|
|
27
|
+
const packages = [];
|
|
28
|
+
if (installChoice === 'mcp' || installChoice === 'both') packages.push('open-browser-mcp');
|
|
29
|
+
if (installChoice === 'cli' || installChoice === 'both') packages.push('open-browser-cli');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
await installPackages(packages);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
p.log.error(err.message);
|
|
35
|
+
p.log.info('You may need to run your terminal as Administrator for global installs.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Step 3: Install skill (CLI users)
|
|
40
|
+
if (installChoice === 'cli' || installChoice === 'both') {
|
|
41
|
+
const alreadyInstalled = isSkillInstalled();
|
|
42
|
+
|
|
43
|
+
const installSkillChoice = await p.confirm({
|
|
44
|
+
message: alreadyInstalled
|
|
45
|
+
? 'The open-browser skill is already installed. Overwrite?'
|
|
46
|
+
: 'Install the Claude Code skill? (enables /open-browser command)',
|
|
47
|
+
initialValue: !alreadyInstalled
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!p.isCancel(installSkillChoice) && installSkillChoice) {
|
|
51
|
+
try {
|
|
52
|
+
await installSkill();
|
|
53
|
+
} catch (err) {
|
|
54
|
+
p.log.error(err.message);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Step 4: Configure MCP client (MCP users)
|
|
60
|
+
if (installChoice === 'mcp' || installChoice === 'both') {
|
|
61
|
+
const detected = detectClients();
|
|
62
|
+
const allClients = getAllClients();
|
|
63
|
+
|
|
64
|
+
const clientOptions = allClients.map(name => ({
|
|
65
|
+
value: name,
|
|
66
|
+
label: name,
|
|
67
|
+
hint: detected.includes(name) ? 'detected' : undefined
|
|
68
|
+
}));
|
|
69
|
+
clientOptions.push({ value: 'skip', label: 'Skip' });
|
|
70
|
+
|
|
71
|
+
const clientChoice = await p.select({
|
|
72
|
+
message: 'Configure an MCP client?',
|
|
73
|
+
options: clientOptions
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!p.isCancel(clientChoice) && clientChoice !== 'skip') {
|
|
77
|
+
try {
|
|
78
|
+
await configureClient(clientChoice);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
p.log.error(err.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Step 5: Chrome extension
|
|
86
|
+
const extensionChoice = await p.select({
|
|
87
|
+
message: 'The Open Browser Bridge Chrome extension is required.',
|
|
88
|
+
options: [
|
|
89
|
+
{ value: 'open', label: 'Open Chrome Web Store' },
|
|
90
|
+
{ value: 'skip', label: 'Skip (I\'ll install manually)' }
|
|
91
|
+
]
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!p.isCancel(extensionChoice) && extensionChoice === 'open') {
|
|
95
|
+
const opened = openExtensionStore();
|
|
96
|
+
if (opened) {
|
|
97
|
+
p.log.success('Opened Chrome Web Store in your browser.');
|
|
98
|
+
} else {
|
|
99
|
+
p.log.info(`Visit: ${pc.underline('https://chromewebstore.google.com/detail/open-browser-bridge')}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Outro
|
|
104
|
+
p.note(
|
|
105
|
+
[
|
|
106
|
+
installChoice !== 'cli' ? `${pc.green('MCP')}: Add "open-browser-mcp" to your MCP client config` : '',
|
|
107
|
+
installChoice !== 'mcp' ? `${pc.green('CLI')}: Run ${pc.cyan('open-browser --help')} to see all commands` : '',
|
|
108
|
+
installChoice !== 'mcp' ? `${pc.green('Skill')}: Use ${pc.cyan('/open-browser')} in Claude Code` : '',
|
|
109
|
+
`${pc.green('Extension')}: Load the Open Browser Bridge extension in Chrome`
|
|
110
|
+
].filter(Boolean).join('\n'),
|
|
111
|
+
'Next steps'
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
p.outro(pc.green('Setup complete!'));
|
|
115
|
+
}
|
package/src/packages.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { spinner } from '@clack/prompts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Check if a package is installed globally.
|
|
6
|
+
*/
|
|
7
|
+
function isInstalled(name) {
|
|
8
|
+
try {
|
|
9
|
+
execSync(`npm list -g ${name} --depth=0`, { stdio: 'pipe' });
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Install packages globally.
|
|
18
|
+
*/
|
|
19
|
+
export async function installPackages(packages) {
|
|
20
|
+
const s = spinner();
|
|
21
|
+
|
|
22
|
+
for (const pkg of packages) {
|
|
23
|
+
if (isInstalled(pkg)) {
|
|
24
|
+
s.start(`${pkg} is already installed`);
|
|
25
|
+
s.stop(`${pkg} — already installed`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
s.start(`Installing ${pkg} globally...`);
|
|
30
|
+
try {
|
|
31
|
+
execSync(`npm install -g ${pkg}`, { stdio: 'pipe' });
|
|
32
|
+
s.stop(`${pkg} — installed`);
|
|
33
|
+
} catch (err) {
|
|
34
|
+
s.stop(`${pkg} — failed to install`);
|
|
35
|
+
throw new Error(`Failed to install ${pkg}: ${err.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/skill.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { homedir } from 'os';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { spinner } from '@clack/prompts';
|
|
6
|
+
|
|
7
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const BUNDLED_SKILL = join(__dirname, '..', 'skill', 'SKILL.md');
|
|
9
|
+
const SKILL_DIR = join(homedir(), '.claude', 'skills', 'open-browser');
|
|
10
|
+
const SKILL_PATH = join(SKILL_DIR, 'SKILL.md');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check if the skill is already installed.
|
|
14
|
+
*/
|
|
15
|
+
export function isSkillInstalled() {
|
|
16
|
+
return existsSync(SKILL_PATH);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Install the bundled skill to ~/.claude/skills/open-browser/
|
|
21
|
+
*/
|
|
22
|
+
export async function installSkill() {
|
|
23
|
+
const s = spinner();
|
|
24
|
+
s.start('Installing open-browser skill...');
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(SKILL_DIR, { recursive: true });
|
|
28
|
+
const content = readFileSync(BUNDLED_SKILL, 'utf8');
|
|
29
|
+
writeFileSync(SKILL_PATH, content, 'utf8');
|
|
30
|
+
s.stop(`Skill installed to ${SKILL_DIR}`);
|
|
31
|
+
return true;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
s.stop('Failed to install skill');
|
|
34
|
+
throw new Error(`Failed to install skill: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|