@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 +1 -370
- package/bin/cli.js +0 -2
- package/dockerfiles/base/Dockerfile +74 -0
- package/dockerfiles/base/skills/rtk/SKILL.md +103 -0
- package/dockerfiles/base/skills/rtk-setup/SKILL.md +118 -0
- package/dockerfiles/opencode/Dockerfile +9 -0
- package/dockerfiles/sandbox/Dockerfile +91 -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 +0 -6
- package/lib/install-base.sh +0 -45
- package/package.json +1 -1
- package/setup.sh +6 -71
- package/lib/install-open-design.sh +0 -60
- package/skills/dd-pup/SKILL.md +0 -186
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
|
-
|
|
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%).
|