eagle-mem 4.9.6 → 4.9.7

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/scripts/search.sh CHANGED
@@ -34,6 +34,7 @@ show_help() {
34
34
  echo -e " ${CYAN}-n, --limit${RESET} <N> Max results (default: 10)"
35
35
  echo -e " ${CYAN}-a, --all${RESET} Search across all projects"
36
36
  echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
37
+ echo -e " ${CYAN}--raw, --debug${RESET} Show raw IDs and detailed observation rows"
37
38
  echo ""
38
39
  exit 0
39
40
  }
@@ -45,6 +46,7 @@ project=""
45
46
  limit=10
46
47
  session_id=""
47
48
  json_output=false
49
+ raw_output=false
48
50
  query=""
49
51
  cross_project=false
50
52
 
@@ -61,6 +63,7 @@ while [ $# -gt 0 ]; do
61
63
  --limit|-n) limit="$2"; shift 2 ;;
62
64
  --all|-a) cross_project=true; shift ;;
63
65
  --json|-j) json_output=true; shift ;;
66
+ --raw|--debug) raw_output=true; shift ;;
64
67
  --help|-h) show_help ;;
65
68
  -*)
66
69
  eagle_err "Unknown option: $1"
@@ -93,23 +96,57 @@ search_keyword() {
93
96
  fi
94
97
 
95
98
  if [ "$json_output" = true ]; then
96
- eagle_db_json "SELECT s.id, s.project, s.agent, s.request, s.completed, s.learned, s.created_at
99
+ eagle_db_json "SELECT s.id, s.project, s.agent, s.request, s.completed, s.learned, s.created_at,
100
+ CASE
101
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 'Fresh'
102
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 'Recent'
103
+ ELSE 'Older'
104
+ END AS freshness
97
105
  FROM summaries s
98
106
  JOIN summaries_fts f ON f.rowid = s.id
99
107
  WHERE summaries_fts MATCH '$q'
108
+ AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
109
+ AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
110
+ AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
111
+ AND COALESCE(s.request, '') NOT LIKE '<subagent_notification>%'
112
+ AND COALESCE(s.request, '') NOT LIKE '</subagent_notification>%'
100
113
  $where_project
101
- ORDER BY rank
114
+ ORDER BY
115
+ CASE
116
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 0
117
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 1
118
+ ELSE 2
119
+ END,
120
+ s.created_at DESC,
121
+ rank
102
122
  LIMIT $limit;"
103
123
  return
104
124
  fi
105
125
 
106
126
  local results
107
- results=$(eagle_db "SELECT s.id, s.project, s.agent, s.request, s.completed, s.learned, s.created_at
127
+ results=$(eagle_db "SELECT s.id, s.project, s.agent, s.request, s.completed, s.learned, s.created_at,
128
+ CASE
129
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 'Fresh'
130
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 'Recent'
131
+ ELSE 'Older'
132
+ END AS freshness
108
133
  FROM summaries s
109
134
  JOIN summaries_fts f ON f.rowid = s.id
110
135
  WHERE summaries_fts MATCH '$q'
136
+ AND COALESCE(s.request, '') NOT LIKE '%<local-command-caveat>%'
137
+ AND COALESCE(s.request, '') NOT LIKE '# AGENTS.md instructions%'
138
+ AND COALESCE(s.request, '') NOT LIKE '<environment_context>%'
139
+ AND COALESCE(s.request, '') NOT LIKE '<subagent_notification>%'
140
+ AND COALESCE(s.request, '') NOT LIKE '</subagent_notification>%'
111
141
  $where_project
112
- ORDER BY rank
142
+ ORDER BY
143
+ CASE
144
+ WHEN julianday('now') - julianday(s.created_at) <= 14 THEN 0
145
+ WHEN julianday('now') - julianday(s.created_at) <= 45 THEN 1
146
+ ELSE 2
147
+ END,
148
+ s.created_at DESC,
149
+ rank
113
150
  LIMIT $limit;")
114
151
 
115
152
  if [ -z "$results" ]; then
@@ -118,17 +155,18 @@ search_keyword() {
118
155
  fi
119
156
 
120
157
  echo ""
121
- while IFS='|' read -r sid sproj sagent req completed learned created_at; do
158
+ local idx=1
159
+ while IFS='|' read -r sid sproj sagent req completed learned created_at freshness; do
122
160
  [ -z "$sid" ] && continue
123
- echo -e " ${BOLD}#$sid${RESET} ${DIM}$created_at${RESET}"
161
+ echo -e " ${BOLD}${idx}. ${req:-Memory match}${RESET}"
162
+ echo -e " ${DIM}source:${RESET} $(eagle_agent_label "$sagent") ${DIM}date:${RESET} $created_at ${DIM}freshness:${RESET} $freshness"
124
163
  if [ "$cross_project" = true ]; then
125
164
  echo -e " ${DIM}project:${RESET} $sproj"
126
165
  fi
127
- echo -e " ${DIM}source:${RESET} $(eagle_agent_label "$sagent")"
128
- [ -n "$req" ] && echo -e " ${CYAN}Request:${RESET} $req"
129
166
  [ -n "$completed" ] && echo -e " ${GREEN}Done:${RESET} $completed"
130
167
  [ -n "$learned" ] && echo -e " ${YELLOW}Learned:${RESET} $learned"
131
168
  echo ""
169
+ idx=$((idx + 1))
132
170
  done <<< "$results"
133
171
  }
134
172
 
@@ -163,21 +201,45 @@ search_timeline() {
163
201
  echo -e " ${DIM}─────────────────────────────────────${RESET}"
164
202
  echo ""
165
203
 
204
+ local idx=1
166
205
  while IFS='|' read -r sid sagent req completed learned next_steps created_at; do
167
206
  [ -z "$sid" ] && continue
168
- echo -e " ${BOLD}#$sid${RESET} ${DIM}$created_at${RESET}"
207
+ if [ "$raw_output" = true ]; then
208
+ echo -e " ${BOLD}#$sid${RESET} ${DIM}$created_at${RESET}"
209
+ else
210
+ echo -e " ${BOLD}${idx}. Session summary${RESET} ${DIM}$created_at${RESET}"
211
+ fi
169
212
  echo -e " ${DIM}source:${RESET} $(eagle_agent_label "$sagent")"
170
213
  [ -n "$req" ] && echo -e " ${CYAN}Request:${RESET} $req"
171
214
  [ -n "$completed" ] && echo -e " ${GREEN}Done:${RESET} $completed"
172
215
  [ -n "$learned" ] && echo -e " ${YELLOW}Learned:${RESET} $learned"
173
216
  [ -n "$next_steps" ] && echo -e " ${DIM}Next:${RESET} $next_steps"
174
217
  echo ""
218
+ idx=$((idx + 1))
175
219
  done <<< "$results"
220
+ if [ "$raw_output" = false ]; then
221
+ eagle_dim "Run with --raw to show summary IDs."
222
+ fi
176
223
  }
177
224
 
178
225
  # ─── Session details ──────────────────────────────────────
179
226
 
180
227
  search_session() {
228
+ if [ -z "$session_id" ]; then
229
+ eagle_err "Usage: eagle-mem search --session <session-id-or-summary-id>"
230
+ exit 1
231
+ fi
232
+
233
+ local lookup_sql resolved_session
234
+ lookup_sql=$(eagle_sql_escape "$session_id")
235
+ case "$session_id" in
236
+ *[!0-9]*) ;;
237
+ *)
238
+ resolved_session=$(eagle_db "SELECT session_id FROM summaries WHERE id = '$lookup_sql' LIMIT 1;" 2>/dev/null | awk 'NF { print; exit }')
239
+ [ -n "$resolved_session" ] && session_id="$resolved_session"
240
+ ;;
241
+ esac
242
+
181
243
  local sid_sql; sid_sql=$(eagle_sql_escape "$session_id")
182
244
 
183
245
  if [ "$json_output" = true ]; then
@@ -188,33 +250,111 @@ search_session() {
188
250
  return
189
251
  fi
190
252
 
191
- local results
192
- results=$(eagle_db "SELECT o.agent, o.tool_name, o.tool_input_summary, o.files_read, o.files_modified, o.created_at
193
- FROM observations o
194
- WHERE o.session_id = '$sid_sql'
195
- ORDER BY o.created_at ASC;")
253
+ local results_json
254
+ results_json=$(eagle_db_json "SELECT o.agent, o.tool_name, o.tool_input_summary, o.files_read, o.files_modified, o.created_at
255
+ FROM observations o
256
+ WHERE o.session_id = '$sid_sql'
257
+ ORDER BY o.created_at ASC;")
196
258
 
197
- if [ -z "$results" ]; then
259
+ if [ -z "$results_json" ] || [ "$(printf '%s' "$results_json" | jq 'length' 2>/dev/null)" = "0" ]; then
198
260
  eagle_dim "No observations found for session '$session_id'"
199
261
  return
200
262
  fi
201
263
 
264
+ if [ "$raw_output" = true ]; then
265
+ echo ""
266
+ echo -e " ${BOLD}Session${RESET} ${DIM}$session_id${RESET}"
267
+ echo -e " ${DIM}─────────────────────────────────────${RESET}"
268
+ echo ""
269
+
270
+ printf '%s' "$results_json" | jq -c '.[]' | while IFS= read -r row; do
271
+ oagent=$(printf '%s' "$row" | jq -r '.agent // ""')
272
+ tool_name=$(printf '%s' "$row" | jq -r '.tool_name // ""')
273
+ summary=$(printf '%s' "$row" | jq -r '.tool_input_summary // ""')
274
+ created_at=$(printf '%s' "$row" | jq -r '.created_at // ""')
275
+ [ -z "$tool_name" ] && continue
276
+ local icon="$DOT"
277
+ case "$tool_name" in
278
+ Read) icon="${CYAN}R${RESET}" ;;
279
+ Write) icon="${GREEN}W${RESET}" ;;
280
+ Edit) icon="${YELLOW}E${RESET}" ;;
281
+ Bash|exec_command|shell_command|unified_exec) icon="${BLUE}\$${RESET}" ;;
282
+ esac
283
+ echo -e " ${icon} ${DIM}$created_at $(eagle_agent_label "$oagent")${RESET} $summary"
284
+ done
285
+ echo ""
286
+ return
287
+ fi
288
+
289
+ local first_seen last_seen total_obs agent_counts tool_counts
290
+ first_seen=$(eagle_db "SELECT MIN(created_at) FROM observations WHERE session_id = '$sid_sql';")
291
+ last_seen=$(eagle_db "SELECT MAX(created_at) FROM observations WHERE session_id = '$sid_sql';")
292
+ total_obs=$(printf '%s' "$results_json" | jq 'length' 2>/dev/null)
293
+ agent_counts=$(eagle_db "SELECT agent || ':' || COUNT(*) FROM observations WHERE session_id = '$sid_sql' GROUP BY agent ORDER BY COUNT(*) DESC;")
294
+ tool_counts=$(eagle_db "SELECT tool_name || ':' || COUNT(*) FROM observations WHERE session_id = '$sid_sql' GROUP BY tool_name ORDER BY COUNT(*) DESC LIMIT 8;")
295
+
202
296
  echo ""
203
- echo -e " ${BOLD}Session${RESET} ${DIM}$session_id${RESET}"
297
+ echo -e " ${BOLD}Session Activity${RESET}"
204
298
  echo -e " ${DIM}─────────────────────────────────────${RESET}"
205
299
  echo ""
206
-
207
- while IFS='|' read -r oagent tool_name summary files_r files_m created_at; do
300
+ eagle_kv "Observations:" "${total_obs:-0}"
301
+ [ -n "$first_seen" ] && eagle_kv "Started:" "$first_seen"
302
+ [ -n "$last_seen" ] && eagle_kv "Last seen:" "$last_seen"
303
+ if [ -n "$agent_counts" ]; then
304
+ local agents_display=""
305
+ while IFS=':' read -r a count; do
306
+ [ -z "$a" ] && continue
307
+ [ -n "$agents_display" ] && agents_display+=", "
308
+ agents_display+="$(eagle_agent_label "$a") $count"
309
+ done <<< "$agent_counts"
310
+ [ -n "$agents_display" ] && eagle_kv "Agents:" "$agents_display"
311
+ fi
312
+ if [ -n "$tool_counts" ]; then
313
+ local tools_display=""
314
+ while IFS=':' read -r t count; do
315
+ [ -z "$t" ] && continue
316
+ [ -n "$tools_display" ] && tools_display+=", "
317
+ tools_display+="$t $count"
318
+ done <<< "$tool_counts"
319
+ [ -n "$tools_display" ] && eagle_kv "Tools:" "$tools_display"
320
+ fi
321
+ echo ""
322
+ echo -e " ${BOLD}Recent activity${RESET}"
323
+
324
+ printf '%s' "$results_json" | jq -c --argjson limit "$limit" '.[-$limit:][]' | while IFS= read -r row; do
325
+ oagent=$(printf '%s' "$row" | jq -r '.agent // ""')
326
+ tool_name=$(printf '%s' "$row" | jq -r '.tool_name // ""')
327
+ summary=$(printf '%s' "$row" | jq -r '.tool_input_summary // ""')
328
+ files_r=$(printf '%s' "$row" | jq -r '.files_read // "[]"')
329
+ files_m=$(printf '%s' "$row" | jq -r '.files_modified // "[]"')
330
+ created_at=$(printf '%s' "$row" | jq -r '.created_at // ""')
208
331
  [ -z "$tool_name" ] && continue
209
- local icon="$DOT"
332
+ local label detail
333
+ label="$tool_name"
334
+ detail=$(eagle_trim_text "$summary" 120)
210
335
  case "$tool_name" in
211
- Read) icon="${CYAN}R${RESET}" ;;
212
- Write) icon="${GREEN}W${RESET}" ;;
213
- Edit) icon="${YELLOW}E${RESET}" ;;
214
- Bash) icon="${BLUE}\$${RESET}" ;;
336
+ Read)
337
+ label="Read"
338
+ detail=$(printf '%s' "$files_r" | jq -r 'fromjson? | .[0] // empty' 2>/dev/null)
339
+ [ -n "$detail" ] && detail=$(basename "$detail")
340
+ ;;
341
+ Write|Edit)
342
+ label="$tool_name"
343
+ detail=$(printf '%s' "$files_m" | jq -r 'fromjson? | .[0] // empty' 2>/dev/null)
344
+ [ -n "$detail" ] && detail=$(basename "$detail")
345
+ ;;
346
+ apply_patch)
347
+ label="Patch"
348
+ ;;
349
+ Bash|exec_command|shell_command|unified_exec)
350
+ label="Command"
351
+ ;;
215
352
  esac
216
- echo -e " ${icon} ${DIM}$created_at $(eagle_agent_label "$oagent")${RESET} $summary"
217
- done <<< "$results"
353
+ [ -z "$detail" ] && detail="$(eagle_agent_label "$oagent") activity"
354
+ echo -e " ${DOT} ${DIM}$created_at${RESET} ${BOLD}$label:${RESET} $detail"
355
+ done
356
+ echo ""
357
+ eagle_dim "Run with --raw for full observation rows and session ID."
218
358
  echo ""
219
359
  }
220
360
 
@@ -355,21 +495,45 @@ search_memories() {
355
495
  fi
356
496
 
357
497
  if [ "$json_output" = true ]; then
358
- eagle_db_json "SELECT m.memory_name, m.memory_type, m.description, m.project, m.updated_at, m.origin_agent
498
+ eagle_db_json "SELECT m.memory_name, m.memory_type, m.description, m.project, m.updated_at, m.origin_agent,
499
+ CASE
500
+ WHEN julianday('now') - julianday(m.updated_at) <= 14 THEN 'Fresh'
501
+ WHEN julianday('now') - julianday(m.updated_at) <= 45 THEN 'Recent'
502
+ ELSE 'Older'
503
+ END AS freshness
359
504
  FROM agent_memories m
360
505
  JOIN agent_memories_fts f ON f.rowid = m.id
361
506
  $where_match
362
- ORDER BY rank
507
+ ORDER BY
508
+ CASE
509
+ WHEN julianday('now') - julianday(m.updated_at) <= 14 THEN 0
510
+ WHEN julianday('now') - julianday(m.updated_at) <= 45 THEN 1
511
+ ELSE 2
512
+ END,
513
+ m.updated_at DESC,
514
+ rank
363
515
  LIMIT $limit;"
364
516
  return
365
517
  fi
366
518
 
367
519
  local results
368
- results=$(eagle_db "SELECT m.memory_name, m.memory_type, m.description, m.project, m.updated_at, m.origin_agent
520
+ results=$(eagle_db "SELECT m.memory_name, m.memory_type, m.description, m.project, m.updated_at, m.origin_agent,
521
+ CASE
522
+ WHEN julianday('now') - julianday(m.updated_at) <= 14 THEN 'Fresh'
523
+ WHEN julianday('now') - julianday(m.updated_at) <= 45 THEN 'Recent'
524
+ ELSE 'Older'
525
+ END AS freshness
369
526
  FROM agent_memories m
370
527
  JOIN agent_memories_fts f ON f.rowid = m.id
371
528
  $where_match
372
- ORDER BY rank
529
+ ORDER BY
530
+ CASE
531
+ WHEN julianday('now') - julianday(m.updated_at) <= 14 THEN 0
532
+ WHEN julianday('now') - julianday(m.updated_at) <= 45 THEN 1
533
+ ELSE 2
534
+ END,
535
+ m.updated_at DESC,
536
+ rank
373
537
  LIMIT $limit;")
374
538
 
375
539
  if [ -z "$results" ]; then
@@ -378,9 +542,9 @@ search_memories() {
378
542
  fi
379
543
 
380
544
  echo ""
381
- while IFS='|' read -r mname mtype mdesc mproj mupdated morigin; do
545
+ while IFS='|' read -r mname mtype mdesc mproj mupdated morigin freshness; do
382
546
  [ -z "$mname" ] && continue
383
- echo -e " ${BOLD}$mname${RESET} ${DIM}[$mtype][$(eagle_agent_label "$morigin")]${RESET}"
547
+ echo -e " ${BOLD}$mname${RESET} ${DIM}[$mtype][$(eagle_agent_label "$morigin")][$freshness]${RESET}"
384
548
  [ "$cross_project" = true ] && echo -e " ${DIM}project:${RESET} $mproj"
385
549
  [ -n "$mdesc" ] && echo -e " $mdesc"
386
550
  echo ""
@@ -1,35 +1,79 @@
1
1
  #!/usr/bin/env bash
2
- # Eagle Mem statusline section — outputs a single formatted section
3
- # Called by the user's statusline command to append Eagle Mem stats.
4
- # Usage: source this script OR call eagle_mem_statusline "$project_dir"
2
+ # Eagle Mem statusline renderers.
3
+ # Usage:
4
+ # source this file and call eagle_mem_statusline "$project_dir" "$session_id" "$input"
5
+ # printf '%s' "$input" | bash ~/.eagle-mem/scripts/statusline-em.sh --hud
5
6
 
6
- eagle_mem_statusline() {
7
+ _eagle_statusline_load_common() {
8
+ local script_dir
9
+ script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+ . "$script_dir/../lib/common.sh"
11
+ }
12
+
13
+ _eagle_statusline_relative_time() {
14
+ local raw="${1:-}"
15
+ [ -n "$raw" ] && [ "$raw" != "never" ] || { printf 'never\n'; return; }
16
+
17
+ local last_epoch now_epoch diff_sec
18
+ last_epoch=$(date -j -u -f "%Y-%m-%dT%H:%M:%S" "${raw%%.*}" "+%s" 2>/dev/null \
19
+ || date -u -d "$raw" "+%s" 2>/dev/null \
20
+ || true)
21
+ now_epoch=$(date "+%s" 2>/dev/null || true)
22
+ if [ -z "$last_epoch" ] || [ -z "$now_epoch" ]; then
23
+ printf '%s\n' "$raw"
24
+ return
25
+ fi
26
+
27
+ diff_sec=$((now_epoch - last_epoch))
28
+ if [ "$diff_sec" -lt 60 ]; then
29
+ printf 'just now\n'
30
+ elif [ "$diff_sec" -lt 3600 ]; then
31
+ printf '%sm ago\n' "$((diff_sec / 60))"
32
+ elif [ "$diff_sec" -lt 86400 ]; then
33
+ printf '%sh ago\n' "$((diff_sec / 3600))"
34
+ else
35
+ printf '%sd ago\n' "$((diff_sec / 86400))"
36
+ fi
37
+ }
38
+
39
+ eagle_mem_statusline_stats() {
7
40
  local project_dir="${1:-}"
8
41
  local session_id="${2:-}"
9
42
  local statusline_input="${3:-}"
43
+ local current_dir="${4:-}"
10
44
  local em_db="$HOME/.eagle-mem/memory.db"
11
45
  [ -f "$em_db" ] || return
12
46
 
13
- local SCRIPT_DIR; SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
14
- . "$SCRIPT_DIR/../lib/common.sh"
47
+ _eagle_statusline_load_common
48
+
15
49
  local sqlite_bin
16
50
  sqlite_bin=$(eagle_sqlite_path)
17
51
  [ -n "$sqlite_bin" ] || return
18
52
 
19
- local proj
53
+ if [ -n "$statusline_input" ]; then
54
+ [ -z "$session_id" ] && session_id=$(printf '%s' "$statusline_input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
55
+ [ -z "$project_dir" ] && project_dir=$(printf '%s' "$statusline_input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // empty' 2>/dev/null)
56
+ [ -z "$current_dir" ] && current_dir=$(printf '%s' "$statusline_input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
57
+ fi
20
58
  [ -z "$project_dir" ] && project_dir="$(pwd)"
21
- proj=$(eagle_project_from_statusline_input "$statusline_input" "$project_dir" "$project_dir" "$session_id")
22
- [ -z "$proj" ] && return
59
+ [ -z "$current_dir" ] && current_dir="$project_dir"
23
60
 
24
- proj=$(eagle_sql_escape "$proj")
61
+ local project_key project_sql stats sessions memories last_raw turns version latest
62
+ project_key=$(eagle_project_from_statusline_input "$statusline_input" "$project_dir" "$current_dir" "$session_id")
63
+ [ -n "$project_key" ] || return
64
+ project_sql=$(eagle_sql_escape "$project_key")
65
+
66
+ stats=$("$sqlite_bin" "$em_db" "SELECT
67
+ COUNT(*) || '|' ||
68
+ (SELECT COUNT(*) FROM agent_memories WHERE project = '$project_sql') || '|' ||
69
+ COALESCE(MAX(COALESCE(last_activity_at, started_at)), 'never')
70
+ FROM sessions
71
+ WHERE project = '$project_sql';" 2>/dev/null)
72
+ IFS='|' read -r sessions memories last_raw <<< "${stats:-0|0|never}"
73
+ sessions=${sessions:-0}
74
+ memories=${memories:-0}
75
+ last_raw=${last_raw:-never}
25
76
 
26
- local version cnt mem turns
27
- version=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.version" 2>/dev/null)
28
- [ -z "$version" ] && version="?"
29
- cnt=$(echo ".headers off
30
- SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
31
- mem=$(echo ".headers off
32
- SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | "$sqlite_bin" "$em_db" 2>/dev/null | tr -d '[:space:]')
33
77
  if [ -n "$session_id" ] && [ -f "$HOME/.eagle-mem/.turn-counter.${session_id}" ]; then
34
78
  turns=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.turn-counter.${session_id}" 2>/dev/null)
35
79
  else
@@ -40,26 +84,85 @@ SELECT COUNT(*) FROM agent_memories WHERE project = '${proj}';" | "$sqlite_bin"
40
84
  done \
41
85
  | awk '($1+0)>max{max=$1+0} END{print max+0}')
42
86
  fi
43
- cnt=${cnt:-0}; mem=${mem:-0}
44
87
  turns=${turns:-0}
45
88
 
89
+ version=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.version" 2>/dev/null)
90
+ latest=$(tr -d '[:space:]' < "$HOME/.eagle-mem/.latest-version" 2>/dev/null)
91
+ [ -z "$version" ] && version="?"
92
+ [ -z "$latest" ] && latest="$version"
93
+
94
+ printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$project_key" "$version" "$latest" "$sessions" "$memories" "$turns" "$last_raw"
95
+ }
96
+
97
+ eagle_mem_statusline() {
98
+ local stats project_key version latest sessions memories turns last_raw
99
+ stats=$(eagle_mem_statusline_stats "${1:-}" "${2:-}" "${3:-}" "${4:-}") || return
100
+ IFS=$'\t' read -r project_key version latest sessions memories turns last_raw <<< "$stats"
101
+
46
102
  local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
47
- printf "%bEM%b %bv%s%b ses %b%s%b mem %b%s%b turns %b%s%b" \
103
+ printf "%bEagle%b %bv%s%b | %b%s%b sessions | %b%s%b memories | turn %b%s%b" \
48
104
  "$CYAN" "$R" \
49
105
  "$WHT" "$version" "$DIM" \
50
- "$WHT" "$cnt" "$DIM" \
51
- "$WHT" "$mem" "$DIM" \
52
- "$WHT" "$turns" "$R"
106
+ "$WHT" "${sessions:-0}" "$DIM" \
107
+ "$WHT" "${memories:-0}" "$DIM" \
108
+ "$WHT" "${turns:-0}" "$R"
109
+ }
110
+
111
+ eagle_mem_statusline_hud() {
112
+ local stats project_key version latest sessions memories turns last_raw last_label
113
+ stats=$(eagle_mem_statusline_stats "${1:-}" "${2:-}" "${3:-}" "${4:-}") || return
114
+ IFS=$'\t' read -r project_key version latest sessions memories turns last_raw <<< "$stats"
115
+ last_label=$(_eagle_statusline_relative_time "$last_raw")
116
+
117
+ local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
118
+ local GRN='\033[92m' ORG='\033[38;5;214m' RED='\033[91m'
119
+ local turn_color pressure_label em_ver newest
120
+
121
+ turns=${turns:-0}
122
+ if [ "$turns" -ge 30 ] 2>/dev/null; then
123
+ turn_color="$RED"; pressure_label="CRITICAL"
124
+ elif [ "$turns" -ge 20 ] 2>/dev/null; then
125
+ turn_color="$ORG"; pressure_label="HIGH"
126
+ else
127
+ turn_color="$GRN"; pressure_label="OK"
128
+ fi
129
+
130
+ em_ver="v${version:-?}"
131
+ newest=$(printf '%s\n' "${version:-}" "${latest:-}" | sort -V 2>/dev/null | tail -1 || true)
132
+ if [ -n "$latest" ] && [ -n "$newest" ] && [ "$newest" != "$version" ]; then
133
+ em_ver="${em_ver} ${ORG}↑${latest}${R}"
134
+ elif [ -n "$latest" ] && [ "$latest" = "$version" ]; then
135
+ em_ver="${em_ver} ${GRN}✓${R}"
136
+ fi
137
+
138
+ printf "%bEagle Mem%b %b %b│%b %bSessions:%b %b%s%b %b│%b %bMemories:%b %b%s%b %b│%b %bTurns:%b %b%s/30%b %b(%s)%b %b│%b %bUpdated:%b %b%s%b" \
139
+ "$CYAN" "$R" "$em_ver" \
140
+ "$DIM" "$R" "$DIM" "$R" "$WHT" "${sessions:-0}" "$R" "$DIM" "$R" \
141
+ "$DIM" "$R" "$WHT" "${memories:-0}" "$R" "$DIM" "$R" \
142
+ "$DIM" "$R" "$turn_color" "$turns" "$R" "$turn_color" "$pressure_label" "$R" "$DIM" "$R" \
143
+ "$DIM" "$R" "$WHT" "$last_label" "$R"
53
144
  }
54
145
 
55
- # When run directly, read project_dir from stdin JSON (statusline format)
56
146
  if [ "${BASH_SOURCE[0]}" = "$0" ]; then
147
+ mode="${1:-compact}"
57
148
  if [ -t 0 ]; then
58
149
  input=""
59
150
  else
60
151
  input=$(cat)
61
152
  fi
62
- project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // empty' 2>/dev/null)
63
- session_id=$(echo "$input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
64
- eagle_mem_statusline "${project_dir:-$(pwd)}" "$session_id" "$input"
153
+ project_dir=$(printf '%s' "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // empty' 2>/dev/null)
154
+ current_dir=$(printf '%s' "$input" | jq -r '.workspace.current_dir // .cwd // empty' 2>/dev/null)
155
+ session_id=$(printf '%s' "$input" | jq -r '.session_id // .session.id // empty' 2>/dev/null)
156
+
157
+ case "$mode" in
158
+ --hud|hud)
159
+ eagle_mem_statusline_hud "${project_dir:-$(pwd)}" "$session_id" "$input" "$current_dir"
160
+ ;;
161
+ --stats|stats)
162
+ eagle_mem_statusline_stats "${project_dir:-$(pwd)}" "$session_id" "$input" "$current_dir"
163
+ ;;
164
+ *)
165
+ eagle_mem_statusline "${project_dir:-$(pwd)}" "$session_id" "$input" "$current_dir"
166
+ ;;
167
+ esac
65
168
  fi
@@ -13,12 +13,32 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
13
13
  . "$LIB_DIR/codex-hooks.sh"
14
14
 
15
15
  SETTINGS="$EAGLE_SETTINGS"
16
+ dry_run=false
17
+
18
+ for arg in "$@"; do
19
+ case "$arg" in
20
+ --dry-run|--check)
21
+ dry_run=true
22
+ ;;
23
+ --help|-h)
24
+ echo "Usage: eagle-mem uninstall [--dry-run]"
25
+ exit 0
26
+ ;;
27
+ esac
28
+ done
16
29
 
17
30
  eagle_header "Uninstall"
31
+ eagle_uninstall_change_plan
32
+
33
+ if [ "$dry_run" = true ]; then
34
+ eagle_footer "Dry run complete. No files changed."
35
+ exit 0
36
+ fi
18
37
 
19
38
  # ─── Remove hooks from settings.json ──────────────────────
20
39
 
21
40
  if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
41
+ settings_backup=$(eagle_backup_user_file "$SETTINGS" 2>/dev/null || true)
22
42
  for event in SessionStart Stop PostToolUse PreToolUse SessionEnd UserPromptSubmit; do
23
43
  if jq -e ".hooks.${event}" "$SETTINGS" &>/dev/null; then
24
44
  tmp=$(mktemp)
@@ -30,16 +50,55 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
30
50
  tmp=$(mktemp)
31
51
  jq 'if .hooks == {} then del(.hooks) else . end' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
32
52
  eagle_ok "Hooks removed from settings.json"
53
+ [ -n "${settings_backup:-}" ] && eagle_dim " Backup: $settings_backup"
33
54
  else
34
55
  eagle_warn "Could not patch settings.json (jq not found or file missing)"
35
56
  fi
36
57
 
58
+ codex_hooks_backup=""
59
+ if [ -f "$EAGLE_CODEX_HOOKS" ]; then
60
+ codex_hooks_backup=$(eagle_backup_user_file "$EAGLE_CODEX_HOOKS" 2>/dev/null || true)
61
+ fi
37
62
  if eagle_remove_codex_hooks; then
38
63
  eagle_ok "Hooks removed from Codex hooks.json"
64
+ [ -n "$codex_hooks_backup" ] && eagle_dim " Backup: $codex_hooks_backup"
39
65
  else
40
66
  eagle_warn "Could not patch Codex hooks.json (jq not found or file missing)"
41
67
  fi
42
68
 
69
+ # ─── Remove instruction blocks and statusline integration ───
70
+
71
+ claude_md="$HOME/.claude/CLAUDE.md"
72
+ if [ -f "$claude_md" ] && grep -qF "## Eagle Mem — Persistent Memory" "$claude_md" 2>/dev/null; then
73
+ claude_md_backup=$(eagle_backup_user_file "$claude_md" 2>/dev/null || true)
74
+ if eagle_remove_marked_markdown_section "$claude_md"; then
75
+ eagle_ok "Eagle Mem block removed from Claude CLAUDE.md"
76
+ [ -n "${claude_md_backup:-}" ] && eagle_dim " Backup: $claude_md_backup"
77
+ else
78
+ eagle_warn "Could not remove Eagle Mem block from Claude CLAUDE.md"
79
+ fi
80
+ else
81
+ eagle_info "Claude CLAUDE.md block not present"
82
+ fi
83
+
84
+ if [ -f "$EAGLE_CODEX_AGENTS_MD" ] && grep -qF "## Eagle Mem — Persistent Memory" "$EAGLE_CODEX_AGENTS_MD" 2>/dev/null; then
85
+ codex_agents_backup=$(eagle_backup_user_file "$EAGLE_CODEX_AGENTS_MD" 2>/dev/null || true)
86
+ if eagle_remove_marked_markdown_section "$EAGLE_CODEX_AGENTS_MD"; then
87
+ eagle_ok "Eagle Mem block removed from Codex AGENTS.md"
88
+ [ -n "${codex_agents_backup:-}" ] && eagle_dim " Backup: $codex_agents_backup"
89
+ else
90
+ eagle_warn "Could not remove Eagle Mem block from Codex AGENTS.md"
91
+ fi
92
+ else
93
+ eagle_info "Codex AGENTS.md block not present"
94
+ fi
95
+
96
+ if eagle_remove_statusline_integration "$SETTINGS"; then
97
+ eagle_ok "Eagle Mem statusline integration removed"
98
+ else
99
+ eagle_info "Statusline integration not present or not auto-removable"
100
+ fi
101
+
43
102
  # ─── Remove skill symlinks ────────────────────────────────
44
103
 
45
104
  if [ -d "$EAGLE_SKILLS_DIR" ]; then
@@ -50,6 +109,14 @@ if [ -d "$EAGLE_SKILLS_DIR" ]; then
50
109
  done
51
110
  fi
52
111
 
112
+ if [ -d "$EAGLE_CODEX_SKILLS_DIR" ]; then
113
+ for target in "$EAGLE_CODEX_SKILLS_DIR"/eagle-mem-*; do
114
+ [ -L "$target" ] || [ -d "$target" ] || continue
115
+ rm -rf "$target"
116
+ eagle_ok "Codex skill removed: $(basename "$target")"
117
+ done
118
+ fi
119
+
53
120
  # ─── Optionally wipe data ─────────────────────────────────
54
121
 
55
122
  if [ -d "$EAGLE_MEM_DIR" ]; then