@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.
Files changed (3) hide show
  1. package/README.md +17 -4
  2. package/chrome-wsl +171 -19
  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,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
- port_in_use_by_non_chrome() {
122
- run_powershell "if (Get-NetTCPConnection -LocalPort ${PORT} -ErrorAction SilentlyContinue | Where-Object { \$p = Get-Process -Id \_.OwningProcess -ErrorAction SilentlyContinue; -not (\$p -and \$p.Name -eq 'chrome') }) { exit 0 } else { exit 1 }"
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 '\r')
139
- 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}"
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
- 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)."
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
- err "socat not found. Installing via apt..."
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
- 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
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 [[ "${1-}" == "--stop" ]]; then
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
- local host_ip
306
- host_ip=$(get_wsl_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.1",
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
+ }