@kokorolx/ai-sandbox-wrapper 2.7.0 → 3.0.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 +208 -102
- package/bin/cli.js +51 -7
- package/dockerfiles/base/Dockerfile +7 -0
- package/dockerfiles/base/skills/rtk/SKILL.md +103 -0
- package/dockerfiles/base/skills/rtk-setup/SKILL.md +118 -0
- package/dockerfiles/sandbox/Dockerfile +114 -0
- package/dockerfiles/sandbox/skills/rtk/SKILL.md +103 -0
- package/dockerfiles/sandbox/skills/rtk-setup/SKILL.md +118 -0
- package/lib/build-sandbox.sh +89 -0
- package/lib/install-aider.sh +11 -1
- package/lib/install-amp.sh +20 -12
- package/lib/install-auggie.sh +16 -1
- package/lib/install-base.sh +25 -0
- package/lib/install-claude.sh +20 -1
- package/lib/install-codebuddy.sh +16 -1
- package/lib/install-codex.sh +16 -1
- package/lib/install-droid.sh +15 -0
- package/lib/install-gemini.sh +16 -1
- package/lib/install-jules.sh +16 -1
- package/lib/install-kilo.sh +12 -2
- package/lib/install-openclaw.sh +10 -1
- package/lib/install-opencode.sh +17 -4
- package/lib/install-qoder.sh +16 -1
- package/lib/install-qwen.sh +16 -1
- package/lib/install-shai.sh +13 -1
- package/package.json +1 -1
- package/setup.sh +55 -52
package/bin/ai-run
CHANGED
|
@@ -4,9 +4,10 @@ set -e
|
|
|
4
4
|
# Show help if requested
|
|
5
5
|
show_help() {
|
|
6
6
|
cat << 'EOF'
|
|
7
|
-
Usage: ai-run
|
|
7
|
+
Usage: ai-run [tool] [options] [-- tool-args...]
|
|
8
8
|
|
|
9
9
|
Run AI tools in a secure Docker sandbox.
|
|
10
|
+
When no tool is specified, opens an interactive shell with all installed tools.
|
|
10
11
|
|
|
11
12
|
Options:
|
|
12
13
|
-s, --shell Start interactive shell instead of running tool directly
|
|
@@ -35,12 +36,13 @@ OpenCode Server Authentication (web/serve mode only):
|
|
|
35
36
|
Precedence: --password > --password-env > OPENCODE_SERVER_PASSWORD env > interactive prompt
|
|
36
37
|
|
|
37
38
|
Examples:
|
|
39
|
+
ai-run # Open interactive shell with all tools
|
|
38
40
|
ai-run claude # Run Claude in sandbox
|
|
39
41
|
ai-run opencode web -e 4096 # Run OpenCode web with port exposed
|
|
40
42
|
ai-run opencode web -p secret # Run with password
|
|
41
43
|
ai-run opencode --shell # Start shell, run tool manually
|
|
42
44
|
ai-run aider -n mynetwork # Connect to Docker network
|
|
43
|
-
ai-run opencode --git-fetch
|
|
45
|
+
ai-run opencode --git-fetch # Git fetch only (no push)
|
|
44
46
|
|
|
45
47
|
Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
|
|
46
48
|
EOF
|
|
@@ -126,11 +128,26 @@ if [[ "${1:-}" == "--help-env" ]]; then
|
|
|
126
128
|
show_help_env
|
|
127
129
|
fi
|
|
128
130
|
|
|
129
|
-
TOOL="$1"
|
|
130
|
-
|
|
131
|
+
TOOL="${1:-}"
|
|
132
|
+
if [[ -n "$TOOL" && ! "$TOOL" =~ ^- ]]; then
|
|
133
|
+
shift
|
|
134
|
+
else
|
|
135
|
+
TOOL=""
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Handle no tool specified
|
|
139
|
+
if [[ -z "$TOOL" ]]; then
|
|
140
|
+
if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
|
|
141
|
+
echo "❌ ERROR: No tool specified and no TTY available."
|
|
142
|
+
echo " Use: ai-run <tool> [args...]"
|
|
143
|
+
exit 1
|
|
144
|
+
fi
|
|
145
|
+
SHELL_MODE=true
|
|
146
|
+
fi
|
|
131
147
|
|
|
132
148
|
# Parse flags
|
|
133
|
-
SHELL_MODE
|
|
149
|
+
# Note: SHELL_MODE may already be true if no tool was specified
|
|
150
|
+
SHELL_MODE="${SHELL_MODE:-false}"
|
|
134
151
|
NETWORK_FLAG=false
|
|
135
152
|
NETWORK_ARG=""
|
|
136
153
|
EXPOSE_ARG=""
|
|
@@ -462,7 +479,7 @@ while IFS= read -r ws; do
|
|
|
462
479
|
done < <(read_workspaces)
|
|
463
480
|
|
|
464
481
|
if [[ "$ALLOWED" != "true" ]]; then
|
|
465
|
-
echo "⚠️ SECURITY WARNING: You are running $TOOL outside a whitelisted workspace."
|
|
482
|
+
echo "⚠️ SECURITY WARNING: You are running ${TOOL:-shell} outside a whitelisted workspace."
|
|
466
483
|
echo " Current path: $CURRENT_DIR"
|
|
467
484
|
echo ""
|
|
468
485
|
echo "Allowing this path gives the AI container access to this folder."
|
|
@@ -540,17 +557,86 @@ if [[ "$(uname)" == "Darwin" ]] && [[ -t 0 ]]; then
|
|
|
540
557
|
fi
|
|
541
558
|
|
|
542
559
|
if [[ "$AI_IMAGE_SOURCE" == "registry" ]]; then
|
|
543
|
-
IMAGE="registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai
|
|
560
|
+
IMAGE="registry.gitlab.com/kokorolee/ai-sandbox-wrapper/ai-sandbox:latest"
|
|
544
561
|
else
|
|
545
|
-
IMAGE="ai
|
|
562
|
+
IMAGE="ai-sandbox:latest"
|
|
563
|
+
fi
|
|
564
|
+
|
|
565
|
+
# Tool validation (warn if tool not in config.json)
|
|
566
|
+
if [[ -n "$TOOL" ]]; then
|
|
567
|
+
if command -v jq &>/dev/null && [[ -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
568
|
+
INSTALLED_TOOLS=$(jq -r '.tools.installed // [] | .[]' "$AI_SANDBOX_CONFIG" 2>/dev/null)
|
|
569
|
+
if [[ -n "$INSTALLED_TOOLS" ]] && ! echo "$INSTALLED_TOOLS" | grep -qx "$TOOL"; then
|
|
570
|
+
echo "⚠️ WARNING: Tool '$TOOL' may not be installed in the sandbox image."
|
|
571
|
+
echo " Installed tools: $(echo "$INSTALLED_TOOLS" | tr '\n' ', ' | sed 's/,$//')"
|
|
572
|
+
echo " Run setup.sh to add tools."
|
|
573
|
+
echo ""
|
|
546
574
|
fi
|
|
575
|
+
fi
|
|
576
|
+
fi
|
|
547
577
|
|
|
548
|
-
#
|
|
549
|
-
HOME_DIR="$SANDBOX_DIR/
|
|
578
|
+
# Shared home directory for all tools (unified image)
|
|
579
|
+
HOME_DIR="$SANDBOX_DIR/home"
|
|
550
580
|
GIT_SHARED_DIR="$SANDBOX_DIR/shared/git"
|
|
581
|
+
CACHE_DIR="$SANDBOX_DIR/cache"
|
|
551
582
|
|
|
552
583
|
mkdir -p "$HOME_DIR"
|
|
553
584
|
mkdir -p "$GIT_SHARED_DIR"
|
|
585
|
+
mkdir -p "$CACHE_DIR/npm" "$CACHE_DIR/bun" "$CACHE_DIR/pip" "$CACHE_DIR/playwright-browsers"
|
|
586
|
+
|
|
587
|
+
# ============================================================================
|
|
588
|
+
# MIGRATION V3: Merge per-tool home directories into unified home
|
|
589
|
+
# ============================================================================
|
|
590
|
+
migrate_to_unified_home() {
|
|
591
|
+
local marker_file="$SANDBOX_DIR/.migrated-unified-home"
|
|
592
|
+
|
|
593
|
+
# Skip if already migrated
|
|
594
|
+
[[ -f "$marker_file" ]] && return 0
|
|
595
|
+
|
|
596
|
+
# Check if any per-tool home directories exist
|
|
597
|
+
local needs_migration=false
|
|
598
|
+
for tool_home in "$SANDBOX_DIR"/tools/*/home; do
|
|
599
|
+
if [[ -d "$tool_home" ]]; then
|
|
600
|
+
needs_migration=true
|
|
601
|
+
break
|
|
602
|
+
fi
|
|
603
|
+
done
|
|
604
|
+
|
|
605
|
+
[[ "$needs_migration" == "false" ]] && { touch "$marker_file"; return 0; }
|
|
606
|
+
|
|
607
|
+
echo "🔄 Migrating per-tool home directories to unified home..."
|
|
608
|
+
|
|
609
|
+
local migrated_count=0
|
|
610
|
+
for tool_home in "$SANDBOX_DIR"/tools/*/home; do
|
|
611
|
+
[[ -d "$tool_home" ]] || continue
|
|
612
|
+
local tool_name
|
|
613
|
+
tool_name=$(basename "$(dirname "$tool_home")")
|
|
614
|
+
echo " 🔄 Migrating tools/$tool_name/home/ → home/"
|
|
615
|
+
|
|
616
|
+
# Use cp -rn (no-clobber) - works on macOS and Linux
|
|
617
|
+
# Falls back to rsync if cp -rn fails (some older systems)
|
|
618
|
+
# KNOWN ISSUE: glob * doesn't match dotfiles (.nano-brain, .config, etc.)
|
|
619
|
+
# TODO: fix with dotglob or rsync-first approach
|
|
620
|
+
if cp -rn "$tool_home/"* "$HOME_DIR/" 2>/dev/null; then
|
|
621
|
+
((migrated_count++))
|
|
622
|
+
elif command -v rsync &>/dev/null; then
|
|
623
|
+
rsync -a --ignore-existing "$tool_home/" "$HOME_DIR/" 2>/dev/null && ((migrated_count++))
|
|
624
|
+
else
|
|
625
|
+
echo " ⚠️ Could not migrate $tool_name (cp -rn and rsync unavailable)"
|
|
626
|
+
fi
|
|
627
|
+
done
|
|
628
|
+
|
|
629
|
+
# Create marker file
|
|
630
|
+
date -Iseconds > "$marker_file" 2>/dev/null || date > "$marker_file"
|
|
631
|
+
|
|
632
|
+
if [[ $migrated_count -gt 0 ]]; then
|
|
633
|
+
echo "✅ Migration complete! Merged $migrated_count tool home directories."
|
|
634
|
+
echo ""
|
|
635
|
+
echo "ℹ️ Old per-tool home directories preserved. Run 'npx @kokorolx/ai-sandbox-wrapper clean' to remove them."
|
|
636
|
+
fi
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
migrate_to_unified_home
|
|
554
640
|
|
|
555
641
|
# Build volume mounts for all whitelisted workspaces
|
|
556
642
|
VOLUME_MOUNTS=""
|
|
@@ -558,13 +644,47 @@ while IFS= read -r ws; do
|
|
|
558
644
|
VOLUME_MOUNTS="$VOLUME_MOUNTS -v $ws:$ws:delegated"
|
|
559
645
|
done < "$WORKSPACES_FILE"
|
|
560
646
|
|
|
561
|
-
#
|
|
562
|
-
#
|
|
563
|
-
|
|
647
|
+
# Config mount registry for all tools
|
|
648
|
+
# Maps tool name to space-separated config paths (relative to $HOME)
|
|
649
|
+
get_tool_configs() {
|
|
650
|
+
case "$1" in
|
|
651
|
+
amp) echo ".config/amp .local/share/amp" ;;
|
|
652
|
+
opencode) echo ".config/opencode .local/share/opencode" ;;
|
|
653
|
+
claude) echo ".claude .ccs" ;;
|
|
654
|
+
openclaw) echo ".openclaw" ;;
|
|
655
|
+
droid) echo ".config/droid" ;;
|
|
656
|
+
qoder) echo ".config/qoder" ;;
|
|
657
|
+
auggie) echo ".config/auggie" ;;
|
|
658
|
+
codebuddy) echo ".config/codebuddy" ;;
|
|
659
|
+
jules) echo ".config/jules" ;;
|
|
660
|
+
shai) echo ".config/shai" ;;
|
|
661
|
+
gemini) echo ".config/gemini" ;;
|
|
662
|
+
aider) echo ".config/aider .aider" ;;
|
|
663
|
+
kilo) echo ".config/kilo" ;;
|
|
664
|
+
codex) echo ".config/codex" ;;
|
|
665
|
+
qwen) echo ".config/qwen" ;;
|
|
666
|
+
esac
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
# All known tools (for fallback)
|
|
670
|
+
ALL_KNOWN_TOOLS="amp opencode claude openclaw droid qoder auggie codebuddy jules shai gemini aider kilo codex qwen"
|
|
564
671
|
|
|
565
|
-
#
|
|
566
|
-
|
|
567
|
-
|
|
672
|
+
# Get list of installed tools from config.json, fallback to all known tools
|
|
673
|
+
get_installed_tools() {
|
|
674
|
+
local installed
|
|
675
|
+
if command -v jq &>/dev/null && [[ -f "$AI_SANDBOX_CONFIG" ]]; then
|
|
676
|
+
installed=$(jq -r '.tools.installed[]? // empty' "$AI_SANDBOX_CONFIG" 2>/dev/null)
|
|
677
|
+
fi
|
|
678
|
+
if [[ -z "$installed" ]]; then
|
|
679
|
+
# Fallback: return all known tools in registry
|
|
680
|
+
echo "$ALL_KNOWN_TOOLS"
|
|
681
|
+
else
|
|
682
|
+
echo "$installed"
|
|
683
|
+
fi
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
# Tool config persistence via bind mounts
|
|
687
|
+
# Bind-mount host paths directly to ensure changes persist to the host.
|
|
568
688
|
TOOL_CONFIG_MOUNTS=""
|
|
569
689
|
|
|
570
690
|
mount_tool_config() {
|
|
@@ -582,81 +702,45 @@ mount_tool_config() {
|
|
|
582
702
|
fi
|
|
583
703
|
}
|
|
584
704
|
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
mount_tool_config "$HOME
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
openclaw)
|
|
609
|
-
mount_tool_config "$HOME/.openclaw" ".openclaw"
|
|
610
|
-
;;
|
|
611
|
-
claude)
|
|
612
|
-
mount_tool_config "$HOME/.claude" ".claude"
|
|
613
|
-
mount_tool_config "$HOME/.ccs" ".ccs"
|
|
614
|
-
;;
|
|
615
|
-
droid)
|
|
616
|
-
mount_tool_config "$HOME/.config/droid" ".config/droid"
|
|
617
|
-
;;
|
|
618
|
-
qoder)
|
|
619
|
-
mount_tool_config "$HOME/.config/qoder" ".config/qoder"
|
|
620
|
-
;;
|
|
621
|
-
auggie)
|
|
622
|
-
mount_tool_config "$HOME/.config/auggie" ".config/auggie"
|
|
623
|
-
;;
|
|
624
|
-
codebuddy)
|
|
625
|
-
mount_tool_config "$HOME/.config/codebuddy" ".config/codebuddy"
|
|
626
|
-
;;
|
|
627
|
-
jules)
|
|
628
|
-
mount_tool_config "$HOME/.config/jules" ".config/jules"
|
|
629
|
-
;;
|
|
630
|
-
shai)
|
|
631
|
-
mount_tool_config "$HOME/.config/shai" ".config/shai"
|
|
632
|
-
;;
|
|
633
|
-
gemini)
|
|
634
|
-
mount_tool_config "$HOME/.config/gemini" ".config/gemini"
|
|
635
|
-
;;
|
|
636
|
-
aider)
|
|
637
|
-
mount_tool_config "$HOME/.config/aider" ".config/aider"
|
|
638
|
-
mount_tool_config "$HOME/.aider" ".aider"
|
|
639
|
-
;;
|
|
640
|
-
kilo)
|
|
641
|
-
mount_tool_config "$HOME/.config/kilo" ".config/kilo"
|
|
642
|
-
;;
|
|
643
|
-
codex)
|
|
644
|
-
mount_tool_config "$HOME/.config/codex" ".config/codex"
|
|
645
|
-
;;
|
|
646
|
-
qwen)
|
|
647
|
-
mount_tool_config "$HOME/.config/qwen" ".config/qwen"
|
|
648
|
-
;;
|
|
649
|
-
esac
|
|
705
|
+
# Mount configs for all installed tools (unified image)
|
|
706
|
+
for tool in $(get_installed_tools); do
|
|
707
|
+
for cfg_path in $(get_tool_configs "$tool"); do
|
|
708
|
+
mount_tool_config "$HOME/$cfg_path" "$cfg_path"
|
|
709
|
+
done
|
|
710
|
+
done
|
|
711
|
+
|
|
712
|
+
# Bundle OpenCode default skills (if opencode is installed)
|
|
713
|
+
if get_installed_tools | grep -qw "opencode"; then
|
|
714
|
+
AIRUN_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
715
|
+
BUNDLED_SKILLS_DIR="$AIRUN_DIR/../skills"
|
|
716
|
+
if [[ -d "$BUNDLED_SKILLS_DIR" ]]; then
|
|
717
|
+
for skill_dir in "$BUNDLED_SKILLS_DIR"/*/; do
|
|
718
|
+
[[ ! -d "$skill_dir" ]] && continue
|
|
719
|
+
skill_name=$(basename "$skill_dir")
|
|
720
|
+
target_dir="$HOME/.config/opencode/skills/$skill_name"
|
|
721
|
+
if [[ ! -d "$target_dir" ]]; then
|
|
722
|
+
mkdir -p "$target_dir"
|
|
723
|
+
cp -r "$skill_dir"* "$target_dir/" 2>/dev/null || true
|
|
724
|
+
fi
|
|
725
|
+
done
|
|
726
|
+
fi
|
|
727
|
+
fi
|
|
650
728
|
|
|
651
729
|
# Ensure required directories exist in HOME_DIR
|
|
652
730
|
mkdir -p "$HOME_DIR/.config" "$HOME_DIR/.local/share" "$HOME_DIR/.cache" "$HOME_DIR/.bun"
|
|
653
731
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
732
|
+
SHARED_CACHE_MOUNTS="-v $CACHE_DIR/npm:/home/agent/.npm:delegated"
|
|
733
|
+
SHARED_CACHE_MOUNTS="$SHARED_CACHE_MOUNTS -v $CACHE_DIR/bun:/home/agent/.bun/install/cache:delegated"
|
|
734
|
+
SHARED_CACHE_MOUNTS="$SHARED_CACHE_MOUNTS -v $CACHE_DIR/pip:/home/agent/.cache/pip:delegated"
|
|
735
|
+
SHARED_CACHE_MOUNTS="$SHARED_CACHE_MOUNTS -v $CACHE_DIR/playwright-browsers:/opt/playwright-browsers:delegated"
|
|
657
736
|
|
|
658
|
-
if
|
|
659
|
-
|
|
737
|
+
# Project-level config mount (if exists and tool specified)
|
|
738
|
+
CONFIG_MOUNT=""
|
|
739
|
+
if [[ -n "$TOOL" ]]; then
|
|
740
|
+
PROJECT_CONFIG="$CURRENT_DIR/.$TOOL.json"
|
|
741
|
+
if [[ -f "$PROJECT_CONFIG" ]]; then
|
|
742
|
+
CONFIG_MOUNT="-v $PROJECT_CONFIG:$CURRENT_DIR/.$TOOL.json:delegated"
|
|
743
|
+
fi
|
|
660
744
|
fi
|
|
661
745
|
|
|
662
746
|
# ============================================================================
|
|
@@ -1436,7 +1520,7 @@ generate_container_name() {
|
|
|
1436
1520
|
# Generate random 6-character suffix (hex)
|
|
1437
1521
|
local random_suffix=$(openssl rand -hex 3)
|
|
1438
1522
|
|
|
1439
|
-
echo "${TOOL}-${folder_name}-${random_suffix}"
|
|
1523
|
+
echo "${TOOL:-shell}-${folder_name}-${random_suffix}"
|
|
1440
1524
|
}
|
|
1441
1525
|
|
|
1442
1526
|
# Container name and TTY allocation
|
|
@@ -2019,15 +2103,33 @@ fi
|
|
|
2019
2103
|
|
|
2020
2104
|
# Prepare command based on mode
|
|
2021
2105
|
ENTRYPOINT_OVERRIDE=""
|
|
2022
|
-
if [[ "$SHELL_MODE"
|
|
2023
|
-
#
|
|
2106
|
+
if [[ -n "$TOOL" && "$SHELL_MODE" != "true" ]]; then
|
|
2107
|
+
# Direct tool execution: override entrypoint to the tool
|
|
2108
|
+
ENTRYPOINT_OVERRIDE="--entrypoint $TOOL"
|
|
2109
|
+
DOCKER_COMMAND=("${TOOL_ARGS[@]}")
|
|
2110
|
+
elif [[ "$SHELL_MODE" == "true" || -z "$TOOL" ]]; then
|
|
2111
|
+
# Shell mode (explicit --shell or no tool specified)
|
|
2024
2112
|
ENTRYPOINT_OVERRIDE="--entrypoint bash"
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2113
|
+
# Build welcome message with installed tools
|
|
2114
|
+
INSTALLED_TOOLS_MSG=""
|
|
2115
|
+
if command -v jq &>/dev/null && [[ -f "$SANDBOX_DIR/config.json" ]]; then
|
|
2116
|
+
INSTALLED_TOOLS_MSG=$(jq -r '.tools.installed // [] | join(", ")' "$SANDBOX_DIR/config.json" 2>/dev/null || echo "")
|
|
2117
|
+
fi
|
|
2118
|
+
if [[ -n "$TOOL" ]]; then
|
|
2119
|
+
# Shell mode with specific tool (ai-run claude --shell)
|
|
2120
|
+
DOCKER_COMMAND=(
|
|
2121
|
+
"-c"
|
|
2122
|
+
"echo ''; echo '🚀 AI Tool Container - Interactive Shell'; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; echo \"Tool available: ${TOOL}\"; echo 'Run the tool: ${TOOL}'; echo 'Exit container: exit or Ctrl+D'; echo ''; echo \"Additional tools:\"; echo ' - specify (spec-kit): Spec-driven development'; echo ' - uipro (ux-ui-promax): UI/UX design intelligence'; echo ' - openspec: OpenSpec workflow'; echo ''; echo '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'; echo ''; exec bash"
|
|
2123
|
+
)
|
|
2124
|
+
else
|
|
2125
|
+
# Shell mode without tool (ai-run with no args)
|
|
2126
|
+
DOCKER_COMMAND=(
|
|
2127
|
+
"-c"
|
|
2128
|
+
"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"
|
|
2129
|
+
)
|
|
2130
|
+
fi
|
|
2029
2131
|
else
|
|
2030
|
-
#
|
|
2132
|
+
# Fallback: direct mode with image's default entrypoint
|
|
2031
2133
|
DOCKER_COMMAND=("${TOOL_ARGS[@]}")
|
|
2032
2134
|
fi
|
|
2033
2135
|
|
|
@@ -2061,13 +2163,17 @@ fi
|
|
|
2061
2163
|
mkdir -p "$HOME_DIR" "$GIT_SHARED_DIR"
|
|
2062
2164
|
chmod -R u+w "$HOME_DIR" "$GIT_SHARED_DIR" 2>/dev/null || true
|
|
2063
2165
|
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2166
|
+
if [[ ! -f "$CACHE_DIR/playwright-browsers/.seeded" ]]; then
|
|
2167
|
+
if docker run --rm "$IMAGE" test -d /opt/playwright-browsers 2>/dev/null; then
|
|
2168
|
+
echo "🔄 Seeding shared Playwright browser cache..."
|
|
2169
|
+
docker run --rm -v "$CACHE_DIR/playwright-browsers":/export "$IMAGE" \
|
|
2170
|
+
cp -a /opt/playwright-browsers/. /export/ 2>/dev/null && \
|
|
2171
|
+
touch "$CACHE_DIR/playwright-browsers/.seeded" && \
|
|
2172
|
+
echo "✅ Playwright browsers cached" || \
|
|
2173
|
+
echo "⚠️ Playwright browser seeding failed (will retry next run)"
|
|
2174
|
+
else
|
|
2175
|
+
touch "$CACHE_DIR/playwright-browsers/.seeded"
|
|
2176
|
+
fi
|
|
2071
2177
|
fi
|
|
2072
2178
|
|
|
2073
2179
|
# Detect display configuration (clipboard integration)
|
|
@@ -2145,12 +2251,12 @@ docker run $CONTAINER_NAME --rm $TTY_FLAGS \
|
|
|
2145
2251
|
$TOOL_CONFIG_MOUNTS \
|
|
2146
2252
|
$GIT_MOUNTS \
|
|
2147
2253
|
$NETWORK_OPTIONS \
|
|
2148
|
-
$CACHE_MOUNTS \
|
|
2149
2254
|
$DISPLAY_FLAGS \
|
|
2150
2255
|
$HOST_ACCESS_ARGS \
|
|
2151
2256
|
$PORT_MAPPINGS \
|
|
2152
2257
|
$OPENCODE_PASSWORD_ENV \
|
|
2153
2258
|
-v "$HOME_DIR":/home/agent \
|
|
2259
|
+
$SHARED_CACHE_MOUNTS \
|
|
2154
2260
|
-w "$CURRENT_DIR" \
|
|
2155
2261
|
--env-file "$ENV_FILE" \
|
|
2156
2262
|
-e TERM="$TERM" \
|
package/bin/cli.js
CHANGED
|
@@ -23,6 +23,7 @@ Commands:
|
|
|
23
23
|
setup Run interactive setup (configure workspaces, select tools)
|
|
24
24
|
update Interactive menu to manage config (workspaces, git, networks)
|
|
25
25
|
clean Interactive cleanup for caches/configs
|
|
26
|
+
clean cache [type] Clear shared package caches (npm, bun, pip, playwright-browsers)
|
|
26
27
|
config show [--json] Display current global configuration
|
|
27
28
|
config tool <tool> [--show] Display host paths and config for a specific tool
|
|
28
29
|
|
|
@@ -326,9 +327,9 @@ async function runConfigTool(toolName, showContent) {
|
|
|
326
327
|
process.exit(1);
|
|
327
328
|
}
|
|
328
329
|
|
|
329
|
-
const toolHome = path.join(SANDBOX_DIR, '
|
|
330
|
+
const toolHome = path.join(SANDBOX_DIR, 'home');
|
|
330
331
|
console.log(`\n🔍 Sandbox Configuration for: ${toolName}`);
|
|
331
|
-
console.log(`
|
|
332
|
+
console.log(`Sandbox Home: ${toolHome}`);
|
|
332
333
|
|
|
333
334
|
if (!fs.existsSync(toolHome)) {
|
|
334
335
|
console.log('Status: ⚠️ Not yet initialized (folder missing on host)');
|
|
@@ -1259,6 +1260,45 @@ async function manageNetworksMenu(rl) {
|
|
|
1259
1260
|
}
|
|
1260
1261
|
}
|
|
1261
1262
|
|
|
1263
|
+
// ============================================================================
|
|
1264
|
+
// CLEAN CACHE COMMAND (non-interactive)
|
|
1265
|
+
// ============================================================================
|
|
1266
|
+
const CACHE_TYPES = ['npm', 'bun', 'pip', 'playwright-browsers']
|
|
1267
|
+
|
|
1268
|
+
function runCleanCache(cacheType) {
|
|
1269
|
+
const cacheDir = path.join(SANDBOX_DIR, 'cache')
|
|
1270
|
+
|
|
1271
|
+
if (cacheType && !CACHE_TYPES.includes(cacheType)) {
|
|
1272
|
+
console.error(`❌ Unknown cache type: ${cacheType}`)
|
|
1273
|
+
console.error(`Valid types: ${CACHE_TYPES.join(', ')}`)
|
|
1274
|
+
process.exit(1)
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const targets = cacheType ? [cacheType] : CACHE_TYPES
|
|
1278
|
+
|
|
1279
|
+
let totalFreed = 0
|
|
1280
|
+
for (const t of targets) {
|
|
1281
|
+
const targetPath = path.join(cacheDir, t)
|
|
1282
|
+
if (!pathExists(targetPath)) {
|
|
1283
|
+
console.log(` ⏭ ${t}/ (not found)`)
|
|
1284
|
+
continue
|
|
1285
|
+
}
|
|
1286
|
+
const size = getPathSize(targetPath)
|
|
1287
|
+
const sizeNum = typeof size === 'number' ? size : 0
|
|
1288
|
+
try {
|
|
1289
|
+
fs.rmSync(targetPath, { recursive: true, force: true })
|
|
1290
|
+
fs.mkdirSync(targetPath, { recursive: true })
|
|
1291
|
+
totalFreed += sizeNum
|
|
1292
|
+
console.log(` ✓ ${t}/ cleared (${formatBytes(sizeNum)})`)
|
|
1293
|
+
} catch (err) {
|
|
1294
|
+
const msg = err && err.message ? err.message : String(err)
|
|
1295
|
+
console.error(` ❌ ${t}/: ${msg}`)
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
console.log(`\n🧹 Freed ${formatBytes(totalFreed)}`)
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1262
1302
|
// Parse subcommand and options
|
|
1263
1303
|
const subCommand = positionalArgs[1];
|
|
1264
1304
|
const subArg = positionalArgs[2];
|
|
@@ -1286,11 +1326,15 @@ switch (command) {
|
|
|
1286
1326
|
});
|
|
1287
1327
|
break;
|
|
1288
1328
|
case 'clean':
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1329
|
+
if (subCommand === 'cache') {
|
|
1330
|
+
runCleanCache(subArg)
|
|
1331
|
+
} else {
|
|
1332
|
+
runClean().catch((err) => {
|
|
1333
|
+
const message = err && err.message ? err.message : String(err)
|
|
1334
|
+
console.error('❌ Cleanup failed:', message)
|
|
1335
|
+
process.exit(1)
|
|
1336
|
+
})
|
|
1337
|
+
}
|
|
1294
1338
|
break;
|
|
1295
1339
|
case 'config':
|
|
1296
1340
|
if (subCommand === 'show') {
|
|
@@ -10,6 +10,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends git cur
|
|
|
10
10
|
|
|
11
11
|
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg && chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | tee /etc/apt/sources.list.d/github-cli.list > /dev/null && apt-get update && apt-get install -y gh && rm -rf /var/lib/apt/lists/*
|
|
12
12
|
|
|
13
|
+
# Install bun (used by most AI tool install scripts)
|
|
14
|
+
RUN npm install -g bun
|
|
15
|
+
|
|
13
16
|
# Install pnpm globally using npm (not bun, for stability)
|
|
14
17
|
RUN npm install -g pnpm
|
|
15
18
|
|
|
@@ -40,6 +43,10 @@ RUN mkdir -p /usr/local/lib/openspec && \
|
|
|
40
43
|
chmod +x /usr/local/bin/openspec
|
|
41
44
|
# Install RTK - token optimizer for AI coding agents (built from source)
|
|
42
45
|
COPY --from=rtk-builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk
|
|
46
|
+
# Install RTK OpenCode skills (auto-discovered by OpenCode agents)
|
|
47
|
+
RUN mkdir -p /home/agent/.config/opencode/skills/rtk /home/agent/.config/opencode/skills/rtk-setup
|
|
48
|
+
COPY skills/rtk/SKILL.md /home/agent/.config/opencode/skills/rtk/SKILL.md
|
|
49
|
+
COPY skills/rtk-setup/SKILL.md /home/agent/.config/opencode/skills/rtk-setup/SKILL.md
|
|
43
50
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
44
51
|
libglib2.0-0 \
|
|
45
52
|
libnspr4 \
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rtk
|
|
3
|
+
description: "Token optimizer for AI coding agents. Wraps common CLI commands with rtk to reduce LLM token consumption by 60-90%."
|
|
4
|
+
compatibility: "OpenCode with RTK binary installed"
|
|
5
|
+
metadata:
|
|
6
|
+
author: Sisyphus
|
|
7
|
+
version: "1.0.0"
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# RTK — Rust Token Killer
|
|
11
|
+
|
|
12
|
+
**Purpose**: Reduce token consumption by 60-90% on shell commands. RTK filters and compresses command output before it reaches your context.
|
|
13
|
+
|
|
14
|
+
## When This Skill Activates
|
|
15
|
+
|
|
16
|
+
This skill is loaded automatically. Once active, **always** prefix supported commands with `rtk`.
|
|
17
|
+
|
|
18
|
+
## Command Mappings (MANDATORY)
|
|
19
|
+
|
|
20
|
+
When running shell commands via Bash tool, use `rtk` prefix for these commands:
|
|
21
|
+
|
|
22
|
+
| Instead of | Use | Savings |
|
|
23
|
+
|-----------|-----|---------|
|
|
24
|
+
| `git status` | `rtk git status` | ~80% |
|
|
25
|
+
| `git diff` | `rtk git diff` | ~75% |
|
|
26
|
+
| `git log` | `rtk git log` | ~80% |
|
|
27
|
+
| `git add/commit/push/pull` | `rtk git add/commit/push/pull` | ~90% |
|
|
28
|
+
| `ls` / `ls -la` | `rtk ls` | ~80% |
|
|
29
|
+
| `cat file` | `rtk read file` | ~70% |
|
|
30
|
+
| `grep pattern .` | `rtk grep pattern .` | ~80% |
|
|
31
|
+
| `rg pattern` | `rtk grep pattern .` | ~80% |
|
|
32
|
+
| `npm test` / `cargo test` / `pytest` | `rtk test <cmd>` | ~90% |
|
|
33
|
+
| `npm run build` / `cargo build` | `rtk err <cmd>` | ~80% |
|
|
34
|
+
| `gh pr list/view` | `rtk gh pr list/view` | ~70% |
|
|
35
|
+
| `docker ps` | `rtk docker ps` | ~80% |
|
|
36
|
+
| `eslint` / `tsc` | `rtk lint` / `rtk tsc` | ~80% |
|
|
37
|
+
|
|
38
|
+
## Searching Inside `node_modules` / Ignored Directories
|
|
39
|
+
|
|
40
|
+
By default, `rtk grep` respects `.gitignore` rules — meaning `node_modules`, `.nuxt`, `dist`, etc. are **excluded**. This is the right behavior 99% of the time.
|
|
41
|
+
|
|
42
|
+
When you **need** to search inside ignored directories (debugging a library, checking an API signature, tracing a dependency bug):
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
# Search all files including node_modules (--no-ignore bypasses .gitignore)
|
|
46
|
+
rtk grep "defineStore" . --no-ignore
|
|
47
|
+
|
|
48
|
+
# Search a specific package only (combine --no-ignore with --glob)
|
|
49
|
+
rtk grep "defineStore" . --no-ignore --glob 'node_modules/pinia/**'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**What does NOT work:**
|
|
53
|
+
- `rtk grep "pattern" node_modules/pinia/` — still excluded even with direct path
|
|
54
|
+
- `rtk grep "pattern" . --glob 'node_modules/**'` — glob alone doesn't override .gitignore
|
|
55
|
+
|
|
56
|
+
**Key flag: `--no-ignore`** — this is the ONLY way to search ignored directories with rtk grep.
|
|
57
|
+
|
|
58
|
+
### Other useful `rtk grep` flags
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
rtk grep "pattern" . -t ts # Filter by file type (ts, py, rust, etc.)
|
|
62
|
+
rtk grep "pattern" . -m 100 # Increase max results (default: 50)
|
|
63
|
+
rtk grep "pattern" . -u # Ultra-compact mode (even fewer tokens)
|
|
64
|
+
rtk grep "pattern" . -l 120 # Max line length before truncation (default: 80)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Commands to NOT Wrap
|
|
68
|
+
|
|
69
|
+
Do NOT prefix these with `rtk` (unsupported or counterproductive):
|
|
70
|
+
|
|
71
|
+
- `npx`, `npm install`, `pip install` (package managers)
|
|
72
|
+
- `node`, `python3`, `ruby` (interpreters)
|
|
73
|
+
- `nano-brain`, `openspec`, `opencode` (custom tools)
|
|
74
|
+
- Heredocs (`<<EOF`)
|
|
75
|
+
- Piped commands (`cmd1 | cmd2`) — wrap only the first command if applicable
|
|
76
|
+
- Commands already prefixed with `rtk`
|
|
77
|
+
|
|
78
|
+
## How RTK Works
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Without RTK: git status → 50 lines raw output → 2,000 tokens
|
|
82
|
+
With RTK: rtk git status → "3 modified, 1 untracked ✓" → 200 tokens
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
RTK runs the real command, then filters/compresses the output. The agent sees a compact summary instead of verbose raw output.
|
|
86
|
+
|
|
87
|
+
## Detection
|
|
88
|
+
|
|
89
|
+
Before using RTK commands, verify it's installed:
|
|
90
|
+
```bash
|
|
91
|
+
rtk --version
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
If `rtk` is not found, skip this skill — run commands normally without the `rtk` prefix.
|
|
95
|
+
|
|
96
|
+
## Token Savings Reference
|
|
97
|
+
|
|
98
|
+
Typical 30-min coding session:
|
|
99
|
+
- Without RTK: ~150,000 tokens
|
|
100
|
+
- With RTK: ~45,000 tokens
|
|
101
|
+
- **Savings: ~70%**
|
|
102
|
+
|
|
103
|
+
Biggest wins: test output (`rtk test` — 90%), git operations (`rtk git` — 80%), file reading (`rtk read` — 70%).
|