loki-mode 6.6.0 → 6.7.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 +1 -1
- package/SKILL.md +2 -2
- package/VERSION +1 -1
- package/autonomy/completion-council.sh +5 -1
- package/autonomy/loki +918 -37
- package/autonomy/run.sh +456 -29
- package/dashboard/__init__.py +1 -1
- package/docs/INSTALLATION.md +2 -2
- package/docs/architecture/STATE-MACHINES.md +1767 -0
- package/mcp/__init__.py +1 -1
- package/package.json +7 -1
- package/providers/aider.sh +6 -6
- package/src/observability/otel.js +62 -23
package/autonomy/loki
CHANGED
|
@@ -428,6 +428,9 @@ show_help() {
|
|
|
428
428
|
echo " doctor [--json] Check system prerequisites and skill symlinks"
|
|
429
429
|
echo " setup-skill Create skill symlinks for all providers"
|
|
430
430
|
echo " watchdog [cmd] Process health monitoring (status|help)"
|
|
431
|
+
echo " telemetry [cmd] OpenTelemetry management (status|enable|disable)"
|
|
432
|
+
echo " worktree [cmd] Parallel worktree management (list|merge|clean|status)"
|
|
433
|
+
echo " agent [cmd] Agent type dispatch (list|info|run|start|review)"
|
|
431
434
|
echo " remote [PRD] Start remote session (connect from phone/browser, Claude Pro/Max)"
|
|
432
435
|
echo " version Show version"
|
|
433
436
|
echo " help Show this help"
|
|
@@ -1633,7 +1636,7 @@ cmd_provider() {
|
|
|
1633
1636
|
|
|
1634
1637
|
case "$subcommand" in
|
|
1635
1638
|
show|current)
|
|
1636
|
-
cmd_provider_show
|
|
1639
|
+
cmd_provider_show "$@"
|
|
1637
1640
|
;;
|
|
1638
1641
|
set)
|
|
1639
1642
|
cmd_provider_set "$@"
|
|
@@ -4598,6 +4601,55 @@ cmd_doctor() {
|
|
|
4598
4601
|
done
|
|
4599
4602
|
echo ""
|
|
4600
4603
|
|
|
4604
|
+
echo -e "${CYAN}Integrations:${NC}"
|
|
4605
|
+
# MCP SDK check
|
|
4606
|
+
if python3 -c "import mcp" 2>/dev/null; then
|
|
4607
|
+
echo -e " ${GREEN}PASS${NC} MCP SDK (Python)"
|
|
4608
|
+
pass_count=$((pass_count + 1))
|
|
4609
|
+
else
|
|
4610
|
+
echo -e " ${YELLOW}WARN${NC} MCP SDK - not installed (pip3 install mcp)"
|
|
4611
|
+
warn_count=$((warn_count + 1))
|
|
4612
|
+
fi
|
|
4613
|
+
# Detect best Python for ML packages (3.12 preferred, 3.14+ has compat issues)
|
|
4614
|
+
local _py_ml="python3"
|
|
4615
|
+
if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
|
|
4616
|
+
_py_ml="/opt/homebrew/bin/python3.12"
|
|
4617
|
+
elif command -v python3.12 &>/dev/null; then
|
|
4618
|
+
_py_ml="python3.12"
|
|
4619
|
+
fi
|
|
4620
|
+
# Vector search dependencies
|
|
4621
|
+
if $_py_ml -c "import numpy" 2>/dev/null; then
|
|
4622
|
+
echo -e " ${GREEN}PASS${NC} numpy (vector search)"
|
|
4623
|
+
pass_count=$((pass_count + 1))
|
|
4624
|
+
else
|
|
4625
|
+
echo -e " ${YELLOW}WARN${NC} numpy - not installed (pip3 install numpy)"
|
|
4626
|
+
warn_count=$((warn_count + 1))
|
|
4627
|
+
fi
|
|
4628
|
+
if $_py_ml -c "import sentence_transformers" 2>/dev/null; then
|
|
4629
|
+
echo -e " ${GREEN}PASS${NC} sentence-transformers (embeddings)"
|
|
4630
|
+
pass_count=$((pass_count + 1))
|
|
4631
|
+
else
|
|
4632
|
+
echo -e " ${YELLOW}WARN${NC} sentence-transformers - not installed (loki memory vectors setup)"
|
|
4633
|
+
warn_count=$((warn_count + 1))
|
|
4634
|
+
fi
|
|
4635
|
+
# ChromaDB check
|
|
4636
|
+
if curl -sf http://localhost:8100/api/v2/heartbeat >/dev/null 2>&1; then
|
|
4637
|
+
echo -e " ${GREEN}PASS${NC} ChromaDB server (port 8100)"
|
|
4638
|
+
pass_count=$((pass_count + 1))
|
|
4639
|
+
else
|
|
4640
|
+
echo -e " ${YELLOW}WARN${NC} ChromaDB - not running (docker start loki-chroma)"
|
|
4641
|
+
warn_count=$((warn_count + 1))
|
|
4642
|
+
fi
|
|
4643
|
+
# OTEL status
|
|
4644
|
+
if [ -n "${LOKI_OTEL_ENDPOINT:-}" ]; then
|
|
4645
|
+
echo -e " ${GREEN}PASS${NC} OTEL endpoint: $LOKI_OTEL_ENDPOINT"
|
|
4646
|
+
pass_count=$((pass_count + 1))
|
|
4647
|
+
else
|
|
4648
|
+
echo -e " ${YELLOW}WARN${NC} OTEL - not configured (set LOKI_OTEL_ENDPOINT)"
|
|
4649
|
+
warn_count=$((warn_count + 1))
|
|
4650
|
+
fi
|
|
4651
|
+
echo ""
|
|
4652
|
+
|
|
4601
4653
|
echo -e "${CYAN}System:${NC}"
|
|
4602
4654
|
doctor_check "bash (>= 4.0)" bash recommended 4.0 || true
|
|
4603
4655
|
|
|
@@ -7657,6 +7709,12 @@ main() {
|
|
|
7657
7709
|
cluster)
|
|
7658
7710
|
cmd_cluster "$@"
|
|
7659
7711
|
;;
|
|
7712
|
+
worktree|wt)
|
|
7713
|
+
cmd_worktree "$@"
|
|
7714
|
+
;;
|
|
7715
|
+
agent)
|
|
7716
|
+
cmd_agent "$@"
|
|
7717
|
+
;;
|
|
7660
7718
|
state)
|
|
7661
7719
|
cmd_state "$@"
|
|
7662
7720
|
;;
|
|
@@ -7666,6 +7724,9 @@ main() {
|
|
|
7666
7724
|
syslog)
|
|
7667
7725
|
cmd_syslog "$@"
|
|
7668
7726
|
;;
|
|
7727
|
+
telemetry|otel)
|
|
7728
|
+
cmd_telemetry "$@"
|
|
7729
|
+
;;
|
|
7669
7730
|
remote|rc)
|
|
7670
7731
|
cmd_remote "$@"
|
|
7671
7732
|
;;
|
|
@@ -7686,6 +7747,166 @@ main() {
|
|
|
7686
7747
|
esac
|
|
7687
7748
|
}
|
|
7688
7749
|
|
|
7750
|
+
# Worktree management (v6.7.0)
|
|
7751
|
+
cmd_worktree() {
|
|
7752
|
+
local subcommand="${1:-list}"
|
|
7753
|
+
shift 2>/dev/null || true
|
|
7754
|
+
|
|
7755
|
+
case "$subcommand" in
|
|
7756
|
+
list)
|
|
7757
|
+
echo -e "${BOLD}Worktree Status${NC}"
|
|
7758
|
+
echo ""
|
|
7759
|
+
|
|
7760
|
+
# List git worktrees
|
|
7761
|
+
local worktrees
|
|
7762
|
+
worktrees=$(git worktree list --porcelain 2>/dev/null)
|
|
7763
|
+
if [ -z "$worktrees" ]; then
|
|
7764
|
+
echo " No active worktrees"
|
|
7765
|
+
return 0
|
|
7766
|
+
fi
|
|
7767
|
+
|
|
7768
|
+
local count=0
|
|
7769
|
+
local current_wt=""
|
|
7770
|
+
while IFS= read -r line; do
|
|
7771
|
+
case "$line" in
|
|
7772
|
+
"worktree "*)
|
|
7773
|
+
current_wt="${line#worktree }"
|
|
7774
|
+
;;
|
|
7775
|
+
"branch "*)
|
|
7776
|
+
local branch="${line#branch refs/heads/}"
|
|
7777
|
+
local status="running"
|
|
7778
|
+
local stream_name=""
|
|
7779
|
+
|
|
7780
|
+
# Check for merge signal
|
|
7781
|
+
if [[ "$branch" == loki-parallel-* ]]; then
|
|
7782
|
+
stream_name="${branch#loki-parallel-}"
|
|
7783
|
+
if [ -f ".loki/signals/MERGE_REQUESTED_${stream_name}" ]; then
|
|
7784
|
+
status="merge-ready"
|
|
7785
|
+
elif [ -f ".loki/signals/WORKTREE_FAILED_${stream_name}" ]; then
|
|
7786
|
+
status="failed"
|
|
7787
|
+
fi
|
|
7788
|
+
fi
|
|
7789
|
+
|
|
7790
|
+
# Skip main worktree
|
|
7791
|
+
if [ "$current_wt" != "$(git rev-parse --show-toplevel 2>/dev/null)" ]; then
|
|
7792
|
+
case "$status" in
|
|
7793
|
+
merge-ready) echo -e " ${GREEN}[merge-ready]${NC} $branch ${DIM}$current_wt${NC}" ;;
|
|
7794
|
+
failed) echo -e " ${RED}[failed]${NC} $branch ${DIM}$current_wt${NC}" ;;
|
|
7795
|
+
*) echo -e " ${CYAN}[active]${NC} $branch ${DIM}$current_wt${NC}" ;;
|
|
7796
|
+
esac
|
|
7797
|
+
count=$((count + 1))
|
|
7798
|
+
fi
|
|
7799
|
+
current_wt=""
|
|
7800
|
+
;;
|
|
7801
|
+
esac
|
|
7802
|
+
done <<< "$worktrees"
|
|
7803
|
+
|
|
7804
|
+
if [ "$count" -eq 0 ]; then
|
|
7805
|
+
echo " No parallel worktrees (only main)"
|
|
7806
|
+
fi
|
|
7807
|
+
echo ""
|
|
7808
|
+
echo " Total: $count worktree(s)"
|
|
7809
|
+
;;
|
|
7810
|
+
|
|
7811
|
+
merge)
|
|
7812
|
+
local name="${1:-}"
|
|
7813
|
+
if [ -z "$name" ]; then
|
|
7814
|
+
echo -e "${RED}Usage: loki worktree merge <name>${NC}"
|
|
7815
|
+
return 1
|
|
7816
|
+
fi
|
|
7817
|
+
|
|
7818
|
+
echo -e "${BOLD}Merging worktree: $name${NC}"
|
|
7819
|
+
local signal_file=".loki/signals/MERGE_REQUESTED_${name}"
|
|
7820
|
+
if [ ! -f "$signal_file" ]; then
|
|
7821
|
+
echo -e "${RED}No merge signal found for: $name${NC}"
|
|
7822
|
+
echo "Use 'loki worktree list' to see available worktrees."
|
|
7823
|
+
return 1
|
|
7824
|
+
fi
|
|
7825
|
+
|
|
7826
|
+
local branch
|
|
7827
|
+
branch=$(python3 -c "import json; print(json.load(open('$signal_file'))['branch'])" 2>/dev/null)
|
|
7828
|
+
local worktree_path
|
|
7829
|
+
worktree_path=$(python3 -c "import json; print(json.load(open('$signal_file'))['worktree'])" 2>/dev/null)
|
|
7830
|
+
|
|
7831
|
+
if git merge --no-ff "$branch" -m "merge($name): auto-merge from parallel worktree"; then
|
|
7832
|
+
echo -e "${GREEN}Merge successful${NC}"
|
|
7833
|
+
rm -f "$signal_file"
|
|
7834
|
+
if [ -n "$worktree_path" ] && [ -d "$worktree_path" ]; then
|
|
7835
|
+
git worktree remove "$worktree_path" --force 2>/dev/null || true
|
|
7836
|
+
git branch -d "$branch" 2>/dev/null || true
|
|
7837
|
+
fi
|
|
7838
|
+
else
|
|
7839
|
+
echo -e "${RED}Merge conflict - resolve manually${NC}"
|
|
7840
|
+
git merge --abort 2>/dev/null || true
|
|
7841
|
+
return 1
|
|
7842
|
+
fi
|
|
7843
|
+
;;
|
|
7844
|
+
|
|
7845
|
+
clean)
|
|
7846
|
+
echo -e "${BOLD}Cleaning all worktrees...${NC}"
|
|
7847
|
+
local removed=0
|
|
7848
|
+
local main_wt
|
|
7849
|
+
main_wt=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
|
|
7850
|
+
while read -r line; do
|
|
7851
|
+
local wt="${line#worktree }"
|
|
7852
|
+
if [ -n "$wt" ] && [ "$wt" != "$main_wt" ]; then
|
|
7853
|
+
echo " Removing: $wt"
|
|
7854
|
+
git worktree remove "$wt" --force 2>/dev/null || true
|
|
7855
|
+
removed=$((removed + 1))
|
|
7856
|
+
fi
|
|
7857
|
+
done < <(git worktree list --porcelain 2>/dev/null | grep "^worktree ")
|
|
7858
|
+
rm -f .loki/signals/MERGE_REQUESTED_* .loki/signals/WORKTREE_FAILED_* 2>/dev/null || true
|
|
7859
|
+
echo -e "${GREEN}Cleanup complete ($removed worktrees removed)${NC}"
|
|
7860
|
+
;;
|
|
7861
|
+
|
|
7862
|
+
status)
|
|
7863
|
+
echo -e "${BOLD}Parallel Orchestrator Status${NC}"
|
|
7864
|
+
echo ""
|
|
7865
|
+
|
|
7866
|
+
local merge_ready=0
|
|
7867
|
+
local failed=0
|
|
7868
|
+
local active=0
|
|
7869
|
+
|
|
7870
|
+
for f in .loki/signals/MERGE_REQUESTED_*; do
|
|
7871
|
+
[ -f "$f" ] && merge_ready=$((merge_ready + 1))
|
|
7872
|
+
done
|
|
7873
|
+
for f in .loki/signals/WORKTREE_FAILED_*; do
|
|
7874
|
+
[ -f "$f" ] && failed=$((failed + 1))
|
|
7875
|
+
done
|
|
7876
|
+
|
|
7877
|
+
local total_wt
|
|
7878
|
+
total_wt=$(git worktree list 2>/dev/null | wc -l | tr -d ' ')
|
|
7879
|
+
total_wt=$((total_wt - 1)) # Exclude main
|
|
7880
|
+
[ "$total_wt" -lt 0 ] && total_wt=0
|
|
7881
|
+
active=$((total_wt - merge_ready - failed))
|
|
7882
|
+
[ "$active" -lt 0 ] && active=0
|
|
7883
|
+
|
|
7884
|
+
echo " Active worktrees: $active"
|
|
7885
|
+
echo " Merge-ready: $merge_ready"
|
|
7886
|
+
echo " Failed: $failed"
|
|
7887
|
+
echo " Total: $total_wt"
|
|
7888
|
+
;;
|
|
7889
|
+
|
|
7890
|
+
--help|-h|help)
|
|
7891
|
+
echo -e "${BOLD}loki worktree${NC} - Manage parallel worktrees (v6.7.0)"
|
|
7892
|
+
echo ""
|
|
7893
|
+
echo "Usage: loki worktree <command>"
|
|
7894
|
+
echo ""
|
|
7895
|
+
echo "Commands:"
|
|
7896
|
+
echo " list List active worktrees with status"
|
|
7897
|
+
echo " merge NAME Merge a completed worktree back"
|
|
7898
|
+
echo " clean Remove all worktrees"
|
|
7899
|
+
echo " status Show parallel orchestrator state"
|
|
7900
|
+
echo ""
|
|
7901
|
+
;;
|
|
7902
|
+
*)
|
|
7903
|
+
echo -e "${RED}Unknown worktree command: $subcommand${NC}"
|
|
7904
|
+
echo "Run 'loki worktree help' for usage."
|
|
7905
|
+
return 1
|
|
7906
|
+
;;
|
|
7907
|
+
esac
|
|
7908
|
+
}
|
|
7909
|
+
|
|
7689
7910
|
# SQLite queryable state inspection
|
|
7690
7911
|
cmd_state() {
|
|
7691
7912
|
local subcmd="${1:-help}"
|
|
@@ -7967,6 +8188,126 @@ print()
|
|
|
7967
8188
|
cmd_audit scan "$@"
|
|
7968
8189
|
return
|
|
7969
8190
|
;;
|
|
8191
|
+
lint)
|
|
8192
|
+
echo -e "${BOLD}Running static analysis...${NC}"
|
|
8193
|
+
echo ""
|
|
8194
|
+
local findings=0
|
|
8195
|
+
local target="${2:-.}"
|
|
8196
|
+
|
|
8197
|
+
# JavaScript/TypeScript
|
|
8198
|
+
if [ -f "$target/package.json" ]; then
|
|
8199
|
+
echo -e "${CYAN}JavaScript/TypeScript:${NC}"
|
|
8200
|
+
local js_files
|
|
8201
|
+
js_files=$(find "$target" -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" | grep -v node_modules | grep -v dist | head -50)
|
|
8202
|
+
if [ -n "$js_files" ]; then
|
|
8203
|
+
if [ -f "$target/.eslintrc.js" ] || [ -f "$target/.eslintrc.json" ] || [ -f "$target/.eslintrc.yml" ] || [ -f "$target/eslint.config.js" ] || [ -f "$target/eslint.config.mjs" ]; then
|
|
8204
|
+
npx eslint $js_files 2>&1 | head -50
|
|
8205
|
+
[ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
|
|
8206
|
+
else
|
|
8207
|
+
echo " No ESLint config found - running syntax check"
|
|
8208
|
+
for f in $js_files; do
|
|
8209
|
+
node --check "$f" 2>&1 || findings=$((findings + 1))
|
|
8210
|
+
done
|
|
8211
|
+
fi
|
|
8212
|
+
fi
|
|
8213
|
+
fi
|
|
8214
|
+
|
|
8215
|
+
# Python
|
|
8216
|
+
if [ -f "$target/setup.py" ] || [ -f "$target/pyproject.toml" ] || [ -f "$target/requirements.txt" ]; then
|
|
8217
|
+
echo -e "${CYAN}Python:${NC}"
|
|
8218
|
+
local py_files
|
|
8219
|
+
py_files=$(find "$target" -name "*.py" | grep -v __pycache__ | grep -v .venv | grep -v venv | head -50)
|
|
8220
|
+
if [ -n "$py_files" ]; then
|
|
8221
|
+
if command -v ruff &>/dev/null; then
|
|
8222
|
+
ruff check $py_files 2>&1 | head -50
|
|
8223
|
+
[ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
|
|
8224
|
+
else
|
|
8225
|
+
echo " ruff not available - running py_compile"
|
|
8226
|
+
for f in $py_files; do
|
|
8227
|
+
python3 -m py_compile "$f" 2>&1 || findings=$((findings + 1))
|
|
8228
|
+
done
|
|
8229
|
+
fi
|
|
8230
|
+
fi
|
|
8231
|
+
fi
|
|
8232
|
+
|
|
8233
|
+
# Shell scripts
|
|
8234
|
+
local sh_files
|
|
8235
|
+
sh_files=$(find "$target" -name "*.sh" | grep -v node_modules | head -20)
|
|
8236
|
+
if [ -n "$sh_files" ]; then
|
|
8237
|
+
echo -e "${CYAN}Shell:${NC}"
|
|
8238
|
+
if command -v shellcheck &>/dev/null; then
|
|
8239
|
+
shellcheck $sh_files 2>&1 | head -50
|
|
8240
|
+
[ ${PIPESTATUS[0]} -ne 0 ] && findings=$((findings + 1))
|
|
8241
|
+
else
|
|
8242
|
+
echo " shellcheck not available - running bash -n"
|
|
8243
|
+
for f in $sh_files; do
|
|
8244
|
+
bash -n "$f" 2>&1 || findings=$((findings + 1))
|
|
8245
|
+
done
|
|
8246
|
+
fi
|
|
8247
|
+
fi
|
|
8248
|
+
|
|
8249
|
+
echo ""
|
|
8250
|
+
if [ "$findings" -gt 0 ]; then
|
|
8251
|
+
echo -e "${RED}Static analysis found issues ($findings checks failed)${NC}"
|
|
8252
|
+
return 1
|
|
8253
|
+
else
|
|
8254
|
+
echo -e "${GREEN}Static analysis passed${NC}"
|
|
8255
|
+
return 0
|
|
8256
|
+
fi
|
|
8257
|
+
;;
|
|
8258
|
+
|
|
8259
|
+
test)
|
|
8260
|
+
echo -e "${BOLD}Running test coverage check...${NC}"
|
|
8261
|
+
echo ""
|
|
8262
|
+
local target="${2:-.}"
|
|
8263
|
+
local min_coverage="${LOKI_MIN_COVERAGE:-80}"
|
|
8264
|
+
local test_failed=0
|
|
8265
|
+
|
|
8266
|
+
# JavaScript/TypeScript (jest/vitest)
|
|
8267
|
+
if [ -f "$target/package.json" ]; then
|
|
8268
|
+
local test_cmd=""
|
|
8269
|
+
if grep -q '"vitest"' "$target/package.json" 2>/dev/null; then
|
|
8270
|
+
test_cmd="npx vitest run --coverage"
|
|
8271
|
+
elif grep -q '"jest"' "$target/package.json" 2>/dev/null; then
|
|
8272
|
+
test_cmd="npx jest --coverage --passWithNoTests"
|
|
8273
|
+
fi
|
|
8274
|
+
if [ -n "$test_cmd" ]; then
|
|
8275
|
+
echo -e "${CYAN}Running: $test_cmd${NC}"
|
|
8276
|
+
(cd "$target" && eval "$test_cmd") 2>&1
|
|
8277
|
+
[ $? -ne 0 ] && test_failed=1
|
|
8278
|
+
fi
|
|
8279
|
+
fi
|
|
8280
|
+
|
|
8281
|
+
# Python (pytest)
|
|
8282
|
+
if [ -f "$target/setup.py" ] || [ -f "$target/pyproject.toml" ]; then
|
|
8283
|
+
if command -v pytest &>/dev/null; then
|
|
8284
|
+
echo -e "${CYAN}Running: pytest --cov${NC}"
|
|
8285
|
+
(cd "$target" && pytest --cov 2>&1) || test_failed=1
|
|
8286
|
+
fi
|
|
8287
|
+
fi
|
|
8288
|
+
|
|
8289
|
+
# Go
|
|
8290
|
+
if [ -f "$target/go.mod" ]; then
|
|
8291
|
+
echo -e "${CYAN}Running: go test -cover${NC}"
|
|
8292
|
+
(cd "$target" && go test -cover ./... 2>&1) || test_failed=1
|
|
8293
|
+
fi
|
|
8294
|
+
|
|
8295
|
+
# Rust
|
|
8296
|
+
if [ -f "$target/Cargo.toml" ]; then
|
|
8297
|
+
echo -e "${CYAN}Running: cargo test${NC}"
|
|
8298
|
+
(cd "$target" && cargo test 2>&1) || test_failed=1
|
|
8299
|
+
fi
|
|
8300
|
+
|
|
8301
|
+
echo ""
|
|
8302
|
+
if [ "$test_failed" -ne 0 ]; then
|
|
8303
|
+
echo -e "${RED}Test coverage gate FAILED${NC}"
|
|
8304
|
+
return 1
|
|
8305
|
+
else
|
|
8306
|
+
echo -e "${GREEN}Tests passed (min coverage: ${min_coverage}%)${NC}"
|
|
8307
|
+
return 0
|
|
8308
|
+
fi
|
|
8309
|
+
;;
|
|
8310
|
+
|
|
7970
8311
|
--help|-h|help)
|
|
7971
8312
|
echo -e "${BOLD}loki audit${NC} - Agent audit log and quality scanning"
|
|
7972
8313
|
echo ""
|
|
@@ -7976,6 +8317,8 @@ print()
|
|
|
7976
8317
|
echo " log [N] Show last N audit log entries (default: 50)"
|
|
7977
8318
|
echo " count Count actions by type"
|
|
7978
8319
|
echo " scan Run quality scan against dashboard API"
|
|
8320
|
+
echo " lint Run static analysis on project files"
|
|
8321
|
+
echo " test Run test coverage check"
|
|
7979
8322
|
echo " help Show this help"
|
|
7980
8323
|
echo ""
|
|
7981
8324
|
echo "Quality Scan Options (loki audit scan):"
|
|
@@ -8227,33 +8570,97 @@ if count == 0:
|
|
|
8227
8570
|
local query="${2:-}"
|
|
8228
8571
|
if [ -z "$query" ]; then
|
|
8229
8572
|
echo -e "${RED}Usage: loki memory search <query>${NC}"
|
|
8230
|
-
|
|
8573
|
+
return 1
|
|
8231
8574
|
fi
|
|
8232
8575
|
|
|
8233
|
-
echo -e "${BOLD}Search
|
|
8234
|
-
echo "
|
|
8576
|
+
echo -e "${BOLD}Memory Search: $query${NC}"
|
|
8577
|
+
echo ""
|
|
8235
8578
|
|
|
8236
|
-
|
|
8237
|
-
|
|
8579
|
+
# Try vector search first, fall back to keyword
|
|
8580
|
+
python3 << PYEOF
|
|
8581
|
+
import os, json, sys, glob
|
|
8238
8582
|
|
|
8239
|
-
|
|
8240
|
-
|
|
8583
|
+
query = """$query"""
|
|
8584
|
+
results = []
|
|
8241
8585
|
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8586
|
+
# Keyword search across all memory files
|
|
8587
|
+
memory_dirs = [
|
|
8588
|
+
'.loki/memory/episodic',
|
|
8589
|
+
'.loki/memory/semantic',
|
|
8590
|
+
'.loki/memory/skills',
|
|
8591
|
+
'.loki/memory/handoffs',
|
|
8592
|
+
]
|
|
8593
|
+
|
|
8594
|
+
for mdir in memory_dirs:
|
|
8595
|
+
if not os.path.isdir(mdir):
|
|
8245
8596
|
continue
|
|
8597
|
+
for fpath in glob.glob(os.path.join(mdir, '*.json')):
|
|
8598
|
+
try:
|
|
8599
|
+
with open(fpath) as f:
|
|
8600
|
+
data = json.load(f)
|
|
8601
|
+
text = json.dumps(data).lower()
|
|
8602
|
+
if query.lower() in text:
|
|
8603
|
+
# Score by number of matches
|
|
8604
|
+
score = text.count(query.lower())
|
|
8605
|
+
title = data.get('title', data.get('task_id', data.get('name', os.path.basename(fpath))))
|
|
8606
|
+
category = os.path.basename(mdir)
|
|
8607
|
+
results.append((score, category, title, fpath))
|
|
8608
|
+
except Exception:
|
|
8609
|
+
continue
|
|
8246
8610
|
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8253
|
-
|
|
8254
|
-
|
|
8255
|
-
|
|
8256
|
-
|
|
8611
|
+
# Also search markdown files
|
|
8612
|
+
for mdir in ['.loki/memory', '.loki']:
|
|
8613
|
+
if not os.path.isdir(mdir):
|
|
8614
|
+
continue
|
|
8615
|
+
for fpath in glob.glob(os.path.join(mdir, '*.md')):
|
|
8616
|
+
try:
|
|
8617
|
+
with open(fpath) as f:
|
|
8618
|
+
text = f.read().lower()
|
|
8619
|
+
if query.lower() in text:
|
|
8620
|
+
score = text.count(query.lower())
|
|
8621
|
+
title = os.path.basename(fpath)
|
|
8622
|
+
results.append((score, 'docs', title, fpath))
|
|
8623
|
+
except Exception:
|
|
8624
|
+
continue
|
|
8625
|
+
|
|
8626
|
+
# Try vector search if available
|
|
8627
|
+
vector_results = []
|
|
8628
|
+
try:
|
|
8629
|
+
sys.path.insert(0, '.')
|
|
8630
|
+
from memory.retrieval import MemoryRetrieval
|
|
8631
|
+
from memory.storage import MemoryStorage
|
|
8632
|
+
storage = MemoryStorage('.loki/memory')
|
|
8633
|
+
retriever = MemoryRetrieval(storage)
|
|
8634
|
+
vr = retriever.search(query, top_k=5)
|
|
8635
|
+
for item in vr:
|
|
8636
|
+
vector_results.append(item)
|
|
8637
|
+
if vector_results:
|
|
8638
|
+
print(" [Vector Search Results]")
|
|
8639
|
+
for item in vector_results[:5]:
|
|
8640
|
+
if isinstance(item, dict):
|
|
8641
|
+
print(f" {item.get('title', item.get('id', '?'))}: {item.get('summary', '')[:80]}")
|
|
8642
|
+
else:
|
|
8643
|
+
print(f" {str(item)[:80]}")
|
|
8644
|
+
print()
|
|
8645
|
+
except ImportError:
|
|
8646
|
+
pass # Vector search not available
|
|
8647
|
+
except Exception:
|
|
8648
|
+
pass
|
|
8649
|
+
|
|
8650
|
+
# Show keyword results
|
|
8651
|
+
if results:
|
|
8652
|
+
results.sort(key=lambda x: -x[0])
|
|
8653
|
+
print(" [Keyword Search Results]")
|
|
8654
|
+
for score, category, title, fpath in results[:10]:
|
|
8655
|
+
print(f" [{category}] {title} ({score} matches)")
|
|
8656
|
+
print(f" {fpath}")
|
|
8657
|
+
print()
|
|
8658
|
+
print(f" Found: {len(results)} result(s)")
|
|
8659
|
+
else:
|
|
8660
|
+
if not vector_results:
|
|
8661
|
+
print(" No results found for: " + query)
|
|
8662
|
+
print(" Tip: Try broader search terms")
|
|
8663
|
+
PYEOF
|
|
8257
8664
|
;;
|
|
8258
8665
|
|
|
8259
8666
|
clear)
|
|
@@ -8611,10 +9018,96 @@ except Exception as e:
|
|
|
8611
9018
|
;;
|
|
8612
9019
|
|
|
8613
9020
|
vectors)
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
9021
|
+
local vec_cmd="${2:-stats}"
|
|
9022
|
+
case "$vec_cmd" in
|
|
9023
|
+
setup)
|
|
9024
|
+
echo -e "${BOLD}Vector Search Setup${NC}"
|
|
9025
|
+
echo ""
|
|
9026
|
+
|
|
9027
|
+
# Detect best Python for ML packages (3.12 preferred, 3.14+ has compat issues)
|
|
9028
|
+
local py_ml="python3"
|
|
9029
|
+
local pip_ml="pip3"
|
|
9030
|
+
if command -v /opt/homebrew/bin/python3.12 &>/dev/null; then
|
|
9031
|
+
py_ml="/opt/homebrew/bin/python3.12"
|
|
9032
|
+
pip_ml="/opt/homebrew/bin/pip3.12"
|
|
9033
|
+
echo -e " Using Python 3.12 for ML packages (${DIM}system python3 may be 3.14+${NC})"
|
|
9034
|
+
elif command -v python3.12 &>/dev/null; then
|
|
9035
|
+
py_ml="python3.12"
|
|
9036
|
+
pip_ml="pip3.12"
|
|
9037
|
+
echo -e " Using Python 3.12 for ML packages"
|
|
9038
|
+
else
|
|
9039
|
+
local py_ver
|
|
9040
|
+
py_ver=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" 2>/dev/null || echo "0.0")
|
|
9041
|
+
local py_minor
|
|
9042
|
+
py_minor=$(echo "$py_ver" | cut -d. -f2)
|
|
9043
|
+
if [ "${py_minor:-0}" -ge 13 ] 2>/dev/null; then
|
|
9044
|
+
echo -e " ${YELLOW}WARNING: Python $py_ver detected. sentence-transformers may not install.${NC}"
|
|
9045
|
+
echo -e " ${YELLOW}Install Python 3.12 for best compatibility: brew install python@3.12${NC}"
|
|
9046
|
+
fi
|
|
9047
|
+
fi
|
|
9048
|
+
|
|
9049
|
+
# Check/install numpy
|
|
9050
|
+
echo -n " Checking numpy... "
|
|
9051
|
+
if $py_ml -c "import numpy" 2>/dev/null; then
|
|
9052
|
+
echo -e "${GREEN}installed${NC}"
|
|
9053
|
+
else
|
|
9054
|
+
echo -n "installing... "
|
|
9055
|
+
if $pip_ml install numpy 2>/dev/null || $pip_ml install --break-system-packages numpy 2>/dev/null; then
|
|
9056
|
+
echo -e "${GREEN}done${NC}"
|
|
9057
|
+
else
|
|
9058
|
+
echo -e "${RED}failed${NC}"
|
|
9059
|
+
echo " Try: $pip_ml install numpy"
|
|
9060
|
+
fi
|
|
9061
|
+
fi
|
|
9062
|
+
|
|
9063
|
+
# Check/install sentence-transformers
|
|
9064
|
+
echo -n " Checking sentence-transformers... "
|
|
9065
|
+
if $py_ml -c "import sentence_transformers" 2>/dev/null; then
|
|
9066
|
+
echo -e "${GREEN}installed${NC}"
|
|
9067
|
+
else
|
|
9068
|
+
echo -n "installing (this may take a while)... "
|
|
9069
|
+
if $pip_ml install sentence-transformers 2>/dev/null || $pip_ml install --break-system-packages sentence-transformers 2>/dev/null; then
|
|
9070
|
+
echo -e "${GREEN}done${NC}"
|
|
9071
|
+
else
|
|
9072
|
+
echo -e "${RED}failed${NC}"
|
|
9073
|
+
echo " Try: $pip_ml install sentence-transformers"
|
|
9074
|
+
fi
|
|
9075
|
+
fi
|
|
9076
|
+
|
|
9077
|
+
# Create vector directory
|
|
9078
|
+
mkdir -p .loki/memory/vectors
|
|
9079
|
+
echo -e " Vector directory: ${GREEN}.loki/memory/vectors/${NC}"
|
|
9080
|
+
|
|
9081
|
+
# Build initial index if memory files exist
|
|
9082
|
+
echo ""
|
|
9083
|
+
echo -n " Building vector indices... "
|
|
9084
|
+
python3 -c "
|
|
9085
|
+
try:
|
|
9086
|
+
import sys
|
|
9087
|
+
sys.path.insert(0, '.')
|
|
9088
|
+
from memory.retrieval import MemoryRetrieval
|
|
9089
|
+
from memory.storage import MemoryStorage
|
|
9090
|
+
storage = MemoryStorage('.loki/memory')
|
|
9091
|
+
retriever = MemoryRetrieval(storage)
|
|
9092
|
+
retriever.build_indices()
|
|
9093
|
+
retriever.save_indices()
|
|
9094
|
+
print('done')
|
|
9095
|
+
except ImportError as e:
|
|
9096
|
+
print(f'skipped (missing: {e})')
|
|
9097
|
+
except Exception as e:
|
|
9098
|
+
print(f'error: {e}')
|
|
9099
|
+
" 2>/dev/null || echo "skipped"
|
|
9100
|
+
|
|
9101
|
+
echo ""
|
|
9102
|
+
echo -e "${GREEN}Vector search setup complete${NC}"
|
|
9103
|
+
echo "Run 'loki memory search <query>' to search."
|
|
9104
|
+
;;
|
|
9105
|
+
|
|
9106
|
+
rebuild)
|
|
9107
|
+
python3 -c "
|
|
8617
9108
|
try:
|
|
9109
|
+
import sys
|
|
9110
|
+
sys.path.insert(0, '.')
|
|
8618
9111
|
from memory.retrieval import MemoryRetrieval
|
|
8619
9112
|
from memory.storage import MemoryStorage
|
|
8620
9113
|
storage = MemoryStorage('.loki/memory')
|
|
@@ -8627,19 +9120,23 @@ except ImportError as e:
|
|
|
8627
9120
|
except Exception as e:
|
|
8628
9121
|
print(f'Error: {e}')
|
|
8629
9122
|
" 2>/dev/null
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
|
|
8633
|
-
|
|
8634
|
-
|
|
8635
|
-
|
|
8636
|
-
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
9123
|
+
;;
|
|
9124
|
+
|
|
9125
|
+
stats|*)
|
|
9126
|
+
echo "Vector index stats:"
|
|
9127
|
+
if ls -1 .loki/memory/vectors/*.npz 2>/dev/null | head -1 >/dev/null 2>&1; then
|
|
9128
|
+
for f in .loki/memory/vectors/*.npz; do
|
|
9129
|
+
if [ -f "$f" ]; then
|
|
9130
|
+
count=$(python3 -c "import numpy as np; d=np.load('$f'); print(len(d['ids']))" 2>/dev/null || echo "error")
|
|
9131
|
+
echo " $(basename "$f"): $count vectors"
|
|
9132
|
+
fi
|
|
9133
|
+
done
|
|
9134
|
+
else
|
|
9135
|
+
echo " No vector indices found"
|
|
9136
|
+
echo " Run 'loki memory vectors setup' to initialize"
|
|
9137
|
+
fi
|
|
9138
|
+
;;
|
|
9139
|
+
esac
|
|
8643
9140
|
;;
|
|
8644
9141
|
|
|
8645
9142
|
namespace|ns)
|
|
@@ -10630,6 +11127,113 @@ for line in sys.stdin:
|
|
|
10630
11127
|
esac
|
|
10631
11128
|
}
|
|
10632
11129
|
|
|
11130
|
+
# Telemetry management (v6.7.0)
|
|
11131
|
+
cmd_telemetry() {
|
|
11132
|
+
local subcommand="${1:-status}"
|
|
11133
|
+
shift 2>/dev/null || true
|
|
11134
|
+
|
|
11135
|
+
case "$subcommand" in
|
|
11136
|
+
status)
|
|
11137
|
+
echo -e "${BOLD}Telemetry Status${NC}"
|
|
11138
|
+
echo ""
|
|
11139
|
+
|
|
11140
|
+
local endpoint="${LOKI_OTEL_ENDPOINT:-}"
|
|
11141
|
+
if [ -n "$endpoint" ]; then
|
|
11142
|
+
echo -e " Endpoint: ${GREEN}$endpoint${NC}"
|
|
11143
|
+
else
|
|
11144
|
+
echo -e " Endpoint: ${YELLOW}not configured${NC}"
|
|
11145
|
+
fi
|
|
11146
|
+
|
|
11147
|
+
# Check SDK mode
|
|
11148
|
+
local sdk_mode="unknown"
|
|
11149
|
+
if command -v node &>/dev/null; then
|
|
11150
|
+
sdk_mode=$(node -e "
|
|
11151
|
+
try {
|
|
11152
|
+
require('@opentelemetry/sdk-trace-node');
|
|
11153
|
+
console.log('real-sdk');
|
|
11154
|
+
} catch(_) {
|
|
11155
|
+
console.log('custom-exporter');
|
|
11156
|
+
}
|
|
11157
|
+
" 2>/dev/null || echo "unavailable")
|
|
11158
|
+
fi
|
|
11159
|
+
echo -e " SDK mode: $sdk_mode"
|
|
11160
|
+
|
|
11161
|
+
# Check config file
|
|
11162
|
+
local config_file=".loki/config.json"
|
|
11163
|
+
if [ -f "$config_file" ]; then
|
|
11164
|
+
local saved_endpoint
|
|
11165
|
+
saved_endpoint=$(python3 -c "import json; print(json.load(open('$config_file')).get('otel_endpoint',''))" 2>/dev/null || echo "")
|
|
11166
|
+
if [ -n "$saved_endpoint" ]; then
|
|
11167
|
+
echo -e " Saved: $saved_endpoint"
|
|
11168
|
+
fi
|
|
11169
|
+
fi
|
|
11170
|
+
echo ""
|
|
11171
|
+
;;
|
|
11172
|
+
|
|
11173
|
+
enable)
|
|
11174
|
+
local endpoint="${1:-http://localhost:4318}"
|
|
11175
|
+
echo -e "${BOLD}Enabling telemetry${NC}"
|
|
11176
|
+
|
|
11177
|
+
# Save to config
|
|
11178
|
+
mkdir -p .loki
|
|
11179
|
+
local config_file=".loki/config.json"
|
|
11180
|
+
if [ -f "$config_file" ]; then
|
|
11181
|
+
python3 -c "
|
|
11182
|
+
import json
|
|
11183
|
+
with open('$config_file') as f:
|
|
11184
|
+
config = json.load(f)
|
|
11185
|
+
config['otel_endpoint'] = '$endpoint'
|
|
11186
|
+
with open('$config_file', 'w') as f:
|
|
11187
|
+
json.dump(config, f, indent=2)
|
|
11188
|
+
" 2>/dev/null
|
|
11189
|
+
else
|
|
11190
|
+
echo "{\"otel_endpoint\": \"$endpoint\"}" > "$config_file"
|
|
11191
|
+
fi
|
|
11192
|
+
|
|
11193
|
+
echo -e " Endpoint set to: ${GREEN}$endpoint${NC}"
|
|
11194
|
+
echo ""
|
|
11195
|
+
echo " Set LOKI_OTEL_ENDPOINT=$endpoint in your shell for immediate use."
|
|
11196
|
+
echo " Or restart loki to pick up the saved config."
|
|
11197
|
+
;;
|
|
11198
|
+
|
|
11199
|
+
disable)
|
|
11200
|
+
echo -e "${BOLD}Disabling telemetry${NC}"
|
|
11201
|
+
|
|
11202
|
+
local config_file=".loki/config.json"
|
|
11203
|
+
if [ -f "$config_file" ]; then
|
|
11204
|
+
python3 -c "
|
|
11205
|
+
import json
|
|
11206
|
+
with open('$config_file') as f:
|
|
11207
|
+
config = json.load(f)
|
|
11208
|
+
config.pop('otel_endpoint', None)
|
|
11209
|
+
with open('$config_file', 'w') as f:
|
|
11210
|
+
json.dump(config, f, indent=2)
|
|
11211
|
+
" 2>/dev/null
|
|
11212
|
+
fi
|
|
11213
|
+
|
|
11214
|
+
echo -e " Telemetry disabled"
|
|
11215
|
+
echo " Unset LOKI_OTEL_ENDPOINT in your shell for immediate effect."
|
|
11216
|
+
;;
|
|
11217
|
+
|
|
11218
|
+
--help|-h|help)
|
|
11219
|
+
echo -e "${BOLD}loki telemetry${NC} - OpenTelemetry management (v6.7.0)"
|
|
11220
|
+
echo ""
|
|
11221
|
+
echo "Usage: loki telemetry <command>"
|
|
11222
|
+
echo ""
|
|
11223
|
+
echo "Commands:"
|
|
11224
|
+
echo " status Show current telemetry config"
|
|
11225
|
+
echo " enable [endpoint] Enable OTEL (default: http://localhost:4318)"
|
|
11226
|
+
echo " disable Disable OTEL"
|
|
11227
|
+
echo ""
|
|
11228
|
+
;;
|
|
11229
|
+
*)
|
|
11230
|
+
echo -e "${RED}Unknown telemetry command: $subcommand${NC}"
|
|
11231
|
+
echo "Run 'loki telemetry help' for usage."
|
|
11232
|
+
return 1
|
|
11233
|
+
;;
|
|
11234
|
+
esac
|
|
11235
|
+
}
|
|
11236
|
+
|
|
10633
11237
|
# Remote Control - start a remote-controllable Claude session
|
|
10634
11238
|
cmd_remote() {
|
|
10635
11239
|
local rc_flags=()
|
|
@@ -10945,4 +11549,281 @@ cmd_completions() {
|
|
|
10945
11549
|
esac
|
|
10946
11550
|
}
|
|
10947
11551
|
|
|
11552
|
+
# Agent type dispatch (v6.7.0)
|
|
11553
|
+
cmd_agent() {
|
|
11554
|
+
local subcommand="${1:-list}"
|
|
11555
|
+
shift 2>/dev/null || true
|
|
11556
|
+
|
|
11557
|
+
# Find agents/types.json relative to script location
|
|
11558
|
+
local script_dir
|
|
11559
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
11560
|
+
local types_file="${script_dir}/../agents/types.json"
|
|
11561
|
+
if [ ! -f "$types_file" ]; then
|
|
11562
|
+
# Try relative to current dir (development)
|
|
11563
|
+
types_file="agents/types.json"
|
|
11564
|
+
fi
|
|
11565
|
+
if [ ! -f "$types_file" ]; then
|
|
11566
|
+
echo -e "${RED}Error: agents/types.json not found${NC}"
|
|
11567
|
+
echo "Expected at: ${script_dir}/../agents/types.json"
|
|
11568
|
+
return 1
|
|
11569
|
+
fi
|
|
11570
|
+
|
|
11571
|
+
case "$subcommand" in
|
|
11572
|
+
list)
|
|
11573
|
+
local filter_swarm="${1:-}"
|
|
11574
|
+
echo -e "${BOLD}Agent Types${NC}"
|
|
11575
|
+
echo ""
|
|
11576
|
+
python3 << PYEOF
|
|
11577
|
+
import json, sys
|
|
11578
|
+
|
|
11579
|
+
with open("$types_file") as f:
|
|
11580
|
+
agents = json.load(f)
|
|
11581
|
+
|
|
11582
|
+
filter_swarm = "$filter_swarm"
|
|
11583
|
+
if filter_swarm and filter_swarm.startswith("--swarm"):
|
|
11584
|
+
# Handle --swarm=X or --swarm X
|
|
11585
|
+
if "=" in filter_swarm:
|
|
11586
|
+
filter_swarm = filter_swarm.split("=")[1]
|
|
11587
|
+
else:
|
|
11588
|
+
filter_swarm = "${2:-}"
|
|
11589
|
+
|
|
11590
|
+
swarms = {}
|
|
11591
|
+
for a in agents:
|
|
11592
|
+
s = a["swarm"]
|
|
11593
|
+
if filter_swarm and s != filter_swarm:
|
|
11594
|
+
continue
|
|
11595
|
+
swarms.setdefault(s, []).append(a)
|
|
11596
|
+
|
|
11597
|
+
total = 0
|
|
11598
|
+
for swarm_name in sorted(swarms.keys()):
|
|
11599
|
+
print(f" {swarm_name.upper()} SWARM:")
|
|
11600
|
+
for a in swarms[swarm_name]:
|
|
11601
|
+
caps = a.get("capabilities", "")
|
|
11602
|
+
if len(caps) > 60:
|
|
11603
|
+
caps = caps[:57] + "..."
|
|
11604
|
+
print(f" {a['type']:22s} {caps}")
|
|
11605
|
+
total += 1
|
|
11606
|
+
print()
|
|
11607
|
+
|
|
11608
|
+
print(f" Total: {total} agent type(s)")
|
|
11609
|
+
PYEOF
|
|
11610
|
+
;;
|
|
11611
|
+
|
|
11612
|
+
info)
|
|
11613
|
+
local agent_type="${1:-}"
|
|
11614
|
+
if [ -z "$agent_type" ]; then
|
|
11615
|
+
echo -e "${RED}Usage: loki agent info <type>${NC}"
|
|
11616
|
+
return 1
|
|
11617
|
+
fi
|
|
11618
|
+
python3 << PYEOF
|
|
11619
|
+
import json, sys
|
|
11620
|
+
|
|
11621
|
+
with open("$types_file") as f:
|
|
11622
|
+
agents = json.load(f)
|
|
11623
|
+
|
|
11624
|
+
target = "$agent_type"
|
|
11625
|
+
found = None
|
|
11626
|
+
for a in agents:
|
|
11627
|
+
if a["type"] == target:
|
|
11628
|
+
found = a
|
|
11629
|
+
break
|
|
11630
|
+
|
|
11631
|
+
if not found:
|
|
11632
|
+
print(f"Agent type not found: {target}")
|
|
11633
|
+
print("Run 'loki agent list' to see all types.")
|
|
11634
|
+
sys.exit(1)
|
|
11635
|
+
|
|
11636
|
+
print(f"Type: {found['type']}")
|
|
11637
|
+
print(f"Name: {found['name']}")
|
|
11638
|
+
print(f"Swarm: {found['swarm']}")
|
|
11639
|
+
print(f"Capabilities: {found['capabilities']}")
|
|
11640
|
+
print(f"Focus: {', '.join(found.get('focus', []))}")
|
|
11641
|
+
print()
|
|
11642
|
+
print(f"Persona:")
|
|
11643
|
+
print(f" {found['persona']}")
|
|
11644
|
+
PYEOF
|
|
11645
|
+
;;
|
|
11646
|
+
|
|
11647
|
+
run)
|
|
11648
|
+
local agent_type="${1:-}"
|
|
11649
|
+
local prompt="${2:-}"
|
|
11650
|
+
if [ -z "$agent_type" ] || [ -z "$prompt" ]; then
|
|
11651
|
+
echo -e "${RED}Usage: loki agent run <type> \"<prompt>\"${NC}"
|
|
11652
|
+
return 1
|
|
11653
|
+
fi
|
|
11654
|
+
|
|
11655
|
+
# Get persona from types.json
|
|
11656
|
+
local persona
|
|
11657
|
+
persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
|
|
11658
|
+
import json, os
|
|
11659
|
+
with open(os.environ['TYPES_FILE']) as f:
|
|
11660
|
+
agents = json.load(f)
|
|
11661
|
+
for a in agents:
|
|
11662
|
+
if a['type'] == os.environ['AGENT_TYPE']:
|
|
11663
|
+
print(a['persona'])
|
|
11664
|
+
break
|
|
11665
|
+
" 2>/dev/null)
|
|
11666
|
+
|
|
11667
|
+
if [ -z "$persona" ]; then
|
|
11668
|
+
echo -e "${RED}Unknown agent type: $agent_type${NC}"
|
|
11669
|
+
return 1
|
|
11670
|
+
fi
|
|
11671
|
+
|
|
11672
|
+
local full_prompt="$persona $prompt"
|
|
11673
|
+
echo -e "${BOLD}Running as: $agent_type${NC}"
|
|
11674
|
+
echo -e "${DIM}Persona: ${persona:0:80}...${NC}"
|
|
11675
|
+
echo ""
|
|
11676
|
+
|
|
11677
|
+
# Invoke current provider
|
|
11678
|
+
local provider="${LOKI_PROVIDER:-claude}"
|
|
11679
|
+
if [ -f ".loki/state/provider" ]; then
|
|
11680
|
+
provider=$(cat ".loki/state/provider" 2>/dev/null)
|
|
11681
|
+
fi
|
|
11682
|
+
|
|
11683
|
+
case "$provider" in
|
|
11684
|
+
claude)
|
|
11685
|
+
claude -p "$full_prompt" 2>&1
|
|
11686
|
+
;;
|
|
11687
|
+
codex)
|
|
11688
|
+
codex exec --full-auto "$full_prompt" 2>&1
|
|
11689
|
+
;;
|
|
11690
|
+
gemini)
|
|
11691
|
+
gemini --approval-mode=yolo "$full_prompt" 2>&1
|
|
11692
|
+
;;
|
|
11693
|
+
cline)
|
|
11694
|
+
cline -y "$full_prompt" 2>&1
|
|
11695
|
+
;;
|
|
11696
|
+
aider)
|
|
11697
|
+
aider --message "$full_prompt" --yes-always --no-auto-commits < /dev/null 2>&1
|
|
11698
|
+
;;
|
|
11699
|
+
*)
|
|
11700
|
+
echo -e "${RED}Unknown provider: $provider${NC}"
|
|
11701
|
+
return 1
|
|
11702
|
+
;;
|
|
11703
|
+
esac
|
|
11704
|
+
;;
|
|
11705
|
+
|
|
11706
|
+
start)
|
|
11707
|
+
local agent_type="${1:-}"
|
|
11708
|
+
local prd="${2:-}"
|
|
11709
|
+
if [ -z "$agent_type" ] || [ -z "$prd" ]; then
|
|
11710
|
+
echo -e "${RED}Usage: loki agent start <type> \"<prompt>\" or loki agent start <type> <prd-file>${NC}"
|
|
11711
|
+
return 1
|
|
11712
|
+
fi
|
|
11713
|
+
|
|
11714
|
+
# Get persona
|
|
11715
|
+
local persona
|
|
11716
|
+
persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
|
|
11717
|
+
import json, os
|
|
11718
|
+
with open(os.environ['TYPES_FILE']) as f:
|
|
11719
|
+
agents = json.load(f)
|
|
11720
|
+
for a in agents:
|
|
11721
|
+
if a['type'] == os.environ['AGENT_TYPE']:
|
|
11722
|
+
print(a['persona'])
|
|
11723
|
+
break
|
|
11724
|
+
" 2>/dev/null)
|
|
11725
|
+
|
|
11726
|
+
if [ -z "$persona" ]; then
|
|
11727
|
+
echo -e "${RED}Unknown agent type: $agent_type${NC}"
|
|
11728
|
+
return 1
|
|
11729
|
+
fi
|
|
11730
|
+
|
|
11731
|
+
echo -e "${BOLD}Starting autonomous session as: $agent_type${NC}"
|
|
11732
|
+
|
|
11733
|
+
# Set agent persona as env var for run.sh to pick up
|
|
11734
|
+
export LOKI_AGENT_PERSONA="$persona"
|
|
11735
|
+
export LOKI_AGENT_TYPE="$agent_type"
|
|
11736
|
+
|
|
11737
|
+
# Delegate to regular start
|
|
11738
|
+
if [ -f "$prd" ]; then
|
|
11739
|
+
cmd_start "$prd"
|
|
11740
|
+
else
|
|
11741
|
+
# Treat as inline prompt - create temp PRD
|
|
11742
|
+
local tmp_prd
|
|
11743
|
+
tmp_prd=$(mktemp /tmp/loki-agent-prd-XXXXXX.md)
|
|
11744
|
+
echo "# Agent Task: $agent_type" > "$tmp_prd"
|
|
11745
|
+
echo "" >> "$tmp_prd"
|
|
11746
|
+
echo "$prd" >> "$tmp_prd"
|
|
11747
|
+
cmd_start "$tmp_prd"
|
|
11748
|
+
rm -f "$tmp_prd"
|
|
11749
|
+
fi
|
|
11750
|
+
;;
|
|
11751
|
+
|
|
11752
|
+
review)
|
|
11753
|
+
local agent_type="${1:-ops-security}"
|
|
11754
|
+
echo -e "${BOLD}Code review as: $agent_type${NC}"
|
|
11755
|
+
|
|
11756
|
+
local persona
|
|
11757
|
+
persona=$(AGENT_TYPE="$agent_type" TYPES_FILE="$types_file" python3 -c "
|
|
11758
|
+
import json, os
|
|
11759
|
+
with open(os.environ['TYPES_FILE']) as f:
|
|
11760
|
+
agents = json.load(f)
|
|
11761
|
+
for a in agents:
|
|
11762
|
+
if a['type'] == os.environ['AGENT_TYPE']:
|
|
11763
|
+
print(a['persona'])
|
|
11764
|
+
break
|
|
11765
|
+
" 2>/dev/null)
|
|
11766
|
+
|
|
11767
|
+
if [ -z "$persona" ]; then
|
|
11768
|
+
echo -e "${RED}Unknown agent type: $agent_type${NC}"
|
|
11769
|
+
return 1
|
|
11770
|
+
fi
|
|
11771
|
+
|
|
11772
|
+
local diff
|
|
11773
|
+
diff=$(git diff HEAD~1 2>/dev/null || git diff --cached 2>/dev/null || echo "No changes to review")
|
|
11774
|
+
|
|
11775
|
+
local review_prompt="$persona
|
|
11776
|
+
|
|
11777
|
+
Review the following code changes. Focus on your area of expertise.
|
|
11778
|
+
Provide findings as:
|
|
11779
|
+
- CRITICAL: Must fix before merge
|
|
11780
|
+
- HIGH: Should fix
|
|
11781
|
+
- MEDIUM: Improvement suggestion
|
|
11782
|
+
- LOW: Nitpick
|
|
11783
|
+
|
|
11784
|
+
Changes:
|
|
11785
|
+
$diff"
|
|
11786
|
+
|
|
11787
|
+
local provider="${LOKI_PROVIDER:-claude}"
|
|
11788
|
+
[ -f ".loki/state/provider" ] && provider=$(cat ".loki/state/provider" 2>/dev/null)
|
|
11789
|
+
|
|
11790
|
+
case "$provider" in
|
|
11791
|
+
claude) claude -p "$review_prompt" 2>&1 ;;
|
|
11792
|
+
codex) codex exec --full-auto "$review_prompt" 2>&1 ;;
|
|
11793
|
+
gemini) gemini --approval-mode=yolo "$review_prompt" 2>&1 ;;
|
|
11794
|
+
cline) cline -y "$review_prompt" 2>&1 ;;
|
|
11795
|
+
*) echo -e "${RED}Unknown provider: $provider${NC}"; return 1 ;;
|
|
11796
|
+
esac
|
|
11797
|
+
;;
|
|
11798
|
+
|
|
11799
|
+
--help|-h|help)
|
|
11800
|
+
echo -e "${BOLD}loki agent${NC} - Agent type dispatch (v6.7.0)"
|
|
11801
|
+
echo ""
|
|
11802
|
+
echo "Usage: loki agent <command> [args]"
|
|
11803
|
+
echo ""
|
|
11804
|
+
echo "Commands:"
|
|
11805
|
+
echo " list [--swarm NAME] List all agent types (optionally filter by swarm)"
|
|
11806
|
+
echo " info <type> Show agent type details"
|
|
11807
|
+
echo " run <type> \"<prompt>\" Single-shot: run prompt with agent persona"
|
|
11808
|
+
echo " start <type> <prd|prompt> Full autonomous loop with agent persona"
|
|
11809
|
+
echo " review [type] Code review with agent persona (default: ops-security)"
|
|
11810
|
+
echo " help Show this help"
|
|
11811
|
+
echo ""
|
|
11812
|
+
echo "Examples:"
|
|
11813
|
+
echo " loki agent list"
|
|
11814
|
+
echo " loki agent list --swarm engineering"
|
|
11815
|
+
echo " loki agent info eng-frontend"
|
|
11816
|
+
echo " loki agent run eng-frontend \"Create a responsive navbar\""
|
|
11817
|
+
echo " loki agent start eng-backend ./api-prd.md"
|
|
11818
|
+
echo " loki agent review ops-security"
|
|
11819
|
+
echo ""
|
|
11820
|
+
;;
|
|
11821
|
+
*)
|
|
11822
|
+
echo -e "${RED}Unknown agent command: $subcommand${NC}"
|
|
11823
|
+
echo "Run 'loki agent help' for usage."
|
|
11824
|
+
return 1
|
|
11825
|
+
;;
|
|
11826
|
+
esac
|
|
11827
|
+
}
|
|
11828
|
+
|
|
10948
11829
|
main "$@"
|