@kokorolx/ai-sandbox-wrapper 3.0.8 → 3.1.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/bin/ai-run CHANGED
@@ -43,7 +43,7 @@ Examples:
43
43
  ai-run opencode web -e 4096 # Run OpenCode web with port exposed
44
44
  ai-run opencode web -p secret # Run with password
45
45
  ai-run opencode --shell # Start shell, run tool manually
46
- ai-run aider -n mynetwork # Connect to Docker network
46
+ # ai-run aider -n mynetwork # Connect to Docker network
47
47
  ai-run opencode --git-fetch # Git fetch only (no push)
48
48
 
49
49
  Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
@@ -663,26 +663,26 @@ done < "$WORKSPACES_FILE"
663
663
  # Maps tool name to space-separated config paths (relative to $HOME)
664
664
  get_tool_configs() {
665
665
  case "$1" in
666
- amp) echo ".config/amp .local/share/amp" ;;
666
+ # amp) echo ".config/amp .local/share/amp" ;;
667
667
  opencode) echo ".config/opencode .local/share/opencode" ;;
668
- claude) echo ".claude .ccs" ;;
669
- openclaw) echo ".openclaw" ;;
670
- droid) echo ".config/droid" ;;
671
- qoder) echo ".config/qoder" ;;
672
- auggie) echo ".config/auggie" ;;
673
- codebuddy) echo ".config/codebuddy" ;;
674
- jules) echo ".config/jules" ;;
675
- shai) echo ".config/shai" ;;
676
- gemini) echo ".config/gemini" ;;
677
- aider) echo ".config/aider .aider" ;;
678
- kilo) echo ".config/kilo" ;;
668
+ claude) echo ".claude" ;;
669
+ # openclaw) echo ".openclaw" ;;
670
+ # droid) echo ".config/droid" ;;
671
+ # qoder) echo ".config/qoder" ;;
672
+ # auggie) echo ".config/auggie" ;;
673
+ # codebuddy) echo ".config/codebuddy" ;;
674
+ # jules) echo ".config/jules" ;;
675
+ # shai) echo ".config/shai" ;;
676
+ # gemini) echo ".config/gemini" ;;
677
+ # aider) echo ".config/aider .aider" ;;
678
+ # kilo) echo ".config/kilo" ;;
679
679
  codex) echo ".config/codex" ;;
680
- qwen) echo ".config/qwen" ;;
680
+ # qwen) echo ".config/qwen" ;;
681
681
  esac
682
682
  }
683
683
 
684
684
  # All known tools (for fallback)
685
- ALL_KNOWN_TOOLS="amp opencode claude openclaw droid qoder auggie codebuddy jules shai gemini aider kilo codex qwen"
685
+ ALL_KNOWN_TOOLS="opencode claude codex"
686
686
 
687
687
  # Get list of installed tools from config.json, fallback to all known tools
688
688
  get_installed_tools() {
@@ -2296,9 +2296,27 @@ run_with_capture() {
2296
2296
  if [[ $exit_code -ne 0 ]] && grep -Eqi "$REPAIR_PATTERN" "$err_file"; then
2297
2297
  cat "$err_file" >&2
2298
2298
  echo "⚠️ Detected nano-brain native module issue."
2299
- echo "🔧 Running automatic repair (clearing npx/node-gyp caches)..."
2299
+ echo "🔧 Running automatic repair (rebuilding native modules for container arch)..."
2300
2300
  rm -rf /home/agent/.npm/_npx /home/agent/.cache/node-gyp 2>/dev/null || true
2301
2301
  npm cache clean --force >/dev/null 2>&1 || true
2302
+ # Root cause: host macOS prebuilds are wrong arch for Linux container.
2303
+ # Fix: delete wrong-arch prebuilds and reinstall to compile from source.
2304
+ local NB_DIR
2305
+ NB_DIR=$(dirname "$(dirname "$(readlink -f "$(which npx)")")")/lib/node_modules/nano-brain 2>/dev/null || true
2306
+ if [[ -z "$NB_DIR" || ! -d "$NB_DIR" ]]; then
2307
+ # npx cache location
2308
+ NB_DIR=$(find /home/agent/.npm/_npx -maxdepth 4 -name "nano-brain" -type d 2>/dev/null | head -1)
2309
+ fi
2310
+ if [[ -n "$NB_DIR" && -d "$NB_DIR/node_modules" ]]; then
2311
+ echo " 📦 Rebuilding tree-sitter in $NB_DIR..."
2312
+ for pkg in tree-sitter-typescript tree-sitter-javascript tree-sitter-python; do
2313
+ local prebuild_dir="$NB_DIR/node_modules/$pkg/prebuilds/linux-arm64"
2314
+ if [[ -d "$prebuild_dir" ]]; then
2315
+ rm -rf "$prebuild_dir"
2316
+ fi
2317
+ done
2318
+ (cd "$NB_DIR" && npm rebuild tree-sitter tree-sitter-typescript tree-sitter-javascript tree-sitter-python 2>/dev/null) || true
2319
+ fi
2302
2320
  echo "🔁 Retrying nano-brain command once..."
2303
2321
  set +e
2304
2322
  "${ORIG_CMD[@]}"
@@ -2344,9 +2362,26 @@ nano_brain_shell_wrapper() {
2344
2362
  if [[ $exit_code -ne 0 ]] && grep -Eqi "$REPAIR_PATTERN" "$err_file"; then
2345
2363
  cat "$err_file" >&2
2346
2364
  echo "⚠️ Detected nano-brain native module issue."
2347
- echo "🔧 Running automatic repair (clearing npx/node-gyp caches)..."
2365
+ echo "🔧 Running automatic repair (rebuilding native modules for container arch)..."
2348
2366
  rm -rf /home/agent/.npm/_npx /home/agent/.cache/node-gyp 2>/dev/null || true
2349
2367
  npm cache clean --force >/dev/null 2>&1 || true
2368
+ # Root cause: host macOS prebuilds are wrong arch for Linux container.
2369
+ # Fix: delete wrong-arch prebuilds and reinstall to compile from source.
2370
+ local NB_DIR
2371
+ NB_DIR=$(dirname "$(dirname "$(readlink -f "$(which npx)")")")/lib/node_modules/nano-brain 2>/dev/null || true
2372
+ if [[ -z "$NB_DIR" || ! -d "$NB_DIR" ]]; then
2373
+ NB_DIR=$(find /home/agent/.npm/_npx -maxdepth 4 -name "nano-brain" -type d 2>/dev/null | head -1)
2374
+ fi
2375
+ if [[ -n "$NB_DIR" && -d "$NB_DIR/node_modules" ]]; then
2376
+ echo " 📦 Rebuilding tree-sitter in $NB_DIR..."
2377
+ for pkg in tree-sitter-typescript tree-sitter-javascript tree-sitter-python; do
2378
+ local prebuild_dir="$NB_DIR/node_modules/$pkg/prebuilds/linux-arm64"
2379
+ if [[ -d "$prebuild_dir" ]]; then
2380
+ rm -rf "$prebuild_dir"
2381
+ fi
2382
+ done
2383
+ (cd "$NB_DIR" && npm rebuild tree-sitter tree-sitter-typescript tree-sitter-javascript tree-sitter-python 2>/dev/null) || true
2384
+ fi
2350
2385
  echo "🔁 Retrying nano-brain command once..."
2351
2386
  "${ORIG_CMD[@]}"
2352
2387
  local retry_code=$?
@@ -2406,7 +2441,7 @@ elif [[ "$SHELL_MODE" == "true" || -z "$TOOL" ]]; then
2406
2441
  # Shell mode without tool (ai-run with no args)
2407
2442
  DOCKER_COMMAND=(
2408
2443
  "-c"
2409
- "echo ''; echo '🚀 AI Sandbox - Interactive Shell'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; echo 'Installed tools: ${INSTALLED_TOOLS_MSG:-unknown}'; echo ''; echo 'Run any tool by name: claude, opencode, gemini, etc.'; echo 'Exit: exit or Ctrl+D'; echo ''; echo 'Enhancement tools: specify, uipro, openspec, rtk'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; exec bash"
2444
+ "echo ''; echo '🚀 AI Sandbox - Interactive Shell'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; echo 'Installed tools: ${INSTALLED_TOOLS_MSG:-unknown}'; echo ''; echo 'Run any tool by name: claude, opencode, codex, etc.'; echo 'Exit: exit or Ctrl+D'; echo ''; echo 'Enhancement tools: specify, uipro, openspec, rtk'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; exec bash"
2410
2445
  )
2411
2446
  fi
2412
2447
  else
@@ -2444,7 +2479,7 @@ if [[ -z "$PLATFORM" ]]; then
2444
2479
  esac
2445
2480
  fi
2446
2481
 
2447
- # Terminal size for TUI apps (important for opencode, aider, etc.)
2482
+ # Terminal size for TUI apps (important for opencode, etc.)
2448
2483
  TERMINAL_SIZE=""
2449
2484
  if [[ -n "$TTY_FLAGS" ]]; then
2450
2485
  # Get current terminal size
@@ -2486,65 +2521,65 @@ DISPLAY_FLAGS=$(detect_display_config)
2486
2521
  # ============================================================================
2487
2522
  # OPENCLAW DOCKER-COMPOSE MODE
2488
2523
  # ============================================================================
2489
- if [[ "$TOOL" == "openclaw" ]]; then
2490
- OPENCLAW_REPO_DIR="$HOME/.ai-sandbox/tools/openclaw/repo"
2491
-
2492
- if [[ ! -d "$OPENCLAW_REPO_DIR" ]]; then
2493
- echo "❌ ERROR: OpenClaw repository not found at $OPENCLAW_REPO_DIR"
2494
- echo " Run: npx @kokorolx/ai-sandbox-wrapper setup"
2495
- exit 1
2496
- fi
2497
-
2498
- cd "$OPENCLAW_REPO_DIR"
2499
-
2500
- OPENCLAW_COMPOSE_FILE="$OPENCLAW_REPO_DIR/docker-compose.yml"
2501
- OPENCLAW_OVERRIDE_FILE="$OPENCLAW_REPO_DIR/docker-compose.override.yml"
2502
-
2503
- echo "🔄 Generating OpenClaw docker-compose override..."
2504
-
2505
- cat > "$OPENCLAW_OVERRIDE_FILE" <<EOF
2506
- services:
2507
- openclaw-gateway:
2508
- environment:
2509
- HOME: /home/node
2510
- OPENCLAW_GATEWAY_TOKEN: \${OPENCLAW_GATEWAY_TOKEN:-}
2511
- volumes:
2512
- - $HOME/.openclaw:/home/node/.openclaw
2513
- EOF
2514
-
2515
- for workspace in "${WORKSPACES[@]}"; do
2516
- echo " - $workspace:$workspace" >> "$OPENCLAW_OVERRIDE_FILE"
2517
- done
2518
-
2519
- cat >> "$OPENCLAW_OVERRIDE_FILE" <<EOF
2520
- ports:
2521
- - "18789:18789"
2522
- - "18790:18790"
2523
- working_dir: $CURRENT_DIR
2524
- EOF
2525
-
2526
- if [[ -n "$NETWORK_OPTIONS" ]]; then
2527
- echo " networks:" >> "$OPENCLAW_OVERRIDE_FILE"
2528
- for net in ${DOCKER_NETWORKS//,/ }; do
2529
- echo " - $net" >> "$OPENCLAW_OVERRIDE_FILE"
2530
- done
2531
- echo "" >> "$OPENCLAW_OVERRIDE_FILE"
2532
- echo "networks:" >> "$OPENCLAW_OVERRIDE_FILE"
2533
- for net in ${DOCKER_NETWORKS//,/ }; do
2534
- echo " $net:" >> "$OPENCLAW_OVERRIDE_FILE"
2535
- echo " external: true" >> "$OPENCLAW_OVERRIDE_FILE"
2536
- done
2537
- fi
2538
-
2539
- echo "🚀 Starting OpenClaw with docker-compose..."
2540
- echo "🌐 Gateway: http://localhost:18789"
2541
- echo "🌐 Bridge: http://localhost:18790"
2542
- echo ""
2543
-
2544
- exec docker compose -f "$OPENCLAW_COMPOSE_FILE" -f "$OPENCLAW_OVERRIDE_FILE" \
2545
- --env-file "$ENV_FILE" \
2546
- up --remove-orphans
2547
- fi
2524
+ # if [[ "$TOOL" == "openclaw" ]]; then
2525
+ # OPENCLAW_REPO_DIR="$HOME/.ai-sandbox/tools/openclaw/repo"
2526
+ #
2527
+ # if [[ ! -d "$OPENCLAW_REPO_DIR" ]]; then
2528
+ # echo "❌ ERROR: OpenClaw repository not found at $OPENCLAW_REPO_DIR"
2529
+ # echo " Run: npx @kokorolx/ai-sandbox-wrapper setup"
2530
+ # exit 1
2531
+ # fi
2532
+ #
2533
+ # cd "$OPENCLAW_REPO_DIR"
2534
+ #
2535
+ # OPENCLAW_COMPOSE_FILE="$OPENCLAW_REPO_DIR/docker-compose.yml"
2536
+ # OPENCLAW_OVERRIDE_FILE="$OPENCLAW_REPO_DIR/docker-compose.override.yml"
2537
+ #
2538
+ # echo "🔄 Generating OpenClaw docker-compose override..."
2539
+ #
2540
+ # cat > "$OPENCLAW_OVERRIDE_FILE" <<EOF
2541
+ # services:
2542
+ # openclaw-gateway:
2543
+ # environment:
2544
+ # HOME: /home/node
2545
+ # OPENCLAW_GATEWAY_TOKEN: \${OPENCLAW_GATEWAY_TOKEN:-}
2546
+ # volumes:
2547
+ # - $HOME/.openclaw:/home/node/.openclaw
2548
+ # EOF
2549
+ #
2550
+ # for workspace in "${WORKSPACES[@]}"; do
2551
+ # echo " - $workspace:$workspace" >> "$OPENCLAW_OVERRIDE_FILE"
2552
+ # done
2553
+ #
2554
+ # cat >> "$OPENCLAW_OVERRIDE_FILE" <<EOF
2555
+ # ports:
2556
+ # - "18789:18789"
2557
+ # - "18790:18790"
2558
+ # working_dir: $CURRENT_DIR
2559
+ # EOF
2560
+ #
2561
+ # if [[ -n "$NETWORK_OPTIONS" ]]; then
2562
+ # echo " networks:" >> "$OPENCLAW_OVERRIDE_FILE"
2563
+ # for net in ${DOCKER_NETWORKS//,/ }; do
2564
+ # echo " - $net" >> "$OPENCLAW_OVERRIDE_FILE"
2565
+ # done
2566
+ # echo "" >> "$OPENCLAW_OVERRIDE_FILE"
2567
+ # echo "networks:" >> "$OPENCLAW_OVERRIDE_FILE"
2568
+ # for net in ${DOCKER_NETWORKS//,/ }; do
2569
+ # echo " $net:" >> "$OPENCLAW_OVERRIDE_FILE"
2570
+ # echo " external: true" >> "$OPENCLAW_OVERRIDE_FILE"
2571
+ # done
2572
+ # fi
2573
+ #
2574
+ # echo "🚀 Starting OpenClaw with docker-compose..."
2575
+ # echo "🌐 Gateway: http://localhost:18789"
2576
+ # echo "🌐 Bridge: http://localhost:18790"
2577
+ # echo ""
2578
+ #
2579
+ # exec docker compose -f "$OPENCLAW_COMPOSE_FILE" -f "$OPENCLAW_OVERRIDE_FILE" \
2580
+ # --env-file "$ENV_FILE" \
2581
+ # up --remove-orphans
2582
+ # fi
2548
2583
 
2549
2584
  docker run $CONTAINER_NAME --rm $TTY_FLAGS \
2550
2585
  --init \
@@ -6,7 +6,7 @@ FROM node:22-bookworm-slim
6
6
 
7
7
  ARG AGENT_UID=1001
8
8
 
9
- RUN apt-get update && apt-get install -y --no-install-recommends git curl ssh ca-certificates jq python3 python3-pip python3-venv python3-dev python3-setuptools build-essential libopenblas-dev pipx unzip xclip wl-clipboard ripgrep tmux fd-find sqlite3 poppler-utils qpdf tesseract-ocr && curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh && rm -rf /var/lib/apt/lists/* && pipx ensurepath
9
+ RUN apt-get update && apt-get install -y --no-install-recommends git curl ssh ca-certificates jq python3 python3-pip python3-venv python3-dev python3-setuptools build-essential libopenblas-dev pipx unzip xclip wl-clipboard ripgrep tmux fd-find sqlite3 vim-tiny poppler-utils qpdf tesseract-ocr && curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh && rm -rf /var/lib/apt/lists/* && pipx ensurepath
10
10
 
11
11
  # Install Python PDF processing tools for PDF skill
12
12
  RUN pip3 install --no-cache-dir --break-system-packages pypdf pdfplumber reportlab pytesseract pdf2image
@@ -6,7 +6,7 @@ FROM node:22-bookworm-slim
6
6
 
7
7
  ARG AGENT_UID=1001
8
8
 
9
- RUN apt-get update && apt-get install -y --no-install-recommends git curl ssh ca-certificates jq python3 python3-pip python3-venv python3-dev python3-setuptools build-essential libopenblas-dev pipx unzip xclip wl-clipboard ripgrep tmux fd-find sqlite3 poppler-utils qpdf tesseract-ocr && curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh && rm -rf /var/lib/apt/lists/* && pipx ensurepath
9
+ RUN apt-get update && apt-get install -y --no-install-recommends git curl ssh ca-certificates jq python3 python3-pip python3-venv python3-dev python3-setuptools build-essential libopenblas-dev pipx unzip xclip wl-clipboard ripgrep tmux fd-find sqlite3 vim-tiny poppler-utils qpdf tesseract-ocr && curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=/usr/local/bin sh && rm -rf /var/lib/apt/lists/* && pipx ensurepath
10
10
 
11
11
  # Install Python PDF processing tools for PDF skill
12
12
  RUN pip3 install --no-cache-dir --break-system-packages pypdf pdfplumber reportlab pytesseract pdf2image
@@ -4,12 +4,6 @@ set -e
4
4
  dockerfile_snippet() {
5
5
  cat <<'SNIPPET'
6
6
  USER root
7
- RUN apt-get update && apt-get install -y --no-install-recommends tmux && rm -rf /var/lib/apt/lists/*
8
- RUN npm install -g @kaitranntt/ccs --ignore-scripts && \
9
- mkdir -p /home/agent/.ccs && \
10
- chown -R agent:agent /home/agent/.ccs && \
11
- which ccs && ccs --version && \
12
- sed -i 's/fs\.symlinkSync(sourcePath, targetPath, symlinkType)/fs\.symlinkSync(require("path").relative(require("path").dirname(targetPath), sourcePath), targetPath, symlinkType)/g' /usr/local/lib/node_modules/@kaitranntt/ccs/dist/utils/claude-symlink-manager.js
13
7
  RUN export HOME=/root && curl -fsSL https://claude.ai/install.sh | bash && \
14
8
  mkdir -p /usr/local/share && \
15
9
  mv /root/.local/share/claude /usr/local/share/claude && \
@@ -36,17 +30,6 @@ cat <<'EOF' > "dockerfiles/$TOOL/Dockerfile"
36
30
  FROM ai-base:latest
37
31
 
38
32
  USER root
39
- # Install tmux for Agent Teams split-pane mode
40
- RUN apt-get update && apt-get install -y --no-install-recommends tmux && rm -rf /var/lib/apt/lists/*
41
-
42
- # Install CCS (Claude Code Switch) for multi-provider model switching
43
- # Use --ignore-scripts to avoid postinstall failures when HOME=/home/agent but running as root
44
- RUN npm install -g @kaitranntt/ccs --ignore-scripts && \
45
- mkdir -p /home/agent/.ccs && \
46
- chown -R agent:agent /home/agent/.ccs && \
47
- which ccs && ccs --version && \
48
- sed -i 's/fs\.symlinkSync(sourcePath, targetPath, symlinkType)/fs\.symlinkSync(require("path").relative(require("path").dirname(targetPath), sourcePath), targetPath, symlinkType)/g' /usr/local/lib/node_modules/@kaitranntt/ccs/dist/utils/claude-symlink-manager.js
49
-
50
33
  # Install Claude Code using official native installer
51
34
  RUN export HOME=/root && curl -fsSL https://claude.ai/install.sh | bash && \
52
35
  mkdir -p /usr/local/share && \
@@ -68,11 +51,7 @@ echo " ✓ Official native binary"
68
51
  echo " ✓ Claude 3.5 Sonnet/Opus models"
69
52
  echo " ✓ Agentic coding with file editing"
70
53
  echo " ✓ Web search and fetch built-in"
71
- echo " ✓ Agent Teams (multi-agent tmux split-pane workflows)"
72
- echo " ✓ CCS (Claude Code Switch) for multi-provider model switching"
73
54
  echo ""
74
55
  echo "Usage: ai-run claude"
75
56
  echo "Auth: Set ANTHROPIC_API_KEY in ~/.ai-sandbox/env"
76
57
  echo ""
77
- echo "Agent Teams: Add CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 to ~/.ai-sandbox/env"
78
- echo "CCS: Run 'ai-run claude --shell' then 'ccs help' to configure providers"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokorolx/ai-sandbox-wrapper",
3
- "version": "3.0.8",
3
+ "version": "3.1.1",
4
4
  "description": "Docker-based security sandbox for AI coding agents. Isolate Claude, Gemini, Aider, and other AI tools from your host system.",
5
5
  "keywords": [
6
6
  "ai",
package/setup.sh CHANGED
@@ -281,8 +281,9 @@ echo "📁 Legacy workspaces file: $WORKSPACES_FILE"
281
281
  WORKSPACE="${WORKSPACES[0]}"
282
282
 
283
283
  # Tool definitions
284
- TOOL_OPTIONS="amp,opencode,openclaw,droid,claude,gemini,kilo,qwen,codex,qoder,auggie,codebuddy,jules,shai"
285
- TOOL_DESCS="AI coding assistant from @sourcegraph/amp,Open-source coding tool from opencode-ai,OpenClaw AI gateway (Docker Compose),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"
284
+ # DISABLED: amp,openclaw,droid,gemini,kilo,qwen,qoder,auggie,codebuddy,jules,shai
285
+ TOOL_OPTIONS="opencode,claude,codex"
286
+ TOOL_DESCS="Open-source coding tool from opencode-ai,Claude Code CLI from Anthropic,OpenAI Codex terminal agent"
286
287
 
287
288
  # Pre-select previously installed tools
288
289
  PRESELECTED_TOOLS=""
@@ -303,7 +304,7 @@ echo "Installing tools: ${TOOLS[*]}"
303
304
 
304
305
  CONTAINERIZED_TOOLS=()
305
306
  for tool in "${TOOLS[@]}"; do
306
- if [[ "$tool" =~ ^(amp|opencode|openclaw|claude|aider|droid|gemini|kilo|qwen|codex|qoder|auggie|codebuddy|jules|shai)$ ]]; then
307
+ if [[ "$tool" =~ ^(opencode|claude|codex)$ ]]; then
307
308
  CONTAINERIZED_TOOLS+=("$tool")
308
309
  fi
309
310
  done