@kokorolx/ai-sandbox-wrapper 1.1.2 â 1.2.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/README.md +45 -1
- package/bin/ai-run +396 -98
- package/dockerfiles/base/Dockerfile +34 -2
- package/lib/install-base.sh +12 -5
- package/package.json +1 -1
- package/setup.sh +0 -60
- package/bin/ai-network +0 -144
package/README.md
CHANGED
|
@@ -252,6 +252,50 @@ echo '/path/to/project' >> ~/.ai-workspaces
|
|
|
252
252
|
cat ~/.ai-workspaces
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
+
### Network Configuration
|
|
256
|
+
|
|
257
|
+
AI containers can join Docker networks to communicate with other services (databases, APIs, MetaMCP).
|
|
258
|
+
|
|
259
|
+
#### Runtime Selection (Recommended)
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
# Interactive network selection
|
|
263
|
+
ai-run opencode -n
|
|
264
|
+
|
|
265
|
+
# Direct network specification
|
|
266
|
+
ai-run opencode -n metamcp_metamcp-network
|
|
267
|
+
ai-run opencode -n network1,network2,network3
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### Saved Configuration
|
|
271
|
+
|
|
272
|
+
Network selections are saved to `~/.ai-sandbox/config.json`:
|
|
273
|
+
- **Per-workspace**: Saved for specific project directories
|
|
274
|
+
- **Global**: Default for all workspaces
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
# View current config
|
|
278
|
+
cat ~/.ai-sandbox/config.json
|
|
279
|
+
|
|
280
|
+
# Example config structure
|
|
281
|
+
{
|
|
282
|
+
"version": 1,
|
|
283
|
+
"networks": {
|
|
284
|
+
"global": ["shared-services"],
|
|
285
|
+
"workspaces": {
|
|
286
|
+
"/Users/you/projects/my-app": ["my-app_default", "redis_network"]
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
#### Without `-n` Flag
|
|
293
|
+
|
|
294
|
+
When running without the flag, saved networks are used silently:
|
|
295
|
+
- Workspace-specific config takes priority
|
|
296
|
+
- Falls back to global config
|
|
297
|
+
- Non-existent networks are skipped silently
|
|
298
|
+
|
|
255
299
|
### Environment Variables
|
|
256
300
|
|
|
257
301
|
All environment variables are configured in `~/.ai-env` or passed at runtime:
|
|
@@ -596,4 +640,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
|
596
640
|
|
|
597
641
|
## đ License
|
|
598
642
|
|
|
599
|
-
MIT
|
|
643
|
+
MIT
|
package/bin/ai-run
CHANGED
|
@@ -6,6 +6,8 @@ shift
|
|
|
6
6
|
|
|
7
7
|
# Parse flags
|
|
8
8
|
SHELL_MODE=false
|
|
9
|
+
NETWORK_FLAG=false
|
|
10
|
+
NETWORK_ARG=""
|
|
9
11
|
TOOL_ARGS=()
|
|
10
12
|
|
|
11
13
|
while [[ $# -gt 0 ]]; do
|
|
@@ -14,6 +16,15 @@ while [[ $# -gt 0 ]]; do
|
|
|
14
16
|
SHELL_MODE=true
|
|
15
17
|
shift
|
|
16
18
|
;;
|
|
19
|
+
--network|-n)
|
|
20
|
+
NETWORK_FLAG=true
|
|
21
|
+
shift
|
|
22
|
+
# Check if next arg is a network name (not another flag)
|
|
23
|
+
if [[ $# -gt 0 && ! "$1" =~ ^- ]]; then
|
|
24
|
+
NETWORK_ARG="$1"
|
|
25
|
+
shift
|
|
26
|
+
fi
|
|
27
|
+
;;
|
|
17
28
|
*)
|
|
18
29
|
TOOL_ARGS+=("$1")
|
|
19
30
|
shift
|
|
@@ -148,131 +159,418 @@ else
|
|
|
148
159
|
esac
|
|
149
160
|
fi
|
|
150
161
|
|
|
151
|
-
#
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
GIT_CACHE_DIR="$HOME/.ai-cache/git"
|
|
155
|
-
touch "$GIT_ALLOWED_FILE"
|
|
162
|
+
# ============================================================================
|
|
163
|
+
# NETWORK CONFIGURATION
|
|
164
|
+
# ============================================================================
|
|
156
165
|
|
|
157
|
-
|
|
158
|
-
NETWORK_FILE="$HOME/.ai-networks"
|
|
159
|
-
NETWORK_MOUNTS=""
|
|
160
|
-
NETWORK_OPTIONS=""
|
|
161
|
-
HOST_ACCESS_ARGS=""
|
|
162
|
-
METAMCP_JOINED=false
|
|
166
|
+
AI_SANDBOX_CONFIG="$HOME/.ai-sandbox/config.json"
|
|
163
167
|
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
# Ensure jq is available (fallback to basic parsing if not)
|
|
169
|
+
has_jq() {
|
|
170
|
+
command -v jq &>/dev/null
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
# Initialize config file if it doesn't exist
|
|
174
|
+
init_config() {
|
|
175
|
+
mkdir -p "$(dirname "$AI_SANDBOX_CONFIG")"
|
|
176
|
+
if [[ ! -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
177
|
+
echo '{"version":1,"networks":{"global":[],"workspaces":{}}}' > "$AI_SANDBOX_CONFIG"
|
|
178
|
+
chmod 600 "$AI_SANDBOX_CONFIG"
|
|
179
|
+
fi
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
# Read networks for current workspace (workspace > global > empty)
|
|
183
|
+
read_network_config() {
|
|
184
|
+
init_config
|
|
185
|
+
local workspace="$CURRENT_DIR"
|
|
186
|
+
|
|
187
|
+
if has_jq; then
|
|
188
|
+
# Try workspace-specific first
|
|
189
|
+
local ws_networks=$(jq -r --arg ws "$workspace" '.networks.workspaces[$ws] // empty | .[]?' "$AI_SANDBOX_CONFIG" 2>/dev/null)
|
|
190
|
+
if [[ -n "$ws_networks" ]]; then
|
|
191
|
+
echo "$ws_networks"
|
|
192
|
+
return
|
|
172
193
|
fi
|
|
194
|
+
# Fall back to global
|
|
195
|
+
jq -r '.networks.global[]?' "$AI_SANDBOX_CONFIG" 2>/dev/null
|
|
196
|
+
else
|
|
197
|
+
# Fallback: grep-based parsing (basic)
|
|
198
|
+
grep -o '"global":\s*\[[^]]*\]' "$AI_SANDBOX_CONFIG" 2>/dev/null | grep -o '"[^"]*"' | tr -d '"' | grep -v global
|
|
199
|
+
fi
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# Write networks to config (scope: workspace or global)
|
|
203
|
+
write_network_config() {
|
|
204
|
+
local scope="$1" # "workspace" or "global"
|
|
205
|
+
shift
|
|
206
|
+
local networks=("$@")
|
|
207
|
+
|
|
208
|
+
init_config
|
|
209
|
+
|
|
210
|
+
if has_jq; then
|
|
211
|
+
local json_array=$(printf '%s\n' "${networks[@]}" | jq -R . | jq -s .)
|
|
173
212
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
# Check if this network requires host access
|
|
179
|
-
if [[ "$network" == *"metamcp"* ]]; then
|
|
180
|
-
# Add host.docker.internal for host service access (Linux requires --add-host)
|
|
181
|
-
# Docker Desktop on Mac/Windows has this by default
|
|
182
|
-
HOST_ACCESS_ARGS="--add-host=host.docker.internal:host-gateway"
|
|
183
|
-
METAMCP_JOINED=true
|
|
184
|
-
fi
|
|
213
|
+
if [[ "$scope" == "workspace" ]]; then
|
|
214
|
+
jq --arg ws "$CURRENT_DIR" --argjson nets "$json_array" \
|
|
215
|
+
'.networks.workspaces[$ws] = $nets' "$AI_SANDBOX_CONFIG" > "$AI_SANDBOX_CONFIG.tmp" \
|
|
216
|
+
&& mv "$AI_SANDBOX_CONFIG.tmp" "$AI_SANDBOX_CONFIG"
|
|
185
217
|
else
|
|
186
|
-
|
|
218
|
+
jq --argjson nets "$json_array" \
|
|
219
|
+
'.networks.global = $nets' "$AI_SANDBOX_CONFIG" > "$AI_SANDBOX_CONFIG.tmp" \
|
|
220
|
+
&& mv "$AI_SANDBOX_CONFIG.tmp" "$AI_SANDBOX_CONFIG"
|
|
187
221
|
fi
|
|
188
|
-
|
|
189
|
-
|
|
222
|
+
chmod 600 "$AI_SANDBOX_CONFIG"
|
|
223
|
+
else
|
|
224
|
+
echo "â ī¸ jq not found. Please install jq to save network configuration."
|
|
225
|
+
return 1
|
|
226
|
+
fi
|
|
227
|
+
}
|
|
190
228
|
|
|
191
|
-
#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
229
|
+
# Validate networks exist, return only valid ones
|
|
230
|
+
validate_networks() {
|
|
231
|
+
local networks=("$@")
|
|
232
|
+
local valid=()
|
|
233
|
+
|
|
234
|
+
for net in "${networks[@]}"; do
|
|
235
|
+
[[ -z "$net" ]] && continue
|
|
236
|
+
if docker network inspect "$net" &>/dev/null; then
|
|
237
|
+
valid+=("$net")
|
|
238
|
+
fi
|
|
239
|
+
done
|
|
240
|
+
|
|
241
|
+
printf '%s\n' "${valid[@]}"
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
# Discover Docker Compose networks (have com.docker.compose.project label)
|
|
245
|
+
discover_compose_networks() {
|
|
246
|
+
docker network ls --filter "label=com.docker.compose.project" --format "{{.Name}}" 2>/dev/null | sort
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
# Discover custom networks (not system, not compose)
|
|
250
|
+
discover_custom_networks() {
|
|
251
|
+
local compose_nets=$(discover_compose_networks)
|
|
252
|
+
docker network ls --format "{{.Name}}" 2>/dev/null | while read -r net; do
|
|
253
|
+
# Skip system networks
|
|
254
|
+
[[ "$net" == "bridge" || "$net" == "host" || "$net" == "none" ]] && continue
|
|
255
|
+
# Skip compose networks
|
|
256
|
+
echo "$compose_nets" | grep -q "^${net}$" && continue
|
|
257
|
+
echo "$net"
|
|
258
|
+
done | sort
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
# Get containers in a network
|
|
262
|
+
get_network_containers() {
|
|
263
|
+
local network="$1"
|
|
264
|
+
docker network inspect "$network" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | xargs | tr ' ' ', '
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
# Interactive network selection menu (multi-select)
|
|
268
|
+
show_network_menu() {
|
|
269
|
+
local compose_nets=()
|
|
270
|
+
local custom_nets=()
|
|
271
|
+
local all_nets=()
|
|
272
|
+
local selected=()
|
|
273
|
+
|
|
274
|
+
echo "đ Discovering Docker networks..."
|
|
275
|
+
echo ""
|
|
276
|
+
|
|
277
|
+
# Gather networks
|
|
278
|
+
while IFS= read -r net; do
|
|
279
|
+
[[ -n "$net" ]] && compose_nets+=("$net")
|
|
280
|
+
done < <(discover_compose_networks)
|
|
281
|
+
|
|
282
|
+
while IFS= read -r net; do
|
|
283
|
+
[[ -n "$net" ]] && custom_nets+=("$net")
|
|
284
|
+
done < <(discover_custom_networks)
|
|
285
|
+
|
|
286
|
+
# Build combined list: select-all first, then compose, then custom, then "none"
|
|
287
|
+
# Index layout: [0]=select-all, [1..compose_count]=compose, [compose_count+1..custom_end]=custom, [last]=none
|
|
288
|
+
local network_count=$((${#compose_nets[@]} + ${#custom_nets[@]}))
|
|
289
|
+
all_nets=("select-all" "${compose_nets[@]}" "${custom_nets[@]}" "none")
|
|
290
|
+
|
|
291
|
+
if [[ $network_count -eq 0 ]]; then
|
|
292
|
+
echo "âšī¸ No Docker networks found (besides system networks)."
|
|
293
|
+
SELECTED_NETWORKS=()
|
|
294
|
+
return 0
|
|
295
|
+
fi
|
|
296
|
+
|
|
297
|
+
# Initialize selection array (none is pre-selected)
|
|
298
|
+
local sel=()
|
|
299
|
+
local select_all_idx=0
|
|
300
|
+
local compose_start=1
|
|
301
|
+
local compose_end=$((1 + ${#compose_nets[@]}))
|
|
302
|
+
local custom_start=$compose_end
|
|
303
|
+
local none_idx=$((${#all_nets[@]} - 1))
|
|
304
|
+
|
|
305
|
+
for ((i=0; i<${#all_nets[@]}; i++)); do
|
|
306
|
+
if [[ "${all_nets[$i]}" == "none" ]]; then
|
|
307
|
+
sel[$i]=1
|
|
308
|
+
else
|
|
309
|
+
sel[$i]=0
|
|
310
|
+
fi
|
|
311
|
+
done
|
|
312
|
+
|
|
313
|
+
local cursor=0
|
|
314
|
+
|
|
315
|
+
tput civis
|
|
316
|
+
trap 'tput cnorm' INT TERM EXIT
|
|
317
|
+
|
|
318
|
+
while true; do
|
|
319
|
+
clear
|
|
320
|
+
echo "đ Network Selection"
|
|
321
|
+
echo "ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"
|
|
322
|
+
echo ""
|
|
198
323
|
|
|
199
|
-
|
|
200
|
-
|
|
324
|
+
# Select All option
|
|
325
|
+
local check="[ ]"
|
|
326
|
+
[[ ${sel[$select_all_idx]} -eq 1 ]] && check="[x]"
|
|
327
|
+
if [[ $cursor -eq $select_all_idx ]]; then
|
|
328
|
+
tput setaf 6
|
|
329
|
+
printf " â %s Select All\n" "$check"
|
|
330
|
+
else
|
|
331
|
+
printf " %s Select All\n" "$check"
|
|
332
|
+
fi
|
|
333
|
+
tput sgr0
|
|
334
|
+
echo ""
|
|
201
335
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
echo "
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
336
|
+
# Compose Networks section
|
|
337
|
+
if [[ ${#compose_nets[@]} -gt 0 ]]; then
|
|
338
|
+
echo "Compose Networks:"
|
|
339
|
+
for ((i=compose_start; i<compose_end; i++)); do
|
|
340
|
+
local net="${all_nets[$i]}"
|
|
341
|
+
local containers=$(get_network_containers "$net")
|
|
342
|
+
local check="[ ]"
|
|
343
|
+
[[ ${sel[$i]} -eq 1 ]] && check="[x]"
|
|
344
|
+
|
|
345
|
+
if [[ $i -eq $cursor ]]; then
|
|
346
|
+
tput setaf 6
|
|
347
|
+
printf " â %s %-30s" "$check" "$net"
|
|
348
|
+
else
|
|
349
|
+
printf " %s %-30s" "$check" "$net"
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
if [[ -n "$containers" ]]; then
|
|
353
|
+
tput setaf 8
|
|
354
|
+
printf " (%s)" "$containers"
|
|
355
|
+
fi
|
|
356
|
+
tput sgr0
|
|
357
|
+
echo ""
|
|
358
|
+
done
|
|
209
359
|
echo ""
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
360
|
+
fi
|
|
361
|
+
|
|
362
|
+
# Custom Networks section
|
|
363
|
+
if [[ ${#custom_nets[@]} -gt 0 ]]; then
|
|
364
|
+
echo "Other Networks:"
|
|
365
|
+
for ((i=custom_start; i<none_idx; i++)); do
|
|
366
|
+
local net="${all_nets[$i]}"
|
|
367
|
+
local containers=$(get_network_containers "$net")
|
|
368
|
+
local check="[ ]"
|
|
369
|
+
[[ ${sel[$i]} -eq 1 ]] && check="[x]"
|
|
370
|
+
|
|
371
|
+
if [[ $i -eq $cursor ]]; then
|
|
214
372
|
tput setaf 6
|
|
373
|
+
printf " â %s %-30s" "$check" "$net"
|
|
215
374
|
else
|
|
216
|
-
|
|
375
|
+
printf " %s %-30s" "$check" "$net"
|
|
217
376
|
fi
|
|
218
377
|
|
|
219
|
-
|
|
378
|
+
if [[ -n "$containers" ]]; then
|
|
379
|
+
tput setaf 8
|
|
380
|
+
printf " (%s)" "$containers"
|
|
381
|
+
fi
|
|
220
382
|
tput sgr0
|
|
383
|
+
echo ""
|
|
221
384
|
done
|
|
222
|
-
|
|
223
385
|
echo ""
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
386
|
+
fi
|
|
387
|
+
|
|
388
|
+
# None option
|
|
389
|
+
echo ""
|
|
390
|
+
local check="[ ]"
|
|
391
|
+
[[ ${sel[$none_idx]} -eq 1 ]] && check="[x]"
|
|
392
|
+
if [[ $cursor -eq $none_idx ]]; then
|
|
393
|
+
tput setaf 6
|
|
394
|
+
printf " â %s None (no network)\n" "$check"
|
|
395
|
+
else
|
|
396
|
+
printf " %s None (no network)\n" "$check"
|
|
397
|
+
fi
|
|
398
|
+
tput sgr0
|
|
399
|
+
|
|
400
|
+
echo ""
|
|
401
|
+
echo "ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"
|
|
402
|
+
echo "ââ Move SPACE Select a Select All ENTER Confirm"
|
|
403
|
+
|
|
404
|
+
# Read input
|
|
405
|
+
IFS= read -rsn1 key
|
|
406
|
+
if [[ "$key" == $'\x1b' ]]; then
|
|
407
|
+
read -rsn2 -t 1 escape_seq
|
|
408
|
+
case "$escape_seq" in
|
|
409
|
+
'[A') ((cursor > 0)) && ((cursor--)) ;;
|
|
410
|
+
'[B') ((cursor < ${#all_nets[@]} - 1)) && ((cursor++)) ;;
|
|
411
|
+
esac
|
|
412
|
+
else
|
|
413
|
+
case "$key" in
|
|
414
|
+
' ')
|
|
415
|
+
# Toggle selection
|
|
416
|
+
if [[ $cursor -eq $select_all_idx ]]; then
|
|
417
|
+
# Toggle select all
|
|
418
|
+
if [[ ${sel[$select_all_idx]} -eq 1 ]]; then
|
|
419
|
+
# Deselect all, select none
|
|
420
|
+
for ((i=0; i<${#all_nets[@]}; i++)); do sel[$i]=0; done
|
|
421
|
+
sel[$none_idx]=1
|
|
422
|
+
else
|
|
423
|
+
# Select all networks, deselect none
|
|
424
|
+
for ((i=0; i<${#all_nets[@]}; i++)); do sel[$i]=1; done
|
|
425
|
+
sel[$none_idx]=0
|
|
426
|
+
fi
|
|
427
|
+
elif [[ $cursor -eq $none_idx ]]; then
|
|
428
|
+
# Selecting "none" clears all others
|
|
429
|
+
for ((i=0; i<${#all_nets[@]}; i++)); do sel[$i]=0; done
|
|
430
|
+
sel[$none_idx]=1
|
|
431
|
+
else
|
|
432
|
+
# Selecting a network clears "none" and updates select-all state
|
|
433
|
+
sel[$none_idx]=0
|
|
434
|
+
if [[ ${sel[$cursor]} -eq 1 ]]; then
|
|
435
|
+
sel[$cursor]=0
|
|
436
|
+
sel[$select_all_idx]=0
|
|
437
|
+
else
|
|
438
|
+
sel[$cursor]=1
|
|
439
|
+
# Check if all networks are now selected
|
|
440
|
+
local all_selected=1
|
|
441
|
+
for ((i=compose_start; i<none_idx; i++)); do
|
|
442
|
+
[[ ${sel[$i]} -eq 0 ]] && all_selected=0 && break
|
|
443
|
+
done
|
|
444
|
+
sel[$select_all_idx]=$all_selected
|
|
445
|
+
fi
|
|
446
|
+
fi
|
|
447
|
+
;;
|
|
448
|
+
'a'|'A')
|
|
449
|
+
# Quick select all
|
|
450
|
+
for ((i=0; i<${#all_nets[@]}; i++)); do sel[$i]=1; done
|
|
451
|
+
sel[$none_idx]=0
|
|
452
|
+
;;
|
|
453
|
+
''|$'\n'|$'\r')
|
|
454
|
+
break
|
|
455
|
+
;;
|
|
456
|
+
k) ((cursor > 0)) && ((cursor--)) ;;
|
|
457
|
+
j) ((cursor < ${#all_nets[@]} - 1)) && ((cursor++)) ;;
|
|
458
|
+
esac
|
|
459
|
+
fi
|
|
460
|
+
done
|
|
461
|
+
|
|
462
|
+
tput cnorm
|
|
463
|
+
trap - INT TERM EXIT
|
|
464
|
+
|
|
465
|
+
# Collect selected networks (exclude select-all and none)
|
|
466
|
+
SELECTED_NETWORKS=()
|
|
467
|
+
for ((i=1; i<${#all_nets[@]}; i++)); do
|
|
468
|
+
if [[ ${sel[$i]} -eq 1 && "${all_nets[$i]}" != "none" && "${all_nets[$i]}" != "select-all" ]]; then
|
|
469
|
+
SELECTED_NETWORKS+=("${all_nets[$i]}")
|
|
470
|
+
fi
|
|
471
|
+
done
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
# Save prompt after selection
|
|
475
|
+
show_save_prompt() {
|
|
476
|
+
local options=("This workspace" "Global (all workspaces)" "Don't save")
|
|
477
|
+
local cursor=0
|
|
478
|
+
|
|
479
|
+
echo ""
|
|
480
|
+
|
|
481
|
+
tput civis
|
|
482
|
+
trap 'tput cnorm' INT TERM EXIT
|
|
483
|
+
|
|
484
|
+
while true; do
|
|
485
|
+
# Move cursor up to redraw (3 options + header)
|
|
486
|
+
tput cuu 5 2>/dev/null || true
|
|
487
|
+
tput ed 2>/dev/null || true
|
|
488
|
+
|
|
489
|
+
echo "Save selection?"
|
|
490
|
+
for ((i=0; i<${#options[@]}; i++)); do
|
|
491
|
+
if [[ $i -eq $cursor ]]; then
|
|
492
|
+
tput setaf 6
|
|
493
|
+
if [[ $i -eq 0 ]]; then
|
|
494
|
+
printf " â %s (%s)\n" "${options[$i]}" "$CURRENT_DIR"
|
|
495
|
+
else
|
|
496
|
+
printf " â %s\n" "${options[$i]}"
|
|
497
|
+
fi
|
|
234
498
|
else
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
""
|
|
239
|
-
|
|
240
|
-
esac
|
|
499
|
+
if [[ $i -eq 0 ]]; then
|
|
500
|
+
printf " %s (%s)\n" "${options[$i]}" "$CURRENT_DIR"
|
|
501
|
+
else
|
|
502
|
+
printf " %s\n" "${options[$i]}"
|
|
503
|
+
fi
|
|
241
504
|
fi
|
|
242
|
-
|
|
243
|
-
if [ "$cursor" -lt 0 ]; then cursor=$((${#options[@]} - 1)); fi
|
|
244
|
-
if [ "$cursor" -ge "${#options[@]}" ]; then cursor=0; fi
|
|
505
|
+
tput sgr0
|
|
245
506
|
done
|
|
246
507
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
NETWORK_OPTIONS="$NETWORK_OPTIONS --network metamcp_metamcp-network"
|
|
255
|
-
HOST_ACCESS_ARGS="--add-host=host.docker.internal:host-gateway"
|
|
256
|
-
METAMCP_JOINED=true
|
|
257
|
-
echo ""
|
|
258
|
-
echo "â
Network joined. Both host.docker.internal and MetaMCP network enabled."
|
|
508
|
+
IFS= read -rsn1 key
|
|
509
|
+
if [[ "$key" == $'\x1b' ]]; then
|
|
510
|
+
read -rsn2 -t 1 escape_seq
|
|
511
|
+
case "$escape_seq" in
|
|
512
|
+
'[A') ((cursor > 0)) && ((cursor--)) ;;
|
|
513
|
+
'[B') ((cursor < 2)) && ((cursor++)) ;;
|
|
514
|
+
esac
|
|
259
515
|
else
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
516
|
+
case "$key" in
|
|
517
|
+
''|$'\n'|$'\r') break ;;
|
|
518
|
+
k) ((cursor > 0)) && ((cursor--)) ;;
|
|
519
|
+
j) ((cursor < 2)) && ((cursor++)) ;;
|
|
520
|
+
esac
|
|
521
|
+
fi
|
|
522
|
+
done
|
|
523
|
+
|
|
524
|
+
tput cnorm
|
|
525
|
+
trap - INT TERM EXIT
|
|
526
|
+
|
|
527
|
+
SAVE_CHOICE=$cursor # 0=workspace, 1=global, 2=don't save
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
# Network configuration
|
|
531
|
+
NETWORK_OPTIONS=""
|
|
532
|
+
HOST_ACCESS_ARGS="--add-host=host.docker.internal:host-gateway"
|
|
533
|
+
SELECTED_NETWORKS=()
|
|
534
|
+
|
|
535
|
+
if [[ "$NETWORK_FLAG" == "true" ]]; then
|
|
536
|
+
if [[ -n "$NETWORK_ARG" ]]; then
|
|
537
|
+
# Direct specification: -n net1,net2
|
|
538
|
+
IFS=',' read -ra SELECTED_NETWORKS <<< "$NETWORK_ARG"
|
|
539
|
+
elif [[ -t 0 ]]; then
|
|
540
|
+
# Interactive mode: show menu
|
|
541
|
+
show_network_menu
|
|
542
|
+
|
|
543
|
+
if [[ ${#SELECTED_NETWORKS[@]} -gt 0 ]]; then
|
|
544
|
+
show_save_prompt
|
|
545
|
+
case $SAVE_CHOICE in
|
|
546
|
+
0) write_network_config "workspace" "${SELECTED_NETWORKS[@]}" && echo "â
Saved for this workspace" ;;
|
|
547
|
+
1) write_network_config "global" "${SELECTED_NETWORKS[@]}" && echo "â
Saved globally" ;;
|
|
548
|
+
*) echo "âšī¸ Using for this session only" ;;
|
|
549
|
+
esac
|
|
264
550
|
fi
|
|
265
|
-
echo "ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"
|
|
266
|
-
echo ""
|
|
267
551
|
else
|
|
268
|
-
|
|
269
|
-
HOST_ACCESS_ARGS="--add-host=host.docker.internal:host-gateway"
|
|
552
|
+
echo "â ī¸ Non-interactive mode. Use -n network1,network2 to specify networks."
|
|
270
553
|
fi
|
|
271
|
-
|
|
272
|
-
# No
|
|
273
|
-
|
|
554
|
+
else
|
|
555
|
+
# No flag: use saved config silently
|
|
556
|
+
while IFS= read -r net; do
|
|
557
|
+
[[ -n "$net" ]] && SELECTED_NETWORKS+=("$net")
|
|
558
|
+
done < <(read_network_config)
|
|
274
559
|
fi
|
|
275
560
|
|
|
561
|
+
# Validate and build network options
|
|
562
|
+
if [[ ${#SELECTED_NETWORKS[@]} -gt 0 ]]; then
|
|
563
|
+
while IFS= read -r net; do
|
|
564
|
+
[[ -n "$net" ]] && NETWORK_OPTIONS="$NETWORK_OPTIONS --network $net"
|
|
565
|
+
done < <(validate_networks "${SELECTED_NETWORKS[@]}")
|
|
566
|
+
fi
|
|
567
|
+
|
|
568
|
+
# Git access control (opt-in per workspace)
|
|
569
|
+
GIT_MOUNTS=""
|
|
570
|
+
GIT_ALLOWED_FILE="$HOME/.ai-git-allowed"
|
|
571
|
+
GIT_CACHE_DIR="$HOME/.ai-cache/git"
|
|
572
|
+
touch "$GIT_ALLOWED_FILE"
|
|
573
|
+
|
|
276
574
|
# Check if Git access is allowed for this workspace
|
|
277
575
|
if grep -q "^$CURRENT_DIR$" "$GIT_ALLOWED_FILE" 2>/dev/null; then
|
|
278
576
|
# Previously allowed for this workspace
|
|
@@ -26,11 +26,13 @@ RUN curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - \
|
|
|
26
26
|
# Install pnpm globally via npm
|
|
27
27
|
RUN npm install -g pnpm
|
|
28
28
|
|
|
29
|
+
# Install TypeScript and LSP tools for AI coding assistants
|
|
30
|
+
RUN npm install -g typescript typescript-language-server
|
|
31
|
+
|
|
29
32
|
# Verify installations
|
|
30
|
-
RUN node --version && npm --version && pnpm --version && bun --version
|
|
33
|
+
RUN node --version && npm --version && pnpm --version && bun --version && tsc --version
|
|
31
34
|
|
|
32
35
|
# Install additional tools (if selected)
|
|
33
|
-
RUN pipx install specify-cli --pip-args="git+https://github.com/github/spec-kit.git"
|
|
34
36
|
RUN bun install -g uipro-cli
|
|
35
37
|
RUN mkdir -p /usr/local/lib/openspec && \
|
|
36
38
|
cd /usr/local/lib/openspec && \
|
|
@@ -62,6 +64,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
62
64
|
&& rm -rf /var/lib/apt/lists/*
|
|
63
65
|
# Install Playwright and browsers via npm (avoids pnpm global bin issues)
|
|
64
66
|
RUN npm install -g playwright && npx playwright install
|
|
67
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
68
|
+
libssl-dev \
|
|
69
|
+
libreadline-dev \
|
|
70
|
+
zlib1g-dev \
|
|
71
|
+
libyaml-dev \
|
|
72
|
+
libffi-dev \
|
|
73
|
+
libgdbm-dev \
|
|
74
|
+
libncurses5-dev \
|
|
75
|
+
libpq-dev \
|
|
76
|
+
default-libmysqlclient-dev \
|
|
77
|
+
libsqlite3-dev \
|
|
78
|
+
imagemagick \
|
|
79
|
+
libmagickwand-dev \
|
|
80
|
+
libvips-dev \
|
|
81
|
+
autoconf \
|
|
82
|
+
bison \
|
|
83
|
+
rustc \
|
|
84
|
+
libxml2-dev \
|
|
85
|
+
libxslt1-dev \
|
|
86
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
87
|
+
|
|
88
|
+
RUN git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv && \
|
|
89
|
+
git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build
|
|
90
|
+
|
|
91
|
+
ENV RBENV_ROOT=/usr/local/rbenv
|
|
92
|
+
ENV PATH=$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH
|
|
93
|
+
|
|
94
|
+
RUN rbenv install 3.3.0 && rbenv global 3.3.0 && rbenv rehash
|
|
95
|
+
|
|
96
|
+
RUN gem install rails -v 8.0.2 && gem install bundler && rbenv rehash
|
|
65
97
|
|
|
66
98
|
# Create workspace
|
|
67
99
|
WORKDIR /workspace
|
package/lib/install-base.sh
CHANGED
|
@@ -60,8 +60,7 @@ fi
|
|
|
60
60
|
|
|
61
61
|
if [[ "${INSTALL_RUBY:-0}" -eq 1 ]]; then
|
|
62
62
|
echo "đĻ Ruby 3.3.0 + Rails 8.0.2 will be installed in base image"
|
|
63
|
-
ADDITIONAL_TOOLS_INSTALL+='
|
|
64
|
-
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
63
|
+
ADDITIONAL_TOOLS_INSTALL+='RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
65
64
|
libssl-dev \
|
|
66
65
|
libreadline-dev \
|
|
67
66
|
zlib1g-dev \
|
|
@@ -69,19 +68,27 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
69
68
|
libffi-dev \
|
|
70
69
|
libgdbm-dev \
|
|
71
70
|
libncurses5-dev \
|
|
71
|
+
libpq-dev \
|
|
72
|
+
default-libmysqlclient-dev \
|
|
73
|
+
libsqlite3-dev \
|
|
74
|
+
imagemagick \
|
|
75
|
+
libmagickwand-dev \
|
|
76
|
+
libvips-dev \
|
|
77
|
+
autoconf \
|
|
78
|
+
bison \
|
|
79
|
+
rustc \
|
|
80
|
+
libxml2-dev \
|
|
81
|
+
libxslt1-dev \
|
|
72
82
|
&& rm -rf /var/lib/apt/lists/*
|
|
73
83
|
|
|
74
|
-
# Install rbenv and ruby-build
|
|
75
84
|
RUN git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv && \
|
|
76
85
|
git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build
|
|
77
86
|
|
|
78
87
|
ENV RBENV_ROOT=/usr/local/rbenv
|
|
79
88
|
ENV PATH=$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH
|
|
80
89
|
|
|
81
|
-
# Install Ruby 3.3.0
|
|
82
90
|
RUN rbenv install 3.3.0 && rbenv global 3.3.0 && rbenv rehash
|
|
83
91
|
|
|
84
|
-
# Install Rails 8.0.2 and Bundler
|
|
85
92
|
RUN gem install rails -v 8.0.2 && gem install bundler && rbenv rehash
|
|
86
93
|
'
|
|
87
94
|
fi
|
package/package.json
CHANGED
package/setup.sh
CHANGED
|
@@ -231,66 +231,6 @@ echo "đ Whitelisted workspaces saved to: $WORKSPACES_FILE"
|
|
|
231
231
|
# Use first workspace as default for backwards compatibility
|
|
232
232
|
WORKSPACE="${WORKSPACES[0]}"
|
|
233
233
|
|
|
234
|
-
# Network configuration for Docker network access
|
|
235
|
-
echo ""
|
|
236
|
-
echo "đ Docker Network Configuration"
|
|
237
|
-
echo "ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"
|
|
238
|
-
echo "You can configure AI tools to join existing Docker networks"
|
|
239
|
-
echo "(e.g., for MetaMCP services, databases, or other containers)."
|
|
240
|
-
echo ""
|
|
241
|
-
|
|
242
|
-
# Check for existing MetaMCP network
|
|
243
|
-
if docker network inspect "metamcp_metamcp-network" >/dev/null 2>&1; then
|
|
244
|
-
echo "â
Found existing network: metamcp_metamcp-network"
|
|
245
|
-
echo ""
|
|
246
|
-
|
|
247
|
-
# Use single_select for interactive choice
|
|
248
|
-
single_select "MetaMCP Access Method" "join,host-only" "Join network (container-to-container communication),Use host.docker.internal (host access)"
|
|
249
|
-
|
|
250
|
-
case "$SELECTED_ITEM" in
|
|
251
|
-
join)
|
|
252
|
-
NETWORK_FILE="$HOME/.ai-networks"
|
|
253
|
-
echo "metamcp_metamcp-network" >> "$NETWORK_FILE"
|
|
254
|
-
chmod 600 "$NETWORK_FILE"
|
|
255
|
-
echo ""
|
|
256
|
-
echo "â
Network joined. Both host.docker.internal and MetaMCP network enabled."
|
|
257
|
-
;;
|
|
258
|
-
host-only|"")
|
|
259
|
-
echo ""
|
|
260
|
-
echo "âšī¸ Using host.docker.internal only. MetaMCP accessible at localhost:12008 on host."
|
|
261
|
-
;;
|
|
262
|
-
esac
|
|
263
|
-
else
|
|
264
|
-
echo "No existing MetaMCP network detected."
|
|
265
|
-
echo ""
|
|
266
|
-
echo "You have two options:"
|
|
267
|
-
echo ""
|
|
268
|
-
echo "Option 1: Use host.docker.internal (recommended for most cases)"
|
|
269
|
-
echo " MetaMCP at localhost:12008 on your host machine"
|
|
270
|
-
echo " Already enabled by default"
|
|
271
|
-
echo ""
|
|
272
|
-
echo "Option 2: Join a Docker network"
|
|
273
|
-
echo " For container-to-container communication"
|
|
274
|
-
echo ""
|
|
275
|
-
read -p "Enter a Docker network name to join (leave empty to skip): " network_name
|
|
276
|
-
|
|
277
|
-
if [[ -n "$network_name" ]]; then
|
|
278
|
-
if docker network inspect "$network_name" >/dev/null 2>&1; then
|
|
279
|
-
NETWORK_FILE="$HOME/.ai-networks"
|
|
280
|
-
echo "$network_name" >> "$NETWORK_FILE"
|
|
281
|
-
chmod 600 "$NETWORK_FILE"
|
|
282
|
-
echo "â
Network '$network_name' saved"
|
|
283
|
-
else
|
|
284
|
-
echo "â ī¸ Network '$network_name' not found. Skipping."
|
|
285
|
-
fi
|
|
286
|
-
else
|
|
287
|
-
echo "âšī¸ Skipped. host.docker.internal is enabled for host access."
|
|
288
|
-
fi
|
|
289
|
-
fi
|
|
290
|
-
|
|
291
|
-
echo "ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ"
|
|
292
|
-
echo ""
|
|
293
|
-
|
|
294
234
|
# Tool definitions
|
|
295
235
|
TOOL_OPTIONS="amp,opencode,droid,claude,gemini,kilo,qwen,codex,qoder,auggie,codebuddy,jules,shai,vscode,codeserver"
|
|
296
236
|
TOOL_DESCS="AI coding assistant from @sourcegraph/amp,Open-source coding tool from opencode-ai,Factory CLI from factory.ai,Claude Code CLI from Anthropic,Google Gemini CLI (free tier),AI pair programmer (Git-native),Kilo Code (500+ models),Alibaba Qwen CLI (1M context),OpenAI Codex terminal agent,Qoder AI CLI assistant,Augment Auggie CLI,Tencent CodeBuddy CLI,Google Jules CLI,OVHcloud SHAI agent,VSCode Desktop in Docker (X11),VSCode in browser (fast)"
|
package/bin/ai-network
DELETED
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Network management helper for AI Sandbox Wrapper
|
|
3
|
-
# Usage: ai-network <command> [network-name]
|
|
4
|
-
#
|
|
5
|
-
# Commands:
|
|
6
|
-
# list List configured networks
|
|
7
|
-
# add <name> Add a network to join
|
|
8
|
-
# remove <name> Remove a network
|
|
9
|
-
# check <name> Check if network exists
|
|
10
|
-
# metamcp Quick command to add MetaMCP network
|
|
11
|
-
|
|
12
|
-
set -e
|
|
13
|
-
|
|
14
|
-
NETWORK_FILE="$HOME/.ai-networks"
|
|
15
|
-
COMMAND="${1:-list}"
|
|
16
|
-
|
|
17
|
-
# Ensure network file exists
|
|
18
|
-
touch "$NETWORK_FILE"
|
|
19
|
-
|
|
20
|
-
list_networks() {
|
|
21
|
-
echo "đ Configured Networks:"
|
|
22
|
-
echo ""
|
|
23
|
-
if [[ -s "$NETWORK_FILE" ]]; then
|
|
24
|
-
while IFS= read -r network; do
|
|
25
|
-
if [[ -n "$network" ]]; then
|
|
26
|
-
if docker network inspect "$network" >/dev/null 2>&1; then
|
|
27
|
-
echo " â
$network"
|
|
28
|
-
else
|
|
29
|
-
echo " â $network (not found)"
|
|
30
|
-
fi
|
|
31
|
-
fi
|
|
32
|
-
done < "$NETWORK_FILE"
|
|
33
|
-
else
|
|
34
|
-
echo " No networks configured."
|
|
35
|
-
echo ""
|
|
36
|
-
echo " To add a network:"
|
|
37
|
-
echo " ai-network add <network-name>"
|
|
38
|
-
fi
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
add_network() {
|
|
42
|
-
local network_name="$2"
|
|
43
|
-
|
|
44
|
-
if [[ -z "$network_name" ]]; then
|
|
45
|
-
echo "â Error: Network name required"
|
|
46
|
-
echo " Usage: ai-network add <network-name>"
|
|
47
|
-
exit 1
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Check if network exists
|
|
51
|
-
if ! docker network inspect "$network_name" >/dev/null 2>&1; then
|
|
52
|
-
echo "â ī¸ Network '$network_name' does not exist yet."
|
|
53
|
-
read -p "Add it anyway? [y/N]: " confirm
|
|
54
|
-
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then
|
|
55
|
-
exit 0
|
|
56
|
-
fi
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# Add to config (avoid duplicates)
|
|
60
|
-
if grep -q "^${network_name}$" "$NETWORK_FILE" 2>/dev/null; then
|
|
61
|
-
echo "â
Network '$network_name' already configured"
|
|
62
|
-
else
|
|
63
|
-
echo "$network_name" >> "$NETWORK_FILE"
|
|
64
|
-
chmod 600 "$NETWORK_FILE"
|
|
65
|
-
echo "â
Added network: $network_name"
|
|
66
|
-
fi
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
remove_network() {
|
|
70
|
-
local network_name="$2"
|
|
71
|
-
|
|
72
|
-
if [[ -z "$network_name" ]]; then
|
|
73
|
-
echo "â Error: Network name required"
|
|
74
|
-
echo " Usage: ai-network remove <network-name>"
|
|
75
|
-
exit 1
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
|
-
if grep -q "^${network_name}$" "$NETWORK_FILE" 2>/dev/null; then
|
|
79
|
-
# Remove the network from file
|
|
80
|
-
grep -v "^${network_name}$" "$NETWORK_FILE" > "$NETWORK_FILE.tmp"
|
|
81
|
-
mv "$NETWORK_FILE.tmp" "$NETWORK_FILE"
|
|
82
|
-
chmod 600 "$NETWORK_FILE"
|
|
83
|
-
echo "â
Removed network: $network_name"
|
|
84
|
-
else
|
|
85
|
-
echo "â ī¸ Network '$network_name' not found in config"
|
|
86
|
-
fi
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
check_network() {
|
|
90
|
-
local network_name="${2:-metamcp_metamcp-network}"
|
|
91
|
-
|
|
92
|
-
if docker network inspect "$network_name" >/dev/null 2>&1; then
|
|
93
|
-
echo "â
Network '$network_name' exists"
|
|
94
|
-
return 0
|
|
95
|
-
else
|
|
96
|
-
echo "â Network '$network_name' not found"
|
|
97
|
-
return 1
|
|
98
|
-
fi
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
add_metamcp() {
|
|
102
|
-
echo "đ Adding MetaMCP network..."
|
|
103
|
-
add_network "metamcp_metamcp-network"
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
case "$COMMAND" in
|
|
107
|
-
list)
|
|
108
|
-
list_networks
|
|
109
|
-
;;
|
|
110
|
-
add)
|
|
111
|
-
add_network "$@"
|
|
112
|
-
;;
|
|
113
|
-
remove|rm|delete)
|
|
114
|
-
remove_network "$@"
|
|
115
|
-
;;
|
|
116
|
-
check)
|
|
117
|
-
check_network "$@"
|
|
118
|
-
;;
|
|
119
|
-
metamcp|meta)
|
|
120
|
-
add_metamcp
|
|
121
|
-
;;
|
|
122
|
-
help|--help|-h)
|
|
123
|
-
echo "AI Network Manager"
|
|
124
|
-
echo ""
|
|
125
|
-
echo "Usage: ai-network <command> [network-name]"
|
|
126
|
-
echo ""
|
|
127
|
-
echo "Commands:"
|
|
128
|
-
echo " list Show all configured networks"
|
|
129
|
-
echo " add <name> Add a Docker network"
|
|
130
|
-
echo " remove <name> Remove a configured network"
|
|
131
|
-
echo " check [name] Check if a network exists"
|
|
132
|
-
echo " metamcp Quick-add MetaMCP network"
|
|
133
|
-
echo ""
|
|
134
|
-
echo "Examples:"
|
|
135
|
-
echo " ai-network list"
|
|
136
|
-
echo " ai-network add my-network"
|
|
137
|
-
echo " ai-network metamcp"
|
|
138
|
-
;;
|
|
139
|
-
*)
|
|
140
|
-
echo "Unknown command: $COMMAND"
|
|
141
|
-
echo "Run 'ai-network help' for usage"
|
|
142
|
-
exit 1
|
|
143
|
-
;;
|
|
144
|
-
esac
|