@react-ui-org/react-ui 0.63.1 → 0.64.1
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/.claude/agents/code-reviewer.md +122 -0
- package/.claude/rules/code.md +22 -0
- package/.claude/rules/docs.md +35 -0
- package/.claude/rules/frontend.md +72 -0
- package/.claude/rules/git.md +13 -0
- package/.claude/rules/safety-guards.md +9 -0
- package/.claude/rules/styling.md +32 -0
- package/.claude/rules/testing.md +59 -0
- package/.claude/settings.json +32 -0
- package/.claude/skills/commit/SKILL.md +54 -0
- package/.devcontainer/devcontainer.json +23 -0
- package/.mcp.json +8 -0
- package/CLAUDE.md +82 -0
- package/dist/react-ui.css +4 -4
- package/dist/react-ui.development.css +600 -590
- package/dist/react-ui.development.js +20 -10
- package/dist/react-ui.js +1 -1
- package/docker-compose.base.yml +93 -0
- package/docker-compose.yml.dist +41 -0
- package/opencode.json +10 -0
- package/package.json +3 -2
- package/scripts/auto-start-mkdocs.sh +5 -0
- package/scripts/auto-start-node.sh +33 -0
- package/scripts/mcps/chrome-host/README.md +118 -0
- package/scripts/mcps/chrome-host/cdp_proxy.py +90 -0
- package/scripts/mcps/chrome-host/mcp-entry.sh +33 -0
- package/scripts/mcps/chrome-host/mcp-launch.sh +36 -0
- package/scripts/mcps/chrome-host/mcp-setup.sh +70 -0
- package/scripts/mcps/chrome-host/start-host-chrome.sh +267 -0
- package/scripts/write-lockfile-hash.sh +21 -0
- package/setup.sh +107 -0
- package/src/components/Button/Button.jsx +4 -0
- package/src/components/Button/Button.module.scss +11 -0
- package/src/components/FileInputField/FileInputField.jsx +9 -2
- package/src/components/FormLayout/FormLayoutCustomField.jsx +4 -1
- package/src/components/FormLayout/FormLayoutCustomFieldContext.js +3 -0
- package/src/components/FormLayout/README.md +168 -161
- package/src/components/FormLayout/index.js +1 -0
- package/src/components/Popover/Popover.jsx +1 -0
- package/src/components/Popover/Popover.module.scss +1 -0
- package/src/components/Popover/README.md +29 -0
- package/src/components/Popover/_helpers/cleanPlacementStyle.js +1 -0
- package/src/components/Popover/_theme.scss +1 -0
- package/src/components/SelectField/SelectField.jsx +8 -3
- package/src/components/TextArea/TextArea.jsx +9 -2
- package/src/components/TextField/TextField.jsx +8 -3
- package/src/theme.scss +1 -0
- package/.env.playwright +0 -9
- package/.env.playwright.dist +0 -9
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
services:
|
|
2
|
+
# This service is responsible for providing the main development environment for developers
|
|
3
|
+
devcontainer:
|
|
4
|
+
hostname: ${COMPOSE_PROJECT_NAME:-react-ui}_devcontainer
|
|
5
|
+
build:
|
|
6
|
+
context: docker/react_ui_devcontainer/
|
|
7
|
+
dockerfile: Dockerfile
|
|
8
|
+
# Start dependent services before starting the `devcontainer` service to ensure that the necessary environments
|
|
9
|
+
# and tools are available when the `devcontainer` starts.
|
|
10
|
+
depends_on:
|
|
11
|
+
node:
|
|
12
|
+
condition: service_started
|
|
13
|
+
playwright:
|
|
14
|
+
condition: service_started
|
|
15
|
+
docs:
|
|
16
|
+
condition: service_started
|
|
17
|
+
# Run as host UID/GID so files created in mounted volumes are owned correctly on the host.
|
|
18
|
+
# The images use `fixuid` (https://github.com/boxboat/fixuid) to remap the built-in `developer`
|
|
19
|
+
# user to these IDs at startup — the `user:` directive is required for that remap to work.
|
|
20
|
+
# Applied to all services below for the same reason.
|
|
21
|
+
user: ${COMPOSE_UID}:${COMPOSE_GID}
|
|
22
|
+
# Keep the container running indefinitely to allow developers to attach to it and use it as their development environment
|
|
23
|
+
command: sleep infinity
|
|
24
|
+
# Injects environment variables from the `.env` file into the `devcontainer` service,
|
|
25
|
+
# making them accessible within the container's environment.
|
|
26
|
+
env_file:
|
|
27
|
+
- .env
|
|
28
|
+
environment:
|
|
29
|
+
# This must be set correctly for the `devcontainer` to be able to access the host's Docker daemon,
|
|
30
|
+
# enabling Docker-from-Docker capabilities (e.g., running Docker commands from within the `devcontainer`).
|
|
31
|
+
COMPOSE_PROJECT_NAME: ${COMPOSE_PROJECT_NAME:-react-ui}
|
|
32
|
+
init: true
|
|
33
|
+
volumes:
|
|
34
|
+
- .:/workspace:z
|
|
35
|
+
# The following volume is used to allow the `devcontainer` to access the host's Docker daemon,
|
|
36
|
+
# enabling Docker-from-Docker capabilities (e.g., running Docker commands from within the `devcontainer`).
|
|
37
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
38
|
+
# The following named volumes persist data (e.g. terminal history, AI tools data, etc.) across container restarts.
|
|
39
|
+
# Using separate named volumes (instead of a single volume with subpaths) allows Docker to automatically
|
|
40
|
+
# seed the volume with data from the image on first use.
|
|
41
|
+
- terminal-history:/home/developer/.terminal_history
|
|
42
|
+
- claude-config:/home/developer/.config/claude
|
|
43
|
+
- claude-state:/home/developer/.local/state/claude
|
|
44
|
+
- copilot:/home/developer/.copilot
|
|
45
|
+
- copilot-config:/home/developer/.config/copilot
|
|
46
|
+
- opencode-config:/home/developer/.config/opencode
|
|
47
|
+
- opencode-share:/home/developer/.local/share/opencode
|
|
48
|
+
- opencode-state:/home/developer/.local/state/opencode
|
|
49
|
+
|
|
50
|
+
# This service provides Node environment and NPM
|
|
51
|
+
node:
|
|
52
|
+
build: docker/node
|
|
53
|
+
user: ${COMPOSE_UID}:${COMPOSE_GID}
|
|
54
|
+
entrypoint: sh -c 'if [ "$$COMPOSE_AUTOSTART" = "true" ]; then sh scripts/auto-start-node.sh; else sleep infinity; fi'
|
|
55
|
+
env_file:
|
|
56
|
+
- .env
|
|
57
|
+
volumes:
|
|
58
|
+
- .:/workspace:z
|
|
59
|
+
|
|
60
|
+
# This service provides Playwright environment and tools for browser automation and testing
|
|
61
|
+
playwright:
|
|
62
|
+
build: docker/playwright
|
|
63
|
+
user: ${COMPOSE_UID}:${COMPOSE_GID}
|
|
64
|
+
command: sleep infinity
|
|
65
|
+
env_file:
|
|
66
|
+
- .env
|
|
67
|
+
ports:
|
|
68
|
+
- ${COMPOSE_PLAYWRIGHT_REPORT_PORT}:9323
|
|
69
|
+
volumes:
|
|
70
|
+
- .:/workspace:z
|
|
71
|
+
|
|
72
|
+
# This provides server for documentation
|
|
73
|
+
docs:
|
|
74
|
+
build: docker/mkdocs
|
|
75
|
+
user: ${COMPOSE_UID}:${COMPOSE_GID}
|
|
76
|
+
entrypoint: sh -c 'if [ "$$COMPOSE_AUTOSTART" = "true" ]; then sh scripts/auto-start-mkdocs.sh; else sleep infinity; fi'
|
|
77
|
+
env_file:
|
|
78
|
+
- .env
|
|
79
|
+
ports:
|
|
80
|
+
- ${COMPOSE_DOCS_SERVER_PORT}:8000
|
|
81
|
+
volumes:
|
|
82
|
+
- .:/workspace:z
|
|
83
|
+
|
|
84
|
+
volumes:
|
|
85
|
+
# The following volumes are used to persist data (e.g. terminal history, AI tools data, etc.) across container restarts
|
|
86
|
+
terminal-history:
|
|
87
|
+
claude-config:
|
|
88
|
+
claude-state:
|
|
89
|
+
copilot:
|
|
90
|
+
copilot-config:
|
|
91
|
+
opencode-config:
|
|
92
|
+
opencode-share:
|
|
93
|
+
opencode-state:
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
services:
|
|
2
|
+
# This service is responsible for providing the main development environment for developers
|
|
3
|
+
devcontainer:
|
|
4
|
+
extends:
|
|
5
|
+
file: docker-compose.base.yml
|
|
6
|
+
service: devcontainer
|
|
7
|
+
# Use `build` when you want to customize the devcontainer using `docker/react_ui_devcontainer_local/Dockerfile`
|
|
8
|
+
# build:
|
|
9
|
+
# context: ./docker/react_ui_devcontainer_local/
|
|
10
|
+
# dockerfile: Dockerfile
|
|
11
|
+
# Use `image` when you want to use the default devcontainer
|
|
12
|
+
image: react-ui_devcontainer
|
|
13
|
+
|
|
14
|
+
# This service provides Node environment and NPM
|
|
15
|
+
node:
|
|
16
|
+
extends:
|
|
17
|
+
file: docker-compose.base.yml
|
|
18
|
+
service: node
|
|
19
|
+
|
|
20
|
+
# This service provides Playwright environment and tools for browser automation and testing
|
|
21
|
+
playwright:
|
|
22
|
+
extends:
|
|
23
|
+
file: docker-compose.base.yml
|
|
24
|
+
service: playwright
|
|
25
|
+
|
|
26
|
+
# This provides server for documentation
|
|
27
|
+
docs:
|
|
28
|
+
extends:
|
|
29
|
+
file: docker-compose.base.yml
|
|
30
|
+
service: docs
|
|
31
|
+
|
|
32
|
+
volumes:
|
|
33
|
+
# The following volumes are used to persist data (e.g. terminal history, AI tools data, etc.) across container restarts
|
|
34
|
+
terminal-history:
|
|
35
|
+
claude-config:
|
|
36
|
+
claude-state:
|
|
37
|
+
copilot:
|
|
38
|
+
copilot-config:
|
|
39
|
+
opencode-config:
|
|
40
|
+
opencode-share:
|
|
41
|
+
opencode-state:
|
package/opencode.json
ADDED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-ui-org/react-ui",
|
|
3
3
|
"description": "React UI is a themeable UI library for React apps.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.64.1",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
7
7
|
"ui",
|
|
@@ -54,10 +54,11 @@
|
|
|
54
54
|
"lint": "npm run eslint && npm run markdownlint && npm run stylelint",
|
|
55
55
|
"markdownlint": "markdownlint-cli2 \"README.md\" \"src/**/*.md\"",
|
|
56
56
|
"postbuild": "npm run copy",
|
|
57
|
-
"postinstall": "
|
|
57
|
+
"postinstall": "sh scripts/write-lockfile-hash.sh",
|
|
58
58
|
"precopy": "rm -rf dist && mkdir dist",
|
|
59
59
|
"prepublishOnly": "npm run build",
|
|
60
60
|
"start": "webpack --watch --mode=development",
|
|
61
|
+
"start:chrome": "sh scripts/mcps/chrome-host/start-host-chrome.sh",
|
|
61
62
|
"stylelint": "stylelint \"src/**/*.{css,scss}\" \"!src/docs/_assets/generated/**\" --config stylelint.config.js",
|
|
62
63
|
"test": "npm run test:jest",
|
|
63
64
|
"test:jest": "npm run test:jest:ts && npm run test:jest:js",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
# File to read the hash of the package-lock.json
|
|
6
|
+
LOCK_HASH_FILE="node_modules/.package-lock-hash"
|
|
7
|
+
|
|
8
|
+
# Parent directory of the script
|
|
9
|
+
SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
|
|
10
|
+
|
|
11
|
+
# Change to the parent directory of the script
|
|
12
|
+
cd "$SCRIPT_DIR/.."
|
|
13
|
+
|
|
14
|
+
# Install dependencies if node_modules is missing or out of date
|
|
15
|
+
CURRENT_HASH=$(sha256sum package-lock.json | awk '{print $1}')
|
|
16
|
+
if [ ! -d "node_modules" ]; then
|
|
17
|
+
echo "Installing dependencies (node_modules directory is missing)..."
|
|
18
|
+
npm ci
|
|
19
|
+
elif [ ! -f "$LOCK_HASH_FILE" ]; then
|
|
20
|
+
echo "Installing dependencies (lockfile of package-lock.json is missing)..."
|
|
21
|
+
npm ci
|
|
22
|
+
elif [ "$(cat "$LOCK_HASH_FILE")" != "$CURRENT_HASH" ]; then
|
|
23
|
+
echo "Installing dependencies (package-lock.json has changed)..."
|
|
24
|
+
npm ci
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Build the application (must be run prior to starting the server to ensure the latest code is used)
|
|
28
|
+
echo "Building the application..."
|
|
29
|
+
npm run build
|
|
30
|
+
|
|
31
|
+
# Start the application
|
|
32
|
+
echo "Starting the application..."
|
|
33
|
+
npm start
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# Chrome-host bridge
|
|
2
|
+
|
|
3
|
+
Lets an MCP-capable AI assistant in the `devcontainer` drive **Chrome on the
|
|
4
|
+
host** via [`chrome-devtools-mcp`](https://github.com/ChromeDevTools/chrome-devtools-mcp)
|
|
5
|
+
over the Chrome DevTools Protocol (CDP).
|
|
6
|
+
|
|
7
|
+
## Principles
|
|
8
|
+
|
|
9
|
+
- The container reaches the host only via `host.docker.internal`.
|
|
10
|
+
- Chrome's DevTools endpoint rejects any `Host` header that is not an IP literal
|
|
11
|
+
(or the name `localhost`) — so the container cannot connect to it directly.
|
|
12
|
+
- Fix: a loopback proxy **inside the container** (`127.0.0.1:9334`); the `Host`
|
|
13
|
+
Chrome finally sees is `127.0.0.1`, an IP, which it accepts.
|
|
14
|
+
- Chrome runs in a dedicated, persistent profile (separate from your everyday
|
|
15
|
+
one) and is started **by hand** on the host — never from a Claude action,
|
|
16
|
+
which can reap it with its process group.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
Claude (devcontainer)
|
|
22
|
+
│ .mcp.json → mcp-entry.sh → docker compose exec node mcp-launch.sh
|
|
23
|
+
▼
|
|
24
|
+
node container
|
|
25
|
+
├─ cdp_proxy.py 127.0.0.1:9334 ──► host.docker.internal:9333
|
|
26
|
+
└─ npx chrome-devtools-mcp --browser-url http://127.0.0.1:9334
|
|
27
|
+
│
|
|
28
|
+
▼
|
|
29
|
+
host: Chrome --remote-debugging-port=9333 (throwaway profile)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
| Script | Runs on | Purpose |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `start-host-chrome.sh` | host | Launch the debug Chrome on `:9333`. Idempotent; auto-detects Chrome/Chromium/Edge. On native Docker, also runs an `ncat` forwarder to expose `:9333` on the bridge gateway. |
|
|
35
|
+
| `cdp_proxy.py` | node container | Loopback CDP proxy `:9334` → host `:9333`. Binds-or-exits. |
|
|
36
|
+
| `mcp-launch.sh` | node container | Ensure the proxy is up, then `exec` `chrome-devtools-mcp`. |
|
|
37
|
+
| `mcp-entry.sh` | host / devcontainer | The `.mcp.json` command; routes into the node container (adds `sudo` inside the container). |
|
|
38
|
+
| `mcp-setup.sh` | host (setup) | Add the `host.docker.internal:host-gateway` mapping on native Docker Engine. Idempotent; no-op on Docker Desktop. |
|
|
39
|
+
|
|
40
|
+
## Registration
|
|
41
|
+
|
|
42
|
+
Register `mcp-entry.sh` as an MCP server named `chrome-host`. Any MCP client
|
|
43
|
+
works — point its config at the script.
|
|
44
|
+
|
|
45
|
+
**Claude / GitHub Copilot CLI** — `.mcp.json` (repo root):
|
|
46
|
+
|
|
47
|
+
```json
|
|
48
|
+
{
|
|
49
|
+
"mcpServers": {
|
|
50
|
+
"chrome-host": {
|
|
51
|
+
"command": "sh",
|
|
52
|
+
"args": ["scripts/mcps/chrome-host/mcp-entry.sh"]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**OpenCode** — `opencode.json` (repo root):
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"mcp": {
|
|
63
|
+
"chrome-host": {
|
|
64
|
+
"type": "local",
|
|
65
|
+
"command": ["sh", "scripts/mcps/chrome-host/mcp-entry.sh"],
|
|
66
|
+
"enabled": true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Requirements: a `docker-compose.yml` at the repo root, the repo mounted at
|
|
73
|
+
`/workspace` in the container, and a Compose service named `node` that runs
|
|
74
|
+
`node`/`npx`.
|
|
75
|
+
|
|
76
|
+
## Usage
|
|
77
|
+
|
|
78
|
+
1. Start host Chrome (from a host terminal, **not** from inside an AI action):
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
./scripts/mcps/chrome-host/start-host-chrome.sh
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. On first run, approve the `chrome-host` server in your MCP client.
|
|
85
|
+
|
|
86
|
+
The proxy and MCP server are bootstrapped by `mcp-launch.sh`; you only start
|
|
87
|
+
Chrome by hand.
|
|
88
|
+
|
|
89
|
+
## Platform support
|
|
90
|
+
|
|
91
|
+
Decided by **Docker Desktop vs native Docker Engine**, not the OS.
|
|
92
|
+
|
|
93
|
+
| Host setup | `host.docker.internal` reaches host loopback? | Extra steps |
|
|
94
|
+
| --- | --- | --- |
|
|
95
|
+
| Docker Desktop (macOS/Linux) | Yes (auto) | None. |
|
|
96
|
+
| Native Docker Engine (Linux) | No (bridge gateway only) | `setup.sh` (via `mcp-setup.sh`) adds the host-gateway mapping; the launcher runs a host-side `ncat` forwarder (install `nmap-ncat`/`nmap`) to expose Chrome's loopback CDP port on the bridge gateway. **Firewall the port**: this exposes DevTools beyond loopback. |
|
|
97
|
+
|
|
98
|
+
## Configuration
|
|
99
|
+
|
|
100
|
+
- Ports: host CDP `9333` (configurable), in-container proxy `9334` (fixed).
|
|
101
|
+
- Overrides (host launcher), set in the repo `.env`: `CHROME_DEBUG_PORT` and
|
|
102
|
+
`CHROME_BIN`. `CHROME_DEBUG_BIND` is an advanced escape hatch — the bind is
|
|
103
|
+
auto-detected, so it is not listed in `.env.dist`; set it only when detection
|
|
104
|
+
fails (e.g. a non-default daemon `host-gateway-ip`). **Security:** a
|
|
105
|
+
non-loopback bind exposes the unauthenticated CDP port; firewall it.
|
|
106
|
+
The proxy's listen port and target host are fixed constants; only its upstream
|
|
107
|
+
port is configurable, and it follows the same `CHROME_DEBUG_PORT`.
|
|
108
|
+
- Set these in the repo `.env`; Compose passes `CHROME_DEBUG_PORT` into the
|
|
109
|
+
container, so a single knob drives both sides. Recreate the node container
|
|
110
|
+
after editing. See `.env.dist`.
|
|
111
|
+
- Profile: `~/.chrome-debug-profile-<COMPOSE_PROJECT_NAME>` (per project, fixed).
|
|
112
|
+
|
|
113
|
+
## Troubleshooting
|
|
114
|
+
|
|
115
|
+
- Status: `claude mcp get chrome-host` (`✔ Connected` = healthy chain).
|
|
116
|
+
- Chrome died? Re-run `start-host-chrome.sh` (it persists across sessions).
|
|
117
|
+
- `claude mcp list` shows *Pending approval* until you approve project servers.
|
|
118
|
+
- Disable: remove the `chrome-host` entry from `.mcp.json`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import socket
|
|
3
|
+
import threading
|
|
4
|
+
|
|
5
|
+
# Loopback CDP proxy. Runs INSIDE the node container.
|
|
6
|
+
#
|
|
7
|
+
# A container reaches host Chrome only via `host.docker.internal` (forwarded to
|
|
8
|
+
# the host loopback on Docker Desktop; the bridge gateway on native Linux Docker,
|
|
9
|
+
# where a host-side ncat forwarder exposes Chrome's loopback CDP port). But
|
|
10
|
+
# Chrome's DevTools endpoint rejects any Host header that isn't an IP literal (or
|
|
11
|
+
# the name "localhost") -- anti DNS-rebinding -- so connecting by that *name*
|
|
12
|
+
# fails, and the IPv6 it may also resolve to is often not forwarded.
|
|
13
|
+
#
|
|
14
|
+
# So this proxy listens on 127.0.0.1:LISTEN_PORT and forwards raw bytes to host
|
|
15
|
+
# Chrome. Clients reach it as "127.0.0.1:LISTEN_PORT", so the Host header Chrome
|
|
16
|
+
# sees is an IP (accepted), and the webSocketDebuggerUrl it echoes back stays on
|
|
17
|
+
# 127.0.0.1 too, so the follow-up WebSocket also works.
|
|
18
|
+
#
|
|
19
|
+
# The proxy's own endpoint is fixed: it always listens on the container loopback
|
|
20
|
+
# and forwards to host Chrome over host.docker.internal. Only the upstream Chrome
|
|
21
|
+
# port is configurable, via CHROME_DEBUG_PORT (set in the repo's .env, which
|
|
22
|
+
# Compose passes into the container), so a single knob keeps both sides in sync.
|
|
23
|
+
|
|
24
|
+
LISTEN_HOST = "127.0.0.1"
|
|
25
|
+
LISTEN_PORT = 9334 # fixed; must match PROXY_PORT in mcp-launch.sh
|
|
26
|
+
TARGET_HOST = "host.docker.internal" # fixed; how the container reaches the host
|
|
27
|
+
TARGET_PORT = int(os.environ.get("CHROME_DEBUG_PORT") or "9333") # host Chrome CDP port
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def resolve_target():
|
|
31
|
+
# Force IPv4: host.docker.internal may also resolve to an IPv6 address that
|
|
32
|
+
# Docker does not forward to the host.
|
|
33
|
+
try:
|
|
34
|
+
infos = socket.getaddrinfo(TARGET_HOST, TARGET_PORT, socket.AF_INET, socket.SOCK_STREAM)
|
|
35
|
+
return infos[0][4]
|
|
36
|
+
except Exception:
|
|
37
|
+
# Docker Desktop's well-known host-gateway IPv4 as a last resort.
|
|
38
|
+
return ("192.168.65.254", TARGET_PORT)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
TARGET = resolve_target()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def pipe(source, destination):
|
|
45
|
+
try:
|
|
46
|
+
while True:
|
|
47
|
+
data = source.recv(65536)
|
|
48
|
+
if not data:
|
|
49
|
+
break
|
|
50
|
+
destination.sendall(data)
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
finally:
|
|
54
|
+
for sock in (source, destination):
|
|
55
|
+
try:
|
|
56
|
+
sock.close()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def handle(client):
|
|
62
|
+
try:
|
|
63
|
+
upstream = socket.create_connection(TARGET, timeout=10)
|
|
64
|
+
except Exception:
|
|
65
|
+
client.close()
|
|
66
|
+
return
|
|
67
|
+
threading.Thread(target=pipe, args=(client, upstream), daemon=True).start()
|
|
68
|
+
threading.Thread(target=pipe, args=(upstream, client), daemon=True).start()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def main():
|
|
72
|
+
srv = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
73
|
+
srv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
|
74
|
+
try:
|
|
75
|
+
srv.bind((LISTEN_HOST, LISTEN_PORT))
|
|
76
|
+
except OSError:
|
|
77
|
+
# Another instance already owns the port; let it serve.
|
|
78
|
+
raise SystemExit(0)
|
|
79
|
+
srv.listen(128)
|
|
80
|
+
print("chrome-host: proxy listening %s:%d -> %s:%d" % (LISTEN_HOST, LISTEN_PORT, TARGET[0], TARGET[1]), flush=True)
|
|
81
|
+
while True:
|
|
82
|
+
try:
|
|
83
|
+
conn, _ = srv.accept()
|
|
84
|
+
except Exception:
|
|
85
|
+
continue
|
|
86
|
+
handle(conn)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
if __name__ == "__main__":
|
|
90
|
+
main()
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Referenced by .mcp.json / opencode.json as the `chrome-host` MCP server command.
|
|
4
|
+
#
|
|
5
|
+
# Spawns chrome-devtools-mcp inside the node container (via mcp-launch.sh),
|
|
6
|
+
# whether the assistant runs in the devcontainer or on the host. Docker needs
|
|
7
|
+
# `sudo` inside the container (like the node/npx wrappers) but not on the host;
|
|
8
|
+
# we tell them apart by /.dockerenv, which exists only in a container.
|
|
9
|
+
|
|
10
|
+
set -eu
|
|
11
|
+
|
|
12
|
+
# Compose service that runs the proxy + chrome-devtools-mcp.
|
|
13
|
+
DOCKER_SERVICE_NAME=node
|
|
14
|
+
|
|
15
|
+
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
16
|
+
|
|
17
|
+
# Walk up to the repo root (the directory holding docker-compose.yml) so
|
|
18
|
+
# `docker compose` finds the compose files + .env regardless of the caller's cwd.
|
|
19
|
+
root="$script_dir"
|
|
20
|
+
while [ "$root" != "/" ] && [ ! -f "$root/docker-compose.yml" ]; do
|
|
21
|
+
root="$(dirname "$root")"
|
|
22
|
+
done
|
|
23
|
+
cd "$root"
|
|
24
|
+
|
|
25
|
+
# Path to mcp-launch.sh as seen inside the node container (repo mounted at /workspace).
|
|
26
|
+
rel="${script_dir#"$root"/}"
|
|
27
|
+
launch="/workspace/$rel/mcp-launch.sh"
|
|
28
|
+
|
|
29
|
+
if [ -f /.dockerenv ]; then
|
|
30
|
+
exec sudo docker compose exec -T "$DOCKER_SERVICE_NAME" "$launch"
|
|
31
|
+
else
|
|
32
|
+
exec docker compose exec -T "$DOCKER_SERVICE_NAME" "$launch"
|
|
33
|
+
fi
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Runs INSIDE the node container, invoked by mcp-entry.sh.
|
|
4
|
+
#
|
|
5
|
+
# Ensures the loopback CDP proxy (cdp_proxy.py) is running, then hands stdio over
|
|
6
|
+
# to chrome-devtools-mcp pointed at the proxy. Resolves cdp_proxy.py next to
|
|
7
|
+
# itself, so it works from any location under the repo.
|
|
8
|
+
|
|
9
|
+
set -eu
|
|
10
|
+
|
|
11
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
12
|
+
PROXY_PORT=9334 # fixed; must match LISTEN_PORT in cdp_proxy.py
|
|
13
|
+
PROXY_SCRIPT="$SCRIPT_DIR/cdp_proxy.py"
|
|
14
|
+
|
|
15
|
+
is_up() {
|
|
16
|
+
python3 -c 'import socket,sys; s=socket.socket(); sys.exit(0 if s.connect_ex(("127.0.0.1",'"$PROXY_PORT"'))==0 else 1)' 2>/dev/null
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if ! is_up; then
|
|
20
|
+
# cdp_proxy.py binds-or-exits, so concurrent launches are safe.
|
|
21
|
+
python3 "$PROXY_SCRIPT" >/tmp/cdp_proxy.log 2>&1 &
|
|
22
|
+
i=0
|
|
23
|
+
while [ "$i" -lt 25 ]; do
|
|
24
|
+
if is_up; then
|
|
25
|
+
break
|
|
26
|
+
fi
|
|
27
|
+
i=$((i + 1))
|
|
28
|
+
sleep 0.2
|
|
29
|
+
done
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# chrome-devtools-mcp is fetched on demand via npx rather than being added to
|
|
33
|
+
# package.json: it is host/dev MCP tooling for this bridge, not an app build or
|
|
34
|
+
# runtime dependency, so it deliberately stays out of the lockfile. `-y` confirms
|
|
35
|
+
# the one-off npx install; `@latest` tracks upstream fixes for the dev tool.
|
|
36
|
+
exec npx -y chrome-devtools-mcp@latest --browser-url "http://127.0.0.1:${PROXY_PORT}" "$@"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
|
|
3
|
+
# Run on the HOST during project setup (called from setup.sh).
|
|
4
|
+
#
|
|
5
|
+
# Native Docker Engine on Linux doesn't inject `host.docker.internal`, so the
|
|
6
|
+
# proxy container can't reach host Chrome. This adds the host-gateway mapping to
|
|
7
|
+
# the node service -- but only there. On Docker Desktop/macOS it's a no-op:
|
|
8
|
+
# the name already works, and adding the mapping would repoint it to the bridge
|
|
9
|
+
# gateway and break Desktop.
|
|
10
|
+
#
|
|
11
|
+
# Idempotent; after patching a running setup, recreate the service:
|
|
12
|
+
# `docker compose up -d node`.
|
|
13
|
+
|
|
14
|
+
set -eu
|
|
15
|
+
|
|
16
|
+
ROOT="$(CDPATH= cd -- "$(dirname -- "$0")/../../.." && pwd)"
|
|
17
|
+
COMPOSE_FILE="$ROOT/docker-compose.yml"
|
|
18
|
+
DOCKER_SERVICE_NAME=node
|
|
19
|
+
MAPPING="host.docker.internal:host-gateway"
|
|
20
|
+
|
|
21
|
+
# Only native Docker Engine on Linux needs the mapping. macOS is always Desktop.
|
|
22
|
+
if [ "$(uname -s)" = "Darwin" ]; then
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
DOCKER_OS="$(docker info --format '{{.OperatingSystem}}' 2>/dev/null || true)"
|
|
27
|
+
if [ -z "$DOCKER_OS" ]; then
|
|
28
|
+
echo "chrome-host: warning: could not query Docker (is it running?); skipping host-gateway setup." >&2
|
|
29
|
+
exit 0
|
|
30
|
+
fi
|
|
31
|
+
case "$DOCKER_OS" in
|
|
32
|
+
*"Docker Desktop"*) exit 0 ;;
|
|
33
|
+
esac
|
|
34
|
+
|
|
35
|
+
# Native Docker Engine from here on.
|
|
36
|
+
|
|
37
|
+
# The host launcher forwards Chrome's loopback CDP port to the bridge gateway
|
|
38
|
+
# with ncat (headed Chrome only binds loopback). Warn now rather than failing at
|
|
39
|
+
# first browser launch; don't auto-install -- that needs sudo plus distro
|
|
40
|
+
# detection, and adding host packages requires explicit approval.
|
|
41
|
+
if ! command -v ncat >/dev/null 2>&1; then
|
|
42
|
+
echo "chrome-host: warning: 'ncat' not found; install it (package 'nmap-ncat' or 'nmap'," >&2
|
|
43
|
+
echo "chrome-host: depending on your distro) so start-host-chrome.sh can expose Chrome's" >&2
|
|
44
|
+
echo "chrome-host: CDP port on the bridge gateway for the container." >&2
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
[ -f "$COMPOSE_FILE" ] || exit 0
|
|
48
|
+
if grep -q "$MAPPING" "$COMPOSE_FILE"; then
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Insert extra_hosts under the `<service>:` block (sibling of `extends:`).
|
|
53
|
+
TMP_FILE="$COMPOSE_FILE.tmp"
|
|
54
|
+
awk -v service="$DOCKER_SERVICE_NAME" '
|
|
55
|
+
{ print }
|
|
56
|
+
$0 ~ ("^ " service ":[[:space:]]*$") && !done {
|
|
57
|
+
print " extra_hosts:"
|
|
58
|
+
print " - \"host.docker.internal:host-gateway\""
|
|
59
|
+
done = 1
|
|
60
|
+
}
|
|
61
|
+
' "$COMPOSE_FILE" > "$TMP_FILE"
|
|
62
|
+
|
|
63
|
+
if grep -q "$MAPPING" "$TMP_FILE"; then
|
|
64
|
+
mv "$TMP_FILE" "$COMPOSE_FILE"
|
|
65
|
+
echo "chrome-host: native Docker Engine detected -- added '$MAPPING' to the '$DOCKER_SERVICE_NAME' service in $COMPOSE_FILE."
|
|
66
|
+
echo "chrome-host: if the '$DOCKER_SERVICE_NAME' container is already running, recreate it: docker compose up -d $DOCKER_SERVICE_NAME"
|
|
67
|
+
else
|
|
68
|
+
rm -f "$TMP_FILE"
|
|
69
|
+
echo "chrome-host: warning: could not locate the '$DOCKER_SERVICE_NAME' service in $COMPOSE_FILE; add '$MAPPING' to its extra_hosts manually." >&2
|
|
70
|
+
fi
|