loki-mode 5.6.0 → 5.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/SKILL.md CHANGED
@@ -3,7 +3,7 @@ name: loki-mode
3
3
  description: Multi-agent autonomous startup system. Triggers on "Loki Mode". Takes PRD to deployed product with zero human intervention. Requires --dangerously-skip-permissions flag.
4
4
  ---
5
5
 
6
- # Loki Mode v5.6.0
6
+ # Loki Mode v5.6.1
7
7
 
8
8
  **You are an autonomous agent. You make decisions. You do not ask questions. You do not stop.**
9
9
 
@@ -210,21 +210,33 @@ When running with `autonomy/run.sh`, you can intervene:
210
210
  | Method | Effect |
211
211
  |--------|--------|
212
212
  | `touch .loki/PAUSE` | Pauses after current session |
213
- | `echo "instructions" > .loki/HUMAN_INPUT.md` | Injects directive into next prompt (executed immediately) |
213
+ | `echo "instructions" > .loki/HUMAN_INPUT.md` | Injects directive (requires `LOKI_PROMPT_INJECTION=true`) |
214
214
  | `touch .loki/STOP` | Stops immediately |
215
215
  | Ctrl+C (once) | Pauses, shows options |
216
216
  | Ctrl+C (twice) | Exits immediately |
217
217
 
218
+ ### Security: Prompt Injection (v5.6.1)
219
+
220
+ **DISABLED by default** for enterprise security. Prompt injection via `HUMAN_INPUT.md` is blocked unless explicitly enabled.
221
+
222
+ ```bash
223
+ # Enable prompt injection (only in trusted environments)
224
+ LOKI_PROMPT_INJECTION=true loki start ./prd.md
225
+
226
+ # Or for sandbox mode
227
+ LOKI_PROMPT_INJECTION=true loki sandbox prompt "start the app"
228
+ ```
229
+
218
230
  ### Hints vs Directives
219
231
 
220
232
  | Type | File | Behavior |
221
233
  |------|------|----------|
222
234
  | **Hint** | `.loki/CONTINUITY.md` "Mistakes & Learnings" | Passive memory - remembered but not acted upon |
223
- | **Directive** | `.loki/HUMAN_INPUT.md` | Active instruction - executed BEFORE normal tasks |
235
+ | **Directive** | `.loki/HUMAN_INPUT.md` | Active instruction (requires `LOKI_PROMPT_INJECTION=true`) |
224
236
 
225
- **Example directive** (check all .astro files):
237
+ **Example directive** (only works with `LOKI_PROMPT_INJECTION=true`):
226
238
  ```bash
227
- echo "Check all .astro files for missing BaseLayout imports. Fix any issues found. Add a compilation test to prevent this regression." > .loki/HUMAN_INPUT.md
239
+ echo "Check all .astro files for missing BaseLayout imports." > .loki/HUMAN_INPUT.md
228
240
  ```
229
241
 
230
242
  ---
@@ -241,4 +253,4 @@ Auto-detected or force with `LOKI_COMPLEXITY`:
241
253
 
242
254
  ---
243
255
 
244
- **v5.6.0 | Docker sandbox mode for secure execution | ~245 lines core**
256
+ **v5.6.1 | Prompt injection disabled by default (enterprise security) | ~250 lines core**
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.6.0
1
+ 5.6.1
package/autonomy/run.sh CHANGED
@@ -1,4 +1,8 @@
1
1
  #!/bin/bash
2
+ # shellcheck disable=SC2034 # Many variables are used by sourced scripts
3
+ # shellcheck disable=SC2155 # Declare and assign separately (acceptable in this codebase)
4
+ # shellcheck disable=SC2329 # Functions may be invoked indirectly or via dynamic dispatch
5
+ # shellcheck disable=SC2086 # Word splitting is intentional in some contexts
2
6
  #===============================================================================
3
7
  # Loki Mode - Autonomous Runner
4
8
  # Single script that handles prerequisites, setup, and autonomous execution
@@ -106,6 +110,10 @@
106
110
  # STOP file: touch .loki/STOP - stops immediately
107
111
  # Ctrl+C (once): Pauses execution, shows options
108
112
  # Ctrl+C (twice): Exits immediately
113
+ #
114
+ # Security (Enterprise):
115
+ # LOKI_PROMPT_INJECTION - Enable HUMAN_INPUT.md processing (default: false)
116
+ # Set to "true" only in trusted environments
109
117
  #===============================================================================
110
118
 
111
119
  set -uo pipefail
@@ -513,7 +521,9 @@ if [ "$BASH_VERSION_MAJOR" -ge 4 ] 2>/dev/null; then
513
521
  declare -A WORKTREE_PATHS
514
522
  else
515
523
  # Fallback: parallel mode will check and warn
524
+ # shellcheck disable=SC2178
516
525
  WORKTREE_PIDS=""
526
+ # shellcheck disable=SC2178
517
527
  WORKTREE_PATHS=""
518
528
  fi
519
529
 
@@ -939,6 +949,7 @@ import_github_issues() {
939
949
  # Append to pending.json with temp file cleanup on error
940
950
  local temp_file
941
951
  temp_file=$(mktemp)
952
+ # shellcheck disable=SC2064
942
953
  trap "rm -f '$temp_file'" RETURN
943
954
  if jq ".tasks += [$task_json]" "$pending_file" > "$temp_file" && mv "$temp_file" "$pending_file"; then
944
955
  log_info "Imported issue #$number: $title"
@@ -1320,8 +1331,8 @@ remove_worktree() {
1320
1331
  fi
1321
1332
  }
1322
1333
 
1323
- unset WORKTREE_PATHS[$stream_name]
1324
- unset WORKTREE_PIDS[$stream_name]
1334
+ unset "WORKTREE_PATHS[$stream_name]"
1335
+ unset "WORKTREE_PIDS[$stream_name]"
1325
1336
 
1326
1337
  log_info "Removed worktree: $stream_name"
1327
1338
  }
@@ -1642,7 +1653,7 @@ run_parallel_orchestrator() {
1642
1653
  local pid="${WORKTREE_PIDS[$stream]}"
1643
1654
  if ! kill -0 "$pid" 2>/dev/null; then
1644
1655
  log_warn "Session ended: $stream"
1645
- unset WORKTREE_PIDS[$stream]
1656
+ unset "WORKTREE_PIDS[$stream]"
1646
1657
  fi
1647
1658
  done
1648
1659
 
@@ -4076,19 +4087,43 @@ check_human_intervention() {
4076
4087
  return 1
4077
4088
  fi
4078
4089
 
4079
- # Check for HUMAN_INPUT.md
4080
- if [ -f "$loki_dir/HUMAN_INPUT.md" ]; then
4081
- local human_input=$(cat "$loki_dir/HUMAN_INPUT.md")
4082
- if [ -n "$human_input" ]; then
4083
- log_info "Human input detected:"
4084
- echo "$human_input"
4085
- echo ""
4086
- # Move to processed
4087
- mv "$loki_dir/HUMAN_INPUT.md" "$loki_dir/logs/human-input-$(date +%Y%m%d-%H%M%S).md"
4088
- # Inject into next prompt
4089
- export LOKI_HUMAN_INPUT="$human_input"
4090
- return 0
4090
+ # Check for HUMAN_INPUT.md (prompt injection)
4091
+ # Security: Check it's a regular file (not symlink) to prevent symlink attacks
4092
+ if [ -f "$loki_dir/HUMAN_INPUT.md" ] && [ ! -L "$loki_dir/HUMAN_INPUT.md" ]; then
4093
+ # Security: Prompt injection disabled by default for enterprise security
4094
+ if [ "${LOKI_PROMPT_INJECTION:-false}" != "true" ]; then
4095
+ log_warn "HUMAN_INPUT.md detected but prompt injection is DISABLED"
4096
+ log_warn "To enable, set LOKI_PROMPT_INJECTION=true (only in trusted environments)"
4097
+ # Move to rejected instead of processed
4098
+ mkdir -p "$loki_dir/logs" 2>/dev/null
4099
+ mv "$loki_dir/HUMAN_INPUT.md" "$loki_dir/logs/human-input-REJECTED-$(date +%Y%m%d-%H%M%S).md" 2>/dev/null || rm -f "$loki_dir/HUMAN_INPUT.md"
4100
+ else
4101
+ # Security: Check file size (1MB limit)
4102
+ local file_size
4103
+ file_size=$(stat -f%z "$loki_dir/HUMAN_INPUT.md" 2>/dev/null || stat -c%s "$loki_dir/HUMAN_INPUT.md" 2>/dev/null || echo "0")
4104
+ if [ "$file_size" -gt 1048576 ]; then
4105
+ log_warn "HUMAN_INPUT.md exceeds 1MB size limit, rejecting"
4106
+ mkdir -p "$loki_dir/logs" 2>/dev/null
4107
+ mv "$loki_dir/HUMAN_INPUT.md" "$loki_dir/logs/human-input-REJECTED-TOOLARGE-$(date +%Y%m%d-%H%M%S).md" 2>/dev/null || rm -f "$loki_dir/HUMAN_INPUT.md"
4108
+ else
4109
+ local human_input=$(cat "$loki_dir/HUMAN_INPUT.md")
4110
+ if [ -n "$human_input" ]; then
4111
+ log_info "Human input detected:"
4112
+ echo "$human_input"
4113
+ echo ""
4114
+ # Move to processed
4115
+ mkdir -p "$loki_dir/logs" 2>/dev/null
4116
+ mv "$loki_dir/HUMAN_INPUT.md" "$loki_dir/logs/human-input-$(date +%Y%m%d-%H%M%S).md"
4117
+ # Inject into next prompt
4118
+ export LOKI_HUMAN_INPUT="$human_input"
4119
+ return 0
4120
+ fi
4121
+ fi
4091
4122
  fi
4123
+ elif [ -L "$loki_dir/HUMAN_INPUT.md" ]; then
4124
+ # Security: Reject symlinks
4125
+ log_warn "HUMAN_INPUT.md is a symlink - rejected for security"
4126
+ rm -f "$loki_dir/HUMAN_INPUT.md"
4092
4127
  fi
4093
4128
 
4094
4129
  # Check for STOP file (immediate stop)
@@ -40,7 +40,13 @@ BOLD='\033[1m'
40
40
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
41
  SKILL_DIR="$(dirname "$SCRIPT_DIR")"
42
42
  PROJECT_DIR="${LOKI_PROJECT_DIR:-$(pwd)}"
43
- CONTAINER_NAME="loki-sandbox-$(basename "$PROJECT_DIR" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')"
43
+ # Normalize PROJECT_DIR (remove trailing slash)
44
+ PROJECT_DIR="${PROJECT_DIR%/}"
45
+
46
+ # Container name includes path hash to avoid collisions between similarly-named projects
47
+ # macOS uses md5 instead of md5sum
48
+ PROJECT_HASH=$(echo "$PROJECT_DIR" | md5sum 2>/dev/null | cut -c1-8 || md5 2>/dev/null | cut -c1-8 || echo "$$")
49
+ CONTAINER_NAME="loki-sandbox-$(basename "$PROJECT_DIR" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9-]/-/g')-${PROJECT_HASH}"
44
50
 
45
51
  # Sandbox settings
46
52
  SANDBOX_IMAGE="${LOKI_SANDBOX_IMAGE:-loki-mode:sandbox}"
@@ -53,6 +59,9 @@ SANDBOX_READONLY="${LOKI_SANDBOX_READONLY:-false}"
53
59
  API_PORT="${LOKI_API_PORT:-9898}"
54
60
  DASHBOARD_PORT="${LOKI_DASHBOARD_PORT:-57374}"
55
61
 
62
+ # Security: Prompt injection disabled by default for enterprise security
63
+ PROMPT_INJECTION_ENABLED="${LOKI_PROMPT_INJECTION:-false}"
64
+
56
65
  #===============================================================================
57
66
  # Utility Functions
58
67
  #===============================================================================
@@ -73,6 +82,65 @@ log_error() {
73
82
  echo -e "${RED}[SANDBOX]${NC} $1" >&2
74
83
  }
75
84
 
85
+ # Check if a port is available
86
+ check_port_available() {
87
+ local port="$1"
88
+ if command -v lsof &>/dev/null; then
89
+ ! lsof -i ":$port" &>/dev/null
90
+ elif command -v nc &>/dev/null; then
91
+ ! nc -z localhost "$port" 2>/dev/null
92
+ else
93
+ # Assume available if we can't check
94
+ return 0
95
+ fi
96
+ }
97
+
98
+ # Validate project directory
99
+ validate_project_dir() {
100
+ if [[ ! -d "$PROJECT_DIR" ]]; then
101
+ log_error "Project directory does not exist: $PROJECT_DIR"
102
+ return 1
103
+ fi
104
+ if [[ ! -r "$PROJECT_DIR" ]]; then
105
+ log_error "Project directory is not readable: $PROJECT_DIR"
106
+ return 1
107
+ fi
108
+ if [[ "$SANDBOX_READONLY" != "true" ]] && [[ ! -w "$PROJECT_DIR" ]]; then
109
+ log_warn "Project directory is not writable: $PROJECT_DIR"
110
+ log_info "Consider using LOKI_SANDBOX_READONLY=true"
111
+ fi
112
+ return 0
113
+ }
114
+
115
+ # Warn about API keys based on provider
116
+ warn_missing_api_keys() {
117
+ local provider="$1"
118
+ case "$provider" in
119
+ claude)
120
+ if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
121
+ log_warn "ANTHROPIC_API_KEY not set - Claude commands will fail inside container"
122
+ fi
123
+ ;;
124
+ codex)
125
+ if [[ -z "${OPENAI_API_KEY:-}" ]]; then
126
+ log_warn "OPENAI_API_KEY not set - Codex commands will fail inside container"
127
+ fi
128
+ ;;
129
+ gemini)
130
+ if [[ -z "${GOOGLE_API_KEY:-}" ]]; then
131
+ log_warn "GOOGLE_API_KEY not set - Gemini commands will fail inside container"
132
+ fi
133
+ ;;
134
+ esac
135
+ }
136
+
137
+ # Cleanup handler for signals
138
+ cleanup_container() {
139
+ log_warn "Interrupted - cleaning up container..."
140
+ docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
141
+ exit 130
142
+ }
143
+
76
144
  check_docker() {
77
145
  if ! command -v docker &> /dev/null; then
78
146
  log_error "Docker not found. Install Docker to use sandbox mode."
@@ -107,6 +175,344 @@ ensure_image() {
107
175
  fi
108
176
  }
109
177
 
178
+ #===============================================================================
179
+ # Git Worktree Sandbox (Fallback for non-Docker environments)
180
+ #===============================================================================
181
+
182
+ # Worktree sandbox settings
183
+ WORKTREE_PREFIX="loki-sandbox"
184
+ WORKTREE_BASE="${LOKI_WORKTREE_BASE:-${TMPDIR:-/tmp}}"
185
+ WORKTREE_STATE_FILE="${PROJECT_DIR}/.loki/sandbox/worktree-state.json"
186
+
187
+ # Check if Docker is available (non-fatal version)
188
+ is_docker_available() {
189
+ command -v docker &>/dev/null && docker info &>/dev/null 2>&1
190
+ }
191
+
192
+ # Check if git worktree is available
193
+ is_git_available() {
194
+ command -v git &>/dev/null && git rev-parse --git-dir &>/dev/null 2>&1
195
+ }
196
+
197
+ # Detect which sandbox mode to use
198
+ detect_sandbox_mode() {
199
+ local requested="${1:-auto}"
200
+
201
+ case "$requested" in
202
+ docker)
203
+ if is_docker_available; then
204
+ echo "docker"
205
+ else
206
+ log_error "Docker requested but not available"
207
+ return 1
208
+ fi
209
+ ;;
210
+ worktree)
211
+ if is_git_available; then
212
+ echo "worktree"
213
+ else
214
+ log_error "Git not available for worktree sandbox"
215
+ return 1
216
+ fi
217
+ ;;
218
+ auto|*)
219
+ if is_docker_available; then
220
+ echo "docker"
221
+ elif is_git_available; then
222
+ log_warn "Docker not available - using worktree sandbox (soft isolation)"
223
+ echo "worktree"
224
+ else
225
+ log_error "Neither Docker nor Git available for sandbox mode"
226
+ return 1
227
+ fi
228
+ ;;
229
+ esac
230
+ }
231
+
232
+ # Create a worktree sandbox
233
+ create_worktree_sandbox() {
234
+ local prd_path="${1:-}"
235
+ local provider="${LOKI_PROVIDER:-claude}"
236
+
237
+ local timestamp=$(date +%Y%m%d-%H%M%S)
238
+ local sandbox_name="${WORKTREE_PREFIX}-${timestamp}"
239
+ local sandbox_branch="${WORKTREE_PREFIX}-${timestamp}"
240
+ local sandbox_path="${WORKTREE_BASE}/${sandbox_name}"
241
+
242
+ log_warn ""
243
+ log_warn "=========================================="
244
+ log_warn " WORKTREE SANDBOX - SOFT ISOLATION ONLY"
245
+ log_warn "=========================================="
246
+ log_warn ""
247
+ log_warn "This provides workspace isolation but NOT:"
248
+ log_warn " - Filesystem isolation (can access any file)"
249
+ log_warn " - Network isolation (full network access)"
250
+ log_warn " - Process isolation (no resource limits)"
251
+ log_warn ""
252
+ log_warn "For full isolation, install Docker."
253
+ log_warn ""
254
+
255
+ log_info "Creating worktree sandbox..."
256
+ log_info " Location: $sandbox_path"
257
+ log_info " Branch: $sandbox_branch"
258
+
259
+ # Check for existing sandbox
260
+ if [[ -f "$WORKTREE_STATE_FILE" ]]; then
261
+ local existing_path=$(jq -r '.sandbox_path // empty' "$WORKTREE_STATE_FILE" 2>/dev/null)
262
+ if [[ -n "$existing_path" ]] && [[ -d "$existing_path" ]]; then
263
+ log_warn "Existing sandbox found: $existing_path"
264
+ log_info "Use 'loki sandbox stop' to stop it first."
265
+ return 1
266
+ fi
267
+ fi
268
+
269
+ # Create branch for sandbox
270
+ if ! git branch "$sandbox_branch" HEAD 2>/dev/null; then
271
+ log_error "Failed to create sandbox branch. Are you in a git repository?"
272
+ return 1
273
+ fi
274
+
275
+ # Create worktree
276
+ if ! git worktree add "$sandbox_path" "$sandbox_branch" 2>/dev/null; then
277
+ git branch -D "$sandbox_branch" 2>/dev/null
278
+ log_error "Failed to create worktree at $sandbox_path"
279
+ return 1
280
+ fi
281
+
282
+ # Set up sandbox environment
283
+ mkdir -p "$sandbox_path/.loki/"{state,logs,signals,queue,memory}
284
+
285
+ # Copy essential files
286
+ for file in ".loki/CONTINUITY.md" ".loki/config.yaml" "SKILL.md" "CLAUDE.md"; do
287
+ if [[ -f "$PROJECT_DIR/$file" ]]; then
288
+ mkdir -p "$(dirname "$sandbox_path/$file")"
289
+ cp "$PROJECT_DIR/$file" "$sandbox_path/$file" 2>/dev/null || true
290
+ fi
291
+ done
292
+
293
+ # Create sandbox marker
294
+ cat > "$sandbox_path/.loki/SANDBOX_MODE" << EOF
295
+ ISOLATION_TYPE=worktree
296
+ CREATED_AT=$(date -Iseconds)
297
+ PARENT_DIR=$PROJECT_DIR
298
+ EOF
299
+
300
+ # Save state
301
+ mkdir -p "$(dirname "$WORKTREE_STATE_FILE")"
302
+ cat > "$WORKTREE_STATE_FILE" << EOF
303
+ {
304
+ "sandbox_path": "$sandbox_path",
305
+ "sandbox_branch": "$sandbox_branch",
306
+ "created_at": "$(date -Iseconds)",
307
+ "provider": "$provider",
308
+ "prd_path": "$prd_path",
309
+ "status": "created",
310
+ "isolation_type": "worktree"
311
+ }
312
+ EOF
313
+
314
+ log_success "Worktree sandbox created: $sandbox_path"
315
+ return 0
316
+ }
317
+
318
+ # Start loki in worktree sandbox
319
+ start_worktree_sandbox() {
320
+ local prd_path="${1:-}"
321
+ local provider="${LOKI_PROVIDER:-claude}"
322
+
323
+ # Create sandbox if needed
324
+ if ! [[ -f "$WORKTREE_STATE_FILE" ]]; then
325
+ create_worktree_sandbox "$prd_path" || return 1
326
+ fi
327
+
328
+ local sandbox_path=$(jq -r '.sandbox_path' "$WORKTREE_STATE_FILE")
329
+
330
+ if [[ ! -d "$sandbox_path" ]]; then
331
+ log_error "Sandbox path does not exist: $sandbox_path"
332
+ rm -f "$WORKTREE_STATE_FILE"
333
+ return 1
334
+ fi
335
+
336
+ log_info "Starting Loki in worktree sandbox..."
337
+ log_info " Path: $sandbox_path"
338
+ log_info " Provider: $provider"
339
+
340
+ # Build loki command
341
+ local loki_cmd="$SKILL_DIR/autonomy/run.sh"
342
+ if [[ -n "$prd_path" ]]; then
343
+ if [[ -f "$sandbox_path/$prd_path" ]]; then
344
+ loki_cmd="$loki_cmd $prd_path"
345
+ elif [[ -f "$prd_path" ]]; then
346
+ cp "$prd_path" "$sandbox_path/" 2>/dev/null || true
347
+ loki_cmd="$loki_cmd $(basename "$prd_path")"
348
+ fi
349
+ fi
350
+ loki_cmd="$loki_cmd --provider $provider"
351
+
352
+ # Set environment
353
+ export LOKI_SANDBOX_MODE=true
354
+ export LOKI_SANDBOX_TYPE=worktree
355
+ export LOKI_NOTIFICATIONS=false
356
+
357
+ log_info ""
358
+ log_info "Commands (in another terminal):"
359
+ log_info " loki sandbox status - Check status"
360
+ log_info " loki sandbox prompt 'msg' - Send prompt"
361
+ log_info " loki sandbox stop - Stop sandbox"
362
+ log_info ""
363
+
364
+ # Run loki in sandbox directory
365
+ cd "$sandbox_path" && $loki_cmd
366
+ }
367
+
368
+ # Stop worktree sandbox
369
+ stop_worktree_sandbox() {
370
+ if [[ ! -f "$WORKTREE_STATE_FILE" ]]; then
371
+ log_warn "No active worktree sandbox found"
372
+ return 0
373
+ fi
374
+
375
+ local sandbox_path=$(jq -r '.sandbox_path' "$WORKTREE_STATE_FILE")
376
+ local sandbox_branch=$(jq -r '.sandbox_branch' "$WORKTREE_STATE_FILE")
377
+
378
+ log_info "Stopping worktree sandbox..."
379
+
380
+ # Send stop signal
381
+ if [[ -d "$sandbox_path" ]]; then
382
+ touch "$sandbox_path/.loki/STOP" 2>/dev/null || true
383
+ fi
384
+
385
+ # Cleanup
386
+ if [[ "${LOKI_SANDBOX_CLEANUP:-true}" == "true" ]]; then
387
+ log_info "Cleaning up worktree..."
388
+
389
+ if [[ -n "$sandbox_path" ]] && [[ -d "$sandbox_path" ]]; then
390
+ git worktree remove "$sandbox_path" --force 2>/dev/null || rm -rf "$sandbox_path" 2>/dev/null
391
+ fi
392
+
393
+ if [[ -n "$sandbox_branch" ]]; then
394
+ git branch -D "$sandbox_branch" 2>/dev/null || true
395
+ fi
396
+
397
+ git worktree prune 2>/dev/null || true
398
+ rm -f "$WORKTREE_STATE_FILE"
399
+
400
+ log_success "Worktree sandbox cleaned up"
401
+ else
402
+ log_info "Sandbox preserved at: $sandbox_path"
403
+ log_info "Run 'loki sandbox cleanup' to remove."
404
+ fi
405
+ }
406
+
407
+ # Worktree sandbox status
408
+ worktree_sandbox_status() {
409
+ if [[ ! -f "$WORKTREE_STATE_FILE" ]]; then
410
+ log_info "No active worktree sandbox"
411
+ return 0
412
+ fi
413
+
414
+ local sandbox_path=$(jq -r '.sandbox_path' "$WORKTREE_STATE_FILE")
415
+ local sandbox_branch=$(jq -r '.sandbox_branch' "$WORKTREE_STATE_FILE")
416
+ local created_at=$(jq -r '.created_at' "$WORKTREE_STATE_FILE")
417
+
418
+ echo ""
419
+ echo -e "${BOLD}Worktree Sandbox Status${NC}"
420
+ echo "========================"
421
+ echo ""
422
+ echo -e " Path: ${CYAN}$sandbox_path${NC}"
423
+ echo -e " Branch: $sandbox_branch"
424
+ echo -e " Created: $created_at"
425
+
426
+ if [[ -d "$sandbox_path" ]]; then
427
+ local disk_usage=$(du -sh "$sandbox_path" 2>/dev/null | cut -f1)
428
+ echo -e " Disk: $disk_usage"
429
+
430
+ if [[ -f "$sandbox_path/.loki/STOP" ]]; then
431
+ echo -e " Status: ${YELLOW}Stopping${NC}"
432
+ else
433
+ echo -e " Status: ${GREEN}Active${NC}"
434
+ fi
435
+ else
436
+ echo -e " Status: ${RED}Missing${NC}"
437
+ fi
438
+
439
+ echo ""
440
+ echo -e "${YELLOW}[SOFT ISOLATION]${NC} - No filesystem/network/process isolation"
441
+ echo ""
442
+ }
443
+
444
+ # Send prompt to worktree sandbox
445
+ worktree_sandbox_prompt() {
446
+ local prompt="$*"
447
+
448
+ # Security check: prompt injection disabled by default
449
+ if [[ "$PROMPT_INJECTION_ENABLED" != "true" ]]; then
450
+ log_error "Prompt injection is disabled for security"
451
+ log_info ""
452
+ log_info "To enable, set LOKI_PROMPT_INJECTION=true"
453
+ log_info " Example: LOKI_PROMPT_INJECTION=true loki sandbox prompt 'your message'"
454
+ log_info ""
455
+ log_warn "WARNING: Only enable in trusted environments"
456
+ return 1
457
+ fi
458
+
459
+ if [[ -z "$prompt" ]]; then
460
+ log_error "Usage: loki sandbox prompt <your message>"
461
+ return 1
462
+ fi
463
+
464
+ if [[ ! -f "$WORKTREE_STATE_FILE" ]]; then
465
+ log_error "No active worktree sandbox"
466
+ return 1
467
+ fi
468
+
469
+ local sandbox_path=$(jq -r '.sandbox_path' "$WORKTREE_STATE_FILE")
470
+
471
+ if [[ ! -d "$sandbox_path" ]]; then
472
+ log_error "Sandbox path does not exist"
473
+ return 1
474
+ fi
475
+
476
+ # Use printf to safely write prompt without interpretation
477
+ printf '%s\n' "$prompt" > "$sandbox_path/.loki/HUMAN_INPUT.md"
478
+ log_success "Prompt sent to worktree sandbox"
479
+ log_info "Check $sandbox_path/.loki/logs/ for response"
480
+ }
481
+
482
+ # Cleanup orphaned worktrees
483
+ cleanup_worktrees() {
484
+ log_info "Scanning for orphaned sandbox worktrees..."
485
+
486
+ local found=0
487
+ while IFS= read -r line; do
488
+ if [[ "$line" == *"$WORKTREE_PREFIX"* ]]; then
489
+ log_info " Found: $line"
490
+ ((found++))
491
+ fi
492
+ done < <(git worktree list 2>/dev/null)
493
+
494
+ if [[ $found -eq 0 ]]; then
495
+ log_info "No sandbox worktrees found"
496
+ return 0
497
+ fi
498
+
499
+ log_info "Pruning worktrees..."
500
+ git worktree prune 2>/dev/null || true
501
+
502
+ # Clean up orphaned branches
503
+ local branches=$(git branch --list "${WORKTREE_PREFIX}*" 2>/dev/null)
504
+ while IFS= read -r branch; do
505
+ branch=$(echo "$branch" | tr -d '* ')
506
+ if [[ -n "$branch" ]]; then
507
+ log_info " Removing branch: $branch"
508
+ git branch -D "$branch" 2>/dev/null || true
509
+ fi
510
+ done <<< "$branches"
511
+
512
+ rm -f "$WORKTREE_STATE_FILE" 2>/dev/null
513
+ log_success "Cleanup complete"
514
+ }
515
+
110
516
  #===============================================================================
111
517
  # Container Management
112
518
  #===============================================================================
@@ -115,9 +521,36 @@ start_sandbox() {
115
521
  local prd_path="${1:-}"
116
522
  local provider="${LOKI_PROVIDER:-claude}"
117
523
 
524
+ # Set up signal handler to cleanup on Ctrl+C
525
+ trap cleanup_container INT TERM
526
+
118
527
  check_docker
528
+ validate_project_dir || return 1
119
529
  ensure_image
120
530
 
531
+ # Check port availability
532
+ if ! check_port_available "$API_PORT"; then
533
+ log_error "Port $API_PORT is already in use"
534
+ log_info "Set LOKI_API_PORT to use a different port"
535
+ return 1
536
+ fi
537
+ if ! check_port_available "$DASHBOARD_PORT"; then
538
+ log_error "Port $DASHBOARD_PORT is already in use"
539
+ log_info "Set LOKI_DASHBOARD_PORT to use a different port"
540
+ return 1
541
+ fi
542
+
543
+ # Warn about missing API keys
544
+ warn_missing_api_keys "$provider"
545
+
546
+ # Warn about network=none implications
547
+ if [[ "$SANDBOX_NETWORK" == "none" ]]; then
548
+ log_warn "Network disabled (--network=none)"
549
+ log_warn " - Git remote operations will fail"
550
+ log_warn " - Package installations will fail"
551
+ log_warn " - API calls to AI providers will fail"
552
+ fi
553
+
121
554
  # Check if already running
122
555
  if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
123
556
  log_warn "Sandbox already running: $CONTAINER_NAME"
@@ -177,9 +610,9 @@ start_sandbox() {
177
610
  docker_args+=("--volume" "$PROJECT_DIR:/workspace:rw")
178
611
  fi
179
612
 
180
- # Mount git config (read-only)
613
+ # Mount git config (read-only) - mount to /home/loki since container runs as user loki
181
614
  if [[ -f "$HOME/.gitconfig" ]]; then
182
- docker_args+=("--volume" "$HOME/.gitconfig:/root/.gitconfig:ro")
615
+ docker_args+=("--volume" "$HOME/.gitconfig:/home/loki/.gitconfig:ro")
183
616
  fi
184
617
 
185
618
  # SSH agent forwarding (more secure than mounting .ssh directory)
@@ -195,9 +628,9 @@ start_sandbox() {
195
628
  log_warn "SSH agent not available. Git operations may require manual auth."
196
629
  fi
197
630
 
198
- # Mount GitHub CLI config (read-only)
631
+ # Mount GitHub CLI config (read-only) - mount to /home/loki since container runs as user loki
199
632
  if [[ -d "$HOME/.config/gh" ]]; then
200
- docker_args+=("--volume" "$HOME/.config/gh:/root/.config/gh:ro")
633
+ docker_args+=("--volume" "$HOME/.config/gh:/home/loki/.config/gh:ro")
201
634
  fi
202
635
 
203
636
  # Pass API keys as environment variables (more secure than mounting files)
@@ -231,6 +664,14 @@ start_sandbox() {
231
664
  "--publish" "$DASHBOARD_PORT:57374"
232
665
  )
233
666
 
667
+ # Expose additional ports for testing (e.g., LOKI_EXTRA_PORTS="3000:3000,8080:8080")
668
+ if [[ -n "${LOKI_EXTRA_PORTS:-}" ]]; then
669
+ IFS=',' read -ra EXTRA_PORTS <<< "$LOKI_EXTRA_PORTS"
670
+ for port_mapping in "${EXTRA_PORTS[@]}"; do
671
+ docker_args+=("--publish" "$port_mapping")
672
+ done
673
+ fi
674
+
234
675
  # Working directory
235
676
  docker_args+=("--workdir" "/workspace")
236
677
 
@@ -240,9 +681,12 @@ start_sandbox() {
240
681
  # Build loki command
241
682
  local loki_cmd="loki start"
242
683
  if [[ -n "$prd_path" ]]; then
243
- # Convert to container path
244
- local container_prd="/workspace/$(realpath --relative-to="$PROJECT_DIR" "$prd_path" 2>/dev/null || echo "$prd_path")"
245
- loki_cmd="$loki_cmd $container_prd"
684
+ # Convert to container path (handle paths with spaces)
685
+ local relative_prd
686
+ relative_prd=$(realpath --relative-to="$PROJECT_DIR" "$prd_path" 2>/dev/null || basename "$prd_path")
687
+ local container_prd="/workspace/${relative_prd}"
688
+ # Quote path to handle spaces
689
+ loki_cmd="$loki_cmd \"$container_prd\""
246
690
  fi
247
691
  loki_cmd="$loki_cmd --provider $provider"
248
692
 
@@ -257,11 +701,20 @@ start_sandbox() {
257
701
  log_info "Access:"
258
702
  log_info " Dashboard: http://localhost:$DASHBOARD_PORT"
259
703
  log_info " API: http://localhost:$API_PORT"
704
+ if [[ -n "${LOKI_EXTRA_PORTS:-}" ]]; then
705
+ log_info " Extra: $LOKI_EXTRA_PORTS"
706
+ fi
260
707
  log_info ""
261
708
  log_info "Commands:"
262
- log_info " loki sandbox logs - View logs"
263
- log_info " loki sandbox shell - Open shell in container"
264
- log_info " loki sandbox stop - Stop sandbox"
709
+ log_info " loki sandbox logs - View logs"
710
+ log_info " loki sandbox shell - Open shell in container"
711
+ log_info " loki sandbox stop - Stop sandbox"
712
+ log_info ""
713
+ log_info "Testing (when ready):"
714
+ log_info " loki sandbox phase - Check SDLC phase & testing tips"
715
+ log_info " loki sandbox test - Run tests"
716
+ log_info " loki sandbox serve - Start dev server"
717
+ log_info " loki sandbox prompt 'msg' - Send real-time prompt"
265
718
  }
266
719
 
267
720
  stop_sandbox() {
@@ -273,11 +726,20 @@ stop_sandbox() {
273
726
  # Try graceful stop first (touch STOP file)
274
727
  docker exec "$CONTAINER_NAME" touch /workspace/.loki/STOP 2>/dev/null || true
275
728
 
276
- # Wait a bit for graceful shutdown
277
- sleep 2
729
+ # Wait for graceful shutdown (check every second for up to 10 seconds)
730
+ local waited=0
731
+ while [ $waited -lt 10 ]; do
732
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
733
+ log_success "Sandbox stopped gracefully"
734
+ return 0
735
+ fi
736
+ sleep 1
737
+ ((waited++))
738
+ done
278
739
 
279
740
  # Force stop if still running
280
- docker stop --time 10 "$CONTAINER_NAME" 2>/dev/null || true
741
+ log_info "Force stopping container..."
742
+ docker stop --time 5 "$CONTAINER_NAME" 2>/dev/null || true
281
743
  docker rm -f "$CONTAINER_NAME" 2>/dev/null || true
282
744
 
283
745
  log_success "Sandbox stopped"
@@ -286,6 +748,300 @@ stop_sandbox() {
286
748
  fi
287
749
  }
288
750
 
751
+ # Send a prompt/directive to the running sandbox
752
+ sandbox_prompt() {
753
+ local prompt="$*"
754
+
755
+ # Security check: prompt injection disabled by default
756
+ if [[ "$PROMPT_INJECTION_ENABLED" != "true" ]]; then
757
+ log_error "Prompt injection is disabled for security"
758
+ log_info ""
759
+ log_info "To enable, set LOKI_PROMPT_INJECTION=true"
760
+ log_info " Example: LOKI_PROMPT_INJECTION=true loki sandbox prompt 'your message'"
761
+ log_info ""
762
+ log_warn "WARNING: Only enable in trusted environments"
763
+ return 1
764
+ fi
765
+
766
+ if [[ -z "$prompt" ]]; then
767
+ log_error "Usage: loki sandbox prompt <your message>"
768
+ log_info "Example: loki sandbox prompt 'start the dev server and show me the URL'"
769
+ return 1
770
+ fi
771
+
772
+ check_docker
773
+
774
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
775
+ log_error "Sandbox is not running. Start it first with: loki sandbox start"
776
+ return 1
777
+ fi
778
+
779
+ log_info "Sending prompt to sandbox..."
780
+
781
+ # Write to HUMAN_INPUT.md inside the container
782
+ # Use heredoc to avoid command injection via single quotes in prompt
783
+ docker exec "$CONTAINER_NAME" bash -c 'cat > /workspace/.loki/HUMAN_INPUT.md' <<< "$prompt"
784
+
785
+ log_success "Prompt sent. Loki will process it in the next iteration."
786
+ log_info ""
787
+ log_info "Watch the response with: loki sandbox logs"
788
+ log_info "Or check status with: loki sandbox status"
789
+ }
790
+
791
+ # Run a command inside the sandbox and show output
792
+ sandbox_run() {
793
+ local cmd="$*"
794
+
795
+ if [[ -z "$cmd" ]]; then
796
+ log_error "Usage: loki sandbox run <command>"
797
+ log_info "Example: loki sandbox run 'npm run dev'"
798
+ return 1
799
+ fi
800
+
801
+ check_docker
802
+
803
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
804
+ log_error "Sandbox is not running"
805
+ return 1
806
+ fi
807
+
808
+ log_info "Running command in sandbox: $cmd"
809
+ docker exec -it "$CONTAINER_NAME" bash -c "cd /workspace && $cmd"
810
+ }
811
+
812
+ # Start a dev server inside the sandbox
813
+ sandbox_serve() {
814
+ local port="${1:-3000}"
815
+
816
+ check_docker
817
+
818
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
819
+ log_error "Sandbox is not running"
820
+ return 1
821
+ fi
822
+
823
+ log_info "Looking for dev server configuration..."
824
+
825
+ # Detect project type and start appropriate server
826
+ local serve_cmd=""
827
+
828
+ if docker exec "$CONTAINER_NAME" test -f /workspace/package.json; then
829
+ # Check for common dev server scripts
830
+ local has_dev=$(docker exec "$CONTAINER_NAME" jq -r '.scripts.dev // empty' /workspace/package.json 2>/dev/null)
831
+ local has_start=$(docker exec "$CONTAINER_NAME" jq -r '.scripts.start // empty' /workspace/package.json 2>/dev/null)
832
+
833
+ if [[ -n "$has_dev" ]]; then
834
+ serve_cmd="npm run dev"
835
+ elif [[ -n "$has_start" ]]; then
836
+ serve_cmd="npm start"
837
+ fi
838
+ elif docker exec "$CONTAINER_NAME" test -f /workspace/requirements.txt; then
839
+ # Python project
840
+ if docker exec "$CONTAINER_NAME" test -f /workspace/manage.py; then
841
+ serve_cmd="python manage.py runserver 0.0.0.0:$port"
842
+ elif docker exec "$CONTAINER_NAME" test -f /workspace/app.py; then
843
+ serve_cmd="python app.py"
844
+ fi
845
+ fi
846
+
847
+ if [[ -z "$serve_cmd" ]]; then
848
+ log_warn "Could not auto-detect dev server"
849
+ log_info "Try: loki sandbox run 'your-dev-command'"
850
+ return 1
851
+ fi
852
+
853
+ log_success "Starting dev server: $serve_cmd"
854
+ log_info ""
855
+ log_info "Access the app at:"
856
+ log_info " http://localhost:$port"
857
+ log_info ""
858
+ log_info "Press Ctrl+C to stop the server (sandbox continues running)"
859
+
860
+ docker exec -it "$CONTAINER_NAME" bash -c "cd /workspace && $serve_cmd"
861
+ }
862
+
863
+ # Run tests inside the sandbox
864
+ sandbox_test() {
865
+ local test_type="${1:-all}"
866
+
867
+ check_docker
868
+
869
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
870
+ log_error "Sandbox is not running"
871
+ return 1
872
+ fi
873
+
874
+ log_info "Running tests in sandbox..."
875
+
876
+ # Detect project type and test command
877
+ local test_cmd=""
878
+
879
+ if docker exec "$CONTAINER_NAME" test -f /workspace/package.json; then
880
+ case "$test_type" in
881
+ unit)
882
+ test_cmd=$(docker exec "$CONTAINER_NAME" jq -r '.scripts["test:unit"] // .scripts.test // "npm test"' /workspace/package.json 2>/dev/null)
883
+ ;;
884
+ integration)
885
+ test_cmd=$(docker exec "$CONTAINER_NAME" jq -r '.scripts["test:integration"] // empty' /workspace/package.json 2>/dev/null)
886
+ [[ -z "$test_cmd" ]] && test_cmd="npm run test:integration"
887
+ ;;
888
+ e2e)
889
+ if docker exec "$CONTAINER_NAME" test -d /workspace/node_modules/.bin/playwright; then
890
+ test_cmd="npx playwright test"
891
+ elif docker exec "$CONTAINER_NAME" test -d /workspace/node_modules/.bin/cypress; then
892
+ test_cmd="npx cypress run"
893
+ else
894
+ test_cmd=$(docker exec "$CONTAINER_NAME" jq -r '.scripts["test:e2e"] // empty' /workspace/package.json 2>/dev/null)
895
+ fi
896
+ ;;
897
+ all|*)
898
+ test_cmd=$(docker exec "$CONTAINER_NAME" jq -r '.scripts.test // "npm test"' /workspace/package.json 2>/dev/null)
899
+ ;;
900
+ esac
901
+ elif docker exec "$CONTAINER_NAME" test -f /workspace/requirements.txt; then
902
+ case "$test_type" in
903
+ unit)
904
+ test_cmd="pytest tests/unit/ -v"
905
+ ;;
906
+ integration)
907
+ test_cmd="pytest tests/integration/ -v"
908
+ ;;
909
+ e2e)
910
+ test_cmd="pytest tests/e2e/ -v"
911
+ ;;
912
+ all|*)
913
+ test_cmd="pytest -v"
914
+ ;;
915
+ esac
916
+ elif docker exec "$CONTAINER_NAME" test -f /workspace/Cargo.toml; then
917
+ test_cmd="cargo test"
918
+ elif docker exec "$CONTAINER_NAME" test -f /workspace/go.mod; then
919
+ test_cmd="go test ./..."
920
+ fi
921
+
922
+ if [[ -z "$test_cmd" ]] || [[ "$test_cmd" == "null" ]]; then
923
+ log_warn "Could not auto-detect test command"
924
+ log_info "Supported test types: unit, integration, e2e, all"
925
+ log_info "Or run: loki sandbox run 'your-test-command'"
926
+ return 1
927
+ fi
928
+
929
+ log_success "Running: $test_cmd"
930
+ docker exec -it "$CONTAINER_NAME" bash -c "cd /workspace && $test_cmd"
931
+ }
932
+
933
+ # Check SDLC phase and suggest testing
934
+ sandbox_phase() {
935
+ check_docker
936
+
937
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
938
+ log_error "Sandbox is not running"
939
+ return 1
940
+ fi
941
+
942
+ # Get current phase from orchestrator state
943
+ local phase=$(docker exec "$CONTAINER_NAME" bash -c \
944
+ "python3 -c \"import json; print(json.load(open('/workspace/.loki/state/orchestrator.json')).get('currentPhase', 'UNKNOWN'))\" 2>/dev/null" \
945
+ || echo "UNKNOWN")
946
+
947
+ echo ""
948
+ echo -e "${BOLD}Current SDLC Phase: ${CYAN}$phase${NC}"
949
+ echo ""
950
+
951
+ case "$phase" in
952
+ BOOTSTRAP|DISCOVERY|ARCHITECTURE)
953
+ log_info "Phase $phase - No testing required yet"
954
+ log_info "Testing begins in DEVELOPMENT phase"
955
+ ;;
956
+ INFRASTRUCTURE)
957
+ log_info "Phase $phase - Infrastructure testing"
958
+ echo " Recommended:"
959
+ echo " loki sandbox run 'docker-compose up -d'"
960
+ echo " loki sandbox run 'terraform plan'"
961
+ ;;
962
+ DEVELOPMENT)
963
+ log_info "Phase $phase - Development testing recommended"
964
+ echo ""
965
+ echo " Run dev server:"
966
+ echo " loki sandbox serve"
967
+ echo ""
968
+ echo " Run unit tests:"
969
+ echo " loki sandbox test unit"
970
+ echo ""
971
+ echo " Manual testing:"
972
+ echo " loki sandbox shell"
973
+ ;;
974
+ QA)
975
+ log_info "Phase $phase - Full testing required"
976
+ echo ""
977
+ echo " Run all tests:"
978
+ echo " loki sandbox test all"
979
+ echo ""
980
+ echo " Run specific test suites:"
981
+ echo " loki sandbox test unit"
982
+ echo " loki sandbox test integration"
983
+ echo " loki sandbox test e2e"
984
+ echo ""
985
+ echo " Interactive testing:"
986
+ echo " loki sandbox serve"
987
+ echo " loki sandbox shell"
988
+ echo ""
989
+ echo " To report issues during testing:"
990
+ echo " loki sandbox prompt 'Found bug: describe the bug here'"
991
+ ;;
992
+ DEPLOYMENT)
993
+ log_info "Phase $phase - Smoke testing"
994
+ echo ""
995
+ echo " Run smoke tests:"
996
+ echo " loki sandbox run 'npm run test:smoke'"
997
+ echo ""
998
+ echo " Check deployment status:"
999
+ echo " loki sandbox run 'curl -s http://localhost:3000/health'"
1000
+ ;;
1001
+ GROWTH|*)
1002
+ log_info "Phase $phase - Continuous testing"
1003
+ echo ""
1004
+ echo " Run regression tests:"
1005
+ echo " loki sandbox test all"
1006
+ echo ""
1007
+ echo " Performance testing:"
1008
+ echo " loki sandbox run 'npx k6 run tests/load.js'"
1009
+ ;;
1010
+ esac
1011
+
1012
+ echo ""
1013
+ }
1014
+
1015
+ # Expose additional ports (for development testing)
1016
+ sandbox_expose() {
1017
+ local port="${1:-}"
1018
+
1019
+ if [[ -z "$port" ]]; then
1020
+ log_error "Usage: loki sandbox expose <port>"
1021
+ log_info "Example: loki sandbox expose 8080"
1022
+ return 1
1023
+ fi
1024
+
1025
+ check_docker
1026
+
1027
+ if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
1028
+ log_error "Sandbox is not running"
1029
+ log_info "Note: Ports must be exposed when starting the sandbox"
1030
+ log_info "Set LOKI_EXTRA_PORTS='$port:$port' before running 'loki sandbox start'"
1031
+ return 1
1032
+ fi
1033
+
1034
+ log_warn "Cannot expose ports on running container"
1035
+ log_info ""
1036
+ log_info "To expose additional ports, restart sandbox with:"
1037
+ echo ""
1038
+ echo " LOKI_EXTRA_PORTS='$port:$port' loki sandbox start"
1039
+ echo ""
1040
+ log_info "Or access via sandbox shell:"
1041
+ echo " loki sandbox shell"
1042
+ echo " # Then run your server inside the container"
1043
+ }
1044
+
289
1045
  sandbox_status() {
290
1046
  check_docker
291
1047
 
@@ -338,17 +1094,39 @@ sandbox_build() {
338
1094
  }
339
1095
 
340
1096
  show_help() {
341
- echo -e "${BOLD}Loki Mode Docker Sandbox${NC}"
1097
+ echo -e "${BOLD}Loki Mode Sandbox${NC}"
342
1098
  echo ""
343
1099
  echo "Usage: loki sandbox <command> [options]"
344
1100
  echo ""
345
1101
  echo "Commands:"
346
- echo " start [PRD] Start sandbox with optional PRD"
347
- echo " stop Stop running sandbox"
348
- echo " status Check sandbox status"
349
- echo " logs [N] View last N log lines (default: 100)"
350
- echo " shell Open bash shell in sandbox"
351
- echo " build Build/rebuild sandbox image"
1102
+ echo " start [PRD] Start sandbox with optional PRD"
1103
+ echo " stop Stop running sandbox"
1104
+ echo " status Check sandbox status"
1105
+ echo " logs [N] View last N log lines (default: 100)"
1106
+ echo " shell Open bash shell in sandbox"
1107
+ echo " build Build/rebuild sandbox image"
1108
+ echo " cleanup Remove orphaned sandbox worktrees/branches"
1109
+ echo ""
1110
+ echo "Interactive Commands (while sandbox is running):"
1111
+ echo " prompt <msg> Send a prompt/directive to Loki in real-time"
1112
+ echo " run <cmd> Run a command inside the sandbox"
1113
+ echo " serve [port] Auto-detect and start dev server (default port: 3000)"
1114
+ echo ""
1115
+ echo "Testing Commands:"
1116
+ echo " test [type] Run tests (type: unit, integration, e2e, all)"
1117
+ echo " phase Check SDLC phase and get testing suggestions"
1118
+ echo " expose <port> Show how to expose additional ports"
1119
+ echo ""
1120
+ echo "Mode Options:"
1121
+ echo " --docker Force Docker sandbox (full isolation)"
1122
+ echo " --worktree Force git worktree sandbox (soft isolation)"
1123
+ echo " --auto Auto-detect best mode (default)"
1124
+ echo ""
1125
+ echo "Sandbox Modes:"
1126
+ echo " Docker (default) - Full isolation with seccomp, dropped capabilities,"
1127
+ echo " resource limits, network control"
1128
+ echo " Worktree - Git worktree isolation (fallback if Docker unavailable)"
1129
+ echo " Warning: No filesystem/network/process isolation"
352
1130
  echo ""
353
1131
  echo "Environment Variables:"
354
1132
  echo " LOKI_SANDBOX_IMAGE Docker image (default: loki-mode:sandbox)"
@@ -356,19 +1134,29 @@ show_help() {
356
1134
  echo " LOKI_SANDBOX_CPUS CPU limit (default: 2)"
357
1135
  echo " LOKI_SANDBOX_MEMORY Memory limit (default: 4g)"
358
1136
  echo " LOKI_SANDBOX_READONLY Mount project read-only (default: false)"
1137
+ echo " LOKI_SANDBOX_CLEANUP Auto-cleanup worktree on stop (default: true)"
1138
+ echo " LOKI_EXTRA_PORTS Expose extra ports (e.g., '3000:3000,8080:8080')"
1139
+ echo " LOKI_PROMPT_INJECTION Enable real-time prompts (default: false)"
359
1140
  echo ""
360
- echo "Security Features:"
1141
+ echo "Security Features (Docker mode):"
361
1142
  echo " - Seccomp profile restricts syscalls"
362
1143
  echo " - No new privileges flag"
363
1144
  echo " - Dropped capabilities"
364
1145
  echo " - Resource limits (CPU, memory, PIDs)"
365
1146
  echo " - API keys passed as env vars (not mounted)"
1147
+ echo " - Prompt injection DISABLED by default (enterprise security)"
366
1148
  echo ""
367
1149
  echo "Examples:"
368
- echo " loki sandbox start # Start with defaults"
369
- echo " loki sandbox start ./prd.md # Start with PRD"
370
- echo " LOKI_SANDBOX_MEMORY=8g loki sandbox start # 8GB memory limit"
371
- echo " LOKI_SANDBOX_NETWORK=none loki sandbox start # No network"
1150
+ echo " loki sandbox start # Start (auto-detect mode)"
1151
+ echo " loki sandbox start --docker ./prd.md # Force Docker mode"
1152
+ echo " loki sandbox start --worktree # Force worktree mode"
1153
+ echo " loki sandbox prompt 'start the app and show URL' # Send prompt"
1154
+ echo " loki sandbox serve # Start dev server"
1155
+ echo " loki sandbox test # Run all tests"
1156
+ echo " loki sandbox test unit # Run unit tests only"
1157
+ echo " loki sandbox phase # Check phase, get testing tips"
1158
+ echo " loki sandbox run 'npm test' # Run custom command"
1159
+ echo " loki sandbox cleanup # Remove old worktrees"
372
1160
  }
373
1161
 
374
1162
  #===============================================================================
@@ -379,18 +1167,50 @@ main() {
379
1167
  local command="${1:-help}"
380
1168
  shift || true
381
1169
 
1170
+ # Parse mode option
1171
+ local mode="auto"
1172
+ local args=()
1173
+ while [[ $# -gt 0 ]]; do
1174
+ case "$1" in
1175
+ --docker) mode="docker"; shift ;;
1176
+ --worktree) mode="worktree"; shift ;;
1177
+ --auto) mode="auto"; shift ;;
1178
+ *) args+=("$1"); shift ;;
1179
+ esac
1180
+ done
1181
+
1182
+ # Detect sandbox mode for commands that need it
1183
+ local sandbox_mode=""
1184
+ case "$command" in
1185
+ start|stop|status|prompt)
1186
+ sandbox_mode=$(detect_sandbox_mode "$mode") || exit 1
1187
+ ;;
1188
+ esac
1189
+
382
1190
  case "$command" in
383
1191
  start)
384
- start_sandbox "$@"
1192
+ if [[ "$sandbox_mode" == "docker" ]]; then
1193
+ start_sandbox "${args[@]}"
1194
+ else
1195
+ start_worktree_sandbox "${args[@]}"
1196
+ fi
385
1197
  ;;
386
1198
  stop)
387
- stop_sandbox
1199
+ if [[ "$sandbox_mode" == "docker" ]]; then
1200
+ stop_sandbox
1201
+ else
1202
+ stop_worktree_sandbox
1203
+ fi
388
1204
  ;;
389
1205
  status)
390
- sandbox_status
1206
+ if [[ "$sandbox_mode" == "docker" ]]; then
1207
+ sandbox_status
1208
+ else
1209
+ worktree_sandbox_status
1210
+ fi
391
1211
  ;;
392
1212
  logs)
393
- sandbox_logs "$@"
1213
+ sandbox_logs "${args[@]}"
394
1214
  ;;
395
1215
  shell)
396
1216
  sandbox_shell
@@ -398,6 +1218,31 @@ main() {
398
1218
  build)
399
1219
  sandbox_build
400
1220
  ;;
1221
+ prompt)
1222
+ if [[ "$sandbox_mode" == "docker" ]]; then
1223
+ sandbox_prompt "${args[@]}"
1224
+ else
1225
+ worktree_sandbox_prompt "${args[@]}"
1226
+ fi
1227
+ ;;
1228
+ run)
1229
+ sandbox_run "${args[@]}"
1230
+ ;;
1231
+ cleanup)
1232
+ cleanup_worktrees
1233
+ ;;
1234
+ serve)
1235
+ sandbox_serve "${args[@]}"
1236
+ ;;
1237
+ test)
1238
+ sandbox_test "${args[@]}"
1239
+ ;;
1240
+ phase)
1241
+ sandbox_phase
1242
+ ;;
1243
+ expose)
1244
+ sandbox_expose "${args[@]}"
1245
+ ;;
401
1246
  help|--help|-h)
402
1247
  show_help
403
1248
  ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loki-mode",
3
- "version": "5.6.0",
3
+ "version": "5.6.1",
4
4
  "description": "Multi-agent autonomous startup system for Claude Code, Codex CLI, and Gemini CLI",
5
5
  "keywords": [
6
6
  "claude",
@@ -11,7 +11,7 @@
11
11
  "startup",
12
12
  "multi-agent"
13
13
  ],
14
- "homepage": "https://github.com/asklokesh/loki-mode",
14
+ "homepage": "https://asklokesh.github.io/loki-mode",
15
15
  "bugs": {
16
16
  "url": "https://github.com/asklokesh/loki-mode/issues"
17
17
  },