eagle-mem 4.10.10 → 4.10.12

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/lib/provider.sh CHANGED
@@ -152,6 +152,8 @@ eagle_config_init() {
152
152
  # Which LLM provider to use for the curator and analysis features
153
153
  # Options: "ollama" (free, local), "agent_cli" (Codex/Claude CLI auth), "anthropic", "openai"
154
154
  type = "$provider"
155
+ # "auto" retries local/agent/API fallbacks when the primary provider fails.
156
+ fallback = "auto"
155
157
 
156
158
  [ollama]
157
159
  url = "$ollama_url"
@@ -207,6 +209,12 @@ rtk = "auto"
207
209
  # "allow" keeps RTK advisory only.
208
210
  raw_bash = "block"
209
211
 
212
+ [read_guard]
213
+ # advisory = score repeated/expensive reads and nudge; block = deny high-score rereads.
214
+ mode = "advisory"
215
+ score_threshold = "70"
216
+ block_threshold = "90"
217
+
210
218
  [redaction]
211
219
  # Additional secret patterns (regex) beyond built-in defaults
212
220
  # extra_patterns = ["MY_CUSTOM_SECRET_.*"]
@@ -223,14 +231,39 @@ eagle_llm_call() {
223
231
  local system_prompt="${2:-You are a helpful assistant that analyzes software development sessions.}"
224
232
  local max_tokens="${3:-1024}"
225
233
 
226
- local provider
234
+ local provider chain candidate result rc tried=0
227
235
  provider=$(eagle_config_get "provider" "type" "none")
236
+ chain=$(_eagle_provider_chain "$provider")
237
+
238
+ if [ -z "$chain" ]; then
239
+ eagle_log "ERROR" "No LLM provider configured or available. Run: eagle-mem config"
240
+ return 1
241
+ fi
242
+
243
+ while IFS= read -r candidate; do
244
+ [ -z "$candidate" ] && continue
245
+ tried=$((tried + 1))
246
+ result=$(_eagle_call_provider_once "$candidate" "$prompt" "$system_prompt" "$max_tokens")
247
+ rc=$?
248
+ if [ "$rc" -eq 0 ] && [ -n "$result" ]; then
249
+ [ "$candidate" != "$provider" ] && eagle_log "INFO" "LLM provider fallback succeeded: primary=$provider used=$candidate"
250
+ printf '%s\n' "$result"
251
+ return 0
252
+ fi
253
+ eagle_log "WARN" "LLM provider candidate failed: primary=$provider candidate=$candidate rc=$rc"
254
+ done <<< "$chain"
255
+
256
+ eagle_log "ERROR" "All LLM provider candidates failed: primary=$provider tried=$tried"
257
+ return 1
258
+ }
228
259
 
260
+ _eagle_call_provider_once() {
261
+ local provider="$1" prompt="$2" system_prompt="$3" max_tokens="$4"
229
262
  case "$provider" in
230
- ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
263
+ ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
231
264
  agent_cli) _eagle_call_agent_cli "$prompt" "$system_prompt" "$max_tokens" ;;
232
265
  anthropic) _eagle_call_anthropic "$prompt" "$system_prompt" "$max_tokens" ;;
233
- openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
266
+ openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
234
267
  none)
235
268
  eagle_log "ERROR" "No LLM provider configured. Run: eagle-mem config"
236
269
  return 1
@@ -242,6 +275,79 @@ eagle_llm_call() {
242
275
  esac
243
276
  }
244
277
 
278
+ _eagle_provider_chain() {
279
+ local primary="${1:-none}" fallback
280
+ fallback=$(eagle_config_get "provider" "fallback" "auto")
281
+
282
+ local chain=""
283
+ _eagle_provider_chain_add() {
284
+ local candidate="$1" required="${2:-available}"
285
+ [ -n "$candidate" ] || return 0
286
+ case " $chain " in *" $candidate "*) return 0 ;; esac
287
+ if [ "$required" = "available" ] && ! _eagle_provider_available "$candidate"; then
288
+ return 0
289
+ fi
290
+ chain="${chain}${chain:+ }$candidate"
291
+ }
292
+
293
+ if [ "$primary" != "none" ]; then
294
+ _eagle_provider_chain_add "$primary" "always"
295
+ fi
296
+
297
+ if [ "$fallback" != "off" ]; then
298
+ case "$primary" in
299
+ ollama) _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
300
+ agent_cli) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
301
+ anthropic) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "openai" ;;
302
+ openai) _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic" ;;
303
+ none|"") _eagle_provider_chain_add "ollama"; _eagle_provider_chain_add "agent_cli"; _eagle_provider_chain_add "anthropic"; _eagle_provider_chain_add "openai" ;;
304
+ esac
305
+ fi
306
+
307
+ printf '%s\n' "$chain" | tr ' ' '\n' | sed '/^$/d'
308
+ }
309
+
310
+ _eagle_provider_available() {
311
+ case "$1" in
312
+ ollama)
313
+ [ -n "$(eagle_detect_ollama "$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")" 2>/dev/null || true)" ]
314
+ ;;
315
+ agent_cli)
316
+ [ -n "$(_eagle_agent_cli_target_chain)" ]
317
+ ;;
318
+ anthropic)
319
+ [ -n "${ANTHROPIC_API_KEY:-}" ]
320
+ ;;
321
+ openai)
322
+ [ -n "${OPENAI_API_KEY:-}" ]
323
+ ;;
324
+ *) return 1 ;;
325
+ esac
326
+ }
327
+
328
+ _eagle_provider_model_label() {
329
+ case "$1" in
330
+ ollama) printf 'ollama:%s' "$(eagle_config_get "ollama" "model" "mistral")" ;;
331
+ agent_cli) printf 'agent_cli:%s' "$(_eagle_agent_cli_target_summary)" ;;
332
+ anthropic) printf 'anthropic:%s' "$(eagle_config_get "anthropic" "model" "claude-haiku-4-5-20251001")" ;;
333
+ openai) printf 'openai:%s' "$(eagle_config_get "openai" "model" "gpt-4o-mini")" ;;
334
+ *) printf '%s' "$1" ;;
335
+ esac
336
+ }
337
+
338
+ eagle_llm_provider_label() {
339
+ local provider chain labels="" candidate sep=""
340
+ provider=$(eagle_config_get "provider" "type" "none")
341
+ chain=$(_eagle_provider_chain "$provider")
342
+ while IFS= read -r candidate; do
343
+ [ -z "$candidate" ] && continue
344
+ labels="${labels}${sep}$(_eagle_provider_model_label "$candidate")"
345
+ sep=" -> "
346
+ done <<< "$chain"
347
+ [ -n "$labels" ] || labels="none"
348
+ printf '%s\n' "$labels"
349
+ }
350
+
245
351
  _eagle_call_ollama() {
246
352
  local prompt="$1" system="$2" max_tokens="$3"
247
353
  local url model
@@ -281,35 +387,60 @@ _eagle_call_ollama() {
281
387
  }
282
388
 
283
389
  _eagle_agent_cli_target() {
284
- local preferred
390
+ local first
391
+ first=$(_eagle_agent_cli_target_chain | sed -n '1p')
392
+ if [ -n "$first" ]; then
393
+ printf '%s\n' "$first"
394
+ else
395
+ printf 'none\n'
396
+ fi
397
+ }
398
+
399
+ _eagle_agent_cli_target_chain() {
400
+ local preferred current preferred_target targets="" candidate
285
401
  preferred=$(eagle_config_get "agent_cli" "preferred" "current")
402
+ current=""
403
+ if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
404
+ current=$(eagle_agent_source)
405
+ fi
286
406
 
287
407
  case "$preferred" in
288
- codex|openai-codex) echo "codex"; return 0 ;;
289
- claude|claude-code|cloud-code) echo "claude-code"; return 0 ;;
290
- auto)
291
- if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
292
- eagle_agent_source
293
- elif command -v codex &>/dev/null; then
294
- echo "codex"
295
- elif command -v claude &>/dev/null; then
296
- echo "claude-code"
297
- else
298
- echo "none"
299
- fi
300
- ;;
301
- current|*)
302
- if [ -n "${EAGLE_AGENT_SOURCE:-${EAGLE_AGENT:-}}" ]; then
303
- eagle_agent_source
304
- elif command -v codex &>/dev/null; then
305
- echo "codex"
306
- elif command -v claude &>/dev/null; then
307
- echo "claude-code"
308
- else
309
- echo "none"
310
- fi
408
+ codex|openai-codex) preferred_target="codex" ;;
409
+ claude|claude-code|cloud-code) preferred_target="claude-code" ;;
410
+ current) preferred_target="$current" ;;
411
+ auto|"") preferred_target="" ;;
412
+ *)
413
+ eagle_log "WARN" "agent_cli unsupported preferred target: $preferred"
414
+ preferred_target=""
311
415
  ;;
312
416
  esac
417
+
418
+ for candidate in "$preferred_target" "$current" codex claude-code; do
419
+ case "$candidate" in
420
+ codex|claude-code) ;;
421
+ *) continue ;;
422
+ esac
423
+ case "$candidate" in
424
+ codex) command -v codex >/dev/null 2>&1 || continue ;;
425
+ claude-code) command -v claude >/dev/null 2>&1 || continue ;;
426
+ esac
427
+ case " $targets " in *" $candidate "*) continue ;; esac
428
+ targets="${targets}${targets:+ }$candidate"
429
+ done
430
+
431
+ printf '%s\n' "$targets" | tr ' ' '\n' | sed '/^$/d'
432
+ }
433
+
434
+ _eagle_agent_cli_target_summary() {
435
+ local chain summary="" sep="" target
436
+ chain=$(_eagle_agent_cli_target_chain)
437
+ while IFS= read -r target; do
438
+ [ -z "$target" ] && continue
439
+ summary="${summary}${sep}${target}"
440
+ sep=" -> "
441
+ done <<< "$chain"
442
+ [ -n "$summary" ] || summary="none"
443
+ printf '%s\n' "$summary"
313
444
  }
314
445
 
315
446
  _eagle_agent_cli_prompt_file() {
@@ -327,17 +458,36 @@ _eagle_agent_cli_prompt_file() {
327
458
 
328
459
  _eagle_call_agent_cli() {
329
460
  local prompt="$1" system="$2" max_tokens="$3"
330
- local target
331
- target=$(_eagle_agent_cli_target)
461
+ local target targets result rc tried=0
462
+ targets=$(_eagle_agent_cli_target_chain)
332
463
 
333
- case "$target" in
334
- codex) _eagle_call_codex_cli "$prompt" "$system" "$max_tokens" ;;
335
- claude-code) _eagle_call_claude_cli "$prompt" "$system" "$max_tokens" ;;
336
- *)
337
- eagle_log "ERROR" "agent_cli provider unavailable: no Codex or Claude CLI found"
338
- return 1
339
- ;;
340
- esac
464
+ if [ -z "$targets" ]; then
465
+ eagle_log "ERROR" "agent_cli provider unavailable: no supported Codex or Claude CLI found"
466
+ return 1
467
+ fi
468
+
469
+ while IFS= read -r target; do
470
+ [ -z "$target" ] && continue
471
+ tried=$((tried + 1))
472
+ case "$target" in
473
+ codex) result=$(_eagle_call_codex_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
474
+ claude-code) result=$(_eagle_call_claude_cli "$prompt" "$system" "$max_tokens"); rc=$? ;;
475
+ *)
476
+ eagle_log "WARN" "agent_cli unsupported target: $target"
477
+ rc=1
478
+ result=""
479
+ ;;
480
+ esac
481
+ if [ "$rc" -eq 0 ] && [ -n "$result" ]; then
482
+ [ "$tried" -gt 1 ] && eagle_log "INFO" "agent_cli fallback succeeded with $target"
483
+ printf '%s\n' "$result"
484
+ return 0
485
+ fi
486
+ eagle_log "WARN" "agent_cli target failed: target=$target rc=$rc"
487
+ done <<< "$targets"
488
+
489
+ eagle_log "ERROR" "All agent_cli targets failed: targets=$(printf '%s' "$targets" | tr '\n' ',')"
490
+ return 1
341
491
  }
342
492
 
343
493
  _eagle_call_codex_cli() {
@@ -539,16 +689,19 @@ eagle_show_config() {
539
689
  return 1
540
690
  fi
541
691
 
542
- local provider model
692
+ local provider model fallback
543
693
  provider=$(eagle_config_get "provider" "type" "none")
694
+ fallback=$(eagle_config_get "provider" "fallback" "auto")
544
695
  if [ "$provider" = "agent_cli" ]; then
545
- model=$(_eagle_agent_cli_target)
696
+ model=$(_eagle_agent_cli_target_summary)
546
697
  else
547
698
  model=$(eagle_config_get "$provider" "model" "unknown")
548
699
  fi
549
700
 
550
701
  echo "Provider: $provider"
551
702
  echo "Model: $model"
703
+ echo "Fallback: $fallback"
704
+ echo "Chain: $(eagle_llm_provider_label)"
552
705
 
553
706
  if [ "$provider" = "ollama" ]; then
554
707
  local url
@@ -564,6 +717,7 @@ eagle_show_config() {
564
717
  fi
565
718
  elif [ "$provider" = "agent_cli" ]; then
566
719
  echo "Preferred: $(eagle_config_get "agent_cli" "preferred" "current")"
720
+ echo "Targets: $(_eagle_agent_cli_target_summary)"
567
721
  echo "Codex: $(command -v codex 2>/dev/null || echo "not found")"
568
722
  echo "Claude: $(command -v claude 2>/dev/null || echo "not found")"
569
723
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.10.10",
3
+ "version": "4.10.12",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code, Codex, Grok, and Google Antigravity",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
package/scripts/config.sh CHANGED
@@ -36,6 +36,7 @@ show_help() {
36
36
  echo -e " eagle-mem config set updates.allow patch"
37
37
  echo -e " eagle-mem config set token_guard.rtk enforce"
38
38
  echo -e " eagle-mem config set token_guard.raw_bash block"
39
+ echo -e " eagle-mem config set read_guard.mode advisory"
39
40
  echo ""
40
41
  exit 0
41
42
  }
@@ -72,6 +73,7 @@ case "$subcommand" in
72
73
  eagle_info " eagle-mem config set anthropic.model claude-haiku-4-5-20251001"
73
74
  eagle_info " eagle-mem config set token_guard.rtk enforce"
74
75
  eagle_info " eagle-mem config set token_guard.raw_bash block"
76
+ eagle_info " eagle-mem config set read_guard.mode advisory"
75
77
  exit 1
76
78
  fi
77
79
  section="${key%%.*}"
package/scripts/curate.sh CHANGED
@@ -39,22 +39,20 @@ EOF
39
39
 
40
40
  parse_consolidations_json() {
41
41
  local result="$1"
42
- local trimmed json_payload
43
-
44
- trimmed=$(printf '%s' "$result" | tr -d '\r' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
45
- case "$trimmed" in
46
- ""|NONE|none|null)
47
- printf '[]\n'
48
- return 0
49
- ;;
50
- esac
51
-
52
- json_payload=$(printf '%s' "$result" \
53
- | sed -e '1s/^[[:space:]]*```json[[:space:]]*$//' \
54
- -e '1s/^[[:space:]]*```[[:space:]]*$//' \
55
- -e '$s/^[[:space:]]*```[[:space:]]*$//')
56
-
57
- printf '%s' "$json_payload" | jq -c '
42
+ printf '%s' "$result" | jq -Rrs -c '
43
+ def text_trim: gsub("^\\s+|\\s+$"; "");
44
+ def parse_payload:
45
+ gsub("\r"; "")
46
+ | gsub("^\\s*```json\\s*\\n"; "")
47
+ | gsub("^\\s*```\\s*\\n"; "")
48
+ | gsub("\\n\\s*```\\s*$"; "")
49
+ | text_trim
50
+ | if . == "" or . == "NONE" or . == "none" or . == "null" then []
51
+ else
52
+ try fromjson catch (
53
+ ([match("(?s)(\\{.*\\}|\\[.*\\])")? | .string][0] // "[]") | fromjson
54
+ )
55
+ end;
58
56
  def trim: gsub("^\\s+|\\s+$"; "");
59
57
  def names:
60
58
  if type == "array" then map(tostring | trim) | map(select(length > 0))
@@ -66,7 +64,8 @@ parse_consolidations_json() {
66
64
  elif type == "object" then (.consolidations // .items // .instructions // [])
67
65
  else []
68
66
  end;
69
- root
67
+ parse_payload
68
+ | root
70
69
  | map({
71
70
  source_names: ((.source_names // .sourceNames // .source_memories // .sourceMemories // .original_names // .originalNames // .originals // .names) | names),
72
71
  new_name: ((.new_name // .newName // .name // .title // "") | tostring | trim),
@@ -100,19 +99,27 @@ fi
100
99
 
101
100
  p_esc=$(eagle_sql_escape "$project")
102
101
 
102
+ cleanup_curate() {
103
+ local rc=$?
104
+ eagle_run_finish "$rc" "$LINENO"
105
+ }
106
+ eagle_run_start "curate" "$project" "$(pwd)"
107
+ trap cleanup_curate EXIT
108
+
103
109
  # Verify provider is configured
104
110
  provider=$(eagle_config_get "provider" "type" "none")
105
111
  if [ "$provider" = "none" ]; then
106
112
  eagle_err "No LLM provider configured. Run: eagle-mem config init"
107
113
  exit 1
108
114
  fi
109
- eagle_info "Provider: $provider ($(eagle_config_get "$provider" "model" "unknown"))"
115
+ eagle_info "Provider: $(eagle_llm_provider_label)"
110
116
  eagle_info "Project: $project"
111
117
  [ "$DRY_RUN" -eq 1 ] && eagle_info "Dry run — no changes will be made"
112
118
  echo ""
113
119
 
114
120
  # ─── 1. Analyze gotchas for promotion ─────────────────────
115
121
 
122
+ eagle_run_step "promote_gotchas"
116
123
  eagle_info "Analyzing gotchas for promotion..."
117
124
 
118
125
  recent_gotchas=$(eagle_db "SELECT gotchas, created_at
package/scripts/health.sh CHANGED
@@ -136,14 +136,11 @@ max_score=$((max_score + 15))
136
136
 
137
137
  provider=$(eagle_config_get "provider" "type" "none")
138
138
  if [ "$provider" != "none" ]; then
139
- if [ "$provider" = "agent_cli" ]; then
140
- model=$(_eagle_agent_cli_target)
141
- else
142
- model=$(eagle_config_get "$provider" "model" "default")
143
- fi
144
- eagle_ok "Provider: ${provider} (${model})"
139
+ provider_chain=$(eagle_llm_provider_label)
140
+ eagle_ok "Provider: ${provider_chain}"
145
141
  score=$((score + 15))
146
142
  else
143
+ provider_chain="none"
147
144
  eagle_fail "No LLM provider — curator and enrichment disabled"
148
145
  issues+=("Configure a provider: eagle-mem config init")
149
146
  fi
@@ -321,6 +318,7 @@ if [ "$JSON_OUT" -eq 1 ]; then
321
318
  --argjson enriched_summaries "${enriched_summaries:-0}" \
322
319
  --argjson features "${feature_count:-0}" \
323
320
  --arg provider "$provider" \
321
+ --arg provider_chain "$provider_chain" \
324
322
  --arg token_guard_rtk "$rtk_mode" \
325
323
  --arg token_guard_raw_bash "$raw_bash_mode" \
326
324
  --arg rtk_bin "${rtk_bin:-}" \
@@ -345,7 +343,7 @@ if [ "$JSON_OUT" -eq 1 ]; then
345
343
  '{project:$project, score:$score, max:$max_score, pct:$pct, grade:$grade,
346
344
  capture:{sessions:$total_sessions, summaries:$total_summaries, heuristic:$heuristic_summaries},
347
345
  enrichment:$enriched_summaries,
348
- features:$features, provider:$provider,
346
+ features:$features, provider:$provider, provider_chain:$provider_chain,
349
347
  token_guard:{rtk:$token_guard_rtk, raw_bash:$token_guard_raw_bash, rtk_bin:$rtk_bin},
350
348
  orchestration:{
351
349
  route:$orchestration_route,
package/scripts/help.sh CHANGED
@@ -23,6 +23,7 @@ echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
23
23
  echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
24
24
  echo -e " ${CYAN}health${RESET} Diagnose pipeline health and background automation"
25
25
  echo -e " ${CYAN}doctor${RESET} Show install footprint, hooks, SQLite, manifest, and runtime drift"
26
+ echo -e " ${CYAN}logs${RESET} Inspect/prune command-scoped scan/index/curate logs"
26
27
  echo -e " ${CYAN}updates${RESET} Auto-update status and policy"
27
28
  echo -e " ${CYAN}overview${RESET} Build or view project overview"
28
29
  echo -e " ${CYAN}session${RESET} Save a manual session summary"
package/scripts/index.sh CHANGED
@@ -40,6 +40,15 @@ TARGET_DIR="${args[0]:-.}"
40
40
  TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
41
41
  PROJECT=$(eagle_project_from_cwd "$TARGET_DIR")
42
42
 
43
+ TMPDIR_IDX=""
44
+ cleanup_index() {
45
+ local rc=$?
46
+ [ -n "${TMPDIR_IDX:-}" ] && rm -rf "$TMPDIR_IDX" 2>/dev/null || true
47
+ eagle_run_finish "$rc" "$LINENO"
48
+ }
49
+ eagle_run_start "index" "$PROJECT" "$TARGET_DIR"
50
+ trap cleanup_index EXIT
51
+
43
52
  CHUNK_SIZE="${EAGLE_MEM_CHUNK_SIZE:-80}"
44
53
  if ! [[ "$CHUNK_SIZE" =~ ^[0-9]+$ ]] || [ "$CHUNK_SIZE" -lt 1 ]; then
45
54
  CHUNK_SIZE=80
@@ -111,10 +120,10 @@ sql_literal_expr() {
111
120
  # ─── Collect files ─────────────────────────────────────────
112
121
 
113
122
  TMPDIR_IDX=$(mktemp -d)
114
- trap 'rm -rf "$TMPDIR_IDX"' EXIT
115
123
 
116
124
  ALL_FILES="$TMPDIR_IDX/all_files"
117
125
 
126
+ eagle_run_step "collect_files"
118
127
  eagle_collect_files "$TARGET_DIR" "$ALL_FILES"
119
128
 
120
129
  # Filter to source files only, skip large files
@@ -147,6 +156,7 @@ NEEDS_INDEX="$TMPDIR_IDX/needs_index"
147
156
  skipped_count=0
148
157
 
149
158
  if [ "$force" = true ]; then
159
+ eagle_run_step "force_clear_index_state"
150
160
  eagle_info "Force rebuild requested: clearing chunks, declarations, and import edges"
151
161
  eagle_graph_clear_index_state "$PROJECT"
152
162
  fi
@@ -183,6 +193,7 @@ if [ "$needs_count" -eq 0 ]; then
183
193
  fi
184
194
 
185
195
  eagle_info "$needs_count files to index"
196
+ eagle_run_step "index_files count=$needs_count"
186
197
 
187
198
  # ─── Chunk and index files ─────────────────────────────────
188
199
 
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Command Run Logs
4
+ # ═══════════════════════════════════════════════════════════
5
+ set -euo pipefail
6
+
7
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ LIB_DIR="$SCRIPTS_DIR/../lib"
9
+
10
+ . "$SCRIPTS_DIR/style.sh"
11
+ . "$LIB_DIR/common.sh"
12
+
13
+ cmd="${1:-list}"
14
+ [ $# -gt 0 ] && shift || true
15
+
16
+ show_help() {
17
+ cat <<EOF
18
+ Usage: eagle-mem logs [list|tail|show|prune] [run-id-or-filename]
19
+
20
+ Commands:
21
+ list Show recent command-scoped run logs
22
+ tail [id|filename] Tail a run log, or the latest run log when omitted
23
+ show <id|filename> Print a run log
24
+ prune [--days N] [--keep N]
25
+ Delete old run logs (defaults: 14 days, latest 50)
26
+ EOF
27
+ }
28
+
29
+ resolve_log_path() {
30
+ local ref="${1:-}"
31
+ local runs_root="${EAGLE_RUNS_DIR%/}" rel_ref
32
+ if [ -z "$ref" ]; then
33
+ ls -t "$runs_root"/*.log 2>/dev/null | while IFS= read -r candidate; do
34
+ [ -L "$candidate" ] && continue
35
+ [ -f "$candidate" ] && printf '%s\n' "$candidate" && break
36
+ done
37
+ return 0
38
+ fi
39
+
40
+ case "$ref" in
41
+ *$'\n'*|*..*) return 1 ;;
42
+ /*)
43
+ rel_ref="${ref#"$runs_root"/}"
44
+ [ "$rel_ref" != "$ref" ] || return 1
45
+ case "$rel_ref" in ""|*/*) return 1 ;; esac
46
+ [ -L "$runs_root/$rel_ref" ] && return 1
47
+ [ -f "$runs_root/$rel_ref" ] && printf '%s\n' "$runs_root/$rel_ref" && return 0
48
+ return 1
49
+ ;;
50
+ */*) return 1 ;;
51
+ esac
52
+
53
+ if [ ! -L "$runs_root/$ref" ] && [ -f "$runs_root/$ref" ]; then
54
+ printf '%s\n' "$runs_root/$ref"
55
+ return 0
56
+ fi
57
+ if [ ! -L "$runs_root/$ref.log" ] && [ -f "$runs_root/$ref.log" ]; then
58
+ printf '%s\n' "$runs_root/$ref.log"
59
+ return 0
60
+ fi
61
+ return 1
62
+ }
63
+
64
+ run_log_count() {
65
+ [ -d "$EAGLE_RUNS_DIR" ] || {
66
+ printf '0\n'
67
+ return 0
68
+ }
69
+ find "$EAGLE_RUNS_DIR" -type f -name '*.log' -print 2>/dev/null | wc -l | tr -d ' '
70
+ }
71
+
72
+ case "$cmd" in
73
+ -h|--help|help)
74
+ show_help
75
+ ;;
76
+ list)
77
+ eagle_header "Run Logs"
78
+ if ! ls "$EAGLE_RUNS_DIR"/*.log >/dev/null 2>&1; then
79
+ eagle_info "No run logs found yet"
80
+ eagle_dim "Run eagle-mem scan, index, or curate to create command-scoped logs."
81
+ exit 0
82
+ fi
83
+ ls -t "$EAGLE_RUNS_DIR"/*.log 2>/dev/null | head -20 | while IFS= read -r log_path; do
84
+ [ -L "$log_path" ] && continue
85
+ first_line=$(sed -n '1p' "$log_path" 2>/dev/null)
86
+ run_id=$(basename "$log_path" .log)
87
+ printf ' %s %s\n' "$run_id" "$first_line"
88
+ done
89
+ ;;
90
+ tail)
91
+ log_path=$(resolve_log_path "${1:-}") || {
92
+ eagle_err "Run log not found"
93
+ exit 1
94
+ }
95
+ tail -n "${EAGLE_LOG_TAIL_LINES:-80}" "$log_path"
96
+ ;;
97
+ show)
98
+ log_path=$(resolve_log_path "${1:-}") || {
99
+ eagle_err "Run log not found"
100
+ exit 1
101
+ }
102
+ cat "$log_path"
103
+ ;;
104
+ prune)
105
+ days="${EAGLE_RUN_LOG_RETENTION_DAYS:-14}"
106
+ keep="${EAGLE_RUN_LOG_MAX_COUNT:-50}"
107
+ while [ $# -gt 0 ]; do
108
+ case "$1" in
109
+ --days)
110
+ days="${2:-}"
111
+ shift 2
112
+ ;;
113
+ --keep)
114
+ keep="${2:-}"
115
+ shift 2
116
+ ;;
117
+ *)
118
+ eagle_err "Unknown prune option: $1"
119
+ show_help
120
+ exit 1
121
+ ;;
122
+ esac
123
+ done
124
+ case "$days" in ""|*[!0-9]*)
125
+ eagle_err "Invalid --days value: $days"
126
+ exit 1
127
+ ;;
128
+ esac
129
+ case "$keep" in ""|*[!0-9]*)
130
+ eagle_err "Invalid --keep value: $keep"
131
+ exit 1
132
+ ;;
133
+ esac
134
+ before=$(run_log_count)
135
+ eagle_run_prune_logs "$days" "$keep"
136
+ after=$(run_log_count)
137
+ eagle_ok "Pruned run logs: before=$before after=$after days=$days keep=$keep"
138
+ ;;
139
+ *)
140
+ eagle_err "Unknown logs command: $cmd"
141
+ show_help
142
+ exit 1
143
+ ;;
144
+ esac
package/scripts/scan.sh CHANGED
@@ -29,6 +29,15 @@ TARGET_DIR="${args[0]:-.}"
29
29
  TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
30
30
  PROJECT=$(eagle_project_from_cwd "$TARGET_DIR")
31
31
 
32
+ TMPFILE=""
33
+ cleanup_scan() {
34
+ local rc=$?
35
+ [ -n "${TMPFILE:-}" ] && rm -f "$TMPFILE" "${TMPFILE}.analysis" 2>/dev/null || true
36
+ eagle_run_finish "$rc" "$LINENO"
37
+ }
38
+ eagle_run_start "scan" "$PROJECT" "$TARGET_DIR"
39
+ trap cleanup_scan EXIT
40
+
32
41
  eagle_header "Scan"
33
42
  eagle_info "Scanning ${BOLD}$PROJECT${RESET} at $TARGET_DIR"
34
43
  echo ""
@@ -52,8 +61,8 @@ if git -C "$TARGET_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
52
61
  fi
53
62
 
54
63
  TMPFILE=$(mktemp)
55
- trap 'rm -f "$TMPFILE"' EXIT
56
64
 
65
+ eagle_run_step "collect_files"
57
66
  eagle_collect_files "$TARGET_DIR" "$TMPFILE"
58
67
 
59
68
  total_files=$(wc -l < "$TMPFILE" | tr -d ' ')
@@ -67,6 +76,7 @@ eagle_ok "$total_files files found"
67
76
 
68
77
  # ─── Language breakdown (bash 3 compatible — no assoc arrays) ──
69
78
 
79
+ eagle_run_step "language_breakdown"
70
80
  while IFS= read -r file; do
71
81
  ext="${file##*.}"
72
82
  [ "$ext" = "$file" ] && continue