@kokorolx/ai-sandbox-wrapper 3.4.3-beta.2 → 3.4.3

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
@@ -141,293 +141,6 @@ else
141
141
  TOOL=""
142
142
  fi
143
143
 
144
- # ────────────────────────────────────────────────────────────────
145
- # OPEN-DESIGN SERVICE-TYPE TOOL DISPATCHER
146
- # Unlike ephemeral CLI tools, open-design is a long-running daemon.
147
- # Routes init/start/stop/restart/status/logs subcommands and exits.
148
- # ────────────────────────────────────────────────────────────────
149
- if [[ "$TOOL" == "open-design" ]]; then
150
- OD_CONTAINER_NAME="ai-open-design"
151
- OD_IMAGE="ai-open-design:latest"
152
- OD_NETWORK="ai-sandbox"
153
- OD_VOLUME="ai-open-design-data"
154
- OD_ENV_FILE="$HOME/.ai-sandbox/env"
155
- OD_DEFAULT_URL="http://ai-open-design:7456"
156
-
157
- od_print_help() {
158
- cat <<HLP
159
- Usage: ai-run open-design <subcommand> [options]
160
-
161
- Subcommands:
162
- init [--force] One-time setup: generate API token, create network/volume
163
- start [--expose] [--port N] Boot daemon (detached). --expose publishes port to host
164
- stop Stop daemon (preserves container for restart)
165
- restart [start-flags...] Stop then start
166
- status Show daemon state, network, port, token, health
167
- logs [-f|--follow] Show daemon logs (-f to follow)
168
- --help, -h Show this help
169
-
170
- Examples:
171
- ai-run open-design init
172
- ai-run open-design start
173
- ai-run open-design start --expose # publishes 7456 to host
174
- ai-run open-design start --expose --port 17456
175
- ai-run open-design status
176
- HLP
177
- }
178
-
179
- od_env_has_token() {
180
- [[ -f "$OD_ENV_FILE" ]] && grep -q "^OD_API_TOKEN=" "$OD_ENV_FILE"
181
- }
182
-
183
- od_read_token() {
184
- [[ -f "$OD_ENV_FILE" ]] && grep "^OD_API_TOKEN=" "$OD_ENV_FILE" | head -1 | cut -d= -f2-
185
- }
186
-
187
- od_init() {
188
- local force=false
189
- if [[ "${1:-}" == "--force" ]]; then
190
- force=true
191
- fi
192
-
193
- mkdir -p "$(dirname "$OD_ENV_FILE")"
194
- touch "$OD_ENV_FILE"
195
- chmod 600 "$OD_ENV_FILE"
196
-
197
- if od_env_has_token && [[ "$force" != "true" ]]; then
198
- echo "ℹ️ OD_API_TOKEN already set in $OD_ENV_FILE — nothing to do"
199
- echo " Use 'ai-run open-design init --force' to regenerate (will invalidate running sessions)"
200
- else
201
- if [[ "$force" == "true" ]] && od_env_has_token; then
202
- printf "⚠️ This will replace the existing OD_API_TOKEN. Continue? [y/N] "
203
- read -r confirm
204
- if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
205
- echo "Aborted."
206
- exit 0
207
- fi
208
- # Strip existing OD_API_TOKEN line
209
- if [[ "$(uname)" == "Darwin" ]]; then
210
- sed -i '' '/^OD_API_TOKEN=/d' "$OD_ENV_FILE"
211
- else
212
- sed -i '/^OD_API_TOKEN=/d' "$OD_ENV_FILE"
213
- fi
214
- fi
215
- if ! command -v openssl >/dev/null 2>&1; then
216
- echo "❌ ERROR: 'openssl' is required to generate a token" >&2
217
- exit 1
218
- fi
219
- local token
220
- token="$(openssl rand -hex 32)"
221
- # Ensure trailing newline before append to avoid joining with previous line
222
- if [[ -s "$OD_ENV_FILE" && -n "$(tail -c 1 "$OD_ENV_FILE" 2>/dev/null)" ]]; then
223
- echo "" >> "$OD_ENV_FILE"
224
- fi
225
- echo "OD_API_TOKEN=$token" >> "$OD_ENV_FILE"
226
- echo "✅ Generated OD_API_TOKEN (256-bit, written to $OD_ENV_FILE)"
227
- fi
228
-
229
- # Ensure OD_DAEMON_URL line
230
- if ! grep -q "^OD_DAEMON_URL=" "$OD_ENV_FILE"; then
231
- if [[ -s "$OD_ENV_FILE" && -n "$(tail -c 1 "$OD_ENV_FILE" 2>/dev/null)" ]]; then
232
- echo "" >> "$OD_ENV_FILE"
233
- fi
234
- echo "OD_DAEMON_URL=$OD_DEFAULT_URL" >> "$OD_ENV_FILE"
235
- echo "✅ Set OD_DAEMON_URL=$OD_DEFAULT_URL in $OD_ENV_FILE"
236
- fi
237
-
238
- chmod 600 "$OD_ENV_FILE"
239
-
240
- # Ensure network
241
- if ensure_network "$OD_NETWORK"; then
242
- echo "✅ Docker network '$OD_NETWORK' ready"
243
- fi
244
-
245
- # Ensure volume
246
- if ! docker volume inspect "$OD_VOLUME" >/dev/null 2>&1; then
247
- docker volume create "$OD_VOLUME" >/dev/null
248
- echo "✅ Docker volume '$OD_VOLUME' created"
249
- else
250
- echo "ℹ️ Docker volume '$OD_VOLUME' already exists"
251
- fi
252
-
253
- echo ""
254
- echo "Next: ai-run open-design start"
255
- }
256
-
257
- od_start() {
258
- local expose=false
259
- local host_port=7456
260
-
261
- while [[ $# -gt 0 ]]; do
262
- case "$1" in
263
- --expose) expose=true; shift ;;
264
- --port)
265
- shift
266
- if [[ $# -gt 0 && "$1" =~ ^[0-9]+$ ]]; then
267
- if (( $1 < 1 || $1 > 65535 )); then
268
- echo "❌ ERROR: --port value '$1' out of range (1-65535)" >&2; exit 1
269
- fi
270
- host_port="$1"
271
- shift
272
- else
273
- echo "❌ ERROR: --port requires a numeric value (e.g. 7456)" >&2; exit 1
274
- fi
275
- ;;
276
- *) echo "❌ ERROR: unknown start flag: $1" >&2; exit 1 ;;
277
- esac
278
- done
279
-
280
- if ! od_env_has_token; then
281
- echo "❌ ERROR: OD_API_TOKEN not found in $OD_ENV_FILE"
282
- echo " Run: ai-run open-design init"
283
- exit 1
284
- fi
285
-
286
- if ! docker image inspect "$OD_IMAGE" >/dev/null 2>&1; then
287
- echo "❌ ERROR: image '$OD_IMAGE' not built"
288
- echo " Run: bash lib/install-open-design.sh"
289
- exit 1
290
- fi
291
-
292
- ensure_network "$OD_NETWORK" || exit 1
293
- docker volume inspect "$OD_VOLUME" >/dev/null 2>&1 || docker volume create "$OD_VOLUME" >/dev/null
294
-
295
- # If a container with this name exists, handle it gracefully
296
- if docker ps -a --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
297
- if docker ps --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
298
- echo "ℹ️ '$OD_CONTAINER_NAME' is already running"
299
- echo " Use 'ai-run open-design restart' to apply new flags"
300
- return 0
301
- else
302
- echo "🔄 Removing stopped container '$OD_CONTAINER_NAME' to recreate with current flags..."
303
- docker rm "$OD_CONTAINER_NAME" >/dev/null
304
- fi
305
- fi
306
-
307
- local run_args=(
308
- run -d
309
- --name "$OD_CONTAINER_NAME"
310
- --network "$OD_NETWORK"
311
- --restart unless-stopped
312
- -v "$OD_VOLUME:/app/.od"
313
- --env-file "$OD_ENV_FILE"
314
- )
315
-
316
- if [[ "$expose" == "true" ]]; then
317
- run_args+=(-p "${host_port}:7456")
318
- fi
319
-
320
- run_args+=("$OD_IMAGE")
321
-
322
- echo "🔄 Starting $OD_CONTAINER_NAME..."
323
- docker "${run_args[@]}" >/dev/null
324
- echo "✅ $OD_CONTAINER_NAME running on network '$OD_NETWORK'"
325
- if [[ "$expose" == "true" ]]; then
326
- echo " Published to host: http://localhost:${host_port}"
327
- else
328
- echo " Internal-only: reachable from sandbox containers as http://ai-open-design:7456"
329
- fi
330
- }
331
-
332
- od_stop() {
333
- if docker ps --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
334
- echo "🔄 Stopping $OD_CONTAINER_NAME..."
335
- docker stop "$OD_CONTAINER_NAME" >/dev/null
336
- echo "✅ $OD_CONTAINER_NAME stopped (data preserved in volume '$OD_VOLUME')"
337
- else
338
- echo "ℹ️ $OD_CONTAINER_NAME is not running"
339
- fi
340
- }
341
-
342
- od_restart() {
343
- od_stop
344
- od_start "$@"
345
- }
346
-
347
- od_status() {
348
- echo "Container : $OD_CONTAINER_NAME"
349
- if docker ps --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
350
- echo "State : running"
351
- local uptime
352
- uptime="$(docker inspect -f '{{.State.StartedAt}}' "$OD_CONTAINER_NAME" 2>/dev/null || echo unknown)"
353
- echo "Started : $uptime"
354
- local ports
355
- ports="$(docker inspect -f '{{json .NetworkSettings.Ports}}' "$OD_CONTAINER_NAME" 2>/dev/null || echo '{}')"
356
- echo "Ports : $ports"
357
- elif docker ps -a --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
358
- echo "State : stopped"
359
- echo " Hint : ai-run open-design start"
360
- else
361
- echo "State : not installed"
362
- echo " Hint : ai-run open-design init && ai-run open-design start"
363
- fi
364
-
365
- echo "Network : $OD_NETWORK"
366
- echo "Volume : $OD_VOLUME"
367
- if od_env_has_token; then
368
- local tok
369
- tok="$(od_read_token)"
370
- echo "API token : set (***${tok: -4})"
371
- else
372
- echo "API token : (unset — run 'ai-run open-design init')"
373
- fi
374
-
375
- # Try health check (only meaningful if container is running)
376
- if docker ps --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
377
- # Try in-container curl first (fast path); fall back to one-off container on same network
378
- # because upstream daemon image may not bundle curl
379
- local health_ok=false
380
- if docker exec "$OD_CONTAINER_NAME" sh -c 'command -v curl' >/dev/null 2>&1; then
381
- if docker exec "$OD_CONTAINER_NAME" curl -sf --max-time 3 http://127.0.0.1:7456/api/health >/dev/null 2>&1; then
382
- health_ok=true
383
- fi
384
- else
385
- # Fallback: probe via a tiny one-off container in the same network
386
- if docker run --rm --network "$OD_NETWORK" curlimages/curl:latest \
387
- -sf --max-time 3 "http://${OD_CONTAINER_NAME}:7456/api/health" >/dev/null 2>&1; then
388
- health_ok=true
389
- fi
390
- fi
391
- if [[ "$health_ok" == "true" ]]; then
392
- echo "Health : OK"
393
- else
394
- echo "Health : FAIL (daemon not responding on /api/health)"
395
- fi
396
- fi
397
- }
398
-
399
- od_logs() {
400
- if ! docker ps -a --format '{{.Names}}' | grep -q "^${OD_CONTAINER_NAME}$"; then
401
- echo "❌ ERROR: container '$OD_CONTAINER_NAME' does not exist" >&2
402
- exit 1
403
- fi
404
- docker logs "$@" "$OD_CONTAINER_NAME"
405
- }
406
-
407
- # Dispatch subcommand
408
- SUBCMD="${1:-}"
409
- if [[ -n "$SUBCMD" ]]; then shift; fi
410
- case "$SUBCMD" in
411
- init) od_init "$@" ;;
412
- start) od_start "$@" ;;
413
- stop) od_stop ;;
414
- restart) od_restart "$@" ;;
415
- status) od_status ;;
416
- logs) od_logs "$@" ;;
417
- --help|-h|"") od_print_help ;;
418
- *)
419
- echo "❌ ERROR: unknown subcommand '$SUBCMD'" >&2
420
- echo ""
421
- od_print_help
422
- exit 1
423
- ;;
424
- esac
425
- exit 0
426
- fi
427
- # ────────────────────────────────────────────────────────────────
428
- # END OPEN-DESIGN DISPATCHER
429
- # ────────────────────────────────────────────────────────────────
430
-
431
144
  # Handle no tool specified
432
145
  if [[ -z "$TOOL" ]]; then
433
146
  if [[ ! -t 0 ]] || [[ ! -t 1 ]]; then
@@ -1097,20 +810,6 @@ unset -f _pmcp_resolve_script_dir
1097
810
 
1098
811
  if [[ "$TOOL" == "opencode" ]] && command -v jq &>/dev/null && [[ -f "$AI_SANDBOX_CONFIG" ]] && declare -f pmcp::sanitize_name >/dev/null; then
1099
812
  PLAYWRIGHT_HOST_CHROME=$(jq -r '.mcp.chromePath // empty' "$AI_SANDBOX_CONFIG" 2>/dev/null)
1100
- if [[ -n "$PLAYWRIGHT_HOST_CHROME" ]] && [[ -f "$PLAYWRIGHT_HOST_CHROME" ]]; then
1101
- # Ask user whether to open host Chrome browser
1102
- if [[ -t 0 ]]; then
1103
- echo ""
1104
- echo "🌐 Host Chrome browser is configured: $PLAYWRIGHT_HOST_CHROME"
1105
- printf " Open browser for AI agent? [y/N] "
1106
- read -r -t 10 OPEN_CHROME_ANSWER || OPEN_CHROME_ANSWER=""
1107
- echo ""
1108
- if [[ ! "$OPEN_CHROME_ANSWER" =~ ^[Yy]$ ]]; then
1109
- echo " ⏭️ Skipping host Chrome (using container Chromium if available)"
1110
- PLAYWRIGHT_HOST_CHROME=""
1111
- fi
1112
- fi
1113
- fi
1114
813
  if [[ -n "$PLAYWRIGHT_HOST_CHROME" ]] && [[ -f "$PLAYWRIGHT_HOST_CHROME" ]]; then
1115
814
  HOST_CHROME_CDP=true
1116
815
  echo "🌐 Host Chrome CDP mode: $PLAYWRIGHT_HOST_CHROME"
@@ -1301,19 +1000,6 @@ get_network_containers() {
1301
1000
  docker network inspect "$network" --format '{{range .Containers}}{{.Name}} {{end}}' 2>/dev/null | xargs | tr ' ' ', '
1302
1001
  }
1303
1002
 
1304
- # Ensure shared Docker network exists for cross-container service discovery
1305
- # (e.g., agent containers reaching the open-design daemon by name)
1306
- ensure_network() {
1307
- local net="${1:-ai-sandbox}"
1308
- if ! docker network inspect "$net" >/dev/null 2>&1; then
1309
- docker network create "$net" >/dev/null 2>&1 || {
1310
- echo "⚠️ WARNING: failed to create Docker network '$net'" >&2
1311
- return 1
1312
- }
1313
- fi
1314
- return 0
1315
- }
1316
-
1317
1003
  # Interactive network selection menu (multi-select)
1318
1004
  show_network_menu() {
1319
1005
  local compose_nets=()
@@ -2911,15 +2597,7 @@ fi
2911
2597
 
2912
2598
  # Nano-brain targeted preflight + auto-repair wrapper
2913
2599
  if [[ "$SHELL_MODE" == "true" ]] && [[ "$NANO_BRAIN_AUTO_REPAIR" == "true" ]] && [[ "${DOCKER_COMMAND[0]:-}" == "-c" ]]; then
2914
- # Separate hook from following command with newline so bash parses them
2915
- # as distinct statements. Without this, the hook's trailing `export -f`
2916
- # line gets joined with the next `echo` call, producing:
2917
- # export -f nano_brain_shell_wrapper npx echo ''; echo '...'
2918
- # which makes bash try to `export -f echo` (a builtin, not a function)
2919
- # and `export -f ''` (empty name), emitting two harmless but ugly errors:
2920
- # bash: line 68: export: echo: not a function
2921
- # bash: line 68: export: : not a function
2922
- DOCKER_COMMAND[1]="$NANO_BRAIN_SHELL_HOOK"$'\n'"${DOCKER_COMMAND[1]}"
2600
+ DOCKER_COMMAND[1]="$NANO_BRAIN_SHELL_HOOK ${DOCKER_COMMAND[1]}"
2923
2601
  fi
2924
2602
 
2925
2603
  if [[ "$SHELL_MODE" != "true" ]] && is_nano_brain_command; then
@@ -3061,23 +2739,12 @@ DOCKER_ARGS+=($TOOL_CONFIG_MOUNTS)
3061
2739
  DOCKER_ARGS+=($RG_COMPAT_MOUNT)
3062
2740
  DOCKER_ARGS+=($GIT_MOUNTS)
3063
2741
  DOCKER_ARGS+=($SSH_AGENT_ENV)
3064
- # Default to ai-sandbox network for service discovery if user didn't specify.
3065
- # Only add --network if creation succeeded; otherwise Docker uses its default bridge.
3066
- if [[ -z "$NETWORK_OPTIONS" ]]; then
3067
- if ensure_network "ai-sandbox" >/dev/null 2>&1; then
3068
- DOCKER_ARGS+=(--network ai-sandbox)
3069
- fi
3070
- fi
3071
2742
  DOCKER_ARGS+=($NETWORK_OPTIONS)
3072
2743
  DOCKER_ARGS+=($DISPLAY_FLAGS)
3073
2744
  DOCKER_ARGS+=($HOST_ACCESS_ARGS)
3074
2745
  DOCKER_ARGS+=($PORT_MAPPINGS)
3075
2746
  DOCKER_ARGS+=($OPENCODE_PASSWORD_ENV)
3076
2747
  DOCKER_ARGS+=(-v "$HOME_DIR":/home/agent)
3077
- # Auto-mount open-design data volume read-only so agents can read generated artifacts
3078
- if docker volume inspect ai-open-design-data >/dev/null 2>&1; then
3079
- DOCKER_ARGS+=(-v "ai-open-design-data:/workspace/.od:ro")
3080
- fi
3081
2748
  DOCKER_ARGS+=($SHARED_CACHE_MOUNTS)
3082
2749
  DOCKER_ARGS+=($NANO_BRAIN_MOUNT)
3083
2750
  DOCKER_ARGS+=(-w "$CURRENT_DIR")
@@ -3096,41 +2763,5 @@ DOCKER_ARGS+=($TERMINAL_SIZE)
3096
2763
  DOCKER_ARGS+=("$IMAGE")
3097
2764
  DOCKER_ARGS+=("${DOCKER_COMMAND[@]}")
3098
2765
 
3099
- # Auto-start open-design daemon if image exists but container not running
3100
- if docker image inspect ai-open-design:latest >/dev/null 2>&1; then
3101
- if ! docker ps --format '{{.Names}}' | grep -q "^ai-open-design$"; then
3102
- if [[ -t 0 && -t 1 ]]; then
3103
- printf "🎨 Open Design daemon is not running. Start it? [Y/n] "
3104
- read -r OD_ANSWER
3105
- if [[ ! "$OD_ANSWER" =~ ^[Nn]$ ]]; then
3106
- OD_ENV_FILE="$HOME/.ai-sandbox/env"
3107
- OD_NETWORK="ai-sandbox"
3108
- OD_VOLUME="ai-open-design-data"
3109
- # Auto-init if token missing
3110
- if ! grep -q "^OD_API_TOKEN=" "$OD_ENV_FILE" 2>/dev/null; then
3111
- mkdir -p "$(dirname "$OD_ENV_FILE")"
3112
- touch "$OD_ENV_FILE"
3113
- chmod 600 "$OD_ENV_FILE"
3114
- OD_TOKEN="$(openssl rand -hex 32)"
3115
- echo "OD_API_TOKEN=$OD_TOKEN" >> "$OD_ENV_FILE"
3116
- echo "OD_DAEMON_URL=http://ai-open-design:7456" >> "$OD_ENV_FILE"
3117
- echo "✅ Generated OD_API_TOKEN"
3118
- fi
3119
- # Ensure network and volume
3120
- docker network inspect "$OD_NETWORK" >/dev/null 2>&1 || docker network create "$OD_NETWORK" >/dev/null
3121
- docker volume inspect "$OD_VOLUME" >/dev/null 2>&1 || docker volume create "$OD_VOLUME" >/dev/null
3122
- # Remove stopped container if exists
3123
- if docker ps -a --format '{{.Names}}' | grep -q "^ai-open-design$"; then
3124
- docker rm ai-open-design >/dev/null 2>&1 || true
3125
- fi
3126
- docker run -d --name ai-open-design --network "$OD_NETWORK" \
3127
- --restart unless-stopped -v "$OD_VOLUME:/app/.od" \
3128
- --env-file "$OD_ENV_FILE" ai-open-design:latest >/dev/null
3129
- echo "✅ Open Design daemon started (http://ai-open-design:7456)"
3130
- fi
3131
- fi
3132
- fi
3133
- fi
3134
-
3135
2766
  # Execute docker run with proper argument handling
3136
2767
  docker run "${DOCKER_ARGS[@]}"
package/bin/cli.js CHANGED
@@ -140,8 +140,6 @@ function runRebuild() {
140
140
  INSTALL_CHROME_DEVTOOLS_MCP: hasMcp('chrome-devtools') ? '1' : '0',
141
141
  INSTALL_PLAYWRIGHT_HOST: useHostChrome ? '1' : '0',
142
142
  INSTALL_RTK: '0',
143
- INSTALL_PUP: '0',
144
- INSTALL_OD_HELPERS: '1',
145
143
  INSTALL_SPEC_KIT: '0',
146
144
  INSTALL_UX_UI_PROMAX: '0',
147
145
  INSTALL_OPENSPEC: '0',
@@ -0,0 +1,74 @@
1
+
2
+ FROM node:22-bookworm-slim
3
+
4
+ ARG AGENT_UID=1001
5
+
6
+ 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 vim-nox 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
7
+
8
+ # Install Python PDF processing tools for PDF skill
9
+ RUN pip3 install --no-cache-dir --break-system-packages pypdf pdfplumber reportlab pytesseract pdf2image
10
+
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
+
13
+ # Install bun (used by most AI tool install scripts)
14
+ RUN npm install -g bun
15
+
16
+ # Install pnpm globally using npm (not bun, for stability)
17
+ RUN npm install -g pnpm
18
+
19
+ # Install TypeScript and LSP tools using npm
20
+ RUN npm install -g typescript typescript-language-server pyright vscode-langservers-extracted
21
+
22
+ # Verify installations
23
+ RUN node --version && npm --version && pnpm --version && tsc --version
24
+
25
+ # Install additional tools (if selected)
26
+ RUN apt-get update && apt-get install -y --no-install-recommends \
27
+ libglib2.0-0 \
28
+ libnspr4 \
29
+ libnss3 \
30
+ libdbus-1-3 \
31
+ libatk1.0-0 \
32
+ libatk-bridge2.0-0 \
33
+ libcups2 \
34
+ libxcb1 \
35
+ libxkbcommon0 \
36
+ libatspi2.0-0 \
37
+ libx11-6 \
38
+ libxcomposite1 \
39
+ libxdamage1 \
40
+ libxext6 \
41
+ libxfixes3 \
42
+ libxrandr2 \
43
+ libgbm1 \
44
+ libdrm2 \
45
+ libcairo2 \
46
+ libpango-1.0-0 \
47
+ libasound2 \
48
+ fonts-liberation \
49
+ libappindicator3-1 \
50
+ libu2f-udev \
51
+ libvulkan1 \
52
+ libxshmfence1 \
53
+ xdg-utils \
54
+ wget \
55
+ && rm -rf /var/lib/apt/lists/*
56
+ ENV PLAYWRIGHT_BROWSERS_PATH=/opt/playwright-browsers
57
+ RUN mkdir -p /opt/playwright-browsers && \
58
+ npm install -g @playwright/mcp@latest && \
59
+ touch /opt/.mcp-playwright-installed
60
+ ENV CHROME_DEVTOOLS_MCP_NO_USAGE_STATISTICS=1
61
+ RUN npm install -g chrome-devtools-mcp@latest && \
62
+ touch /opt/.mcp-chrome-devtools-installed
63
+ RUN touch /opt/.mcp-playwright-installed
64
+
65
+ # Create workspace
66
+ WORKDIR /workspace
67
+
68
+ # Non-root user for security (match host UID)
69
+ RUN useradd -m -u ${AGENT_UID} -d /home/agent agent && \
70
+ mkdir -p /home/agent/.cache /home/agent/.npm /home/agent/.opencode /home/agent/.config && \
71
+ chown -R agent:agent /home/agent/.cache /home/agent/.npm /home/agent/.opencode /home/agent/.config /workspace && \
72
+ ([ -d /opt/playwright-browsers ] && chown -R agent:agent /opt/playwright-browsers || true)
73
+ USER agent
74
+ ENV HOME=/home/agent
@@ -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%).