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 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
+ [![npm version](https://img.shields.io/npm/v/open-browser-setup)](https://www.npmjs.com/package/open-browser-setup)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { run } from '../src/index.js';
4
+
5
+ run();
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
+ }
@@ -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
+ }
@@ -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
+ }