@kokorolx/ai-sandbox-wrapper 2.6.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 CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  Protect your SSH keys, API tokens, and system files while using AI tools that need filesystem access.
6
6
 
7
- *Last updated: February 9, 2026*
7
+ *Last updated: February 25, 2026*
8
8
 
9
9
  ---
10
10
 
@@ -33,21 +33,24 @@ Protect your SSH keys, API tokens, and system files while using AI tools that ne
33
33
 
34
34
  ## ✨ What's New
35
35
 
36
- ### v2.3.0-beta: Web Mode & Port Exposure
36
+ ### v2.7.0: Git Fetch-Only Mode & Bundled Skills
37
37
 
38
- - **Web Auto-Detection**: `opencode web` automatically exposes port 4096 and injects `--hostname 0.0.0.0`
39
- - **`--expose` Flag**: New way to expose ports (replaces deprecated `PORT` env var)
40
- - **Port Conflict Detection**: Fails fast if port is already in use
38
+ - **Git Fetch-Only**: Allow git fetch/pull but block push perfect for AI agents that should read but not write
39
+ - **Bundled Skills**: RTK token optimizer skills auto-installed for OpenCode users
40
+ - **SSH Config Fix**: Resolved crash during git credential setup
41
41
 
42
42
  ```bash
43
- # Web mode - automatic port exposure
44
- opencode web
43
+ # Fetch-only mode (no push allowed)
44
+ opencode --git-fetch
45
45
 
46
- # Custom port
47
- opencode web --port 8080
46
+ # Or select from interactive menu:
47
+ # 4) Fetch only - allow once (no push, this session)
48
+ # 5) Fetch only - always for this workspace (no push)
48
49
 
49
- # Expose additional ports
50
- opencode --expose 3000,5555 web
50
+ # Manage via CLI
51
+ npx @kokorolx/ai-sandbox-wrapper git fetch-only ~/projects/myrepo
52
+ npx @kokorolx/ai-sandbox-wrapper git full ~/projects/myrepo
53
+ npx @kokorolx/ai-sandbox-wrapper git status
51
54
  ```
52
55
 
53
56
  ---
@@ -172,8 +175,21 @@ Git credentials are **not** shared by default. When you run a tool, you'll be pr
172
175
  ```
173
176
  🔐 Git Access Control
174
177
  1) Yes, allow once
175
- 2) Yes, always allow for this workspace
178
+ 2) Yes, always allow for this workspace
176
179
  3) No, keep Git disabled (secure default)
180
+ 4) Fetch only - allow once (no push, this session)
181
+ 5) Fetch only - always for this workspace (no push)
182
+ ```
183
+
184
+ **Fetch-only mode** allows `git fetch`, `git pull`, `git clone` but blocks `git push`. Uses git's `pushInsteadOf` config — no network restrictions needed.
185
+
186
+ ```bash
187
+ # Force fetch-only via flag
188
+ opencode --git-fetch
189
+
190
+ # Manage via CLI
191
+ npx @kokorolx/ai-sandbox-wrapper git fetch-only ~/projects/myrepo
192
+ npx @kokorolx/ai-sandbox-wrapper git full ~/projects/myrepo
177
193
  ```
178
194
 
179
195
  ### Clipboard
@@ -233,6 +249,17 @@ After installation, configure your MCP client (e.g., OpenCode) to use them:
233
249
 
234
250
  > **Note:** The `--no-sandbox` flags are required when running in Docker containers. This is safe because the container itself provides isolation.
235
251
 
252
+ ### Bundled Skills (OpenCode)
253
+
254
+ OpenCode containers auto-install these skills on first run (existing skills are never overwritten):
255
+
256
+ | Skill | Description |
257
+ |-------|-------------|
258
+ | `rtk` | Command reference for RTK token optimizer (60-90% token savings) |
259
+ | `rtk-setup` | Persistent RTK enforcement — updates AGENTS.md and propagates to subagents |
260
+
261
+ Skills are copied to `~/.config/opencode/skills/` and available immediately.
262
+
236
263
  ---
237
264
 
238
265
  ## 📁 Directory Structure
@@ -290,6 +317,9 @@ opencode -e 3000,4000 # Multiple ports
290
317
  # Network
291
318
  opencode -n mynetwork # Join Docker network
292
319
 
320
+ # Git fetch-only
321
+ opencode --git-fetch # Fetch only (no push)
322
+
293
323
  # Management
294
324
  npx @kokorolx/ai-sandbox-wrapper workspace list
295
325
  npx @kokorolx/ai-sandbox-wrapper clean
package/bin/ai-run CHANGED
@@ -15,6 +15,7 @@ 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
19
20
  --help-env Show environment variables reference
20
21
 
@@ -39,6 +40,7 @@ Examples:
39
40
  ai-run opencode web -p secret # Run with password
40
41
  ai-run opencode --shell # Start shell, run tool manually
41
42
  ai-run aider -n mynetwork # Connect to Docker network
43
+ ai-run opencode --git-fetch # Git fetch only (no push)
42
44
 
43
45
  Documentation: https://github.com/kokorolx/ai-sandbox-wrapper
44
46
  EOF
@@ -135,6 +137,7 @@ EXPOSE_ARG=""
135
137
  SERVER_PASSWORD=""
136
138
  PASSWORD_ENV_VAR=""
137
139
  ALLOW_UNSECURED=false
140
+ GIT_FETCH_ONLY_FLAG=false
138
141
  TOOL_ARGS=()
139
142
 
140
143
  while [[ $# -gt 0 ]]; do
@@ -177,6 +180,10 @@ while [[ $# -gt 0 ]]; do
177
180
  ALLOW_UNSECURED=true
178
181
  shift
179
182
  ;;
183
+ --git-fetch)
184
+ GIT_FETCH_ONLY_FLAG=true
185
+ shift
186
+ ;;
180
187
  *)
181
188
  TOOL_ARGS+=("$1")
182
189
  shift
@@ -411,7 +418,7 @@ upgrade_config_to_v2() {
411
418
  fi
412
419
 
413
420
  # Add missing v2 fields while preserving existing networks
414
- jq '.version = 2 | .workspaces = (.workspaces // []) | .git = (.git // {"allowedWorkspaces":[],"keySelections":{}})' \
421
+ jq '.version = 2 | .workspaces = (.workspaces // []) | .git = (.git // {"allowedWorkspaces":[],"keySelections":{}}) | .git.fetchOnlyWorkspaces = (.git.fetchOnlyWorkspaces // [])' \
415
422
  "$AI_SANDBOX_CONFIG" > "$AI_SANDBOX_CONFIG.tmp" \
416
423
  && mv "$AI_SANDBOX_CONFIG.tmp" "$AI_SANDBOX_CONFIG"
417
424
  chmod 600 "$AI_SANDBOX_CONFIG"
@@ -426,7 +433,7 @@ init_config() {
426
433
 
427
434
  if [[ ! -f "$AI_SANDBOX_CONFIG" ]]; then
428
435
  # Create new v2 config
429
- 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":{}}}'
430
437
  echo "$initial_config" > "$AI_SANDBOX_CONFIG"
431
438
  chmod 600 "$AI_SANDBOX_CONFIG"
432
439
 
@@ -583,9 +590,27 @@ case "$TOOL" in
583
590
  opencode)
584
591
  mount_tool_config "$HOME/.config/opencode" ".config/opencode"
585
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"
586
610
  ;;
587
611
  claude)
588
612
  mount_tool_config "$HOME/.claude" ".claude"
613
+ mount_tool_config "$HOME/.ccs" ".ccs"
589
614
  ;;
590
615
  droid)
591
616
  mount_tool_config "$HOME/.config/droid" ".config/droid"
@@ -1047,8 +1072,48 @@ is_git_allowed() {
1047
1072
  return 1
1048
1073
  }
1049
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
+
1050
1115
  # Check if Git access is allowed for this workspace
1051
- 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
1052
1117
  # Previously allowed for this workspace
1053
1118
  # Check if saved keys exist for this workspace
1054
1119
  WORKSPACE_MD5=$(echo "$CURRENT_DIR" | md5sum | cut -c1-8)
@@ -1090,7 +1155,7 @@ if is_git_allowed "$CURRENT_DIR"; then
1090
1155
  if [ -x "$SETUP_SSH" ]; then
1091
1156
  # Join SAVED_KEYS into a comma-separated string for --keys
1092
1157
  KEYS_ARG=$(IFS=,; echo "${SAVED_KEYS[*]}")
1093
- output=$("$SETUP_SSH" --keys "$KEYS_ARG" 2>&1)
1158
+ output=$( "$SETUP_SSH" --keys "$KEYS_ARG" 2>&1 ) || true
1094
1159
  TEMP_CONFIG=$(echo "$output" | grep "Config:" | tail -1 | awk '{print $NF}')
1095
1160
  if [ -f "$TEMP_CONFIG" ]; then
1096
1161
  cp "$TEMP_CONFIG" "$GIT_CACHE_DIR/ssh/config" 2>/dev/null || true
@@ -1123,6 +1188,11 @@ if is_git_allowed "$CURRENT_DIR"; then
1123
1188
  # Copy gitconfig to HOME_DIR (can't mount file inside mounted directory)
1124
1189
  cp "$HOME/.gitconfig" "$HOME_DIR/.gitconfig" 2>/dev/null || true
1125
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
1126
1196
  else
1127
1197
  # Ask user if they want Git access for this workspace (only in interactive mode)
1128
1198
  if [[ -t 0 ]] && ([ -d "$HOME/.ssh" ] || [ -f "$HOME/.gitconfig" ]); then
@@ -1135,11 +1205,13 @@ else
1135
1205
  echo " 1) Yes, allow once (this session only)"
1136
1206
  echo " 2) Yes, always allow for this workspace"
1137
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)"
1138
1210
  echo ""
1139
- read -p "Choice [1-3]: " git_choice
1211
+ read -p "Choice [1-5]: " git_choice
1140
1212
 
1141
1213
  case "$git_choice" in
1142
- 1|2)
1214
+ 1|2|4|5)
1143
1215
  # Interactive SSH key selection
1144
1216
  echo ""
1145
1217
  echo "🔑 SSH Key Selection"
@@ -1195,7 +1267,7 @@ else
1195
1267
  # Join SELECTED_SSH_KEYS into a comma-separated string for --keys
1196
1268
  KEYS_ARG=$(IFS=,; echo "${SELECTED_SSH_KEYS[*]}")
1197
1269
  # Run it and capture the filtered config path
1198
- output=$("$SETUP_SSH" --keys "$KEYS_ARG" 2>&1)
1270
+ output=$( "$SETUP_SSH" --keys "$KEYS_ARG" 2>&1 ) || true
1199
1271
  TEMP_CONFIG=$(echo "$output" | grep "Config:" | tail -1 | awk '{print $NF}')
1200
1272
  if [ -f "$TEMP_CONFIG" ]; then
1201
1273
  cp "$TEMP_CONFIG" "$GIT_CACHE_DIR/ssh/config" 2>/dev/null || true
@@ -1231,6 +1303,9 @@ else
1231
1303
  cp "$HOME/.gitconfig" "$HOME_DIR/.gitconfig" 2>/dev/null || true
1232
1304
  fi
1233
1305
 
1306
+ if [[ "$git_choice" == "4" || "$git_choice" == "5" ]]; then
1307
+ GIT_FETCH_ONLY_MODE=true
1308
+ fi
1234
1309
  if [ "$git_choice" = "2" ]; then
1235
1310
  # Save workspace and selected keys for future sessions
1236
1311
  echo "$CURRENT_DIR" >> "$GIT_ALLOWED_FILE"
@@ -1245,6 +1320,20 @@ else
1245
1320
  mkdir -p "$GIT_SHARED_DIR/keys"
1246
1321
  printf "%s\n" "${SELECTED_SSH_KEYS[@]}" > "$GIT_SHARED_DIR/keys/$WORKSPACE_MD5"
1247
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"
1248
1337
  else
1249
1338
  echo "✅ Git access enabled for this session"
1250
1339
  fi
@@ -1260,6 +1349,11 @@ else
1260
1349
  echo "🔒 Git access disabled (secure mode)"
1261
1350
  ;;
1262
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
1263
1357
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
1264
1358
  echo ""
1265
1359
  fi
@@ -1979,6 +2073,69 @@ fi
1979
2073
  # Detect display configuration (clipboard integration)
1980
2074
  DISPLAY_FLAGS=$(detect_display_config)
1981
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
+
1982
2139
  docker run $CONTAINER_NAME --rm $TTY_FLAGS \
1983
2140
  --init \
1984
2141
  --platform "$PLATFORM" \
package/bin/cli.js CHANGED
@@ -33,6 +33,8 @@ Commands:
33
33
  git status Show git-enabled workspaces
34
34
  git enable <path> Enable git access for a workspace
35
35
  git disable <path> Disable git access for a workspace
36
+ git fetch-only <path> Enable fetch-only git access (no push) for a workspace
37
+ git full <path> Enable full git access for a workspace (moves from fetch-only if needed)
36
38
 
37
39
  network list List configured networks
38
40
  network add <name> [--global|--workspace <path>] Add a network
@@ -462,17 +464,25 @@ function runWorkspaceRemove(inputPath) {
462
464
  // GIT COMMANDS
463
465
  // ============================================================================
464
466
  function runGitStatus() {
465
- const config = readConfig();
466
- const allowed = config.git?.allowedWorkspaces || [];
467
-
468
- console.log('\n🔐 Git-Enabled Workspaces:\n');
469
- if (allowed.length === 0) {
470
- console.log(' (no workspaces with git access)');
471
- console.log('\n Enable git: npx @kokorolx/ai-sandbox-wrapper git enable <workspace-path>');
467
+ const config = readConfig()
468
+ const allowed = config.git?.allowedWorkspaces || []
469
+ const fetchOnly = config.git?.fetchOnlyWorkspaces || []
470
+
471
+ console.log('\n🔐 Git-Enabled Workspaces:\n')
472
+ if (allowed.length === 0 && fetchOnly.length === 0) {
473
+ console.log(' (no workspaces with git access)')
474
+ console.log('\n Enable git: npx @kokorolx/ai-sandbox-wrapper git enable <workspace-path>')
472
475
  } else {
473
- allowed.forEach((ws, i) => console.log(` ${i + 1}. ${ws}`));
476
+ if (allowed.length > 0) {
477
+ console.log(' Full access:')
478
+ allowed.forEach((ws, i) => console.log(` ${i + 1}. ${ws}`))
479
+ }
480
+ if (fetchOnly.length > 0) {
481
+ console.log(' Fetch only (no push):')
482
+ fetchOnly.forEach((ws, i) => console.log(` ${i + 1}. ${ws}`))
483
+ }
474
484
  }
475
- console.log('');
485
+ console.log('')
476
486
  }
477
487
 
478
488
  function runGitEnable(inputPath) {
@@ -541,6 +551,66 @@ function runGitDisable(inputPath) {
541
551
  console.log(`✅ Disabled git access for: ${expandedPath}`);
542
552
  }
543
553
 
554
+ function runGitFetchOnly(inputPath) {
555
+ if (!inputPath) {
556
+ console.error('❌ Please provide a workspace path')
557
+ console.error('Usage: npx @kokorolx/ai-sandbox-wrapper git fetch-only <path>')
558
+ process.exit(1)
559
+ }
560
+
561
+ const expandedPath = expandHome(inputPath)
562
+ const config = readConfig()
563
+
564
+ if (!config.git) config.git = { allowedWorkspaces: [], fetchOnlyWorkspaces: [], keySelections: {} }
565
+ if (!config.git.fetchOnlyWorkspaces) config.git.fetchOnlyWorkspaces = []
566
+
567
+ // Remove from full access if present
568
+ if (config.git.allowedWorkspaces) {
569
+ const fullIdx = config.git.allowedWorkspaces.indexOf(expandedPath)
570
+ if (fullIdx !== -1) config.git.allowedWorkspaces.splice(fullIdx, 1)
571
+ }
572
+
573
+ if (config.git.fetchOnlyWorkspaces.includes(expandedPath)) {
574
+ console.log(`ℹ️ Git fetch-only already enabled for: ${expandedPath}`)
575
+ return
576
+ }
577
+
578
+ config.git.fetchOnlyWorkspaces.push(expandedPath)
579
+ writeConfig(config)
580
+
581
+ console.log(`✅ Enabled git fetch-only for: ${expandedPath}`)
582
+ }
583
+
584
+ function runGitFull(inputPath) {
585
+ if (!inputPath) {
586
+ console.error('❌ Please provide a workspace path')
587
+ console.error('Usage: npx @kokorolx/ai-sandbox-wrapper git full <path>')
588
+ process.exit(1)
589
+ }
590
+
591
+ const expandedPath = expandHome(inputPath)
592
+ const config = readConfig()
593
+
594
+ if (!config.git) config.git = { allowedWorkspaces: [], fetchOnlyWorkspaces: [], keySelections: {} }
595
+ if (!config.git.allowedWorkspaces) config.git.allowedWorkspaces = []
596
+
597
+ // Remove from fetch-only if present
598
+ if (config.git.fetchOnlyWorkspaces) {
599
+ const foIdx = config.git.fetchOnlyWorkspaces.indexOf(expandedPath)
600
+ if (foIdx !== -1) config.git.fetchOnlyWorkspaces.splice(foIdx, 1)
601
+ }
602
+
603
+ if (config.git.allowedWorkspaces.includes(expandedPath)) {
604
+ console.log(`ℹ️ Git full access already enabled for: ${expandedPath}`)
605
+ return
606
+ }
607
+
608
+ config.git.allowedWorkspaces.push(expandedPath)
609
+ writeConfig(config)
610
+
611
+ console.log(`✅ Enabled git full access for: ${expandedPath}`)
612
+ }
613
+
544
614
  // ============================================================================
545
615
  // NETWORK COMMANDS
546
616
  // ============================================================================
@@ -1259,11 +1329,17 @@ switch (command) {
1259
1329
  runGitEnable(subArg);
1260
1330
  break;
1261
1331
  case 'disable':
1262
- runGitDisable(subArg);
1263
- break;
1332
+ runGitDisable(subArg)
1333
+ break
1334
+ case 'fetch-only':
1335
+ runGitFetchOnly(subArg)
1336
+ break
1337
+ case 'full':
1338
+ runGitFull(subArg)
1339
+ break
1264
1340
  default:
1265
- console.error('Usage: npx @kokorolx/ai-sandbox-wrapper git <status|enable|disable> [path]');
1266
- process.exit(1);
1341
+ console.error('Usage: npx @kokorolx/ai-sandbox-wrapper git <status|enable|disable|fetch-only|full> [path]')
1342
+ process.exit(1)
1267
1343
  }
1268
1344
  break;
1269
1345
  case 'network':
@@ -1,3 +1,7 @@
1
+ # Build RTK from source (multi-stage: only binary is kept, Rust toolchain discarded)
2
+ FROM rust:bookworm AS rtk-builder
3
+ RUN cargo install --git https://github.com/rtk-ai/rtk --locked
4
+
1
5
  FROM node:22-bookworm-slim
2
6
 
3
7
  ARG AGENT_UID=1001
@@ -34,31 +38,8 @@ RUN mkdir -p /usr/local/lib/openspec && \
34
38
  ln -sf /usr/local/lib/openspec/node_modules/.bin/openspec /usr/local/bin/openspec && \
35
39
  chmod -R 755 /usr/local/lib/openspec && \
36
40
  chmod +x /usr/local/bin/openspec
37
- # Install Playwright system dependencies
38
- RUN apt-get update && apt-get install -y --no-install-recommends \
39
- libglib2.0-0 \
40
- libnspr4 \
41
- libnss3 \
42
- libdbus-1-3 \
43
- libatk1.0-0 \
44
- libatk-bridge2.0-0 \
45
- libcups2 \
46
- libxcb1 \
47
- libxkbcommon0 \
48
- libatspi2.0-0 \
49
- libx11-6 \
50
- libxcomposite1 \
51
- libxdamage1 \
52
- libxext6 \
53
- libxfixes3 \
54
- libxrandr2 \
55
- libgbm1 \
56
- libcairo2 \
57
- libpango-1.0-0 \
58
- libasound2 \
59
- && rm -rf /var/lib/apt/lists/*
60
- # Install Playwright and browsers via npm
61
- RUN npm install -g playwright && npx playwright install
41
+ # Install RTK - token optimizer for AI coding agents (built from source)
42
+ COPY --from=rtk-builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk
62
43
  RUN apt-get update && apt-get install -y --no-install-recommends \
63
44
  libglib2.0-0 \
64
45
  libnspr4 \
@@ -96,9 +77,6 @@ RUN mkdir -p /opt/playwright-browsers && \
96
77
  npx playwright-core install-deps chromium && \
97
78
  chmod -R 777 /opt/playwright-browsers && \
98
79
  ln -sf $(ls -d /opt/playwright-browsers/chromium-*/chrome-linux/chrome | head -1) /opt/chromium
99
- ENV CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=1
100
- RUN npm install -g chrome-devtools-mcp@latest && \
101
- touch /opt/.mcp-chrome-devtools-installed
102
80
  RUN touch /opt/.mcp-playwright-installed
103
81
 
104
82
  # Create workspace
@@ -5,6 +5,7 @@ set -e
5
5
  mkdir -p "dockerfiles/base"
6
6
 
7
7
  ADDITIONAL_TOOLS_INSTALL=""
8
+ DOCKERFILE_BUILD_STAGES=""
8
9
 
9
10
  if [[ "${INSTALL_SPEC_KIT:-0}" -eq 1 ]]; then
10
11
  echo "📦 spec-kit will be installed in base image"
@@ -39,6 +40,17 @@ if [[ "${INSTALL_OPENSPEC:-0}" -eq 1 ]]; then
39
40
  '
40
41
  fi
41
42
 
43
+ if [[ "${INSTALL_RTK:-0}" -eq 1 ]]; then
44
+ echo "📦 RTK (Rust Token Killer) will be installed in base image (multi-stage build)"
45
+ DOCKERFILE_BUILD_STAGES+='# Build RTK from source (multi-stage: only binary is kept, Rust toolchain discarded)
46
+ FROM rust:bookworm AS rtk-builder
47
+ RUN cargo install --git https://github.com/rtk-ai/rtk --locked
48
+ '
49
+ ADDITIONAL_TOOLS_INSTALL+='# Install RTK - token optimizer for AI coding agents (built from source)
50
+ COPY --from=rtk-builder /usr/local/cargo/bin/rtk /usr/local/bin/rtk
51
+ '
52
+ fi
53
+
42
54
  if [[ "${INSTALL_PLAYWRIGHT:-0}" -eq 1 ]]; then
43
55
  echo "📦 Playwright will be installed in base image"
44
56
  ADDITIONAL_TOOLS_INSTALL+='# Install Playwright system dependencies
@@ -166,6 +178,7 @@ if [[ "${INSTALL_PLAYWRIGHT_MCP:-0}" -eq 1 ]]; then
166
178
  fi
167
179
 
168
180
  cat > "dockerfiles/base/Dockerfile" <<EOF
181
+ ${DOCKERFILE_BUILD_STAGES}
169
182
  FROM node:22-bookworm-slim
170
183
 
171
184
  ARG AGENT_UID=1001
@@ -228,4 +241,3 @@ docker build ${DOCKER_NO_CACHE:+--no-cache} \
228
241
  --build-arg AGENT_UID="${HOST_UID}" \
229
242
  -t "ai-base:latest" "dockerfiles/base"
230
243
  echo "✅ Base image built (ai-base:latest)"
231
-
@@ -16,6 +16,16 @@ cat <<'EOF' > "dockerfiles/$TOOL/Dockerfile"
16
16
  FROM ai-base:latest
17
17
 
18
18
  USER root
19
+ # Install tmux for Agent Teams split-pane mode
20
+ RUN apt-get update && apt-get install -y --no-install-recommends tmux && rm -rf /var/lib/apt/lists/*
21
+
22
+ # Install CCS (Claude Code Switch) for multi-provider model switching
23
+ # Use --ignore-scripts to avoid postinstall failures when HOME=/home/agent but running as root
24
+ RUN npm install -g @kaitranntt/ccs --ignore-scripts && \
25
+ mkdir -p /home/agent/.ccs && \
26
+ chown -R agent:agent /home/agent/.ccs && \
27
+ which ccs && ccs --version
28
+
19
29
  # Install Claude Code using official native installer
20
30
  RUN curl -fsSL https://claude.ai/install.sh | bash && \
21
31
  mkdir -p /usr/local/share && \
@@ -33,10 +43,15 @@ docker build ${DOCKER_NO_CACHE:+--no-cache} -t "ai-$TOOL:latest" "dockerfiles/$T
33
43
  echo "✅ $TOOL installed (Native Binary)"
34
44
  echo ""
35
45
  echo "Features:"
36
- echo " ✓ Official native binary (no Node.js)"
46
+ echo " ✓ Official native binary"
37
47
  echo " ✓ Claude 3.5 Sonnet/Opus models"
38
48
  echo " ✓ Agentic coding with file editing"
39
49
  echo " ✓ Web search and fetch built-in"
50
+ echo " ✓ Agent Teams (multi-agent tmux split-pane workflows)"
51
+ echo " ✓ CCS (Claude Code Switch) for multi-provider model switching"
40
52
  echo ""
41
53
  echo "Usage: ai-run claude"
42
- echo "Auth: Set ANTHROPIC_API_KEY environment variable"
54
+ echo "Auth: Set ANTHROPIC_API_KEY in ~/.ai-sandbox/env"
55
+ echo ""
56
+ echo "Agent Teams: Add CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 to ~/.ai-sandbox/env"
57
+ echo "CCS: Run 'ai-run claude --shell' then 'ccs help' to configure providers"
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ # OpenClaw installer: Uses official Docker Compose setup
5
+ TOOL="openclaw"
6
+
7
+ echo "🔄 Installing $TOOL (OpenClaw - Official Docker Compose)..."
8
+
9
+ # Create directories
10
+ OPENCLAW_REPO_DIR="$HOME/.ai-sandbox/tools/$TOOL/repo"
11
+ mkdir -p "$OPENCLAW_REPO_DIR"
12
+ mkdir -p "$HOME/.openclaw"
13
+
14
+ # Clone OpenClaw repository if not exists
15
+ if [[ ! -d "$OPENCLAW_REPO_DIR/.git" ]]; then
16
+ echo "📦 Cloning OpenClaw repository..."
17
+ git clone https://github.com/openclaw/openclaw.git "$OPENCLAW_REPO_DIR"
18
+ else
19
+ echo "📦 OpenClaw repository already exists, pulling latest..."
20
+ cd "$OPENCLAW_REPO_DIR"
21
+ git pull origin main || git pull origin master || true
22
+ fi
23
+
24
+ cd "$OPENCLAW_REPO_DIR"
25
+
26
+ # Build OpenClaw Docker image using their docker-compose
27
+ echo "🔨 Building OpenClaw Docker image..."
28
+ docker compose build
29
+
30
+ echo "✅ $TOOL installed (Docker Compose)"
31
+ echo ""
32
+ echo "Features:"
33
+ echo " ✓ Official OpenClaw Docker setup"
34
+ echo " ✓ Gateway mode (port 18789)"
35
+ echo " ✓ Bridge mode (port 18790)"
36
+ echo " ✓ Workspace whitelist integration"
37
+ echo ""
38
+ echo "Usage: ai-run openclaw"
39
+ echo "Config: ~/.openclaw/"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kokorolx/ai-sandbox-wrapper",
3
- "version": "2.6.0",
3
+ "version": "2.7.0",
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",
@@ -27,6 +27,7 @@
27
27
  "files": [
28
28
  "bin/",
29
29
  "lib/",
30
+ "skills/",
30
31
  "dockerfiles/",
31
32
  "setup.sh",
32
33
  "README.md",
package/setup.sh CHANGED
@@ -274,8 +274,8 @@ echo "📁 Legacy workspaces file: $WORKSPACES_FILE"
274
274
  WORKSPACE="${WORKSPACES[0]}"
275
275
 
276
276
  # Tool definitions
277
- TOOL_OPTIONS="amp,opencode,droid,claude,gemini,kilo,qwen,codex,qoder,auggie,codebuddy,jules,shai"
278
- 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"
277
+ TOOL_OPTIONS="amp,opencode,openclaw,droid,claude,gemini,kilo,qwen,codex,qoder,auggie,codebuddy,jules,shai"
278
+ 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"
279
279
 
280
280
  # Interactive multi-select
281
281
  multi_select "Select AI Tools to Install" "$TOOL_OPTIONS" "$TOOL_DESCS"
@@ -290,7 +290,7 @@ echo "Installing tools: ${TOOLS[*]}"
290
290
 
291
291
  CONTAINERIZED_TOOLS=()
292
292
  for tool in "${TOOLS[@]}"; do
293
- if [[ "$tool" =~ ^(amp|opencode|claude|aider|droid|gemini|kilo|qwen|codex|qoder|auggie|codebuddy|jules|shai)$ ]]; then
293
+ if [[ "$tool" =~ ^(amp|opencode|openclaw|claude|aider|droid|gemini|kilo|qwen|codex|qoder|auggie|codebuddy|jules|shai)$ ]]; then
294
294
  CONTAINERIZED_TOOLS+=("$tool")
295
295
  fi
296
296
  done
@@ -298,8 +298,8 @@ done
298
298
  echo ""
299
299
  if [[ ${#CONTAINERIZED_TOOLS[@]} -gt 0 ]]; then
300
300
  # Category 1: AI Enhancement Tools (spec-driven development, UI/UX, browser automation)
301
- AI_TOOL_OPTIONS="spec-kit,ux-ui-promax,openspec,playwright"
302
- AI_TOOL_DESCS="Spec-driven development toolkit,UI/UX design intelligence tool,OpenSpec - spec-driven development,Browser automation + Chromium/Firefox/WebKit (~500MB)"
301
+ AI_TOOL_OPTIONS="spec-kit,ux-ui-promax,openspec,playwright,rtk"
302
+ AI_TOOL_DESCS="Spec-driven development toolkit,UI/UX design intelligence tool,OpenSpec - spec-driven development,Browser automation + Chromium/Firefox/WebKit (~500MB),RTK token optimizer - reduces LLM token usage by 60-90% (~5MB)"
303
303
 
304
304
  multi_select "Select AI Enhancement Tools (installed in containers)" "$AI_TOOL_OPTIONS" "$AI_TOOL_DESCS"
305
305
  AI_ENHANCEMENT_TOOLS=("${SELECTED_ITEMS[@]}")
@@ -376,6 +376,7 @@ if [[ $NEEDS_BASE_IMAGE -eq 1 ]]; then
376
376
  INSTALL_RUBY=0
377
377
  INSTALL_CHROME_DEVTOOLS_MCP=0
378
378
  INSTALL_PLAYWRIGHT_MCP=0
379
+ INSTALL_RTK=0
379
380
 
380
381
  for addon in "${ADDITIONAL_TOOLS[@]}"; do
381
382
  case "$addon" in
@@ -400,10 +401,13 @@ if [[ $NEEDS_BASE_IMAGE -eq 1 ]]; then
400
401
  playwright-mcp)
401
402
  INSTALL_PLAYWRIGHT_MCP=1
402
403
  ;;
404
+ rtk)
405
+ INSTALL_RTK=1
406
+ ;;
403
407
  esac
404
408
  done
405
409
 
406
- export INSTALL_SPEC_KIT INSTALL_UX_UI_PROMAX INSTALL_OPENSPEC INSTALL_PLAYWRIGHT INSTALL_RUBY INSTALL_CHROME_DEVTOOLS_MCP INSTALL_PLAYWRIGHT_MCP
410
+ export INSTALL_SPEC_KIT INSTALL_UX_UI_PROMAX INSTALL_OPENSPEC INSTALL_PLAYWRIGHT INSTALL_RUBY INSTALL_CHROME_DEVTOOLS_MCP INSTALL_PLAYWRIGHT_MCP INSTALL_RTK
407
411
  bash "$SCRIPT_DIR/lib/install-base.sh"
408
412
 
409
413
  # Save MCP selections to ~/.ai-sandbox/config.json for ai-run auto-configuration
@@ -431,6 +435,9 @@ for tool in "${TOOLS[@]}"; do
431
435
  opencode)
432
436
  bash "$SCRIPT_DIR/lib/install-opencode.sh"
433
437
  ;;
438
+ openclaw)
439
+ bash "$SCRIPT_DIR/lib/install-openclaw.sh"
440
+ ;;
434
441
  droid)
435
442
  bash "$SCRIPT_DIR/lib/install-droid.sh"
436
443
  ;;
@@ -525,6 +532,9 @@ if [[ ${#ADDITIONAL_TOOLS[@]} -gt 0 ]]; then
525
532
  playwright-mcp)
526
533
  echo " @playwright/mcp - Microsoft Playwright MCP server"
527
534
  ;;
535
+ rtk)
536
+ echo " rtk - Token optimizer for AI coding agents (60-90% savings)"
537
+ ;;
528
538
  esac
529
539
  done
530
540
  fi
@@ -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%).
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: rtk-setup
3
+ description: "One-time setup + ongoing enforcement of RTK (Rust Token Killer) across sessions and subagents. Persists RTK instructions to AGENTS.md and propagates to delegated tasks."
4
+ compatibility: "OpenCode with RTK binary installed"
5
+ metadata:
6
+ author: Sisyphus
7
+ version: "1.0.0"
8
+ ---
9
+
10
+ # RTK Setup — Persistent RTK Enforcement
11
+
12
+ This skill performs **setup** (persist RTK instructions) and **ongoing enforcement** (use rtk, propagate to subagents).
13
+
14
+ ## Phase 1: Detection (ALWAYS run first)
15
+
16
+ Run these checks before anything else:
17
+
18
+ ```bash
19
+ rtk --version
20
+ ```
21
+
22
+ - If `rtk` is NOT found → **STOP**. Tell the user: "RTK is not installed. Install it first: `cargo install --git https://github.com/rtk-ai/rtk --locked`". Do NOT proceed with setup.
23
+ - If `rtk` IS found → continue.
24
+
25
+ ### Check oh-my-opencode
26
+
27
+ ```bash
28
+ ls ~/.config/opencode/oh-my-opencode.json 2>/dev/null
29
+ ```
30
+
31
+ - If found → omo is installed. Note this for Phase 2.
32
+ - If not found → skip omo-specific steps.
33
+
34
+ ## Phase 2: Setup (persist to AGENTS.md)
35
+
36
+ Check BOTH of these files for the RTK managed block:
37
+
38
+ 1. **User-level**: `~/.config/opencode/AGENTS.md`
39
+ 2. **Project-level**: `./AGENTS.md` (current workspace)
40
+
41
+ For EACH file, check if `<!-- RTK:START -->` exists. If it does NOT exist, **append** the following managed block to the file (create the file if it doesn't exist). NEVER replace existing content — only append.
42
+
43
+ ```markdown
44
+ <!-- RTK:START -->
45
+ <!-- Managed block - do not edit manually. Updated by: rtk-setup skill -->
46
+
47
+ ## RTK Token Optimizer
48
+
49
+ This project uses **RTK** (Rust Token Killer) to reduce LLM token consumption by 60-90%.
50
+
51
+ ### Rules for ALL agents (including subagents)
52
+
53
+ **ALWAYS** prefix these commands with `rtk`:
54
+
55
+ | Command | Use instead |
56
+ |---------|-------------|
57
+ | `git status/diff/log/add/commit/push/pull` | `rtk git ...` |
58
+ | `ls`, `ls -la` | `rtk ls` |
59
+ | `cat file` | `rtk read file` |
60
+ | `grep pattern .` / `rg pattern` | `rtk grep pattern .` |
61
+ | `npm test` / `cargo test` / `pytest` | `rtk test <cmd>` |
62
+ | `npm run build` / `cargo build` | `rtk err <cmd>` |
63
+ | `gh pr list/view` | `rtk gh pr list/view` |
64
+ | `docker ps` | `rtk docker ps` |
65
+ | `eslint` / `tsc` | `rtk lint` / `rtk tsc` |
66
+
67
+ **Do NOT** prefix: `npx`, `npm install`, `pip install`, `node`, `python3`, heredocs, piped commands.
68
+
69
+ <!-- RTK:END -->
70
+ ```
71
+
72
+ ### If oh-my-opencode is installed
73
+
74
+ After updating AGENTS.md files, check if the omo config at `~/.config/opencode/oh-my-opencode.json` has `prompt_append` fields on agents. If it does, inform the user:
75
+
76
+ > "omo is installed. For maximum coverage, you can add RTK instructions to agent `prompt_append` fields in `oh-my-opencode.json`. This ensures subagents always use rtk even when AGENTS.md is not loaded. Want me to do this?"
77
+
78
+ **Only modify `oh-my-opencode.json` if the user explicitly confirms.** If they confirm, append this line to the `prompt_append` of `sisyphus-junior` agent (the primary coder):
79
+
80
+ ```
81
+ \n\nRTK: ALWAYS prefix shell commands with `rtk` when available (git, ls, grep, test, build). Example: `rtk git status` not `git status`.
82
+ ```
83
+
84
+ Do NOT modify other agents' prompt_append unless asked.
85
+
86
+ ## Phase 3: Ongoing Enforcement (EVERY time this skill is loaded)
87
+
88
+ ### For the current agent
89
+
90
+ From this point forward in this session, **you MUST**:
91
+
92
+ 1. **Use `rtk` prefix** for all supported shell commands (see table above)
93
+ 2. **Verify rtk is available** before first use: `rtk --version`
94
+ 3. If rtk is not available in the current environment, fall back to normal commands silently
95
+
96
+ ### For subagent delegation
97
+
98
+ When delegating ANY task that involves shell commands (via `task()`, `delegate_task()`, or any delegation mechanism), **ALWAYS** add this to the `MUST DO` section of the delegation prompt:
99
+
100
+ ```
101
+ - Use `rtk` prefix for shell commands when available (git, ls, grep, test, build). Run `rtk --version` first to check. If not installed, use normal commands. Examples: `rtk git status`, `rtk ls`, `rtk grep pattern .`, `rtk test npm test`.
102
+ ```
103
+
104
+ This is NON-NEGOTIABLE. Subagents do not read AGENTS.md, so RTK instructions MUST be passed explicitly in every delegation prompt.
105
+
106
+ ### Quick reference for subagent prompts
107
+
108
+ Copy-paste this one-liner into MUST DO sections:
109
+
110
+ > Use `rtk` prefix for all shell commands (git, ls, grep, test, build, docker). Example: `rtk git status` not `git status`. If `rtk` is not found, use normal commands.
111
+
112
+ ## Summary
113
+
114
+ | Phase | When | What |
115
+ |-------|------|------|
116
+ | Detection | Always first | Check rtk installed, check omo |
117
+ | Setup | Once (idempotent) | Append RTK block to AGENTS.md (user + project) |
118
+ | Enforcement | Every session | Use rtk yourself, propagate to all subagents |
@@ -1,17 +0,0 @@
1
- FROM ai-base:latest
2
-
3
- # Switch to root only for installing bun globally (needed for the system)
4
- USER root
5
- RUN npm install -g bun
6
- USER agent
7
-
8
- # Install Amp into user directory
9
- RUN mkdir -p /home/agent/lib/amp && \
10
- cd /home/agent/lib/amp && \
11
- bun init -y && \
12
- bun add @sourcegraph/amp
13
-
14
- # Add the node_modules .bin to PATH
15
- ENV PATH="/home/agent/lib/amp/node_modules/.bin:${PATH}"
16
-
17
- ENTRYPOINT ["amp"]