@dbalabka/chrome-wsl 0.3.1 → 0.4.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/README.md +17 -4
- package/chrome-wsl +171 -19
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,7 @@ A small WSL helper to open Google Chrome on Windows with remote debugging (port
|
|
|
16
16
|
- WSL with `powershell.exe` available.
|
|
17
17
|
- Windows Chrome installed at `C:\Program Files\Google\Chrome\Application\chrome.exe` (adjust the path in the script if different).
|
|
18
18
|
- Network/apt access to install `socat` on first run (or preinstall manually).
|
|
19
|
+
- Run the script from inside WSL; non-WSL Linux is not supported (Docker has limited proxy-only support, see below).
|
|
19
20
|
|
|
20
21
|
## Usage
|
|
21
22
|
|
|
@@ -64,10 +65,14 @@ Then run:
|
|
|
64
65
|
chrome-wsl --uninstall
|
|
65
66
|
```
|
|
66
67
|
|
|
67
|
-
##
|
|
68
|
-
|
|
69
|
-
- Chrome
|
|
70
|
-
|
|
68
|
+
## Docker
|
|
69
|
+
|
|
70
|
+
`chrome-wsl` can also take care of starting a proxy inside the Docker container and allow to access the MCP server from localhost. It helps to use the same Chrome DevTools MCP configuration for agents running inside the docker container as well as outside.
|
|
71
|
+
```shell
|
|
72
|
+
npx @dbalabka/chrome-wsl --container=<name>
|
|
73
|
+
npx @dbalabka/chrome-wsl --stop --container=<name>
|
|
74
|
+
npx @dbalabka/chrome-wsl --uninstall --container=<name>
|
|
75
|
+
```
|
|
71
76
|
|
|
72
77
|
## Chrome DevTools MCP configuration for agents
|
|
73
78
|
|
|
@@ -83,5 +88,13 @@ args = ["-y", "chrome-devtools-mcp@latest", "--browser-url=http://127.0.0.1:9222
|
|
|
83
88
|
startup_timeout_sec = 20.0
|
|
84
89
|
```
|
|
85
90
|
|
|
91
|
+
To run Codex inside the container and use the same MCP configuration and authorisation token, mount the Codex configuration folder inside the docker container using the following docker composer settings:
|
|
92
|
+
```shell
|
|
93
|
+
services:
|
|
94
|
+
app:
|
|
95
|
+
volumes:
|
|
96
|
+
- ~/.codex:/home/vscode/.codex
|
|
97
|
+
```
|
|
98
|
+
|
|
86
99
|
## License
|
|
87
100
|
MIT License. See `LICENSE` for details.
|
package/chrome-wsl
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# Compatible with bash and zsh.
|
|
3
|
+
PACKAGE_VERSION="0.4.0"
|
|
3
4
|
set -e
|
|
4
5
|
set -u
|
|
5
6
|
if command -v setopt >/dev/null 2>&1; then
|
|
@@ -12,15 +13,25 @@ PORT=9222
|
|
|
12
13
|
WINDOWS_CHROME_PATH='C:\Program Files\Google\Chrome\Application\chrome.exe'
|
|
13
14
|
FIREWALL_RULE_NAME='Chrome Remote Debug'
|
|
14
15
|
PID_FILE="/tmp/start-chrome-wsl.pids"
|
|
16
|
+
HOST_IP=""
|
|
17
|
+
BROWSER_VERSION=""
|
|
15
18
|
OK_MARK="✅"
|
|
19
|
+
WARN_MARK="❗"
|
|
16
20
|
ERR_MARK="❌"
|
|
17
21
|
|
|
18
22
|
ok() {
|
|
19
|
-
|
|
23
|
+
local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
|
|
24
|
+
echo "${OK_MARK} ${prefix}$*"
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
err() {
|
|
23
|
-
|
|
28
|
+
local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
|
|
29
|
+
echo "${ERR_MARK} ${prefix}$*" >&2
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
warn() {
|
|
33
|
+
local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
|
|
34
|
+
echo "${WARN_MARK} ${prefix}$*" >&2
|
|
24
35
|
}
|
|
25
36
|
|
|
26
37
|
set_pid() {
|
|
@@ -90,13 +101,21 @@ run_powershell() {
|
|
|
90
101
|
fi
|
|
91
102
|
}
|
|
92
103
|
|
|
104
|
+
CONFIRM_MARK="❔"
|
|
93
105
|
confirm() {
|
|
94
106
|
local prompt=$1
|
|
95
107
|
local reply
|
|
96
|
-
|
|
108
|
+
local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
|
|
109
|
+
read -r -p "${CONFIRM_MARK} ${prefix}${prompt} [y/N] " reply
|
|
97
110
|
[[ "${reply}" =~ ^[Yy]$ ]]
|
|
98
111
|
}
|
|
99
112
|
|
|
113
|
+
version_suffix() {
|
|
114
|
+
if [[ -n "${BROWSER_VERSION:-}" ]]; then
|
|
115
|
+
printf " (Chrome %s)" "$BROWSER_VERSION"
|
|
116
|
+
fi
|
|
117
|
+
}
|
|
118
|
+
|
|
100
119
|
chrome_running() {
|
|
101
120
|
run_powershell "if (Get-Process -Name chrome -ErrorAction SilentlyContinue) { exit 0 } else { exit 1 }"
|
|
102
121
|
}
|
|
@@ -118,8 +137,12 @@ port_listening_info() {
|
|
|
118
137
|
run_powershell "\$conns = Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue | Select-Object -Property LocalAddress,LocalPort,RemoteAddress,RemotePort,State,OwningProcess; foreach (\$c in \$conns) { \$p = Get-Process -Id \$c.OwningProcess -ErrorAction SilentlyContinue; \$name = if (\$p) { \$p.Name } else { 'unknown' }; Write-Output (\"{0}:{1} owner={2} pid={3} state={4}\" -f \$c.LocalAddress, \$c.LocalPort, \$name, \$c.OwningProcess, \$c.State) }"
|
|
119
138
|
}
|
|
120
139
|
|
|
121
|
-
|
|
122
|
-
|
|
140
|
+
get_browser_version() {
|
|
141
|
+
local host_ip=${1:-$HOST_IP}
|
|
142
|
+
[[ -z "${host_ip:-}" ]] && return 0
|
|
143
|
+
curl -s "http://${host_ip}:${PORT}/json/version" \
|
|
144
|
+
| sed -n "s/.*\"Browser\"[[:space:]]*:[[:space:]]*\"\\([^\"]*\\)\".*/\\1/p" \
|
|
145
|
+
| head -n 1
|
|
123
146
|
}
|
|
124
147
|
|
|
125
148
|
get_wsl_host_ip() {
|
|
@@ -135,13 +158,24 @@ get_wsl_host_ip() {
|
|
|
135
158
|
check_portproxy() {
|
|
136
159
|
local host_ip=$1
|
|
137
160
|
local output regex
|
|
138
|
-
output=$(run_powershell "netsh interface portproxy show all" | tr -d '
|
|
139
|
-
|
|
161
|
+
output=$(run_powershell "netsh interface portproxy show all" | tr -d '
|
|
162
|
+
')
|
|
163
|
+
regex="${host_ip//./\.}[[:space:]]+${PORT}[[:space:]]+127\.0\.0\.1[[:space:]]+${PORT}"
|
|
140
164
|
if echo "$output" | grep -Eq "$regex"; then
|
|
141
165
|
ok "Portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT} is configured."
|
|
142
166
|
return 0
|
|
143
167
|
fi
|
|
144
168
|
|
|
169
|
+
warn "Portproxy on ${host_ip}:${PORT} is missing; attempting to create it (may prompt for admin)."
|
|
170
|
+
if RUN_AS_ADMIN=1 run_powershell "netsh interface portproxy add v4tov4 listenaddress=${host_ip} listenport=${PORT} connectaddress=127.0.0.1 connectport=${PORT}"; then
|
|
171
|
+
output=$(run_powershell "netsh interface portproxy show all" | tr -d '
|
|
172
|
+
')
|
|
173
|
+
if echo "$output" | grep -Eq "$regex"; then
|
|
174
|
+
ok "Created portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT}."
|
|
175
|
+
return 0
|
|
176
|
+
fi
|
|
177
|
+
fi
|
|
178
|
+
|
|
145
179
|
cat <<EOF
|
|
146
180
|
${ERR_MARK} Portproxy on ${host_ip}:${PORT} is missing.
|
|
147
181
|
Run this in an **admin PowerShell** window:
|
|
@@ -158,7 +192,7 @@ check_firewall_rule() {
|
|
|
158
192
|
return 0
|
|
159
193
|
fi
|
|
160
194
|
|
|
161
|
-
|
|
195
|
+
warn "Firewall rule \"${FIREWALL_RULE_NAME}\" is missing; attempting to create it (may prompt for admin)."
|
|
162
196
|
if RUN_AS_ADMIN=1 run_powershell "New-NetFirewallRule -DisplayName \"${FIREWALL_RULE_NAME}\" -Direction Inbound -LocalPort ${PORT} -Protocol TCP -Action Allow" \
|
|
163
197
|
&& run_powershell "${rule_check_cmd}"; then
|
|
164
198
|
ok "Created firewall rule \"${FIREWALL_RULE_NAME}\"."
|
|
@@ -213,13 +247,40 @@ uninstall_socat() {
|
|
|
213
247
|
fi
|
|
214
248
|
}
|
|
215
249
|
|
|
250
|
+
# Remove portproxy entry
|
|
251
|
+
uninstall_portproxy() {
|
|
252
|
+
local host_ip=${1:-$HOST_IP}
|
|
253
|
+
if [[ -z "${host_ip:-}" ]]; then
|
|
254
|
+
host_ip=$(get_wsl_host_ip)
|
|
255
|
+
fi
|
|
256
|
+
local output regex
|
|
257
|
+
output=$(run_powershell "netsh interface portproxy show all" | tr -d '\r')
|
|
258
|
+
regex="${host_ip//./\\.}[[:space:]]+${PORT}[[:space:]]+127\\.0\\.0\\.1[[:space:]]+${PORT}"
|
|
259
|
+
if ! echo "$output" | grep -Eq "$regex"; then
|
|
260
|
+
ok "Portproxy ${host_ip}:${PORT} is already absent."
|
|
261
|
+
return 0
|
|
262
|
+
fi
|
|
263
|
+
|
|
264
|
+
if ! confirm "Remove portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT}?"; then
|
|
265
|
+
ok "Skipped portproxy removal."
|
|
266
|
+
return 0
|
|
267
|
+
fi
|
|
268
|
+
|
|
269
|
+
if RUN_AS_ADMIN=1 run_powershell "netsh interface portproxy delete v4tov4 listenaddress=${host_ip} listenport=${PORT}" \
|
|
270
|
+
&& ! (run_powershell "netsh interface portproxy show all" | tr -d '\r' | grep -Eq "$regex"); then
|
|
271
|
+
ok "Removed portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT}."
|
|
272
|
+
else
|
|
273
|
+
err "Failed to remove portproxy ${host_ip}:${PORT}."
|
|
274
|
+
fi
|
|
275
|
+
}
|
|
276
|
+
|
|
216
277
|
ensure_socat() {
|
|
217
278
|
if command -v socat >/dev/null 2>&1; then
|
|
218
279
|
ok "socat is already installed."
|
|
219
280
|
return 0
|
|
220
281
|
fi
|
|
221
282
|
|
|
222
|
-
|
|
283
|
+
warn "socat not found. Installing via apt..."
|
|
223
284
|
sudo apt-get update
|
|
224
285
|
sudo apt-get install -y socat
|
|
225
286
|
}
|
|
@@ -236,6 +297,40 @@ start_socat() {
|
|
|
236
297
|
ok "Started socat (logging to /tmp/socat-9222.log)."
|
|
237
298
|
}
|
|
238
299
|
|
|
300
|
+
is_docker() {
|
|
301
|
+
[[ -f /.dockerenv ]] && return 0
|
|
302
|
+
grep -qaE 'docker|containerd|kubepods' /proc/1/cgroup 2>/dev/null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
container_exec() {
|
|
306
|
+
local container=$1; shift
|
|
307
|
+
local script_path
|
|
308
|
+
script_path=$(readlink -f "$0")
|
|
309
|
+
ensure_docker
|
|
310
|
+
local tty_flag="-i"
|
|
311
|
+
if [ -t 0 ]; then
|
|
312
|
+
tty_flag="-it"
|
|
313
|
+
fi
|
|
314
|
+
if ! cat "$script_path" | docker exec -i "${container}" sh -c "cat > /tmp/chrome-wsl.sh"; then
|
|
315
|
+
err "Failed to copy script into container ${container}."
|
|
316
|
+
exit 1
|
|
317
|
+
fi
|
|
318
|
+
local inner_cmd="CONTAINER_LABEL=${container} chmod +x /tmp/chrome-wsl.sh && CONTAINER_LABEL=${container} /tmp/chrome-wsl.sh $*"
|
|
319
|
+
docker exec ${tty_flag} "${container}" sh -lc "${inner_cmd}"
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
get_docker_host_ip() {
|
|
323
|
+
getent hosts host.docker.internal 2>/dev/null | awk '{print $1; exit}'
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
ensure_docker() {
|
|
327
|
+
if command -v docker >/dev/null 2>&1; then
|
|
328
|
+
return 0
|
|
329
|
+
fi
|
|
330
|
+
err "Docker CLI is required for --container."
|
|
331
|
+
exit 1
|
|
332
|
+
}
|
|
333
|
+
|
|
239
334
|
stop_chrome() {
|
|
240
335
|
local chrome_pid
|
|
241
336
|
chrome_pid=$(get_pid "CHROME_PID")
|
|
@@ -267,11 +362,6 @@ start_chrome() {
|
|
|
267
362
|
fi
|
|
268
363
|
fi
|
|
269
364
|
|
|
270
|
-
if port_in_use_by_non_chrome; then
|
|
271
|
-
err "Port ${PORT} is already in use by another process (not Chrome); details:"
|
|
272
|
-
port_listening_info
|
|
273
|
-
fi
|
|
274
|
-
|
|
275
365
|
local chrome_pid
|
|
276
366
|
chrome_pid=$(run_powershell "$chrome_cmd" | tr -d '\r' | head -n 1)
|
|
277
367
|
if [[ -n "${chrome_pid:-}" ]]; then
|
|
@@ -289,21 +379,79 @@ start_chrome() {
|
|
|
289
379
|
fi
|
|
290
380
|
}
|
|
291
381
|
|
|
382
|
+
ensure_wsl() {
|
|
383
|
+
if grep -qEi 'Microsoft|WSL' /proc/version >/dev/null 2>&1; then
|
|
384
|
+
return 0
|
|
385
|
+
fi
|
|
386
|
+
err "This tool must be run inside WSL; exiting."
|
|
387
|
+
exit 1
|
|
388
|
+
}
|
|
389
|
+
|
|
292
390
|
main() {
|
|
293
|
-
|
|
391
|
+
local container="" uninstall_flag=0 stop_flag=0
|
|
392
|
+
for arg in "$@"; do
|
|
393
|
+
case "$arg" in
|
|
394
|
+
--container=*) container=${arg#*=} ;;
|
|
395
|
+
--uninstall) uninstall_flag=1 ;;
|
|
396
|
+
--stop) stop_flag=1 ;;
|
|
397
|
+
esac
|
|
398
|
+
done
|
|
399
|
+
|
|
400
|
+
if [[ "${1-}" == "--version" || "${1-}" == "-V" ]]; then
|
|
401
|
+
echo "chrome-wsl ${PACKAGE_VERSION:-unknown}"
|
|
402
|
+
exit 0
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
if is_docker && [[ -n "$container" ]]; then
|
|
406
|
+
err "--container is only supported when running in WSL, not inside Docker."
|
|
407
|
+
exit 1
|
|
408
|
+
fi
|
|
409
|
+
|
|
410
|
+
if is_docker; then
|
|
411
|
+
if [[ $uninstall_flag -eq 1 ]]; then
|
|
412
|
+
uninstall_socat
|
|
413
|
+
exit 0
|
|
414
|
+
fi
|
|
415
|
+
|
|
416
|
+
if [[ $stop_flag -eq 1 ]]; then
|
|
417
|
+
stop_socat
|
|
418
|
+
exit 0
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
HOST_IP=$(get_docker_host_ip)
|
|
422
|
+
if [[ -z "${HOST_IP:-}" ]]; then
|
|
423
|
+
err "Could not resolve host.docker.internal inside Docker."
|
|
424
|
+
exit 1
|
|
425
|
+
fi
|
|
426
|
+
|
|
427
|
+
ensure_socat
|
|
428
|
+
start_socat "$HOST_IP"
|
|
429
|
+
exit 0
|
|
430
|
+
fi
|
|
431
|
+
|
|
432
|
+
ensure_wsl
|
|
433
|
+
|
|
434
|
+
if [[ $uninstall_flag -eq 1 ]]; then
|
|
435
|
+
uninstall_portproxy
|
|
294
436
|
uninstall_firewall_rule
|
|
295
437
|
uninstall_socat
|
|
438
|
+
if [[ -n "$container" ]]; then
|
|
439
|
+
container_exec "$container" --uninstall
|
|
440
|
+
fi
|
|
296
441
|
exit 0
|
|
297
442
|
fi
|
|
298
443
|
|
|
299
|
-
if [[
|
|
444
|
+
if [[ $stop_flag -eq 1 ]]; then
|
|
300
445
|
stop_chrome
|
|
301
446
|
stop_socat
|
|
447
|
+
if [[ -n "$container" ]]; then
|
|
448
|
+
container_exec "$container" --stop
|
|
449
|
+
fi
|
|
302
450
|
exit 0
|
|
303
451
|
fi
|
|
304
452
|
|
|
305
|
-
|
|
306
|
-
host_ip
|
|
453
|
+
HOST_IP=$(get_wsl_host_ip)
|
|
454
|
+
local host_ip="$HOST_IP"
|
|
307
455
|
ok "Detected Windows host IP: ${host_ip}"
|
|
308
456
|
|
|
309
457
|
check_portproxy "$host_ip" || exit 1
|
|
@@ -311,6 +459,10 @@ main() {
|
|
|
311
459
|
ensure_socat
|
|
312
460
|
start_socat "$host_ip"
|
|
313
461
|
start_chrome
|
|
462
|
+
|
|
463
|
+
if [[ -n "$container" ]]; then
|
|
464
|
+
container_exec "$container"
|
|
465
|
+
fi
|
|
314
466
|
}
|
|
315
467
|
|
|
316
|
-
main "$@"
|
|
468
|
+
main "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbalabka/chrome-wsl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "WSL helper to start Windows Chrome with remote debugging and socat port forwarding.",
|
|
5
5
|
"bin": "./chrome-wsl",
|
|
6
6
|
"files": [
|
|
@@ -28,4 +28,4 @@
|
|
|
28
28
|
"publishConfig": {
|
|
29
29
|
"access": "public"
|
|
30
30
|
}
|
|
31
|
-
}
|
|
31
|
+
}
|