@kokorolx/ai-sandbox-wrapper 3.3.0-beta.2 → 3.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/bin/ai-run +29 -6
- package/lib/playwright-mcp-config.sh +75 -0
- package/package.json +1 -1
package/bin/ai-run
CHANGED
|
@@ -791,6 +791,7 @@ fi
|
|
|
791
791
|
HOST_CHROME_CDP=false
|
|
792
792
|
HOST_CHROME_CDP_PORT=19222
|
|
793
793
|
PLAYWRIGHT_MCP_NAME=""
|
|
794
|
+
CHROME_DEVTOOLS_MCP_NAME=""
|
|
794
795
|
# Portable symlink-resolving SCRIPT_DIR (macOS readlink has no -f).
|
|
795
796
|
# Needed because ai-run is typically invoked via the ~/bin/ai-run symlink.
|
|
796
797
|
_pmcp_resolve_script_dir() {
|
|
@@ -823,6 +824,7 @@ if [[ "$TOOL" == "opencode" ]] && command -v jq &>/dev/null && [[ -f "$AI_SANDBO
|
|
|
823
824
|
CONTAINER_HASH=$(echo "$CONTAINER_NAME_VALUE" | md5sum | cut -c1-4)
|
|
824
825
|
HOST_CHROME_CDP_PORT=$((19222 + 0x$CONTAINER_HASH % 100))
|
|
825
826
|
PLAYWRIGHT_MCP_NAME="playwright_port_${HOST_CHROME_CDP_PORT}"
|
|
827
|
+
CHROME_DEVTOOLS_MCP_NAME="chrome-devtools_port_${HOST_CHROME_CDP_PORT}"
|
|
826
828
|
|
|
827
829
|
# Reuse-if-alive: probe before launching
|
|
828
830
|
if pmcp::probe_chrome "$HOST_CHROME_CDP_PORT"; then
|
|
@@ -850,23 +852,29 @@ if [[ "$TOOL" == "opencode" ]] && command -v jq &>/dev/null && [[ -f "$AI_SANDBO
|
|
|
850
852
|
HOST_CHROME_CDP=false
|
|
851
853
|
kill "$CHROME_PID" 2>/dev/null || true
|
|
852
854
|
PLAYWRIGHT_MCP_NAME=""
|
|
855
|
+
CHROME_DEVTOOLS_MCP_NAME=""
|
|
853
856
|
fi
|
|
854
857
|
fi
|
|
855
858
|
|
|
856
|
-
# Locked sweep+
|
|
859
|
+
# Locked sweep+register on the shared OpenCode config — both
|
|
860
|
+
# playwright-mcp and chrome-devtools-mcp point at the same host Chrome.
|
|
857
861
|
if [[ "$HOST_CHROME_CDP" == "true" ]]; then
|
|
858
862
|
OPENCODE_CONFIG_FILE="$HOME/.config/opencode/opencode.json"
|
|
859
863
|
LOCK_FILE="$HOME/.config/opencode/.playwright.lock"
|
|
860
864
|
mkdir -p "$(dirname "$OPENCODE_CONFIG_FILE")"
|
|
861
865
|
[[ -f "$OPENCODE_CONFIG_FILE" ]] || echo '{}' > "$OPENCODE_CONFIG_FILE"
|
|
862
|
-
pmcp::with_lock "$LOCK_FILE" pmcp::
|
|
866
|
+
pmcp::with_lock "$LOCK_FILE" pmcp::register_host_chrome \
|
|
867
|
+
"$OPENCODE_CONFIG_FILE" "$HOST_CHROME_CDP_PORT" \
|
|
868
|
+
"$PLAYWRIGHT_MCP_NAME" "$CHROME_DEVTOOLS_MCP_NAME"
|
|
863
869
|
rc=$?
|
|
864
870
|
if [[ "$rc" == "99" ]]; then
|
|
865
871
|
echo " ⚠️ Could not acquire MCP config lock within 5s; skipping registration."
|
|
866
872
|
PLAYWRIGHT_MCP_NAME=""
|
|
873
|
+
CHROME_DEVTOOLS_MCP_NAME=""
|
|
867
874
|
elif [[ "$rc" != "0" ]]; then
|
|
868
875
|
echo " ⚠️ MCP config update failed (rc=$rc); skipping registration."
|
|
869
876
|
PLAYWRIGHT_MCP_NAME=""
|
|
877
|
+
CHROME_DEVTOOLS_MCP_NAME=""
|
|
870
878
|
fi
|
|
871
879
|
fi
|
|
872
880
|
fi
|
|
@@ -2103,9 +2111,16 @@ configure_opencode_mcp() {
|
|
|
2103
2111
|
for tool in "${all_tools[@]}"; do
|
|
2104
2112
|
case "$tool" in
|
|
2105
2113
|
chrome-devtools)
|
|
2106
|
-
|
|
2107
|
-
|
|
2114
|
+
# Skip static entry under host Chrome mode — per-container
|
|
2115
|
+
# chrome-devtools_port_<port> entry is registered at runtime.
|
|
2116
|
+
if [[ -n "$PLAYWRIGHT_HOST_CHROME" ]] && [[ -f "$PLAYWRIGHT_HOST_CHROME" ]]; then
|
|
2117
|
+
echo " ℹ️ Chrome DevTools MCP entry will be registered per-container at runtime (host Chrome mode)."
|
|
2108
2118
|
configured_any=true
|
|
2119
|
+
else
|
|
2120
|
+
if add_mcp_config "chrome-devtools" "[\"chrome-devtools-mcp\", \"--headless\", \"--isolated\", \"--executablePath\", \"$CHROMIUM_PATH\", \"--chrome-arg=--no-sandbox\"]"; then
|
|
2121
|
+
echo " ✓ Configured Chrome DevTools MCP"
|
|
2122
|
+
configured_any=true
|
|
2123
|
+
fi
|
|
2109
2124
|
fi
|
|
2110
2125
|
;;
|
|
2111
2126
|
playwright)
|
|
@@ -2147,8 +2162,13 @@ configure_opencode_mcp() {
|
|
|
2147
2162
|
if [[ "$tool_choice" =~ ^[Yy]$ ]]; then
|
|
2148
2163
|
case "$tool" in
|
|
2149
2164
|
chrome-devtools)
|
|
2150
|
-
if
|
|
2151
|
-
echo "
|
|
2165
|
+
if [[ -n "$PLAYWRIGHT_HOST_CHROME" ]] && [[ -f "$PLAYWRIGHT_HOST_CHROME" ]]; then
|
|
2166
|
+
echo " ℹ️ Chrome DevTools MCP entry will be registered per-container at runtime (host Chrome mode)."
|
|
2167
|
+
configured_any=true
|
|
2168
|
+
else
|
|
2169
|
+
if add_mcp_config "chrome-devtools" "[\"chrome-devtools-mcp\", \"--headless\", \"--isolated\", \"--executablePath\", \"$CHROMIUM_PATH\", \"--chrome-arg=--no-sandbox\"]"; then
|
|
2170
|
+
echo " ✓ Configured"
|
|
2171
|
+
fi
|
|
2152
2172
|
fi
|
|
2153
2173
|
;;
|
|
2154
2174
|
playwright)
|
|
@@ -2726,6 +2746,9 @@ if [[ -n "${PLAYWRIGHT_MCP_NAME:-}" ]]; then
|
|
|
2726
2746
|
DOCKER_ARGS+=(-e "PLAYWRIGHT_MCP_NAME=$PLAYWRIGHT_MCP_NAME")
|
|
2727
2747
|
DOCKER_ARGS+=(-e "PLAYWRIGHT_PORT=$HOST_CHROME_CDP_PORT")
|
|
2728
2748
|
fi
|
|
2749
|
+
if [[ -n "${CHROME_DEVTOOLS_MCP_NAME:-}" ]]; then
|
|
2750
|
+
DOCKER_ARGS+=(-e "CHROME_DEVTOOLS_MCP_NAME=$CHROME_DEVTOOLS_MCP_NAME")
|
|
2751
|
+
fi
|
|
2729
2752
|
DOCKER_ARGS+=($TERMINAL_SIZE)
|
|
2730
2753
|
|
|
2731
2754
|
DOCKER_ARGS+=("$IMAGE")
|
|
@@ -29,6 +29,81 @@ pmcp::probe_chrome() {
|
|
|
29
29
|
# Host address from container (Docker Desktop on Mac).
|
|
30
30
|
PMCP_DOCKER_HOST_IP="${PMCP_DOCKER_HOST_IP:-192.168.65.254}"
|
|
31
31
|
|
|
32
|
+
# Probe and remove .mcp.<prefix>* entries whose CDP port is no longer alive.
|
|
33
|
+
# Used to garbage-collect stale per-container Chrome MCP entries.
|
|
34
|
+
# Args: $1 = config file path, $2 = key prefix (e.g. "playwright_", "chrome-devtools_")
|
|
35
|
+
pmcp::sweep_dead() {
|
|
36
|
+
local cfg="$1" prefix="$2"
|
|
37
|
+
[[ -f "$cfg" ]] || return 0
|
|
38
|
+
|
|
39
|
+
local keys dead_keys=()
|
|
40
|
+
keys=$(jq -r --arg p "$prefix" '(.mcp // {}) | keys[] | select(startswith($p))' "$cfg" 2>/dev/null || true)
|
|
41
|
+
while IFS= read -r key; do
|
|
42
|
+
[[ -z "$key" ]] && continue
|
|
43
|
+
# Find an http:// or ws:// URL in the command — any flag name works.
|
|
44
|
+
local cmd_url
|
|
45
|
+
cmd_url=$(jq -r --arg k "$key" \
|
|
46
|
+
'.mcp[$k].command[]? | select(startswith("http://") or startswith("ws://"))' \
|
|
47
|
+
"$cfg" 2>/dev/null | head -1)
|
|
48
|
+
[[ -z "$cmd_url" ]] && continue
|
|
49
|
+
# Extract port from http://host:port[/path] or ws://host:port[/path]
|
|
50
|
+
local hostport="${cmd_url#*://}" # host:port[/path]
|
|
51
|
+
hostport="${hostport%%/*}" # host:port
|
|
52
|
+
local entry_port="${hostport##*:}" # port
|
|
53
|
+
if ! pmcp::probe_chrome "$entry_port"; then
|
|
54
|
+
dead_keys+=("$key")
|
|
55
|
+
fi
|
|
56
|
+
done <<< "$keys"
|
|
57
|
+
|
|
58
|
+
(( ${#dead_keys[@]} == 0 )) && return 0
|
|
59
|
+
|
|
60
|
+
local tmp="$cfg.tmp.$$"
|
|
61
|
+
local args=()
|
|
62
|
+
local i=0
|
|
63
|
+
for k in "${dead_keys[@]+"${dead_keys[@]}"}"; do
|
|
64
|
+
args+=(--arg "k$i" "$k")
|
|
65
|
+
i=$((i + 1))
|
|
66
|
+
done
|
|
67
|
+
jq "${args[@]+"${args[@]}"}" \
|
|
68
|
+
'reduce ([$ARGS.named | to_entries[] | select(.key|startswith("k")) | .value][]) as $k (.; del(.mcp[$k]))' \
|
|
69
|
+
"$cfg" > "$tmp"
|
|
70
|
+
mv "$tmp" "$cfg"
|
|
71
|
+
chmod 600 "$cfg"
|
|
72
|
+
|
|
73
|
+
echo " 🧹 pmcp: removed ${#dead_keys[@]} stale ${prefix}* entr$([ ${#dead_keys[@]} -eq 1 ] && echo y || echo ies): ${dead_keys[*]}"
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# Register (or overwrite) an MCP entry.
|
|
77
|
+
# Args: $1 = config file path, $2 = key, $3 = command as JSON array string
|
|
78
|
+
# e.g. pmcp::register cfg playwright_port_19222 '["playwright-mcp","--cdp-endpoint","http://192.168.65.254:19222"]'
|
|
79
|
+
pmcp::register() {
|
|
80
|
+
local cfg="$1" key="$2" cmd_json="$3"
|
|
81
|
+
[[ -f "$cfg" ]] || echo '{}' > "$cfg"
|
|
82
|
+
local tmp="$cfg.tmp.$$"
|
|
83
|
+
jq --arg key "$key" --argjson cmd "$cmd_json" \
|
|
84
|
+
'.mcp = (.mcp // {}) | .mcp[$key] = {"type":"local","command":$cmd}' \
|
|
85
|
+
"$cfg" > "$tmp"
|
|
86
|
+
mv "$tmp" "$cfg"
|
|
87
|
+
chmod 600 "$cfg"
|
|
88
|
+
echo " ➕ pmcp: registered $key"
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# Register both playwright-mcp and chrome-devtools-mcp for a host Chrome on
|
|
92
|
+
# a given CDP port. Sweeps dead entries of both prefixes first, then writes
|
|
93
|
+
# the new entries. MUST be called inside a flock.
|
|
94
|
+
# Args: $1 = cfg, $2 = port, $3 = playwright key, $4 = chrome-devtools key
|
|
95
|
+
pmcp::register_host_chrome() {
|
|
96
|
+
local cfg="$1" port="$2" pw_key="$3" cd_key="$4"
|
|
97
|
+
local url="http://$PMCP_DOCKER_HOST_IP:$port"
|
|
98
|
+
|
|
99
|
+
pmcp::sweep_dead "$cfg" "playwright_"
|
|
100
|
+
pmcp::sweep_dead "$cfg" "chrome-devtools_"
|
|
101
|
+
pmcp::register "$cfg" "$pw_key" \
|
|
102
|
+
"[\"playwright-mcp\",\"--cdp-endpoint\",\"$url\"]"
|
|
103
|
+
pmcp::register "$cfg" "$cd_key" \
|
|
104
|
+
"[\"chrome-devtools-mcp\",\"--browserUrl\",\"$url\"]"
|
|
105
|
+
}
|
|
106
|
+
|
|
32
107
|
# Sweep dead playwright_* entries and append a new one. MUST be called inside
|
|
33
108
|
# a flock by the caller. Does not acquire the lock itself, by design — locking
|
|
34
109
|
# happens around a larger critical section in the caller.
|
package/package.json
CHANGED