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/README.md +100 -2
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/loki +552 -14
- package/autonomy/run.sh +16 -3
- package/dashboard/__init__.py +1 -1
- package/dashboard/server.py +12 -1
- package/dashboard/static/favicon.svg +5 -0
- package/dashboard/static/index.html +260 -7
- package/docs/INSTALLATION.md +110 -8
- package/package.json +1 -1
- package/templates/README.md +2 -1
- package/templates/rest-api-auth.md +252 -0
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
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
echo "
|
|
492
|
-
|
|
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
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
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 "$@"
|