loki-mode 5.30.0 → 5.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/autonomy/loki CHANGED
@@ -306,7 +306,7 @@ show_help() {
306
306
  echo " stop Stop execution immediately"
307
307
  echo " pause Pause after current session"
308
308
  echo " resume Resume paused execution"
309
- echo " status Show current status"
309
+ echo " status [--json] Show current status (--json for machine-readable)"
310
310
  echo " logs Show recent log output"
311
311
  echo " dashboard [cmd] Dashboard server (start|stop|status|url|open)"
312
312
  echo " provider [cmd] Manage AI provider (show|set|list|info)"
@@ -317,11 +317,13 @@ show_help() {
317
317
  echo " voice [cmd] Voice input for PRD creation (status|listen|dictate|speak|start)"
318
318
  echo " import Import GitHub issues as tasks"
319
319
  echo " config [cmd] Manage configuration (show|init|edit|path)"
320
+ echo " completions [bash|zsh] Output shell completion scripts"
320
321
  echo " memory [cmd] Cross-project learnings (list|show|search|stats)"
321
322
  echo " compound [cmd] Knowledge compounding (list|show|search|run|stats)"
322
323
  echo " council [cmd] Completion council (status|verdicts|convergence|force-review|report)"
323
324
  echo " dogfood Show self-development statistics"
324
325
  echo " reset [target] Reset session state (all|retries|failed)"
326
+ echo " doctor [--json] Check system prerequisites"
325
327
  echo " version Show version"
326
328
  echo " help Show this help"
327
329
  echo ""
@@ -389,12 +391,22 @@ cmd_start() {
389
391
  echo " --sandbox Run in Docker sandbox"
390
392
  echo " --skip-memory Skip loading memory context at startup"
391
393
  echo " --budget USD Cost budget limit (auto-pause when exceeded)"
394
+ echo " --yes, -y Skip confirmation prompts (auto-confirm)"
395
+ echo ""
396
+ echo "Environment Variables:"
397
+ echo " LOKI_PRD_FILE Path to PRD file (alternative to positional arg)"
398
+ echo " LOKI_AUTO_CONFIRM Set to 'true'/'false' to control prompts (takes precedence over CI)"
399
+ echo " CI Fallback: auto-confirms when 'true' and LOKI_AUTO_CONFIRM is unset"
400
+ echo " LOKI_MAX_ITERATIONS Max iteration count"
401
+ echo " LOKI_BUDGET_LIMIT Cost budget limit in USD"
392
402
  echo ""
393
403
  echo "Examples:"
394
404
  echo " loki start # Interactive or resume existing"
395
405
  echo " loki start ./prd.md # Start with PRD file"
396
406
  echo " loki start ./prd.md --parallel # Parallel mode with worktrees"
397
407
  echo " loki start --provider codex # Use OpenAI Codex CLI"
408
+ echo " loki start --yes # Skip confirmation prompt"
409
+ echo " LOKI_PRD_FILE=./prd.md loki start # PRD via env var"
398
410
  exit 0
399
411
  ;;
400
412
  --provider)
@@ -444,6 +456,10 @@ cmd_start() {
444
456
  export LOKI_SKIP_MEMORY=true
445
457
  shift
446
458
  ;;
459
+ --yes|-y)
460
+ export LOKI_AUTO_CONFIRM=true
461
+ shift
462
+ ;;
447
463
  --budget)
448
464
  if [[ -n "${2:-}" ]]; then
449
465
  if ! echo "$2" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
@@ -477,19 +493,32 @@ cmd_start() {
477
493
  esac
478
494
  done
479
495
 
496
+ # Support LOKI_PRD_FILE environment variable as fallback
497
+ if [ -z "$prd_file" ] && [ -n "${LOKI_PRD_FILE:-}" ]; then
498
+ prd_file="$LOKI_PRD_FILE"
499
+ fi
500
+
480
501
  if [ -n "$prd_file" ]; then
481
502
  args+=("$prd_file")
482
503
  else
483
504
  # No PRD file specified -- warn and confirm before consuming API credits
484
- echo -e "${YELLOW}Warning: No PRD file specified.${NC}"
485
- echo "Loki Mode will start autonomous execution in the current directory"
486
- echo "without a requirements document."
487
- echo ""
488
- echo -e "This will consume API credits. Continue? [y/N] \c"
489
- read -r confirm
490
- if [[ ! "$confirm" =~ ^[Yy] ]]; then
491
- echo "Aborted. Usage: loki start <path-to-prd.md>"
492
- exit 0
505
+ # Auto-confirm in CI environments or when LOKI_AUTO_CONFIRM is set
506
+ # LOKI_AUTO_CONFIRM takes precedence when explicitly set;
507
+ # fall back to CI env var only when LOKI_AUTO_CONFIRM is unset
508
+ local _auto_confirm="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
509
+ if [[ "$_auto_confirm" == "true" ]]; then
510
+ echo -e "${YELLOW}Warning: No PRD file specified. Auto-confirming (CI mode).${NC}"
511
+ else
512
+ echo -e "${YELLOW}Warning: No PRD file specified.${NC}"
513
+ echo "Loki Mode will start autonomous execution in the current directory"
514
+ echo "without a requirements document."
515
+ echo ""
516
+ echo -e "This will consume API credits. Continue? [y/N] \c"
517
+ read -r confirm
518
+ if [[ ! "$confirm" =~ ^[Yy] ]]; then
519
+ echo "Aborted. Usage: loki start <path-to-prd.md>"
520
+ exit 0
521
+ fi
493
522
  fi
494
523
  fi
495
524
 
@@ -861,6 +890,167 @@ cmd_status() {
861
890
  fi
862
891
  }
863
892
 
893
+ # JSON output for loki status --json
894
+ cmd_status_json() {
895
+ local skill_dir="$SKILL_DIR"
896
+ local loki_dir="$LOKI_DIR"
897
+ local dashboard_port="${LOKI_DASHBOARD_PORT:-57374}"
898
+ local env_provider="${LOKI_PROVIDER:-claude}"
899
+
900
+ python3 -c "
901
+ import json, os, sys, time
902
+
903
+ skill_dir = sys.argv[1]
904
+ loki_dir = sys.argv[2]
905
+ dashboard_port = sys.argv[3]
906
+ env_provider = sys.argv[4]
907
+ result = {}
908
+
909
+ # Version
910
+ version_file = os.path.join(skill_dir, 'VERSION')
911
+ if os.path.isfile(version_file):
912
+ with open(version_file) as f:
913
+ result['version'] = f.read().strip()
914
+ else:
915
+ result['version'] = 'unknown'
916
+
917
+ # Check if session exists
918
+ if not os.path.isdir(loki_dir):
919
+ result['status'] = 'inactive'
920
+ result['phase'] = None
921
+ result['iteration'] = 0
922
+ result['provider'] = env_provider
923
+ result['dashboard_url'] = None
924
+ result['pid'] = None
925
+ result['elapsed_time'] = 0
926
+ result['task_counts'] = {'total': 0, 'completed': 0, 'failed': 0, 'pending': 0}
927
+ print(json.dumps(result, indent=2))
928
+ sys.exit(0)
929
+
930
+ # Status from signals and session.json
931
+ if os.path.isfile(os.path.join(loki_dir, 'PAUSE')):
932
+ result['status'] = 'paused'
933
+ elif os.path.isfile(os.path.join(loki_dir, 'STOP')):
934
+ result['status'] = 'stopped'
935
+ else:
936
+ session_file = os.path.join(loki_dir, 'session.json')
937
+ if os.path.isfile(session_file):
938
+ try:
939
+ with open(session_file) as f:
940
+ session = json.load(f)
941
+ result['status'] = session.get('status', 'unknown')
942
+ except Exception:
943
+ result['status'] = 'unknown'
944
+ else:
945
+ result['status'] = 'unknown'
946
+
947
+ # Phase and iteration from dashboard-state.json
948
+ ds_file = os.path.join(loki_dir, 'dashboard-state.json')
949
+ if os.path.isfile(ds_file):
950
+ try:
951
+ with open(ds_file) as f:
952
+ ds = json.load(f)
953
+ result['phase'] = ds.get('phase', ds.get('currentPhase'))
954
+ result['iteration'] = ds.get('iteration', ds.get('currentIteration', 0))
955
+ except Exception:
956
+ result['phase'] = None
957
+ result['iteration'] = 0
958
+ else:
959
+ orch_file = os.path.join(loki_dir, 'state', 'orchestrator.json')
960
+ if os.path.isfile(orch_file):
961
+ try:
962
+ with open(orch_file) as f:
963
+ orch = json.load(f)
964
+ result['phase'] = orch.get('currentPhase')
965
+ result['iteration'] = orch.get('currentIteration', 0)
966
+ except Exception:
967
+ result['phase'] = None
968
+ result['iteration'] = 0
969
+ else:
970
+ result['phase'] = None
971
+ result['iteration'] = 0
972
+
973
+ # Provider
974
+ provider_file = os.path.join(loki_dir, 'state', 'provider')
975
+ if os.path.isfile(provider_file):
976
+ with open(provider_file) as f:
977
+ result['provider'] = f.read().strip()
978
+ else:
979
+ result['provider'] = env_provider
980
+
981
+ # PID
982
+ pid_file = os.path.join(loki_dir, 'loki.pid')
983
+ if os.path.isfile(pid_file):
984
+ try:
985
+ with open(pid_file) as f:
986
+ result['pid'] = int(f.read().strip())
987
+ except (ValueError, Exception):
988
+ result['pid'] = None
989
+ else:
990
+ result['pid'] = None
991
+
992
+ # Elapsed time from session.json
993
+ session_file = os.path.join(loki_dir, 'session.json')
994
+ if os.path.isfile(session_file):
995
+ try:
996
+ with open(session_file) as f:
997
+ session = json.load(f)
998
+ start_time = session.get('start_time', session.get('startTime'))
999
+ if start_time:
1000
+ if isinstance(start_time, (int, float)):
1001
+ result['elapsed_time'] = int(time.time() - start_time)
1002
+ else:
1003
+ from datetime import datetime
1004
+ dt = datetime.fromisoformat(start_time.replace('Z', '+00:00'))
1005
+ result['elapsed_time'] = int(time.time() - dt.timestamp())
1006
+ else:
1007
+ result['elapsed_time'] = 0
1008
+ except Exception:
1009
+ result['elapsed_time'] = 0
1010
+ else:
1011
+ result['elapsed_time'] = 0
1012
+
1013
+ # Dashboard URL
1014
+ dashboard_pid_file = os.path.join(loki_dir, 'dashboard', 'dashboard.pid')
1015
+ dashboard_url = None
1016
+ if os.path.isfile(dashboard_pid_file):
1017
+ try:
1018
+ with open(dashboard_pid_file) as f:
1019
+ dpid = int(f.read().strip())
1020
+ os.kill(dpid, 0)
1021
+ dashboard_url = 'http://127.0.0.1:' + dashboard_port + '/'
1022
+ except (ProcessLookupError, PermissionError, ValueError, Exception):
1023
+ pass
1024
+ result['dashboard_url'] = dashboard_url
1025
+
1026
+ # Task counts from queue files
1027
+ task_counts = {'total': 0, 'completed': 0, 'failed': 0, 'pending': 0}
1028
+ queue_dir = os.path.join(loki_dir, 'queue')
1029
+ if os.path.isdir(queue_dir):
1030
+ for name, key in [('pending.json', 'pending'), ('completed.json', 'completed'), ('failed.json', 'failed')]:
1031
+ fpath = os.path.join(queue_dir, name)
1032
+ if os.path.isfile(fpath):
1033
+ try:
1034
+ with open(fpath) as f:
1035
+ data = json.load(f)
1036
+ if isinstance(data, list):
1037
+ task_counts[key] = len(data)
1038
+ elif isinstance(data, dict) and 'tasks' in data:
1039
+ task_counts[key] = len(data['tasks'])
1040
+ except Exception:
1041
+ pass
1042
+ task_counts['total'] = task_counts['pending'] + task_counts['completed'] + task_counts['failed']
1043
+ result['task_counts'] = task_counts
1044
+
1045
+ print(json.dumps(result, indent=2))
1046
+ " "$skill_dir" "$loki_dir" "$dashboard_port" "$env_provider"
1047
+
1048
+ if [ $? -ne 0 ]; then
1049
+ echo '{"error": "Failed to generate JSON status. Ensure python3 is available."}' >&2
1050
+ return 1
1051
+ fi
1052
+ }
1053
+
864
1054
  # Provider management
865
1055
  cmd_provider() {
866
1056
  local subcommand="${1:-show}"
@@ -2268,6 +2458,290 @@ cmd_config_path() {
2268
2458
  echo "Create a config file with: loki config init"
2269
2459
  }
2270
2460
 
2461
+ # Check system prerequisites
2462
+ cmd_doctor() {
2463
+ local json_output=false
2464
+
2465
+ while [[ $# -gt 0 ]]; do
2466
+ case "$1" in
2467
+ --json)
2468
+ json_output=true
2469
+ shift
2470
+ ;;
2471
+ --help|-h)
2472
+ echo -e "${BOLD}loki doctor${NC} - Check system prerequisites"
2473
+ echo ""
2474
+ echo "Usage: loki doctor [--json]"
2475
+ echo ""
2476
+ echo "Options:"
2477
+ echo " --json Output machine-readable JSON"
2478
+ echo ""
2479
+ echo "Checks: node, python3, jq, git, curl, bash version,"
2480
+ echo " claude/codex/gemini CLIs, and disk space."
2481
+ return 0
2482
+ ;;
2483
+ *)
2484
+ echo -e "${RED}Unknown option: $1${NC}"
2485
+ echo "Usage: loki doctor [--json]"
2486
+ return 1
2487
+ ;;
2488
+ esac
2489
+ done
2490
+
2491
+ if [ "$json_output" = true ]; then
2492
+ cmd_doctor_json
2493
+ return $?
2494
+ fi
2495
+
2496
+ echo -e "${BOLD}Loki Mode Doctor${NC}"
2497
+ echo ""
2498
+ echo "Checking system prerequisites..."
2499
+ echo ""
2500
+
2501
+ local pass_count=0
2502
+ local fail_count=0
2503
+ local warn_count=0
2504
+
2505
+ # Helper: check command exists and optionally check version
2506
+ doctor_check() {
2507
+ local name="$1"
2508
+ local cmd="$2"
2509
+ local required="$3" # required, recommended, optional
2510
+ local min_version="${4:-}"
2511
+
2512
+ if ! command -v "$cmd" &> /dev/null; then
2513
+ if [ "$required" = "required" ]; then
2514
+ echo -e " ${RED}FAIL${NC} $name - not found"
2515
+ fail_count=$((fail_count + 1))
2516
+ elif [ "$required" = "recommended" ]; then
2517
+ echo -e " ${YELLOW}WARN${NC} $name - not found (recommended)"
2518
+ warn_count=$((warn_count + 1))
2519
+ else
2520
+ echo -e " ${YELLOW}WARN${NC} $name - not found (optional)"
2521
+ warn_count=$((warn_count + 1))
2522
+ fi
2523
+ return 1
2524
+ fi
2525
+
2526
+ local version=""
2527
+ case "$cmd" in
2528
+ node)
2529
+ version=$(node --version 2>/dev/null | tr -d 'v')
2530
+ ;;
2531
+ python3)
2532
+ version=$(python3 --version 2>/dev/null | awk '{print $2}')
2533
+ ;;
2534
+ git)
2535
+ version=$(git --version 2>/dev/null | awk '{print $3}')
2536
+ ;;
2537
+ bash)
2538
+ version=$("$cmd" --version 2>/dev/null | head -1 | sed 's/.*version \([0-9.]*\).*/\1/')
2539
+ ;;
2540
+ jq)
2541
+ version=$(jq --version 2>/dev/null | tr -d 'jq-')
2542
+ ;;
2543
+ curl)
2544
+ version=$(curl --version 2>/dev/null | head -1 | awk '{print $2}')
2545
+ ;;
2546
+ claude)
2547
+ version=$(claude --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
2548
+ ;;
2549
+ codex)
2550
+ version=$(codex --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
2551
+ ;;
2552
+ gemini)
2553
+ version=$(gemini --version 2>/dev/null | head -1 | sed 's/[^0-9.]//g' | head -1)
2554
+ ;;
2555
+ esac
2556
+
2557
+ local version_display=""
2558
+ if [ -n "$version" ]; then
2559
+ version_display=" (v$version)"
2560
+ fi
2561
+
2562
+ # Simple major version check if min_version is specified
2563
+ if [ -n "$min_version" ] && [ -n "$version" ]; then
2564
+ local cur_major cur_minor min_major min_minor
2565
+ cur_major=$(echo "$version" | cut -d. -f1)
2566
+ min_major=$(echo "$min_version" | cut -d. -f1)
2567
+ cur_minor=$(echo "$version" | cut -d. -f2)
2568
+ min_minor=$(echo "$min_version" | cut -d. -f2)
2569
+
2570
+ if [ "$cur_major" -lt "$min_major" ] 2>/dev/null || \
2571
+ { [ "$cur_major" -eq "$min_major" ] 2>/dev/null && [ "$cur_minor" -lt "$min_minor" ] 2>/dev/null; }; then
2572
+ if [ "$required" = "required" ]; then
2573
+ echo -e " ${RED}FAIL${NC} $name$version_display - requires >= $min_version"
2574
+ fail_count=$((fail_count + 1))
2575
+ else
2576
+ echo -e " ${YELLOW}WARN${NC} $name$version_display - recommended >= $min_version"
2577
+ warn_count=$((warn_count + 1))
2578
+ fi
2579
+ return 1
2580
+ fi
2581
+ fi
2582
+
2583
+ echo -e " ${GREEN}PASS${NC} $name$version_display"
2584
+ pass_count=$((pass_count + 1))
2585
+ return 0
2586
+ }
2587
+
2588
+ echo -e "${CYAN}Required:${NC}"
2589
+ doctor_check "Node.js (>= 18)" node required 18.0
2590
+ doctor_check "Python 3 (>= 3.8)" python3 required 3.8
2591
+ doctor_check "jq" jq required
2592
+ doctor_check "git" git required
2593
+ doctor_check "curl" curl required
2594
+ echo ""
2595
+
2596
+ echo -e "${CYAN}AI Providers:${NC}"
2597
+ doctor_check "Claude CLI" claude optional
2598
+ doctor_check "Codex CLI" codex optional
2599
+ doctor_check "Gemini CLI" gemini optional
2600
+ echo ""
2601
+
2602
+ echo -e "${CYAN}System:${NC}"
2603
+ doctor_check "bash (>= 4.0)" bash recommended 4.0
2604
+
2605
+ # Disk space check
2606
+ local disk_avail
2607
+ if command -v df &> /dev/null; then
2608
+ # Get available space in GB (works on macOS and Linux)
2609
+ disk_avail=$(df -g "$HOME" 2>/dev/null | tail -1 | awk '{print $4}')
2610
+ if [ -z "$disk_avail" ]; then
2611
+ # Linux fallback (df -g may not work)
2612
+ disk_avail=$(df -BG "$HOME" 2>/dev/null | tail -1 | awk '{print $4}' | tr -d 'G')
2613
+ fi
2614
+ if [ -n "$disk_avail" ] && [ "$disk_avail" -gt 0 ] 2>/dev/null; then
2615
+ if [ "$disk_avail" -lt 1 ]; then
2616
+ echo -e " ${RED}FAIL${NC} Disk space: ${disk_avail}GB available (need >= 1GB)"
2617
+ fail_count=$((fail_count + 1))
2618
+ elif [ "$disk_avail" -lt 5 ]; then
2619
+ echo -e " ${YELLOW}WARN${NC} Disk space: ${disk_avail}GB available (low)"
2620
+ warn_count=$((warn_count + 1))
2621
+ else
2622
+ echo -e " ${GREEN}PASS${NC} Disk space: ${disk_avail}GB available"
2623
+ pass_count=$((pass_count + 1))
2624
+ fi
2625
+ else
2626
+ echo -e " ${YELLOW}WARN${NC} Disk space: unable to determine"
2627
+ warn_count=$((warn_count + 1))
2628
+ fi
2629
+ fi
2630
+ echo ""
2631
+
2632
+ # Summary
2633
+ echo -e "${BOLD}Summary:${NC} ${GREEN}$pass_count passed${NC}, ${RED}$fail_count failed${NC}, ${YELLOW}$warn_count warnings${NC}"
2634
+ echo ""
2635
+
2636
+ if [ "$fail_count" -gt 0 ]; then
2637
+ echo -e "${RED}Some required prerequisites are missing.${NC}"
2638
+ echo "Install missing dependencies and run 'loki doctor' again."
2639
+ return 1
2640
+ elif [ "$warn_count" -gt 0 ]; then
2641
+ echo -e "${YELLOW}All required checks passed with some warnings.${NC}"
2642
+ return 0
2643
+ else
2644
+ echo -e "${GREEN}All checks passed. System is ready for Loki Mode.${NC}"
2645
+ return 0
2646
+ fi
2647
+ }
2648
+
2649
+ # JSON output for loki doctor --json
2650
+ cmd_doctor_json() {
2651
+ python3 -c "
2652
+ import json, os, subprocess, sys, shutil
2653
+
2654
+ def get_version(cmd, args=None):
2655
+ try:
2656
+ if args is None:
2657
+ args = ['--version']
2658
+ result = subprocess.run([cmd] + args, capture_output=True, text=True, timeout=5)
2659
+ output = result.stdout.strip() or result.stderr.strip()
2660
+ # Extract version number
2661
+ import re
2662
+ match = re.search(r'(\d+\.\d+[\.\d]*)', output)
2663
+ return match.group(1) if match else None
2664
+ except (FileNotFoundError, subprocess.TimeoutExpired, Exception):
2665
+ return None
2666
+
2667
+ def check_tool(name, cmd, required, min_version=None):
2668
+ found = shutil.which(cmd) is not None
2669
+ version = get_version(cmd) if found else None
2670
+ status = 'pass'
2671
+
2672
+ if not found:
2673
+ status = 'fail' if required == 'required' else 'warn'
2674
+ elif min_version and version:
2675
+ cur_parts = [int(x) for x in version.split('.')[:2]]
2676
+ min_parts = [int(x) for x in min_version.split('.')[:2]]
2677
+ while len(cur_parts) < 2: cur_parts.append(0)
2678
+ while len(min_parts) < 2: min_parts.append(0)
2679
+ if cur_parts < min_parts:
2680
+ status = 'fail' if required == 'required' else 'warn'
2681
+
2682
+ return {
2683
+ 'name': name,
2684
+ 'command': cmd,
2685
+ 'found': found,
2686
+ 'version': version,
2687
+ 'required': required,
2688
+ 'min_version': min_version,
2689
+ 'status': status,
2690
+ 'path': shutil.which(cmd)
2691
+ }
2692
+
2693
+ checks = []
2694
+ checks.append(check_tool('Node.js', 'node', 'required', '18.0'))
2695
+ checks.append(check_tool('Python 3', 'python3', 'required', '3.8'))
2696
+ checks.append(check_tool('jq', 'jq', 'required'))
2697
+ checks.append(check_tool('git', 'git', 'required'))
2698
+ checks.append(check_tool('curl', 'curl', 'required'))
2699
+ checks.append(check_tool('bash', 'bash', 'recommended', '4.0'))
2700
+ checks.append(check_tool('Claude CLI', 'claude', 'optional'))
2701
+ checks.append(check_tool('Codex CLI', 'codex', 'optional'))
2702
+ checks.append(check_tool('Gemini CLI', 'gemini', 'optional'))
2703
+
2704
+ # Disk space
2705
+ disk_gb = None
2706
+ try:
2707
+ stat = os.statvfs(os.path.expanduser('~'))
2708
+ disk_gb = round((stat.f_bavail * stat.f_frsize) / (1024**3), 1)
2709
+ except Exception:
2710
+ pass
2711
+
2712
+ disk_status = 'pass'
2713
+ if disk_gb is not None:
2714
+ if disk_gb < 1:
2715
+ disk_status = 'fail'
2716
+ elif disk_gb < 5:
2717
+ disk_status = 'warn'
2718
+
2719
+ pass_count = sum(1 for c in checks if c['status'] == 'pass')
2720
+ fail_count = sum(1 for c in checks if c['status'] == 'fail')
2721
+ warn_count = sum(1 for c in checks if c['status'] == 'warn')
2722
+
2723
+ if disk_status == 'pass': pass_count += 1
2724
+ elif disk_status == 'fail': fail_count += 1
2725
+ elif disk_status == 'warn': warn_count += 1
2726
+
2727
+ result = {
2728
+ 'checks': checks,
2729
+ 'disk': {
2730
+ 'available_gb': disk_gb,
2731
+ 'status': disk_status
2732
+ },
2733
+ 'summary': {
2734
+ 'passed': pass_count,
2735
+ 'failed': fail_count,
2736
+ 'warnings': warn_count,
2737
+ 'ok': fail_count == 0
2738
+ }
2739
+ }
2740
+
2741
+ print(json.dumps(result, indent=2))
2742
+ "
2743
+ }
2744
+
2271
2745
  # Show version
2272
2746
  cmd_version() {
2273
2747
  echo "Loki Mode v$(get_version)"
@@ -3509,7 +3983,7 @@ main() {
3509
3983
  cmd_resume
3510
3984
  ;;
3511
3985
  status)
3512
- cmd_status
3986
+ cmd_status "$@"
3513
3987
  ;;
3514
3988
  dashboard)
3515
3989
  cmd_dashboard "$@"
@@ -3565,9 +4039,15 @@ main() {
3565
4039
  voice)
3566
4040
  cmd_voice "$@"
3567
4041
  ;;
4042
+ doctor)
4043
+ cmd_doctor "$@"
4044
+ ;;
3568
4045
  version|--version|-v)
3569
4046
  cmd_version
3570
4047
  ;;
4048
+ completions)
4049
+ cmd_completions "$@"
4050
+ ;;
3571
4051
  help|--help|-h)
3572
4052
  show_help
3573
4053
  ;;
@@ -3843,11 +4323,19 @@ for filename in ['patterns.jsonl', 'mistakes.jsonl', 'successes.jsonl']:
3843
4323
 
3844
4324
  clear)
3845
4325
  local type="${2:-}"
4326
+ local confirm=""
3846
4327
 
3847
4328
  if [ -z "$type" ]; then
3848
- echo -e "${YELLOW}This will delete ALL cross-project learnings.${NC}"
3849
- echo -n "Are you sure? (yes/no): "
3850
- read -r confirm
4329
+ # LOKI_AUTO_CONFIRM takes precedence when explicitly set;
4330
+ # fall back to CI env var only when LOKI_AUTO_CONFIRM is unset
4331
+ local _auto_confirm="${LOKI_AUTO_CONFIRM:-${CI:-false}}"
4332
+ if [[ "$_auto_confirm" == "true" ]]; then
4333
+ confirm="yes"
4334
+ else
4335
+ echo -e "${YELLOW}This will delete ALL cross-project learnings.${NC}"
4336
+ echo -n "Are you sure? (yes/no): "
4337
+ read -r confirm
4338
+ fi
3851
4339
  if [ "$confirm" = "yes" ]; then
3852
4340
  rm -rf "$learnings_dir"
3853
4341
  mkdir -p "$learnings_dir"
@@ -5888,4 +6376,54 @@ for line in sys.stdin:
5888
6376
  esac
5889
6377
  }
5890
6378
 
6379
+ # Output shell completion scripts
6380
+ cmd_completions() {
6381
+ local shell="${1:-bash}"
6382
+ local skill_dir
6383
+
6384
+ # Find the skill directory (where autonomy/loki is located)
6385
+ skill_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
6386
+ local completions_dir="$skill_dir/completions"
6387
+
6388
+ case "$shell" in
6389
+ bash)
6390
+ if [ -f "$completions_dir/loki.bash" ]; then
6391
+ cat "$completions_dir/loki.bash"
6392
+ else
6393
+ echo -e "${RED}Error: Bash completion script not found at $completions_dir/loki.bash${NC}" >&2
6394
+ exit 1
6395
+ fi
6396
+ ;;
6397
+ zsh)
6398
+ if [ -f "$completions_dir/_loki" ]; then
6399
+ cat "$completions_dir/_loki"
6400
+ else
6401
+ echo -e "${RED}Error: Zsh completion script not found at $completions_dir/_loki${NC}" >&2
6402
+ exit 1
6403
+ fi
6404
+ ;;
6405
+ *)
6406
+ echo -e "${BOLD}Loki Shell Completions${NC}"
6407
+ echo ""
6408
+ echo "Output shell completion scripts for bash or zsh."
6409
+ echo ""
6410
+ echo "Usage: loki completions <shell>"
6411
+ echo ""
6412
+ echo "Shells:"
6413
+ echo " bash Bash completion script"
6414
+ echo " zsh Zsh completion script"
6415
+ echo ""
6416
+ echo "Installation:"
6417
+ echo ""
6418
+ echo "Bash:"
6419
+ echo " eval \"\$(loki completions bash)\" >> ~/.bashrc"
6420
+ echo ""
6421
+ echo "Zsh:"
6422
+ echo " eval \"\$(loki completions zsh)\" >> ~/.zshrc"
6423
+ echo ""
6424
+ exit 1
6425
+ ;;
6426
+ esac
6427
+ }
6428
+
5891
6429
  main "$@"