@dbalabka/chrome-wsl 0.3.2 → 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.
Files changed (3) hide show
  1. package/README.md +17 -4
  2. package/chrome-wsl +173 -12
  3. 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
- ## Notes
68
- - Portproxy check expects forwarding from the detected Windows host IP to `127.0.0.1:9222`.
69
- - Chrome launch is skipped when port 9222 already listens on Windows (assumed active remote-debug session).
70
- - For a different remote debugging port or Chrome path, edit `PORT` or `WINDOWS_CHROME_PATH` in `start_chrome_wsl.sh`.
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
- echo "${OK_MARK} $*"
23
+ local prefix="${CONTAINER_LABEL:+[${CONTAINER_LABEL}] }"
24
+ echo "${OK_MARK} ${prefix}$*"
20
25
  }
21
26
 
22
27
  err() {
23
- echo "${ERR_MARK} $*" >&2
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
- read -r -p "${prompt} [y/N] " reply
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,6 +137,14 @@ 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
 
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
146
+ }
147
+
121
148
  get_wsl_host_ip() {
122
149
  local host_ip
123
150
  host_ip=$(ip route | awk '/^default via / {print $3; exit}')
@@ -131,13 +158,24 @@ get_wsl_host_ip() {
131
158
  check_portproxy() {
132
159
  local host_ip=$1
133
160
  local output regex
134
- output=$(run_powershell "netsh interface portproxy show all" | tr -d '\r')
135
- regex="${host_ip//./\\.}[[:space:]]+${PORT}[[:space:]]+127\\.0\\.0\\.1[[:space:]]+${PORT}"
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}"
136
164
  if echo "$output" | grep -Eq "$regex"; then
137
165
  ok "Portproxy ${host_ip}:${PORT} -> 127.0.0.1:${PORT} is configured."
138
166
  return 0
139
167
  fi
140
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
+
141
179
  cat <<EOF
142
180
  ${ERR_MARK} Portproxy on ${host_ip}:${PORT} is missing.
143
181
  Run this in an **admin PowerShell** window:
@@ -154,7 +192,7 @@ check_firewall_rule() {
154
192
  return 0
155
193
  fi
156
194
 
157
- err "Firewall rule \"${FIREWALL_RULE_NAME}\" is missing; attempting to create it (may prompt for admin)."
195
+ warn "Firewall rule \"${FIREWALL_RULE_NAME}\" is missing; attempting to create it (may prompt for admin)."
158
196
  if RUN_AS_ADMIN=1 run_powershell "New-NetFirewallRule -DisplayName \"${FIREWALL_RULE_NAME}\" -Direction Inbound -LocalPort ${PORT} -Protocol TCP -Action Allow" \
159
197
  && run_powershell "${rule_check_cmd}"; then
160
198
  ok "Created firewall rule \"${FIREWALL_RULE_NAME}\"."
@@ -209,13 +247,40 @@ uninstall_socat() {
209
247
  fi
210
248
  }
211
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
+
212
277
  ensure_socat() {
213
278
  if command -v socat >/dev/null 2>&1; then
214
279
  ok "socat is already installed."
215
280
  return 0
216
281
  fi
217
282
 
218
- err "socat not found. Installing via apt..."
283
+ warn "socat not found. Installing via apt..."
219
284
  sudo apt-get update
220
285
  sudo apt-get install -y socat
221
286
  }
@@ -232,6 +297,40 @@ start_socat() {
232
297
  ok "Started socat (logging to /tmp/socat-9222.log)."
233
298
  }
234
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
+
235
334
  stop_chrome() {
236
335
  local chrome_pid
237
336
  chrome_pid=$(get_pid "CHROME_PID")
@@ -280,21 +379,79 @@ start_chrome() {
280
379
  fi
281
380
  }
282
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
+
283
390
  main() {
284
- if [[ "${1-}" == "--uninstall" ]]; then
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
285
436
  uninstall_firewall_rule
286
437
  uninstall_socat
438
+ if [[ -n "$container" ]]; then
439
+ container_exec "$container" --uninstall
440
+ fi
287
441
  exit 0
288
442
  fi
289
443
 
290
- if [[ "${1-}" == "--stop" ]]; then
444
+ if [[ $stop_flag -eq 1 ]]; then
291
445
  stop_chrome
292
446
  stop_socat
447
+ if [[ -n "$container" ]]; then
448
+ container_exec "$container" --stop
449
+ fi
293
450
  exit 0
294
451
  fi
295
452
 
296
- local host_ip
297
- host_ip=$(get_wsl_host_ip)
453
+ HOST_IP=$(get_wsl_host_ip)
454
+ local host_ip="$HOST_IP"
298
455
  ok "Detected Windows host IP: ${host_ip}"
299
456
 
300
457
  check_portproxy "$host_ip" || exit 1
@@ -302,6 +459,10 @@ main() {
302
459
  ensure_socat
303
460
  start_socat "$host_ip"
304
461
  start_chrome
462
+
463
+ if [[ -n "$container" ]]; then
464
+ container_exec "$container"
465
+ fi
305
466
  }
306
467
 
307
- main "$@"
468
+ main "$@"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbalabka/chrome-wsl",
3
- "version": "0.3.2",
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
+ }