@kokorolx/ai-sandbox-wrapper 2.4.0 → 2.7.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 +150 -13
- package/bin/ai-run +506 -7
- package/bin/cli.js +89 -13
- package/dockerfiles/base/Dockerfile +49 -5
- package/dockerfiles/opencode/Dockerfile +0 -1
- package/lib/install-base.sh +84 -5
- package/lib/install-claude.sh +17 -2
- package/lib/install-openclaw.sh +39 -0
- package/lib/install-opencode.sh +21 -6
- package/package.json +2 -1
- package/setup.sh +60 -8
- package/skills/rtk/SKILL.md +103 -0
- package/skills/rtk-setup/SKILL.md +118 -0
- package/dockerfiles/amp/Dockerfile +0 -17
package/bin/ai-run
CHANGED
|
@@ -15,7 +15,9 @@ Options:
|
|
|
15
15
|
-p, --password <value> Set OpenCode server password (for web/serve mode)
|
|
16
16
|
--password-env <VAR> Read OpenCode server password from environment variable
|
|
17
17
|
--allow-unsecured Allow OpenCode server to run without password (suppresses warning)
|
|
18
|
+
--git-fetch Enable git fetch-only mode (blocks push)
|
|
18
19
|
-h, --help Show this help message
|
|
20
|
+
--help-env Show environment variables reference
|
|
19
21
|
|
|
20
22
|
OpenCode Server Authentication (web/serve mode only):
|
|
21
23
|
When running 'opencode web' or 'opencode serve', you can control authentication:
|
|
@@ -38,17 +40,92 @@ Examples:
|
|
|
38
40
|
ai-run opencode web -p secret # Run with password
|
|
39
41
|
ai-run opencode --shell # Start shell, run tool manually
|
|
40
42
|
ai-run aider -n mynetwork # Connect to Docker network
|
|
43
|
+
ai-run opencode --git-fetch # Git fetch only (no push)
|
|
41
44
|
|
|
42
45
|
Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
|
|
43
46
|
EOF
|
|
44
47
|
exit 0
|
|
45
48
|
}
|
|
46
49
|
|
|
50
|
+
show_help_env() {
|
|
51
|
+
cat << 'EOF'
|
|
52
|
+
Environment Variables Reference
|
|
53
|
+
===============================
|
|
54
|
+
|
|
55
|
+
Runtime Environment Variables (inline with ai-run):
|
|
56
|
+
---------------------------------------------------
|
|
57
|
+
AI_IMAGE_SOURCE=registry Use pre-built images from GitLab registry instead of local
|
|
58
|
+
Example: AI_IMAGE_SOURCE=registry ai-run opencode
|
|
59
|
+
|
|
60
|
+
AI_RUN_DEBUG=1 Enable debug output (shows Docker command details)
|
|
61
|
+
Example: AI_RUN_DEBUG=1 ai-run opencode
|
|
62
|
+
|
|
63
|
+
AI_RUN_PLATFORM=linux/amd64 Force specific platform (useful for cross-arch)
|
|
64
|
+
Example: AI_RUN_PLATFORM=linux/amd64 ai-run opencode
|
|
65
|
+
|
|
66
|
+
PORT_BIND=all Bind exposed ports to all interfaces (0.0.0.0) instead of localhost
|
|
67
|
+
⚠️ Security risk - use only when needed
|
|
68
|
+
Example: PORT_BIND=all ai-run opencode web -e 4096
|
|
69
|
+
|
|
70
|
+
PORT=3000,4000 (Deprecated) Expose ports - use --expose flag instead
|
|
71
|
+
Example: PORT=3000 ai-run opencode
|
|
72
|
+
|
|
73
|
+
Build Environment Variables (inline with setup.sh or install scripts):
|
|
74
|
+
----------------------------------------------------------------------
|
|
75
|
+
DOCKER_NO_CACHE=1 Force rebuild without Docker cache
|
|
76
|
+
Example: DOCKER_NO_CACHE=1 bash setup.sh
|
|
77
|
+
|
|
78
|
+
OPENCODE_VERSION=1.1.52 Install specific OpenCode version instead of latest
|
|
79
|
+
Example: OPENCODE_VERSION=1.1.52 bash lib/install-opencode.sh
|
|
80
|
+
|
|
81
|
+
INSTALL_CHROME_DEVTOOLS_MCP=1 Install Chrome DevTools MCP in base image
|
|
82
|
+
INSTALL_PLAYWRIGHT_MCP=1 Install Playwright MCP in base image
|
|
83
|
+
INSTALL_PLAYWRIGHT=1 Install Playwright browser automation
|
|
84
|
+
INSTALL_RUBY=1 Install Ruby 3.3.0 + Rails 8.0.2
|
|
85
|
+
INSTALL_SPEC_KIT=1 Install spec-kit (specify CLI)
|
|
86
|
+
INSTALL_UX_UI_PROMAX=1 Install uipro CLI
|
|
87
|
+
INSTALL_OPENSPEC=1 Install OpenSpec CLI
|
|
88
|
+
|
|
89
|
+
Container Environment Variables (passed to container via ~/.ai-sandbox/env):
|
|
90
|
+
-----------------------------------------------------------------------------
|
|
91
|
+
ANTHROPIC_API_KEY Anthropic API key for Claude
|
|
92
|
+
OPENAI_API_KEY OpenAI API key
|
|
93
|
+
OPENCODE_SERVER_PASSWORD Password for OpenCode web server
|
|
94
|
+
OPENCODE_SERVER_USERNAME Username for OpenCode web server (default: opencode)
|
|
95
|
+
GITHUB_TOKEN GitHub token for gh CLI authentication
|
|
96
|
+
GH_TOKEN Alternative GitHub token variable
|
|
97
|
+
|
|
98
|
+
Configuration Files:
|
|
99
|
+
--------------------
|
|
100
|
+
~/.ai-sandbox/config.json Main configuration (workspaces, git, networks, MCP)
|
|
101
|
+
~/.ai-sandbox/env API keys and secrets (KEY=value format)
|
|
102
|
+
~/.ai-sandbox/workspaces Legacy workspace list (one path per line)
|
|
103
|
+
|
|
104
|
+
Examples:
|
|
105
|
+
# Debug mode with registry images
|
|
106
|
+
AI_RUN_DEBUG=1 AI_IMAGE_SOURCE=registry ai-run opencode
|
|
107
|
+
|
|
108
|
+
# Rebuild everything from scratch
|
|
109
|
+
DOCKER_NO_CACHE=1 bash setup.sh --no-cache
|
|
110
|
+
|
|
111
|
+
# Install specific OpenCode version
|
|
112
|
+
OPENCODE_VERSION=1.1.52 bash lib/install-opencode.sh
|
|
113
|
+
|
|
114
|
+
# Expose port to network (not just localhost)
|
|
115
|
+
PORT_BIND=all ai-run opencode web -e 4096
|
|
116
|
+
EOF
|
|
117
|
+
exit 0
|
|
118
|
+
}
|
|
119
|
+
|
|
47
120
|
# Check for help flag before anything else
|
|
48
121
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
49
122
|
show_help
|
|
50
123
|
fi
|
|
51
124
|
|
|
125
|
+
if [[ "${1:-}" == "--help-env" ]]; then
|
|
126
|
+
show_help_env
|
|
127
|
+
fi
|
|
128
|
+
|
|
52
129
|
TOOL="$1"
|
|
53
130
|
shift 2>/dev/null || { echo "❌ ERROR: No tool specified. Use 'ai-run --help' for usage."; exit 1; }
|
|
54
131
|
|
|
@@ -60,6 +137,7 @@ EXPOSE_ARG=""
|
|
|
60
137
|
SERVER_PASSWORD=""
|
|
61
138
|
PASSWORD_ENV_VAR=""
|
|
62
139
|
ALLOW_UNSECURED=false
|
|
140
|
+
GIT_FETCH_ONLY_FLAG=false
|
|
63
141
|
TOOL_ARGS=()
|
|
64
142
|
|
|
65
143
|
while [[ $# -gt 0 ]]; do
|
|
@@ -102,6 +180,10 @@ while [[ $# -gt 0 ]]; do
|
|
|
102
180
|
ALLOW_UNSECURED=true
|
|
103
181
|
shift
|
|
104
182
|
;;
|
|
183
|
+
--git-fetch)
|
|
184
|
+
GIT_FETCH_ONLY_FLAG=true
|
|
185
|
+
shift
|
|
186
|
+
;;
|
|
105
187
|
*)
|
|
106
188
|
TOOL_ARGS+=("$1")
|
|
107
189
|
shift
|
|
@@ -336,7 +418,7 @@ upgrade_config_to_v2() {
|
|
|
336
418
|
fi
|
|
337
419
|
|
|
338
420
|
# Add missing v2 fields while preserving existing networks
|
|
339
|
-
jq '.version = 2 | .workspaces = (.workspaces // []) | .git = (.git // {"allowedWorkspaces":[],"keySelections":{}})' \
|
|
421
|
+
jq '.version = 2 | .workspaces = (.workspaces // []) | .git = (.git // {"allowedWorkspaces":[],"keySelections":{}}) | .git.fetchOnlyWorkspaces = (.git.fetchOnlyWorkspaces // [])' \
|
|
340
422
|
"$AI_SANDBOX_CONFIG" > "$AI_SANDBOX_CONFIG.tmp" \
|
|
341
423
|
&& mv "$AI_SANDBOX_CONFIG.tmp" "$AI_SANDBOX_CONFIG"
|
|
342
424
|
chmod 600 "$AI_SANDBOX_CONFIG"
|
|
@@ -351,7 +433,7 @@ init_config() {
|
|
|
351
433
|
|
|
352
434
|
if [[ ! -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
353
435
|
# Create new v2 config
|
|
354
|
-
local initial_config='{"version":2,"workspaces":[],"git":{"allowedWorkspaces":[],"keySelections":{}},"networks":{"global":[],"workspaces":{}}}'
|
|
436
|
+
local initial_config='{"version":2,"workspaces":[],"git":{"allowedWorkspaces":[],"fetchOnlyWorkspaces":[],"keySelections":{}},"networks":{"global":[],"workspaces":{}}}'
|
|
355
437
|
echo "$initial_config" > "$AI_SANDBOX_CONFIG"
|
|
356
438
|
chmod 600 "$AI_SANDBOX_CONFIG"
|
|
357
439
|
|
|
@@ -508,9 +590,27 @@ case "$TOOL" in
|
|
|
508
590
|
opencode)
|
|
509
591
|
mount_tool_config "$HOME/.config/opencode" ".config/opencode"
|
|
510
592
|
mount_tool_config "$HOME/.local/share/opencode" ".local/share/opencode"
|
|
593
|
+
# Bundle default skills (copy if not already present)
|
|
594
|
+
AIRUN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
595
|
+
BUNDLED_SKILLS_DIR="$AIRUN_DIR/../skills"
|
|
596
|
+
if [[ -d "$BUNDLED_SKILLS_DIR" ]]; then
|
|
597
|
+
for skill_dir in "$BUNDLED_SKILLS_DIR"/*/; do
|
|
598
|
+
[[ ! -d "$skill_dir" ]] && continue
|
|
599
|
+
skill_name=$(basename "$skill_dir")
|
|
600
|
+
target_dir="$HOME/.config/opencode/skills/$skill_name"
|
|
601
|
+
if [[ ! -d "$target_dir" ]]; then
|
|
602
|
+
mkdir -p "$target_dir"
|
|
603
|
+
cp -r "$skill_dir"* "$target_dir/" 2>/dev/null || true
|
|
604
|
+
fi
|
|
605
|
+
done
|
|
606
|
+
fi
|
|
607
|
+
;;
|
|
608
|
+
openclaw)
|
|
609
|
+
mount_tool_config "$HOME/.openclaw" ".openclaw"
|
|
511
610
|
;;
|
|
512
611
|
claude)
|
|
513
612
|
mount_tool_config "$HOME/.claude" ".claude"
|
|
613
|
+
mount_tool_config "$HOME/.ccs" ".ccs"
|
|
514
614
|
;;
|
|
515
615
|
droid)
|
|
516
616
|
mount_tool_config "$HOME/.config/droid" ".config/droid"
|
|
@@ -972,8 +1072,48 @@ is_git_allowed() {
|
|
|
972
1072
|
return 1
|
|
973
1073
|
}
|
|
974
1074
|
|
|
1075
|
+
# Check if workspace is in fetch-only mode (config.json)
|
|
1076
|
+
is_git_fetch_only() {
|
|
1077
|
+
local ws="$1"
|
|
1078
|
+
if has_jq && [[ -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
1079
|
+
local fetch_only=$(jq -r --arg ws "$ws" '.git.fetchOnlyWorkspaces // [] | index($ws) // -1' "$AI_SANDBOX_CONFIG" 2>/dev/null)
|
|
1080
|
+
[[ "$fetch_only" -ge 0 ]] && return 0
|
|
1081
|
+
fi
|
|
1082
|
+
return 1
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
# Apply fetch-only restrictions by adding pushInsteadOf to gitconfig
|
|
1086
|
+
apply_git_fetch_only() {
|
|
1087
|
+
local gitconfig_path="$HOME_DIR/.gitconfig"
|
|
1088
|
+
# Ensure .gitconfig exists
|
|
1089
|
+
touch "$gitconfig_path"
|
|
1090
|
+
# Append push-blocking config (redirects all push URLs to non-existent protocol)
|
|
1091
|
+
cat >> "$gitconfig_path" << 'FETCHONLY'
|
|
1092
|
+
|
|
1093
|
+
# AI Sandbox: Git fetch-only mode (push disabled)
|
|
1094
|
+
[url "no-push://blocked"]
|
|
1095
|
+
pushInsteadOf = git@
|
|
1096
|
+
pushInsteadOf = ssh://
|
|
1097
|
+
pushInsteadOf = https://
|
|
1098
|
+
pushInsteadOf = http://
|
|
1099
|
+
FETCHONLY
|
|
1100
|
+
echo "🔒 Git fetch-only mode: push operations are blocked"
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
GIT_FETCH_ONLY_MODE=false
|
|
1104
|
+
|
|
1105
|
+
# Check --git-fetch flag
|
|
1106
|
+
if [[ "$GIT_FETCH_ONLY_FLAG" == "true" ]]; then
|
|
1107
|
+
GIT_FETCH_ONLY_MODE=true
|
|
1108
|
+
fi
|
|
1109
|
+
|
|
1110
|
+
# Check if workspace is in fetch-only list
|
|
1111
|
+
if is_git_fetch_only "$CURRENT_DIR"; then
|
|
1112
|
+
GIT_FETCH_ONLY_MODE=true
|
|
1113
|
+
fi
|
|
1114
|
+
|
|
975
1115
|
# Check if Git access is allowed for this workspace
|
|
976
|
-
if is_git_allowed "$CURRENT_DIR"; then
|
|
1116
|
+
if is_git_allowed "$CURRENT_DIR" || is_git_fetch_only "$CURRENT_DIR" || [[ "$GIT_FETCH_ONLY_FLAG" == "true" ]]; then
|
|
977
1117
|
# Previously allowed for this workspace
|
|
978
1118
|
# Check if saved keys exist for this workspace
|
|
979
1119
|
WORKSPACE_MD5=$(echo "$CURRENT_DIR" | md5sum | cut -c1-8)
|
|
@@ -1015,7 +1155,7 @@ if is_git_allowed "$CURRENT_DIR"; then
|
|
|
1015
1155
|
if [ -x "$SETUP_SSH" ]; then
|
|
1016
1156
|
# Join SAVED_KEYS into a comma-separated string for --keys
|
|
1017
1157
|
KEYS_ARG=$(IFS=,; echo "${SAVED_KEYS[*]}")
|
|
1018
|
-
output=$("$SETUP_SSH" --keys "$KEYS_ARG" 2>&1)
|
|
1158
|
+
output=$( "$SETUP_SSH" --keys "$KEYS_ARG" 2>&1 ) || true
|
|
1019
1159
|
TEMP_CONFIG=$(echo "$output" | grep "Config:" | tail -1 | awk '{print $NF}')
|
|
1020
1160
|
if [ -f "$TEMP_CONFIG" ]; then
|
|
1021
1161
|
cp "$TEMP_CONFIG" "$GIT_CACHE_DIR/ssh/config" 2>/dev/null || true
|
|
@@ -1048,6 +1188,11 @@ if is_git_allowed "$CURRENT_DIR"; then
|
|
|
1048
1188
|
# Copy gitconfig to HOME_DIR (can't mount file inside mounted directory)
|
|
1049
1189
|
cp "$HOME/.gitconfig" "$HOME_DIR/.gitconfig" 2>/dev/null || true
|
|
1050
1190
|
fi
|
|
1191
|
+
|
|
1192
|
+
# Apply fetch-only restrictions if mode is active
|
|
1193
|
+
if [[ "$GIT_FETCH_ONLY_MODE" == "true" ]]; then
|
|
1194
|
+
apply_git_fetch_only
|
|
1195
|
+
fi
|
|
1051
1196
|
else
|
|
1052
1197
|
# Ask user if they want Git access for this workspace (only in interactive mode)
|
|
1053
1198
|
if [[ -t 0 ]] && ([ -d "$HOME/.ssh" ] || [ -f "$HOME/.gitconfig" ]); then
|
|
@@ -1060,11 +1205,13 @@ else
|
|
|
1060
1205
|
echo " 1) Yes, allow once (this session only)"
|
|
1061
1206
|
echo " 2) Yes, always allow for this workspace"
|
|
1062
1207
|
echo " 3) No, keep Git disabled (secure default)"
|
|
1208
|
+
echo " 4) Fetch only - allow once (no push, this session)"
|
|
1209
|
+
echo " 5) Fetch only - always for this workspace (no push)"
|
|
1063
1210
|
echo ""
|
|
1064
|
-
read -p "Choice [1-
|
|
1211
|
+
read -p "Choice [1-5]: " git_choice
|
|
1065
1212
|
|
|
1066
1213
|
case "$git_choice" in
|
|
1067
|
-
1|2)
|
|
1214
|
+
1|2|4|5)
|
|
1068
1215
|
# Interactive SSH key selection
|
|
1069
1216
|
echo ""
|
|
1070
1217
|
echo "🔑 SSH Key Selection"
|
|
@@ -1120,7 +1267,7 @@ else
|
|
|
1120
1267
|
# Join SELECTED_SSH_KEYS into a comma-separated string for --keys
|
|
1121
1268
|
KEYS_ARG=$(IFS=,; echo "${SELECTED_SSH_KEYS[*]}")
|
|
1122
1269
|
# Run it and capture the filtered config path
|
|
1123
|
-
output=$("$SETUP_SSH" --keys "$KEYS_ARG" 2>&1)
|
|
1270
|
+
output=$( "$SETUP_SSH" --keys "$KEYS_ARG" 2>&1 ) || true
|
|
1124
1271
|
TEMP_CONFIG=$(echo "$output" | grep "Config:" | tail -1 | awk '{print $NF}')
|
|
1125
1272
|
if [ -f "$TEMP_CONFIG" ]; then
|
|
1126
1273
|
cp "$TEMP_CONFIG" "$GIT_CACHE_DIR/ssh/config" 2>/dev/null || true
|
|
@@ -1156,6 +1303,9 @@ else
|
|
|
1156
1303
|
cp "$HOME/.gitconfig" "$HOME_DIR/.gitconfig" 2>/dev/null || true
|
|
1157
1304
|
fi
|
|
1158
1305
|
|
|
1306
|
+
if [[ "$git_choice" == "4" || "$git_choice" == "5" ]]; then
|
|
1307
|
+
GIT_FETCH_ONLY_MODE=true
|
|
1308
|
+
fi
|
|
1159
1309
|
if [ "$git_choice" = "2" ]; then
|
|
1160
1310
|
# Save workspace and selected keys for future sessions
|
|
1161
1311
|
echo "$CURRENT_DIR" >> "$GIT_ALLOWED_FILE"
|
|
@@ -1170,6 +1320,20 @@ else
|
|
|
1170
1320
|
mkdir -p "$GIT_SHARED_DIR/keys"
|
|
1171
1321
|
printf "%s\n" "${SELECTED_SSH_KEYS[@]}" > "$GIT_SHARED_DIR/keys/$WORKSPACE_MD5"
|
|
1172
1322
|
echo "✅ Git access enabled and saved for: $CURRENT_DIR"
|
|
1323
|
+
elif [ "$git_choice" = "5" ]; then
|
|
1324
|
+
# Save workspace to fetchOnlyWorkspaces
|
|
1325
|
+
if has_jq && [[ -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
1326
|
+
jq --arg ws "$CURRENT_DIR" '.git.fetchOnlyWorkspaces = ((.git.fetchOnlyWorkspaces // []) + [$ws] | unique)' "$AI_SANDBOX_CONFIG" > "$AI_SANDBOX_CONFIG.tmp" \
|
|
1327
|
+
&& mv "$AI_SANDBOX_CONFIG.tmp" "$AI_SANDBOX_CONFIG"
|
|
1328
|
+
chmod 600 "$AI_SANDBOX_CONFIG"
|
|
1329
|
+
fi
|
|
1330
|
+
# Save selected keys (one per line for easier parsing)
|
|
1331
|
+
WORKSPACE_MD5=$(echo "$CURRENT_DIR" | md5sum | cut -c1-8)
|
|
1332
|
+
mkdir -p "$GIT_SHARED_DIR/keys"
|
|
1333
|
+
printf "%s\n" "${SELECTED_SSH_KEYS[@]}" > "$GIT_SHARED_DIR/keys/$WORKSPACE_MD5"
|
|
1334
|
+
echo "✅ Git fetch-only access enabled and saved for: $CURRENT_DIR"
|
|
1335
|
+
elif [ "$git_choice" = "4" ]; then
|
|
1336
|
+
echo "✅ Git fetch-only access enabled for this session"
|
|
1173
1337
|
else
|
|
1174
1338
|
echo "✅ Git access enabled for this session"
|
|
1175
1339
|
fi
|
|
@@ -1185,6 +1349,11 @@ else
|
|
|
1185
1349
|
echo "🔒 Git access disabled (secure mode)"
|
|
1186
1350
|
;;
|
|
1187
1351
|
esac
|
|
1352
|
+
|
|
1353
|
+
# Apply fetch-only restrictions if mode is active
|
|
1354
|
+
if [[ "$GIT_FETCH_ONLY_MODE" == "true" ]]; then
|
|
1355
|
+
apply_git_fetch_only
|
|
1356
|
+
fi
|
|
1188
1357
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1189
1358
|
echo ""
|
|
1190
1359
|
fi
|
|
@@ -1398,6 +1567,273 @@ if is_opencode_web_mode; then
|
|
|
1398
1567
|
resolve_opencode_password
|
|
1399
1568
|
fi
|
|
1400
1569
|
|
|
1570
|
+
# ============================================================================
|
|
1571
|
+
# MCP AUTO-CONFIGURATION FOR OPENCODE
|
|
1572
|
+
# ============================================================================
|
|
1573
|
+
|
|
1574
|
+
# Check if MCP tool is installed (from config.json, set during setup.sh)
|
|
1575
|
+
is_mcp_installed() {
|
|
1576
|
+
local mcp_tool="$1"
|
|
1577
|
+
if has_jq && [[ -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
1578
|
+
jq -e --arg tool "$mcp_tool" '.mcp.installed | index($tool) != null' "$AI_SANDBOX_CONFIG" &>/dev/null
|
|
1579
|
+
else
|
|
1580
|
+
return 1
|
|
1581
|
+
fi
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
# Check if MCP is already configured in OpenCode config
|
|
1585
|
+
is_mcp_configured() {
|
|
1586
|
+
local mcp_name="$1"
|
|
1587
|
+
local config_file="$HOME/.config/opencode/opencode.json"
|
|
1588
|
+
|
|
1589
|
+
if [[ ! -f "$config_file" ]]; then
|
|
1590
|
+
return 1
|
|
1591
|
+
fi
|
|
1592
|
+
|
|
1593
|
+
if has_jq; then
|
|
1594
|
+
jq -e --arg name "$mcp_name" '.mcp[$name] // empty' "$config_file" &>/dev/null
|
|
1595
|
+
else
|
|
1596
|
+
grep -q "\"$mcp_name\"" "$config_file" 2>/dev/null
|
|
1597
|
+
fi
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
# Add MCP configuration to OpenCode config
|
|
1601
|
+
add_mcp_config() {
|
|
1602
|
+
local mcp_name="$1"
|
|
1603
|
+
local mcp_command="$2"
|
|
1604
|
+
local force="${3:-false}"
|
|
1605
|
+
local config_file="$HOME/.config/opencode/opencode.json"
|
|
1606
|
+
|
|
1607
|
+
# Check if already configured and warn user
|
|
1608
|
+
if [[ "$force" != "true" ]] && is_mcp_configured "$mcp_name"; then
|
|
1609
|
+
echo ""
|
|
1610
|
+
echo " ⚠️ WARNING: '$mcp_name' is already configured!"
|
|
1611
|
+
echo " Reconfiguring will overwrite your existing settings."
|
|
1612
|
+
echo " Any custom credentials or options will be lost."
|
|
1613
|
+
read -p " Are you sure you want to overwrite? [y/N]: " overwrite_choice
|
|
1614
|
+
if [[ ! "$overwrite_choice" =~ ^[Yy]$ ]]; then
|
|
1615
|
+
echo " ⏭️ Kept existing configuration"
|
|
1616
|
+
return 1
|
|
1617
|
+
fi
|
|
1618
|
+
fi
|
|
1619
|
+
|
|
1620
|
+
mkdir -p "$(dirname "$config_file")"
|
|
1621
|
+
|
|
1622
|
+
if [[ ! -f "$config_file" ]]; then
|
|
1623
|
+
# Create new config with MCP
|
|
1624
|
+
echo "{\"mcp\": {\"$mcp_name\": {\"type\": \"local\", \"command\": $mcp_command}}}" > "$config_file"
|
|
1625
|
+
elif has_jq; then
|
|
1626
|
+
# Add MCP to existing config
|
|
1627
|
+
jq --arg name "$mcp_name" --argjson cmd "$mcp_command" \
|
|
1628
|
+
'.mcp = (.mcp // {}) | .mcp[$name] = {"type": "local", "command": $cmd}' \
|
|
1629
|
+
"$config_file" > "$config_file.tmp" && mv "$config_file.tmp" "$config_file"
|
|
1630
|
+
else
|
|
1631
|
+
echo "⚠️ jq not found. Please manually add MCP configuration to $config_file"
|
|
1632
|
+
return 1
|
|
1633
|
+
fi
|
|
1634
|
+
chmod 600 "$config_file"
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
# Mark MCP as skipped for this workspace (don't ask again)
|
|
1638
|
+
mark_mcp_skipped() {
|
|
1639
|
+
local skip_file="$SANDBOX_DIR/.mcp-skip-$WORKSPACE_MD5"
|
|
1640
|
+
date -Iseconds > "$skip_file" 2>/dev/null || date > "$skip_file"
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
# Check if MCP prompt was skipped for this workspace
|
|
1644
|
+
is_mcp_skipped() {
|
|
1645
|
+
local skip_file="$SANDBOX_DIR/.mcp-skip-$WORKSPACE_MD5"
|
|
1646
|
+
[[ -f "$skip_file" ]]
|
|
1647
|
+
}
|
|
1648
|
+
|
|
1649
|
+
# Configure MCP tools for OpenCode
|
|
1650
|
+
configure_opencode_mcp() {
|
|
1651
|
+
# Only run for opencode tool in interactive mode
|
|
1652
|
+
[[ "$TOOL" != "opencode" ]] && return 0
|
|
1653
|
+
[[ ! -t 0 ]] && return 0
|
|
1654
|
+
|
|
1655
|
+
local config_file="$HOME/.config/opencode/opencode.json"
|
|
1656
|
+
|
|
1657
|
+
# Generate workspace hash for skip tracking
|
|
1658
|
+
WORKSPACE_MD5=$(echo "$CURRENT_DIR" | md5sum | cut -c1-8)
|
|
1659
|
+
|
|
1660
|
+
# Check if already skipped for this workspace
|
|
1661
|
+
if is_mcp_skipped; then
|
|
1662
|
+
return 0
|
|
1663
|
+
fi
|
|
1664
|
+
|
|
1665
|
+
# Detect installed MCP tools (from config.json, set during setup.sh)
|
|
1666
|
+
local chrome_installed=false
|
|
1667
|
+
local playwright_installed=false
|
|
1668
|
+
local chrome_configured=false
|
|
1669
|
+
local playwright_configured=false
|
|
1670
|
+
|
|
1671
|
+
if is_mcp_installed "chrome-devtools"; then
|
|
1672
|
+
chrome_installed=true
|
|
1673
|
+
is_mcp_configured "chrome-devtools" && chrome_configured=true
|
|
1674
|
+
fi
|
|
1675
|
+
|
|
1676
|
+
if is_mcp_installed "playwright"; then
|
|
1677
|
+
playwright_installed=true
|
|
1678
|
+
is_mcp_configured "playwright" && playwright_configured=true
|
|
1679
|
+
fi
|
|
1680
|
+
|
|
1681
|
+
# If no MCP tools installed in image, return
|
|
1682
|
+
if [[ "$chrome_installed" == "false" && "$playwright_installed" == "false" ]]; then
|
|
1683
|
+
return 0
|
|
1684
|
+
fi
|
|
1685
|
+
|
|
1686
|
+
# If all installed tools are already configured, return silently
|
|
1687
|
+
local all_configured=true
|
|
1688
|
+
[[ "$chrome_installed" == "true" && "$chrome_configured" == "false" ]] && all_configured=false
|
|
1689
|
+
[[ "$playwright_installed" == "true" && "$playwright_configured" == "false" ]] && all_configured=false
|
|
1690
|
+
|
|
1691
|
+
if [[ "$all_configured" == "true" ]]; then
|
|
1692
|
+
return 0
|
|
1693
|
+
fi
|
|
1694
|
+
|
|
1695
|
+
# Build lists of configured and unconfigured tools
|
|
1696
|
+
local unconfigured=()
|
|
1697
|
+
local configured=()
|
|
1698
|
+
[[ "$chrome_installed" == "true" && "$chrome_configured" == "false" ]] && unconfigured+=("chrome-devtools")
|
|
1699
|
+
[[ "$chrome_installed" == "true" && "$chrome_configured" == "true" ]] && configured+=("chrome-devtools")
|
|
1700
|
+
[[ "$playwright_installed" == "true" && "$playwright_configured" == "false" ]] && unconfigured+=("playwright")
|
|
1701
|
+
[[ "$playwright_installed" == "true" && "$playwright_configured" == "true" ]] && configured+=("playwright")
|
|
1702
|
+
|
|
1703
|
+
# Show prompt
|
|
1704
|
+
echo ""
|
|
1705
|
+
echo "🔌 MCP Tools Detected"
|
|
1706
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1707
|
+
|
|
1708
|
+
# Show already configured tools
|
|
1709
|
+
if [[ ${#configured[@]} -gt 0 ]]; then
|
|
1710
|
+
echo "Already configured:"
|
|
1711
|
+
for tool in "${configured[@]}"; do
|
|
1712
|
+
case "$tool" in
|
|
1713
|
+
chrome-devtools)
|
|
1714
|
+
echo " ✓ Chrome DevTools MCP"
|
|
1715
|
+
;;
|
|
1716
|
+
playwright)
|
|
1717
|
+
echo " ✓ Playwright MCP"
|
|
1718
|
+
;;
|
|
1719
|
+
esac
|
|
1720
|
+
done
|
|
1721
|
+
echo ""
|
|
1722
|
+
fi
|
|
1723
|
+
|
|
1724
|
+
# Show unconfigured tools
|
|
1725
|
+
if [[ ${#unconfigured[@]} -gt 0 ]]; then
|
|
1726
|
+
echo "Not yet configured:"
|
|
1727
|
+
for tool in "${unconfigured[@]}"; do
|
|
1728
|
+
case "$tool" in
|
|
1729
|
+
chrome-devtools)
|
|
1730
|
+
echo " • Chrome DevTools MCP - browser automation + performance profiling"
|
|
1731
|
+
;;
|
|
1732
|
+
playwright)
|
|
1733
|
+
echo " • Playwright MCP - multi-browser automation"
|
|
1734
|
+
;;
|
|
1735
|
+
esac
|
|
1736
|
+
done
|
|
1737
|
+
echo ""
|
|
1738
|
+
fi
|
|
1739
|
+
|
|
1740
|
+
echo "Configure MCP tools in OpenCode?"
|
|
1741
|
+
echo " 1) Yes, configure all detected tools"
|
|
1742
|
+
echo " 2) Yes, let me choose which ones"
|
|
1743
|
+
echo " 3) No, skip (I'll configure manually)"
|
|
1744
|
+
echo " 4) No, don't ask again for this workspace"
|
|
1745
|
+
echo ""
|
|
1746
|
+
read -p "Choice [1-4]: " mcp_choice
|
|
1747
|
+
|
|
1748
|
+
local configured_any=false
|
|
1749
|
+
|
|
1750
|
+
case "$mcp_choice" in
|
|
1751
|
+
1)
|
|
1752
|
+
# Configure all (both unconfigured and offer to reconfigure configured ones)
|
|
1753
|
+
local all_tools=()
|
|
1754
|
+
[[ "$chrome_installed" == "true" ]] && all_tools+=("chrome-devtools")
|
|
1755
|
+
[[ "$playwright_installed" == "true" ]] && all_tools+=("playwright")
|
|
1756
|
+
|
|
1757
|
+
for tool in "${all_tools[@]}"; do
|
|
1758
|
+
case "$tool" in
|
|
1759
|
+
chrome-devtools)
|
|
1760
|
+
if add_mcp_config "chrome-devtools" '["chrome-devtools-mcp", "--headless", "--isolated", "--executablePath", "/opt/chromium"]'; then
|
|
1761
|
+
echo " ✓ Configured Chrome DevTools MCP"
|
|
1762
|
+
configured_any=true
|
|
1763
|
+
fi
|
|
1764
|
+
;;
|
|
1765
|
+
playwright)
|
|
1766
|
+
if add_mcp_config "playwright" '["npx", "@playwright/mcp@latest", "--headless", "--browser", "chromium"]'; then
|
|
1767
|
+
echo " ✓ Configured Playwright MCP"
|
|
1768
|
+
configured_any=true
|
|
1769
|
+
fi
|
|
1770
|
+
;;
|
|
1771
|
+
esac
|
|
1772
|
+
done
|
|
1773
|
+
;;
|
|
1774
|
+
2)
|
|
1775
|
+
# Let user choose from all installed tools
|
|
1776
|
+
local all_tools=()
|
|
1777
|
+
[[ "$chrome_installed" == "true" ]] && all_tools+=("chrome-devtools")
|
|
1778
|
+
[[ "$playwright_installed" == "true" ]] && all_tools+=("playwright")
|
|
1779
|
+
|
|
1780
|
+
for tool in "${all_tools[@]}"; do
|
|
1781
|
+
local tool_desc=""
|
|
1782
|
+
local status_hint=""
|
|
1783
|
+
case "$tool" in
|
|
1784
|
+
chrome-devtools)
|
|
1785
|
+
tool_desc="Chrome DevTools MCP (browser automation + DevTools)"
|
|
1786
|
+
is_mcp_configured "chrome-devtools" && status_hint=" [already configured]"
|
|
1787
|
+
;;
|
|
1788
|
+
playwright)
|
|
1789
|
+
tool_desc="Playwright MCP (multi-browser automation)"
|
|
1790
|
+
is_mcp_configured "playwright" && status_hint=" [already configured]"
|
|
1791
|
+
;;
|
|
1792
|
+
esac
|
|
1793
|
+
read -p " Configure $tool_desc$status_hint? [y/N]: " tool_choice
|
|
1794
|
+
if [[ "$tool_choice" =~ ^[Yy]$ ]]; then
|
|
1795
|
+
case "$tool" in
|
|
1796
|
+
chrome-devtools)
|
|
1797
|
+
if add_mcp_config "chrome-devtools" '["chrome-devtools-mcp", "--headless", "--isolated", "--executablePath", "/opt/chromium"]'; then
|
|
1798
|
+
echo " ✓ Configured"
|
|
1799
|
+
configured_any=true
|
|
1800
|
+
fi
|
|
1801
|
+
;;
|
|
1802
|
+
playwright)
|
|
1803
|
+
if add_mcp_config "playwright" '["npx", "@playwright/mcp@latest", "--headless", "--browser", "chromium"]'; then
|
|
1804
|
+
echo " ✓ Configured"
|
|
1805
|
+
configured_any=true
|
|
1806
|
+
fi
|
|
1807
|
+
;;
|
|
1808
|
+
esac
|
|
1809
|
+
else
|
|
1810
|
+
echo " ⏭️ Skipped"
|
|
1811
|
+
fi
|
|
1812
|
+
done
|
|
1813
|
+
;;
|
|
1814
|
+
4)
|
|
1815
|
+
mark_mcp_skipped
|
|
1816
|
+
echo "ℹ️ Won't ask again for this workspace"
|
|
1817
|
+
;;
|
|
1818
|
+
*)
|
|
1819
|
+
echo "ℹ️ Skipped."
|
|
1820
|
+
;;
|
|
1821
|
+
esac
|
|
1822
|
+
|
|
1823
|
+
# Show config file path for easy editing
|
|
1824
|
+
echo ""
|
|
1825
|
+
if [[ "$configured_any" == "true" ]]; then
|
|
1826
|
+
echo "✅ MCP configuration saved!"
|
|
1827
|
+
fi
|
|
1828
|
+
echo "📄 Config file: $config_file"
|
|
1829
|
+
echo " Edit this file to customize MCP settings or add credentials."
|
|
1830
|
+
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
1831
|
+
echo ""
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
# Run MCP configuration check for opencode
|
|
1835
|
+
configure_opencode_mcp
|
|
1836
|
+
|
|
1401
1837
|
# ============================================================================
|
|
1402
1838
|
# WEB COMMAND DETECTION AND PORT EXPOSURE
|
|
1403
1839
|
# ============================================================================
|
|
@@ -1637,6 +2073,69 @@ fi
|
|
|
1637
2073
|
# Detect display configuration (clipboard integration)
|
|
1638
2074
|
DISPLAY_FLAGS=$(detect_display_config)
|
|
1639
2075
|
|
|
2076
|
+
# ============================================================================
|
|
2077
|
+
# OPENCLAW DOCKER-COMPOSE MODE
|
|
2078
|
+
# ============================================================================
|
|
2079
|
+
if [[ "$TOOL" == "openclaw" ]]; then
|
|
2080
|
+
OPENCLAW_REPO_DIR="$HOME/.ai-sandbox/tools/openclaw/repo"
|
|
2081
|
+
|
|
2082
|
+
if [[ ! -d "$OPENCLAW_REPO_DIR" ]]; then
|
|
2083
|
+
echo "❌ ERROR: OpenClaw repository not found at $OPENCLAW_REPO_DIR"
|
|
2084
|
+
echo " Run: npx @kokorolx/ai-sandbox-wrapper setup"
|
|
2085
|
+
exit 1
|
|
2086
|
+
fi
|
|
2087
|
+
|
|
2088
|
+
cd "$OPENCLAW_REPO_DIR"
|
|
2089
|
+
|
|
2090
|
+
OPENCLAW_COMPOSE_FILE="$OPENCLAW_REPO_DIR/docker-compose.yml"
|
|
2091
|
+
OPENCLAW_OVERRIDE_FILE="$OPENCLAW_REPO_DIR/docker-compose.override.yml"
|
|
2092
|
+
|
|
2093
|
+
echo "🔄 Generating OpenClaw docker-compose override..."
|
|
2094
|
+
|
|
2095
|
+
cat > "$OPENCLAW_OVERRIDE_FILE" <<EOF
|
|
2096
|
+
services:
|
|
2097
|
+
openclaw-gateway:
|
|
2098
|
+
environment:
|
|
2099
|
+
HOME: /home/node
|
|
2100
|
+
OPENCLAW_GATEWAY_TOKEN: \${OPENCLAW_GATEWAY_TOKEN:-}
|
|
2101
|
+
volumes:
|
|
2102
|
+
- $HOME/.openclaw:/home/node/.openclaw
|
|
2103
|
+
EOF
|
|
2104
|
+
|
|
2105
|
+
for workspace in "${WORKSPACES[@]}"; do
|
|
2106
|
+
echo " - $workspace:$workspace" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2107
|
+
done
|
|
2108
|
+
|
|
2109
|
+
cat >> "$OPENCLAW_OVERRIDE_FILE" <<EOF
|
|
2110
|
+
ports:
|
|
2111
|
+
- "18789:18789"
|
|
2112
|
+
- "18790:18790"
|
|
2113
|
+
working_dir: $CURRENT_DIR
|
|
2114
|
+
EOF
|
|
2115
|
+
|
|
2116
|
+
if [[ -n "$NETWORK_OPTIONS" ]]; then
|
|
2117
|
+
echo " networks:" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2118
|
+
for net in ${DOCKER_NETWORKS//,/ }; do
|
|
2119
|
+
echo " - $net" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2120
|
+
done
|
|
2121
|
+
echo "" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2122
|
+
echo "networks:" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2123
|
+
for net in ${DOCKER_NETWORKS//,/ }; do
|
|
2124
|
+
echo " $net:" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2125
|
+
echo " external: true" >> "$OPENCLAW_OVERRIDE_FILE"
|
|
2126
|
+
done
|
|
2127
|
+
fi
|
|
2128
|
+
|
|
2129
|
+
echo "🚀 Starting OpenClaw with docker-compose..."
|
|
2130
|
+
echo "🌐 Gateway: http://localhost:18789"
|
|
2131
|
+
echo "🌐 Bridge: http://localhost:18790"
|
|
2132
|
+
echo ""
|
|
2133
|
+
|
|
2134
|
+
exec docker compose -f "$OPENCLAW_COMPOSE_FILE" -f "$OPENCLAW_OVERRIDE_FILE" \
|
|
2135
|
+
--env-file "$ENV_FILE" \
|
|
2136
|
+
up --remove-orphans
|
|
2137
|
+
fi
|
|
2138
|
+
|
|
1640
2139
|
docker run $CONTAINER_NAME --rm $TTY_FLAGS \
|
|
1641
2140
|
--init \
|
|
1642
2141
|
--platform "$PLATFORM" \
|