eagle-mem 1.0.3 → 1.2.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/bin/eagle-mem +4 -0
- package/db/004_observation_indexes.sql +9 -0
- package/db/migrate.sh +3 -0
- package/db/schema.sql +2 -0
- package/hooks/post-tool-use.sh +4 -4
- package/hooks/session-start.sh +6 -29
- package/hooks/stop.sh +3 -5
- package/hooks/user-prompt-submit.sh +4 -19
- package/lib/common.sh +13 -0
- package/lib/db.sh +84 -9
- package/package.json +1 -1
- package/scripts/help.sh +17 -7
- package/scripts/install.sh +6 -15
- package/scripts/overview.sh +170 -0
- package/scripts/prune.sh +154 -0
- package/scripts/search.sh +294 -0
- package/scripts/tasks.sh +291 -0
- package/scripts/uninstall.sh +5 -5
- package/scripts/update.sh +5 -5
- package/skills/eagle-mem-overview/SKILL.md +35 -55
- package/skills/eagle-mem-search/SKILL.md +36 -84
- package/skills/eagle-mem-tasks/SKILL.md +40 -63
package/scripts/prune.sh
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Prune
|
|
4
|
+
# Removes old observations and orphaned code chunks
|
|
5
|
+
# to keep the database lean
|
|
6
|
+
# ═══════════════════════════════════════════════════════════
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
10
|
+
LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
11
|
+
|
|
12
|
+
. "$SCRIPTS_DIR/style.sh"
|
|
13
|
+
. "$LIB_DIR/common.sh"
|
|
14
|
+
. "$LIB_DIR/db.sh"
|
|
15
|
+
|
|
16
|
+
eagle_ensure_db
|
|
17
|
+
|
|
18
|
+
# ─── Parse arguments ──────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
days=90
|
|
21
|
+
project=""
|
|
22
|
+
dry_run=false
|
|
23
|
+
|
|
24
|
+
while [ $# -gt 0 ]; do
|
|
25
|
+
case "$1" in
|
|
26
|
+
--days|-d) days="$2"; shift 2 ;;
|
|
27
|
+
--project|-p) project="$2"; shift 2 ;;
|
|
28
|
+
--dry-run|-n) dry_run=true; shift ;;
|
|
29
|
+
--help|-h)
|
|
30
|
+
echo -e " ${BOLD}eagle-mem prune${RESET} — Clean up old data"
|
|
31
|
+
echo ""
|
|
32
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
33
|
+
echo -e " eagle-mem prune ${DIM}# prune observations > 90 days${RESET}"
|
|
34
|
+
echo -e " eagle-mem prune ${CYAN}--days 30${RESET} ${DIM}# prune observations > 30 days${RESET}"
|
|
35
|
+
echo -e " eagle-mem prune ${CYAN}--dry-run${RESET} ${DIM}# show what would be pruned${RESET}"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${BOLD}What gets pruned:${RESET}"
|
|
38
|
+
echo -e " ${DOT} Observations older than --days (default: 90)"
|
|
39
|
+
echo -e " ${DOT} Code chunks for files that no longer exist"
|
|
40
|
+
echo ""
|
|
41
|
+
echo -e " ${BOLD}What is preserved:${RESET}"
|
|
42
|
+
echo -e " ${DOT} All sessions and summaries (your session history)"
|
|
43
|
+
echo -e " ${DOT} All tasks"
|
|
44
|
+
echo -e " ${DOT} Project overviews"
|
|
45
|
+
echo ""
|
|
46
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
47
|
+
echo -e " ${CYAN}-d, --days${RESET} <N> Age threshold (default: 90)"
|
|
48
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Only prune for this project"
|
|
49
|
+
echo -e " ${CYAN}-n, --dry-run${RESET} Show counts without deleting"
|
|
50
|
+
echo ""
|
|
51
|
+
exit 0
|
|
52
|
+
;;
|
|
53
|
+
*)
|
|
54
|
+
eagle_err "Unknown option: $1"
|
|
55
|
+
exit 1
|
|
56
|
+
;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
if ! [[ "$days" =~ ^[0-9]+$ ]]; then
|
|
61
|
+
eagle_err "Days must be a number, got: $days"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
eagle_header "Prune"
|
|
66
|
+
|
|
67
|
+
# ─── Count before ────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
total_obs=$(eagle_db "SELECT COUNT(*) FROM observations;")
|
|
70
|
+
total_chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks;")
|
|
71
|
+
eagle_info "Database: ${total_obs:-0} observations, ${total_chunks:-0} chunks"
|
|
72
|
+
echo ""
|
|
73
|
+
|
|
74
|
+
# ─── Old observations ───────────────────────────────────
|
|
75
|
+
|
|
76
|
+
obs_project_filter=""
|
|
77
|
+
if [ -n "$project" ]; then
|
|
78
|
+
obs_project_filter="AND session_id IN (SELECT id FROM sessions WHERE project = '$(eagle_sql_escape "$project")')"
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
old_obs_count=$(eagle_db "SELECT COUNT(*) FROM observations WHERE created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-$days days') $obs_project_filter;")
|
|
82
|
+
|
|
83
|
+
if [ "${old_obs_count:-0}" -gt 0 ]; then
|
|
84
|
+
if [ "$dry_run" = true ]; then
|
|
85
|
+
eagle_dim "Would prune $old_obs_count observations older than $days days"
|
|
86
|
+
else
|
|
87
|
+
eagle_prune_observations "$days" "$project"
|
|
88
|
+
eagle_ok "Pruned $old_obs_count observations older than $days days"
|
|
89
|
+
fi
|
|
90
|
+
else
|
|
91
|
+
eagle_ok "No observations older than $days days"
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# ─── Orphaned code chunks ───────────────────────────────
|
|
95
|
+
|
|
96
|
+
if [ -n "$project" ]; then
|
|
97
|
+
projects="$project"
|
|
98
|
+
else
|
|
99
|
+
projects=$(eagle_db "SELECT DISTINCT project FROM code_chunks;")
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
orphan_total=0
|
|
103
|
+
if [ -n "$projects" ]; then
|
|
104
|
+
while IFS= read -r proj; do
|
|
105
|
+
[ -z "$proj" ] && continue
|
|
106
|
+
|
|
107
|
+
# Try to find the project directory
|
|
108
|
+
proj_cwd=$(eagle_db "SELECT cwd FROM sessions WHERE project = '$(eagle_sql_escape "$proj")' ORDER BY started_at DESC LIMIT 1;")
|
|
109
|
+
|
|
110
|
+
if [ -n "$proj_cwd" ] && [ -d "$proj_cwd" ]; then
|
|
111
|
+
if [ "$dry_run" = true ]; then
|
|
112
|
+
orphan_count=$(eagle_db "SELECT COUNT(DISTINCT file_path) FROM code_chunks WHERE project = '$(eagle_sql_escape "$proj")';")
|
|
113
|
+
# Count files that no longer exist
|
|
114
|
+
orphans=0
|
|
115
|
+
paths=$(eagle_db "SELECT DISTINCT file_path FROM code_chunks WHERE project = '$(eagle_sql_escape "$proj")';")
|
|
116
|
+
while IFS= read -r fpath; do
|
|
117
|
+
[ -z "$fpath" ] && continue
|
|
118
|
+
[ ! -f "$proj_cwd/$fpath" ] && orphans=$((orphans + 1))
|
|
119
|
+
done <<< "$paths"
|
|
120
|
+
if [ "$orphans" -gt 0 ]; then
|
|
121
|
+
eagle_dim "Would prune $orphans orphaned files from '$proj'"
|
|
122
|
+
orphan_total=$((orphan_total + orphans))
|
|
123
|
+
fi
|
|
124
|
+
else
|
|
125
|
+
removed=$(eagle_prune_orphan_chunks "$proj" "$proj_cwd")
|
|
126
|
+
if [ "${removed:-0}" -gt 0 ]; then
|
|
127
|
+
eagle_ok "Pruned $removed orphaned files from '$proj'"
|
|
128
|
+
orphan_total=$((orphan_total + removed))
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
fi
|
|
132
|
+
done <<< "$projects"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
if [ "$orphan_total" -eq 0 ]; then
|
|
136
|
+
eagle_ok "No orphaned code chunks found"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
# ─── Summary ────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
new_obs=$(eagle_db "SELECT COUNT(*) FROM observations;")
|
|
142
|
+
new_chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks;")
|
|
143
|
+
|
|
144
|
+
echo ""
|
|
145
|
+
if [ "$dry_run" = true ]; then
|
|
146
|
+
eagle_footer "Dry run complete (no changes made)."
|
|
147
|
+
else
|
|
148
|
+
eagle_footer "Prune complete."
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
eagle_kv "Observations:" "${new_obs:-0} (was ${total_obs:-0})"
|
|
152
|
+
eagle_kv "Code chunks:" "${new_chunks:-0} (was ${total_chunks:-0})"
|
|
153
|
+
eagle_kv "Database:" "$EAGLE_MEM_DB"
|
|
154
|
+
echo ""
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Search
|
|
4
|
+
# CLI wrapper for memory search (replaces raw SQL in skills)
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
10
|
+
|
|
11
|
+
. "$SCRIPTS_DIR/style.sh"
|
|
12
|
+
. "$LIB_DIR/common.sh"
|
|
13
|
+
. "$LIB_DIR/db.sh"
|
|
14
|
+
|
|
15
|
+
eagle_ensure_db
|
|
16
|
+
|
|
17
|
+
# ─── Parse arguments ──────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
mode="keyword"
|
|
20
|
+
project=""
|
|
21
|
+
limit=10
|
|
22
|
+
session_id=""
|
|
23
|
+
json_output=false
|
|
24
|
+
query=""
|
|
25
|
+
cross_project=false
|
|
26
|
+
|
|
27
|
+
while [ $# -gt 0 ]; do
|
|
28
|
+
case "$1" in
|
|
29
|
+
--timeline|-t) mode="timeline"; shift ;;
|
|
30
|
+
--session|-s) mode="session"; session_id="$2"; shift 2 ;;
|
|
31
|
+
--files|-f) mode="files"; shift ;;
|
|
32
|
+
--stats) mode="stats"; shift ;;
|
|
33
|
+
--project|-p) project="$2"; shift 2 ;;
|
|
34
|
+
--limit|-n) limit="$2"; shift 2 ;;
|
|
35
|
+
--all|-a) cross_project=true; shift ;;
|
|
36
|
+
--json|-j) json_output=true; shift ;;
|
|
37
|
+
--help|-h)
|
|
38
|
+
echo -e " ${BOLD}eagle-mem search${RESET} — Search persistent memory"
|
|
39
|
+
echo ""
|
|
40
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
41
|
+
echo -e " eagle-mem search ${CYAN}<query>${RESET} ${DIM}# keyword search${RESET}"
|
|
42
|
+
echo -e " eagle-mem search ${CYAN}--timeline${RESET} ${DIM}# recent sessions${RESET}"
|
|
43
|
+
echo -e " eagle-mem search ${CYAN}--session <id>${RESET} ${DIM}# session details${RESET}"
|
|
44
|
+
echo -e " eagle-mem search ${CYAN}--files${RESET} ${DIM}# frequently modified files${RESET}"
|
|
45
|
+
echo -e " eagle-mem search ${CYAN}--stats${RESET} ${DIM}# project statistics${RESET}"
|
|
46
|
+
echo ""
|
|
47
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
48
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
49
|
+
echo -e " ${CYAN}-n, --limit${RESET} <N> Max results (default: 10)"
|
|
50
|
+
echo -e " ${CYAN}-a, --all${RESET} Search across all projects"
|
|
51
|
+
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
52
|
+
echo ""
|
|
53
|
+
exit 0
|
|
54
|
+
;;
|
|
55
|
+
-*)
|
|
56
|
+
eagle_err "Unknown option: $1"
|
|
57
|
+
exit 1
|
|
58
|
+
;;
|
|
59
|
+
*)
|
|
60
|
+
query="$1"; shift ;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
[ -z "$project" ] && project=$(eagle_project_from_cwd "$(pwd)")
|
|
65
|
+
limit=$(eagle_sql_int "$limit")
|
|
66
|
+
[ "$limit" -eq 0 ] && limit=10
|
|
67
|
+
|
|
68
|
+
# ─── Keyword search ──────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
search_keyword() {
|
|
71
|
+
local q; q=$(eagle_sql_escape "$(eagle_fts_sanitize "$query")")
|
|
72
|
+
local p; p=$(eagle_sql_escape "$project")
|
|
73
|
+
|
|
74
|
+
local where_project=""
|
|
75
|
+
if [ "$cross_project" = false ]; then
|
|
76
|
+
where_project="AND s.project = '$p'"
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
if [ "$json_output" = true ]; then
|
|
80
|
+
eagle_db_json "SELECT s.id, s.project, s.request, s.completed, s.learned, s.created_at
|
|
81
|
+
FROM summaries s
|
|
82
|
+
JOIN summaries_fts f ON f.rowid = s.id
|
|
83
|
+
WHERE summaries_fts MATCH '$q'
|
|
84
|
+
$where_project
|
|
85
|
+
ORDER BY rank
|
|
86
|
+
LIMIT $limit;"
|
|
87
|
+
return
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
local results
|
|
91
|
+
results=$(eagle_db "SELECT s.id, s.project, s.request, s.completed, s.learned, s.created_at
|
|
92
|
+
FROM summaries s
|
|
93
|
+
JOIN summaries_fts f ON f.rowid = s.id
|
|
94
|
+
WHERE summaries_fts MATCH '$q'
|
|
95
|
+
$where_project
|
|
96
|
+
ORDER BY rank
|
|
97
|
+
LIMIT $limit;")
|
|
98
|
+
|
|
99
|
+
if [ -z "$results" ]; then
|
|
100
|
+
eagle_dim "No results for '$query'"
|
|
101
|
+
return
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
echo ""
|
|
105
|
+
while IFS='|' read -r sid sproj req completed learned created_at; do
|
|
106
|
+
[ -z "$sid" ] && continue
|
|
107
|
+
echo -e " ${BOLD}#$sid${RESET} ${DIM}$created_at${RESET}"
|
|
108
|
+
if [ "$cross_project" = true ]; then
|
|
109
|
+
echo -e " ${DIM}project:${RESET} $sproj"
|
|
110
|
+
fi
|
|
111
|
+
[ -n "$req" ] && echo -e " ${CYAN}Request:${RESET} $req"
|
|
112
|
+
[ -n "$completed" ] && echo -e " ${GREEN}Done:${RESET} $completed"
|
|
113
|
+
[ -n "$learned" ] && echo -e " ${YELLOW}Learned:${RESET} $learned"
|
|
114
|
+
echo ""
|
|
115
|
+
done <<< "$results"
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
# ─── Timeline ────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
search_timeline() {
|
|
121
|
+
local p; p=$(eagle_sql_escape "$project")
|
|
122
|
+
|
|
123
|
+
if [ "$json_output" = true ]; then
|
|
124
|
+
eagle_db_json "SELECT s.id, s.request, s.completed, s.learned, s.next_steps, s.created_at
|
|
125
|
+
FROM summaries s
|
|
126
|
+
WHERE s.project = '$p'
|
|
127
|
+
ORDER BY s.created_at DESC
|
|
128
|
+
LIMIT $limit;"
|
|
129
|
+
return
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
local results
|
|
133
|
+
results=$(eagle_db "SELECT s.id, s.request, s.completed, s.learned, s.next_steps, s.created_at
|
|
134
|
+
FROM summaries s
|
|
135
|
+
WHERE s.project = '$p'
|
|
136
|
+
ORDER BY s.created_at DESC
|
|
137
|
+
LIMIT $limit;")
|
|
138
|
+
|
|
139
|
+
if [ -z "$results" ]; then
|
|
140
|
+
eagle_dim "No sessions found for project '$project'"
|
|
141
|
+
return
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
echo ""
|
|
145
|
+
echo -e " ${BOLD}Recent sessions${RESET} ${DIM}($project)${RESET}"
|
|
146
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
147
|
+
echo ""
|
|
148
|
+
|
|
149
|
+
while IFS='|' read -r sid req completed learned next_steps created_at; do
|
|
150
|
+
[ -z "$sid" ] && continue
|
|
151
|
+
echo -e " ${BOLD}#$sid${RESET} ${DIM}$created_at${RESET}"
|
|
152
|
+
[ -n "$req" ] && echo -e " ${CYAN}Request:${RESET} $req"
|
|
153
|
+
[ -n "$completed" ] && echo -e " ${GREEN}Done:${RESET} $completed"
|
|
154
|
+
[ -n "$learned" ] && echo -e " ${YELLOW}Learned:${RESET} $learned"
|
|
155
|
+
[ -n "$next_steps" ] && echo -e " ${DIM}Next:${RESET} $next_steps"
|
|
156
|
+
echo ""
|
|
157
|
+
done <<< "$results"
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# ─── Session details ──────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
search_session() {
|
|
163
|
+
local sid_sql; sid_sql=$(eagle_sql_escape "$session_id")
|
|
164
|
+
|
|
165
|
+
if [ "$json_output" = true ]; then
|
|
166
|
+
eagle_db_json "SELECT o.tool_name, o.tool_input_summary, o.files_read, o.files_modified, o.created_at
|
|
167
|
+
FROM observations o
|
|
168
|
+
WHERE o.session_id = '$sid_sql'
|
|
169
|
+
ORDER BY o.created_at ASC;"
|
|
170
|
+
return
|
|
171
|
+
fi
|
|
172
|
+
|
|
173
|
+
local results
|
|
174
|
+
results=$(eagle_db "SELECT o.tool_name, o.tool_input_summary, o.files_read, o.files_modified, o.created_at
|
|
175
|
+
FROM observations o
|
|
176
|
+
WHERE o.session_id = '$sid_sql'
|
|
177
|
+
ORDER BY o.created_at ASC;")
|
|
178
|
+
|
|
179
|
+
if [ -z "$results" ]; then
|
|
180
|
+
eagle_dim "No observations found for session '$session_id'"
|
|
181
|
+
return
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
echo ""
|
|
185
|
+
echo -e " ${BOLD}Session${RESET} ${DIM}$session_id${RESET}"
|
|
186
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
187
|
+
echo ""
|
|
188
|
+
|
|
189
|
+
while IFS='|' read -r tool_name summary files_r files_m created_at; do
|
|
190
|
+
[ -z "$tool_name" ] && continue
|
|
191
|
+
local icon="$DOT"
|
|
192
|
+
case "$tool_name" in
|
|
193
|
+
Read) icon="${CYAN}R${RESET}" ;;
|
|
194
|
+
Write) icon="${GREEN}W${RESET}" ;;
|
|
195
|
+
Edit) icon="${YELLOW}E${RESET}" ;;
|
|
196
|
+
Bash) icon="${BLUE}\$${RESET}" ;;
|
|
197
|
+
esac
|
|
198
|
+
echo -e " ${icon} ${DIM}$created_at${RESET} $summary"
|
|
199
|
+
done <<< "$results"
|
|
200
|
+
echo ""
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# ─── Frequently modified files ────────────────────────────
|
|
204
|
+
|
|
205
|
+
search_files() {
|
|
206
|
+
local p; p=$(eagle_sql_escape "$project")
|
|
207
|
+
|
|
208
|
+
if [ "$json_output" = true ]; then
|
|
209
|
+
eagle_db_json "SELECT json_each.value as file, COUNT(*) as times
|
|
210
|
+
FROM observations, json_each(observations.files_modified)
|
|
211
|
+
WHERE observations.project = '$p'
|
|
212
|
+
GROUP BY json_each.value
|
|
213
|
+
ORDER BY times DESC
|
|
214
|
+
LIMIT $limit;"
|
|
215
|
+
return
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
local results
|
|
219
|
+
results=$(eagle_db "SELECT json_each.value as file, COUNT(*) as times
|
|
220
|
+
FROM observations, json_each(observations.files_modified)
|
|
221
|
+
WHERE observations.project = '$p'
|
|
222
|
+
GROUP BY json_each.value
|
|
223
|
+
ORDER BY times DESC
|
|
224
|
+
LIMIT $limit;")
|
|
225
|
+
|
|
226
|
+
if [ -z "$results" ]; then
|
|
227
|
+
eagle_dim "No file history for project '$project'"
|
|
228
|
+
return
|
|
229
|
+
fi
|
|
230
|
+
|
|
231
|
+
echo ""
|
|
232
|
+
echo -e " ${BOLD}Frequently modified files${RESET} ${DIM}($project)${RESET}"
|
|
233
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
234
|
+
echo ""
|
|
235
|
+
|
|
236
|
+
while IFS='|' read -r filepath count; do
|
|
237
|
+
[ -z "$filepath" ] && continue
|
|
238
|
+
printf " ${GREEN}%3s×${RESET} %s\n" "$count" "$filepath"
|
|
239
|
+
done <<< "$results"
|
|
240
|
+
echo ""
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
# ─── Stats ────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
search_stats() {
|
|
246
|
+
local p; p=$(eagle_sql_escape "$project")
|
|
247
|
+
|
|
248
|
+
local sessions summaries observations tasks
|
|
249
|
+
sessions=$(eagle_db "SELECT COUNT(*) FROM sessions WHERE project = '$p';")
|
|
250
|
+
summaries=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE project = '$p';")
|
|
251
|
+
observations=$(eagle_db "SELECT COUNT(*) FROM observations o JOIN sessions s ON s.id = o.session_id WHERE s.project = '$p';")
|
|
252
|
+
tasks=$(eagle_db "SELECT COUNT(*) FROM tasks WHERE project = '$p';")
|
|
253
|
+
local chunks
|
|
254
|
+
chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$p';")
|
|
255
|
+
|
|
256
|
+
if [ "$json_output" = true ]; then
|
|
257
|
+
jq -nc --arg project "$project" \
|
|
258
|
+
--argjson sessions "${sessions:-0}" \
|
|
259
|
+
--argjson summaries "${summaries:-0}" \
|
|
260
|
+
--argjson observations "${observations:-0}" \
|
|
261
|
+
--argjson tasks "${tasks:-0}" \
|
|
262
|
+
--argjson code_chunks "${chunks:-0}" \
|
|
263
|
+
'{project: $project, sessions: $sessions, summaries: $summaries, observations: $observations, tasks: $tasks, code_chunks: $code_chunks}'
|
|
264
|
+
return
|
|
265
|
+
fi
|
|
266
|
+
|
|
267
|
+
echo ""
|
|
268
|
+
echo -e " ${BOLD}Stats${RESET} ${DIM}($project)${RESET}"
|
|
269
|
+
echo -e " ${DIM}─────────────────────────────────────${RESET}"
|
|
270
|
+
echo ""
|
|
271
|
+
eagle_kv "Sessions:" "${sessions:-0}"
|
|
272
|
+
eagle_kv "Summaries:" "${summaries:-0}"
|
|
273
|
+
eagle_kv "Observations:" "${observations:-0}"
|
|
274
|
+
eagle_kv "Tasks:" "${tasks:-0}"
|
|
275
|
+
eagle_kv "Code chunks:" "${chunks:-0}"
|
|
276
|
+
echo ""
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# ─── Dispatch ─────────────────────────────────────────────
|
|
280
|
+
|
|
281
|
+
case "$mode" in
|
|
282
|
+
keyword)
|
|
283
|
+
if [ -z "$query" ]; then
|
|
284
|
+
eagle_err "Usage: eagle-mem search <query>"
|
|
285
|
+
eagle_dim " Run 'eagle-mem search --help' for options"
|
|
286
|
+
exit 1
|
|
287
|
+
fi
|
|
288
|
+
search_keyword
|
|
289
|
+
;;
|
|
290
|
+
timeline) search_timeline ;;
|
|
291
|
+
session) search_session ;;
|
|
292
|
+
files) search_files ;;
|
|
293
|
+
stats) search_stats ;;
|
|
294
|
+
esac
|