eagle-mem 1.6.0 → 1.6.2
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 +1 -1
- package/db/009_drop_dead_tasks.sql +29 -0
- package/db/migrate.sh +9 -22
- package/db/schema.sql +0 -49
- package/hooks/post-tool-use.sh +4 -0
- package/hooks/session-start.sh +4 -3
- package/lib/common.sh +2 -0
- package/lib/db.sh +19 -6
- package/lib/hooks.sh +30 -0
- package/package.json +1 -1
- package/scripts/help.sh +1 -1
- package/scripts/index.sh +0 -1
- package/scripts/install.sh +6 -29
- package/scripts/prune.sh +28 -25
- package/scripts/search.sh +22 -18
- package/scripts/tasks.sh +1 -0
- package/scripts/update.sh +8 -27
package/bin/eagle-mem
CHANGED
|
@@ -29,7 +29,7 @@ case "$command" in
|
|
|
29
29
|
help|--help|-h)
|
|
30
30
|
bash "$SCRIPTS_DIR/help.sh" ;;
|
|
31
31
|
version|--version|-v|-V)
|
|
32
|
-
version=$(
|
|
32
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
33
33
|
echo -e " ${BOLD}Eagle Mem${RESET} v${version}"
|
|
34
34
|
;;
|
|
35
35
|
*)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- ═══════════════════════════════════════════════════════════
|
|
2
|
+
-- Migration 009: Drop dead tasks table
|
|
3
|
+
-- The original tasks table (from schema.sql / migration 001)
|
|
4
|
+
-- was replaced by claude_tasks (migration 007). No code
|
|
5
|
+
-- references the old table. This cleans up the vestigial
|
|
6
|
+
-- schema: 3 FTS triggers, the FTS virtual table, 3 indexes,
|
|
7
|
+
-- and the table itself.
|
|
8
|
+
--
|
|
9
|
+
-- NOTE: schema.sql (migration 001) still contains the CREATE
|
|
10
|
+
-- TABLE tasks definition. We do NOT edit applied migrations.
|
|
11
|
+
-- Fresh installs will CREATE (001) then DROP (009). Existing
|
|
12
|
+
-- installs just run 009. Verified 0 rows in production.
|
|
13
|
+
-- ═══════════════════════════════════════════════════════════
|
|
14
|
+
|
|
15
|
+
-- Drop FTS sync triggers first (they reference the tasks table)
|
|
16
|
+
DROP TRIGGER IF EXISTS tasks_ai;
|
|
17
|
+
DROP TRIGGER IF EXISTS tasks_ad;
|
|
18
|
+
DROP TRIGGER IF EXISTS tasks_au;
|
|
19
|
+
|
|
20
|
+
-- Drop the FTS virtual table
|
|
21
|
+
DROP TABLE IF EXISTS tasks_fts;
|
|
22
|
+
|
|
23
|
+
-- Drop indexes (implicit in DROP TABLE, but explicit for clarity)
|
|
24
|
+
DROP INDEX IF EXISTS idx_tasks_project;
|
|
25
|
+
DROP INDEX IF EXISTS idx_tasks_status;
|
|
26
|
+
DROP INDEX IF EXISTS idx_tasks_parent;
|
|
27
|
+
|
|
28
|
+
-- Drop the dead tasks table
|
|
29
|
+
DROP TABLE IF EXISTS tasks;
|
package/db/migrate.sh
CHANGED
|
@@ -25,7 +25,8 @@ run_migration() {
|
|
|
25
25
|
body=$(grep -v -E '^[[:space:]]*PRAGMA ' "$file")
|
|
26
26
|
|
|
27
27
|
# Set connection PRAGMAs, then run migration body + tracking insert atomically
|
|
28
|
-
|
|
28
|
+
# .bail on ensures sqlite3 stops on the first error instead of continuing
|
|
29
|
+
{ echo ".bail on"; echo "PRAGMA trusted_schema=ON;"; echo "PRAGMA foreign_keys=ON;"; echo "PRAGMA busy_timeout=5000;"; echo "BEGIN;"; echo "$body"; echo "INSERT INTO _migrations (name) VALUES ('$name');"; echo "COMMIT;"; } | sqlite3 "$DB"
|
|
29
30
|
echo " applied: $name"
|
|
30
31
|
fi
|
|
31
32
|
}
|
|
@@ -40,28 +41,14 @@ sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS _migrations (
|
|
|
40
41
|
# Set PRAGMAs (these must be set on every connection)
|
|
41
42
|
sqlite3 "$DB" "PRAGMA journal_mode = WAL; PRAGMA synchronous = NORMAL; PRAGMA busy_timeout = 5000; PRAGMA foreign_keys = ON;"
|
|
42
43
|
|
|
43
|
-
# ─── Migration 001: Initial schema
|
|
44
|
+
# ─── Migration 001: Initial schema (special name) ──────────
|
|
44
45
|
run_migration "001_initial_schema" "$SCRIPT_DIR/schema.sql"
|
|
45
46
|
|
|
46
|
-
# ───
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
run_migration "
|
|
51
|
-
|
|
52
|
-
# ─── Migration 004: Observation indexes ──────────────────
|
|
53
|
-
run_migration "004_observation_indexes" "$SCRIPT_DIR/004_observation_indexes.sql"
|
|
54
|
-
|
|
55
|
-
# ─── Migration 005: Claude Code memory mirror ────────────
|
|
56
|
-
run_migration "005_claude_memories" "$SCRIPT_DIR/005_claude_memories.sql"
|
|
57
|
-
|
|
58
|
-
# ─── Migration 006: Claude Code plan mirror ──────────────
|
|
59
|
-
run_migration "006_claude_plans" "$SCRIPT_DIR/006_claude_plans.sql"
|
|
60
|
-
|
|
61
|
-
# ─── Migration 007: Claude Code task mirror ──────────────
|
|
62
|
-
run_migration "007_claude_tasks" "$SCRIPT_DIR/007_claude_tasks.sql"
|
|
63
|
-
|
|
64
|
-
# ─── Migration 008: Summary UPSERT (unique session_id) ───
|
|
65
|
-
run_migration "008_summary_upsert" "$SCRIPT_DIR/008_summary_upsert.sql"
|
|
47
|
+
# ─── Run numbered migrations (002+) via sorted glob ───────
|
|
48
|
+
for migration_file in "$SCRIPT_DIR"/[0-9][0-9][0-9]_*.sql; do
|
|
49
|
+
[ ! -f "$migration_file" ] && continue
|
|
50
|
+
migration_name=$(basename "$migration_file" .sql)
|
|
51
|
+
run_migration "$migration_name" "$migration_file"
|
|
52
|
+
done
|
|
66
53
|
|
|
67
54
|
echo " Eagle Mem database ready: $DB"
|
package/db/schema.sql
CHANGED
|
@@ -75,28 +75,6 @@ CREATE TABLE IF NOT EXISTS summaries (
|
|
|
75
75
|
CREATE INDEX IF NOT EXISTS idx_summaries_session ON summaries(session_id);
|
|
76
76
|
CREATE INDEX IF NOT EXISTS idx_summaries_project ON summaries(project);
|
|
77
77
|
|
|
78
|
-
-- ─── Tasks (TaskAware Compact Loop) ───────────────────────
|
|
79
|
-
-- Subtasks for multi-step work with compaction between each
|
|
80
|
-
|
|
81
|
-
CREATE TABLE IF NOT EXISTS tasks (
|
|
82
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
83
|
-
project TEXT NOT NULL,
|
|
84
|
-
session_id TEXT REFERENCES sessions(id),
|
|
85
|
-
parent_id INTEGER REFERENCES tasks(id),
|
|
86
|
-
title TEXT NOT NULL,
|
|
87
|
-
instructions TEXT,
|
|
88
|
-
context_snapshot TEXT,
|
|
89
|
-
status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'done', 'blocked', 'cancelled')),
|
|
90
|
-
ordinal INTEGER NOT NULL DEFAULT 0,
|
|
91
|
-
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')),
|
|
92
|
-
started_at TEXT,
|
|
93
|
-
completed_at TEXT
|
|
94
|
-
);
|
|
95
|
-
|
|
96
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_project ON tasks(project);
|
|
97
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
|
|
98
|
-
CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_id);
|
|
99
|
-
|
|
100
78
|
-- ─── FTS5: Full-text search on summaries ───────────────────
|
|
101
79
|
|
|
102
80
|
CREATE VIRTUAL TABLE IF NOT EXISTS summaries_fts USING fts5(
|
|
@@ -127,30 +105,3 @@ CREATE TRIGGER IF NOT EXISTS summaries_au AFTER UPDATE ON summaries BEGIN
|
|
|
127
105
|
INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
|
|
128
106
|
VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
|
|
129
107
|
END;
|
|
130
|
-
|
|
131
|
-
-- ─── FTS5: Full-text search on tasks ──────────────────────
|
|
132
|
-
|
|
133
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS tasks_fts USING fts5(
|
|
134
|
-
title,
|
|
135
|
-
instructions,
|
|
136
|
-
context_snapshot,
|
|
137
|
-
content='tasks',
|
|
138
|
-
content_rowid='id'
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
CREATE TRIGGER IF NOT EXISTS tasks_ai AFTER INSERT ON tasks BEGIN
|
|
142
|
-
INSERT INTO tasks_fts(rowid, title, instructions, context_snapshot)
|
|
143
|
-
VALUES (new.id, new.title, new.instructions, new.context_snapshot);
|
|
144
|
-
END;
|
|
145
|
-
|
|
146
|
-
CREATE TRIGGER IF NOT EXISTS tasks_ad AFTER DELETE ON tasks BEGIN
|
|
147
|
-
INSERT INTO tasks_fts(tasks_fts, rowid, title, instructions, context_snapshot)
|
|
148
|
-
VALUES ('delete', old.id, old.title, old.instructions, old.context_snapshot);
|
|
149
|
-
END;
|
|
150
|
-
|
|
151
|
-
CREATE TRIGGER IF NOT EXISTS tasks_au AFTER UPDATE ON tasks BEGIN
|
|
152
|
-
INSERT INTO tasks_fts(tasks_fts, rowid, title, instructions, context_snapshot)
|
|
153
|
-
VALUES ('delete', old.id, old.title, old.instructions, old.context_snapshot);
|
|
154
|
-
INSERT INTO tasks_fts(rowid, title, instructions, context_snapshot)
|
|
155
|
-
VALUES (new.id, new.title, new.instructions, new.context_snapshot);
|
|
156
|
-
END;
|
package/hooks/post-tool-use.sh
CHANGED
|
@@ -83,7 +83,11 @@ esac
|
|
|
83
83
|
case "$tool_name" in
|
|
84
84
|
Write|Edit)
|
|
85
85
|
if [ -n "$fp" ]; then
|
|
86
|
+
# Reject path traversal: bash case `*` matches `/`, so
|
|
87
|
+
# patterns like projects/*/memory/*.md would match paths
|
|
88
|
+
# containing /../ segments. Block any path with `..` first.
|
|
86
89
|
case "$fp" in
|
|
90
|
+
*..*) ;; # path traversal — skip
|
|
87
91
|
"$HOME/.claude/projects"/*/memory/*.md)
|
|
88
92
|
mem_base=$(basename "$fp")
|
|
89
93
|
if [ "$mem_base" != "MEMORY.md" ] && [ -f "$fp" ]; then
|
package/hooks/session-start.sh
CHANGED
|
@@ -31,8 +31,10 @@ eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$sou
|
|
|
31
31
|
eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
|
|
32
32
|
|
|
33
33
|
# ─── Sweep stuck sessions (older than 4 hours) ─────────────
|
|
34
|
+
# Exclude the current session — it may be a resumed session older than 4h
|
|
34
35
|
eagle_db "UPDATE sessions SET status = 'abandoned'
|
|
35
36
|
WHERE status = 'active'
|
|
37
|
+
AND id != '$(eagle_sql_escape "$session_id")'
|
|
36
38
|
AND started_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-4 hours');"
|
|
37
39
|
|
|
38
40
|
# ─── Build context injection ────────────────────────────────
|
|
@@ -115,7 +117,7 @@ fi
|
|
|
115
117
|
synced_tasks=$(eagle_db "SELECT subject, status, blocked_by FROM claude_tasks
|
|
116
118
|
WHERE project = '$(eagle_sql_escape "$project")'
|
|
117
119
|
AND status IN ('in_progress', 'pending')
|
|
118
|
-
AND updated_at >
|
|
120
|
+
AND updated_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-7 days')
|
|
119
121
|
ORDER BY
|
|
120
122
|
CASE status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
121
123
|
updated_at DESC
|
|
@@ -144,8 +146,7 @@ You have persistent memory powered by Eagle Mem. When you recall context from a
|
|
|
144
146
|
IMPORTANT: At the start of your VERY NEXT response (this fires on session start, /clear, AND context compaction — always show this block, even if you think you showed it before, because prior context may have been compressed away). Show the user what Eagle Mem loaded using this exact format:
|
|
145
147
|
|
|
146
148
|
\`\`\`
|
|
147
|
-
|
|
148
|
-
██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
|
|
149
|
+
$eagle_logo
|
|
149
150
|
|
|
150
151
|
Project: <project name>
|
|
151
152
|
Sessions: N recent | Memories: N | Tasks: N pending
|
package/lib/common.sh
CHANGED
|
@@ -46,6 +46,8 @@ eagle_fts_sanitize() {
|
|
|
46
46
|
# Claude Code session IDs are UUIDs or hex strings — reject anything else.
|
|
47
47
|
eagle_validate_session_id() {
|
|
48
48
|
local sid="$1"
|
|
49
|
+
# Length cap: Claude Code IDs are UUIDs/hex (36-64 chars). Reject oversized input.
|
|
50
|
+
[ ${#sid} -gt 128 ] && return 1
|
|
49
51
|
[[ "$sid" =~ ^[A-Za-z0-9_-]+$ ]]
|
|
50
52
|
}
|
|
51
53
|
|
package/lib/db.sh
CHANGED
|
@@ -18,7 +18,7 @@ eagle_db() {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
eagle_db_pipe() {
|
|
21
|
-
{ echo "$EAGLE_DB_SETUP"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
21
|
+
{ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
eagle_db_json() {
|
|
@@ -45,7 +45,8 @@ eagle_upsert_session() {
|
|
|
45
45
|
ON CONFLICT(id) DO UPDATE SET
|
|
46
46
|
cwd = COALESCE(excluded.cwd, sessions.cwd),
|
|
47
47
|
model = COALESCE(excluded.model, sessions.model),
|
|
48
|
-
source = COALESCE(excluded.source, sessions.source)
|
|
48
|
+
source = COALESCE(excluded.source, sessions.source),
|
|
49
|
+
status = 'active';"
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
eagle_end_session() {
|
|
@@ -78,7 +79,7 @@ eagle_insert_summary() {
|
|
|
78
79
|
local notes; notes=$(eagle_sql_escape "${10:-}")
|
|
79
80
|
|
|
80
81
|
eagle_db_pipe <<SQL
|
|
81
|
-
INSERT
|
|
82
|
+
INSERT INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
|
|
82
83
|
VALUES (
|
|
83
84
|
'$session_id',
|
|
84
85
|
'$project',
|
|
@@ -90,7 +91,17 @@ VALUES (
|
|
|
90
91
|
'$files_read',
|
|
91
92
|
'$files_modified',
|
|
92
93
|
'$notes'
|
|
93
|
-
)
|
|
94
|
+
)
|
|
95
|
+
ON CONFLICT(session_id) DO UPDATE SET
|
|
96
|
+
project = excluded.project,
|
|
97
|
+
request = COALESCE(NULLIF(excluded.request, ''), summaries.request),
|
|
98
|
+
investigated = COALESCE(NULLIF(excluded.investigated, ''), summaries.investigated),
|
|
99
|
+
learned = COALESCE(NULLIF(excluded.learned, ''), summaries.learned),
|
|
100
|
+
completed = COALESCE(NULLIF(excluded.completed, ''), summaries.completed),
|
|
101
|
+
next_steps = COALESCE(NULLIF(excluded.next_steps, ''), summaries.next_steps),
|
|
102
|
+
files_read = COALESCE(NULLIF(excluded.files_read, '[]'), summaries.files_read),
|
|
103
|
+
files_modified = COALESCE(NULLIF(excluded.files_modified, '[]'), summaries.files_modified),
|
|
104
|
+
notes = COALESCE(NULLIF(excluded.notes, ''), summaries.notes);
|
|
94
105
|
SQL
|
|
95
106
|
}
|
|
96
107
|
|
|
@@ -107,7 +118,8 @@ eagle_get_recent_summaries() {
|
|
|
107
118
|
}
|
|
108
119
|
|
|
109
120
|
eagle_search_summaries() {
|
|
110
|
-
local query; query=$(
|
|
121
|
+
local query; query=$(eagle_fts_sanitize "$1")
|
|
122
|
+
query=$(eagle_sql_escape "$query")
|
|
111
123
|
local project="${2:-}"
|
|
112
124
|
local limit; limit=$(eagle_sql_int "${3:-10}")
|
|
113
125
|
|
|
@@ -156,7 +168,8 @@ eagle_get_overview() {
|
|
|
156
168
|
}
|
|
157
169
|
|
|
158
170
|
eagle_search_code_chunks() {
|
|
159
|
-
local query; query=$(
|
|
171
|
+
local query; query=$(eagle_fts_sanitize "$1")
|
|
172
|
+
query=$(eagle_sql_escape "$query")
|
|
160
173
|
local project; project=$(eagle_sql_escape "$2")
|
|
161
174
|
local limit; limit=$(eagle_sql_int "${3:-5}")
|
|
162
175
|
|
package/lib/hooks.sh
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Hook registration helpers
|
|
4
|
+
# Shared by install.sh and update.sh
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
eagle_patch_hook() {
|
|
8
|
+
local settings="$1"
|
|
9
|
+
local event="$2"
|
|
10
|
+
local matcher="$3"
|
|
11
|
+
local command="$4"
|
|
12
|
+
local description="${5:-}"
|
|
13
|
+
|
|
14
|
+
if jq -e ".hooks.${event}[]? | select(.hooks[]?.command == \"$command\")" "$settings" &>/dev/null; then
|
|
15
|
+
[ -n "$description" ] && eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
16
|
+
return
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
local entry
|
|
20
|
+
if [ -n "$matcher" ]; then
|
|
21
|
+
entry=$(jq -nc --arg m "$matcher" --arg c "$command" '{matcher: $m, hooks: [{type: "command", command: $c}]}')
|
|
22
|
+
else
|
|
23
|
+
entry=$(jq -nc --arg c "$command" '{hooks: [{type: "command", command: $c}]}')
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
local tmp
|
|
27
|
+
tmp=$(mktemp)
|
|
28
|
+
jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$settings" > "$tmp" && mv "$tmp" "$settings"
|
|
29
|
+
[ -n "$description" ] && eagle_ok "$description"
|
|
30
|
+
}
|
package/package.json
CHANGED
package/scripts/help.sh
CHANGED
|
@@ -8,7 +8,7 @@ PACKAGE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
|
|
|
8
8
|
|
|
9
9
|
. "$SCRIPTS_DIR/style.sh"
|
|
10
10
|
|
|
11
|
-
version=$(
|
|
11
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
12
12
|
|
|
13
13
|
eagle_banner
|
|
14
14
|
|
package/scripts/index.sh
CHANGED
package/scripts/install.sh
CHANGED
|
@@ -11,6 +11,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
11
11
|
|
|
12
12
|
. "$SCRIPTS_DIR/style.sh"
|
|
13
13
|
. "$LIB_DIR/common.sh"
|
|
14
|
+
. "$LIB_DIR/hooks.sh"
|
|
14
15
|
|
|
15
16
|
SETTINGS="$EAGLE_SETTINGS"
|
|
16
17
|
|
|
@@ -160,47 +161,23 @@ if [ ! -f "$SETTINGS" ]; then
|
|
|
160
161
|
echo '{}' > "$SETTINGS"
|
|
161
162
|
fi
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
local event="$1"
|
|
165
|
-
local matcher="$2"
|
|
166
|
-
local command="$3"
|
|
167
|
-
local description="$4"
|
|
168
|
-
|
|
169
|
-
if jq -e ".hooks.${event}[]? | select(.hooks[]?.command == \"$command\")" "$SETTINGS" &>/dev/null; then
|
|
170
|
-
eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
171
|
-
return
|
|
172
|
-
fi
|
|
173
|
-
|
|
174
|
-
local entry
|
|
175
|
-
if [ -n "$matcher" ]; then
|
|
176
|
-
entry="{\"matcher\": \"$matcher\", \"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
177
|
-
else
|
|
178
|
-
entry="{\"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
179
|
-
fi
|
|
180
|
-
|
|
181
|
-
local tmp
|
|
182
|
-
tmp=$(mktemp)
|
|
183
|
-
jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
|
|
184
|
-
eagle_ok "$description"
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
patch_hook "SessionStart" "" \
|
|
164
|
+
eagle_patch_hook "$SETTINGS" "SessionStart" "" \
|
|
188
165
|
"$EAGLE_MEM_DIR/hooks/session-start.sh" \
|
|
189
166
|
"SessionStart hook"
|
|
190
167
|
|
|
191
|
-
|
|
168
|
+
eagle_patch_hook "$SETTINGS" "Stop" "" \
|
|
192
169
|
"$EAGLE_MEM_DIR/hooks/stop.sh" \
|
|
193
170
|
"Stop hook"
|
|
194
171
|
|
|
195
|
-
|
|
172
|
+
eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" \
|
|
196
173
|
"$EAGLE_MEM_DIR/hooks/post-tool-use.sh" \
|
|
197
174
|
"PostToolUse hook"
|
|
198
175
|
|
|
199
|
-
|
|
176
|
+
eagle_patch_hook "$SETTINGS" "SessionEnd" "" \
|
|
200
177
|
"$EAGLE_MEM_DIR/hooks/session-end.sh" \
|
|
201
178
|
"SessionEnd hook"
|
|
202
179
|
|
|
203
|
-
|
|
180
|
+
eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" \
|
|
204
181
|
"$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh" \
|
|
205
182
|
"UserPromptSubmit hook"
|
|
206
183
|
|
package/scripts/prune.sh
CHANGED
|
@@ -15,6 +15,33 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
15
15
|
|
|
16
16
|
eagle_ensure_db
|
|
17
17
|
|
|
18
|
+
# ─── Help ────────────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
show_help() {
|
|
21
|
+
echo -e " ${BOLD}eagle-mem prune${RESET} — Clean up old data"
|
|
22
|
+
echo ""
|
|
23
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
24
|
+
echo -e " eagle-mem prune ${DIM}# prune observations > 90 days${RESET}"
|
|
25
|
+
echo -e " eagle-mem prune ${CYAN}--days 30${RESET} ${DIM}# prune observations > 30 days${RESET}"
|
|
26
|
+
echo -e " eagle-mem prune ${CYAN}--dry-run${RESET} ${DIM}# show what would be pruned${RESET}"
|
|
27
|
+
echo ""
|
|
28
|
+
echo -e " ${BOLD}What gets pruned:${RESET}"
|
|
29
|
+
echo -e " ${DOT} Observations older than --days (default: 90)"
|
|
30
|
+
echo -e " ${DOT} Code chunks for files that no longer exist"
|
|
31
|
+
echo ""
|
|
32
|
+
echo -e " ${BOLD}What is preserved:${RESET}"
|
|
33
|
+
echo -e " ${DOT} All sessions and summaries (your session history)"
|
|
34
|
+
echo -e " ${DOT} All tasks"
|
|
35
|
+
echo -e " ${DOT} Project overviews"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
38
|
+
echo -e " ${CYAN}-d, --days${RESET} <N> Age threshold (default: 90)"
|
|
39
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Only prune for this project"
|
|
40
|
+
echo -e " ${CYAN}-n, --dry-run${RESET} Show counts without deleting"
|
|
41
|
+
echo ""
|
|
42
|
+
exit 0
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
# ─── Parse arguments ──────────────────────────────────────
|
|
19
46
|
|
|
20
47
|
days=90
|
|
@@ -26,30 +53,7 @@ while [ $# -gt 0 ]; do
|
|
|
26
53
|
--days|-d) days="$2"; shift 2 ;;
|
|
27
54
|
--project|-p) project="$2"; shift 2 ;;
|
|
28
55
|
--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
|
-
;;
|
|
56
|
+
--help|-h) show_help ;;
|
|
53
57
|
*)
|
|
54
58
|
eagle_err "Unknown option: $1"
|
|
55
59
|
exit 1
|
|
@@ -109,7 +113,6 @@ if [ -n "$projects" ]; then
|
|
|
109
113
|
|
|
110
114
|
if [ -n "$proj_cwd" ] && [ -d "$proj_cwd" ]; then
|
|
111
115
|
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
116
|
# Count files that no longer exist
|
|
114
117
|
orphans=0
|
|
115
118
|
paths=$(eagle_db "SELECT DISTINCT file_path FROM code_chunks WHERE project = '$(eagle_sql_escape "$proj")';")
|
package/scripts/search.sh
CHANGED
|
@@ -14,6 +14,27 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
14
14
|
|
|
15
15
|
eagle_ensure_db
|
|
16
16
|
|
|
17
|
+
# ─── Help ────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
show_help() {
|
|
20
|
+
echo -e " ${BOLD}eagle-mem search${RESET} — Search persistent memory"
|
|
21
|
+
echo ""
|
|
22
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
23
|
+
echo -e " eagle-mem search ${CYAN}<query>${RESET} ${DIM}# keyword search${RESET}"
|
|
24
|
+
echo -e " eagle-mem search ${CYAN}--timeline${RESET} ${DIM}# recent sessions${RESET}"
|
|
25
|
+
echo -e " eagle-mem search ${CYAN}--session <id>${RESET} ${DIM}# session details${RESET}"
|
|
26
|
+
echo -e " eagle-mem search ${CYAN}--files${RESET} ${DIM}# frequently modified files${RESET}"
|
|
27
|
+
echo -e " eagle-mem search ${CYAN}--stats${RESET} ${DIM}# project statistics${RESET}"
|
|
28
|
+
echo ""
|
|
29
|
+
echo -e " ${BOLD}Options:${RESET}"
|
|
30
|
+
echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
|
|
31
|
+
echo -e " ${CYAN}-n, --limit${RESET} <N> Max results (default: 10)"
|
|
32
|
+
echo -e " ${CYAN}-a, --all${RESET} Search across all projects"
|
|
33
|
+
echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
|
|
34
|
+
echo ""
|
|
35
|
+
exit 0
|
|
36
|
+
}
|
|
37
|
+
|
|
17
38
|
# ─── Parse arguments ──────────────────────────────────────
|
|
18
39
|
|
|
19
40
|
mode="keyword"
|
|
@@ -34,24 +55,7 @@ while [ $# -gt 0 ]; do
|
|
|
34
55
|
--limit|-n) limit="$2"; shift 2 ;;
|
|
35
56
|
--all|-a) cross_project=true; shift ;;
|
|
36
57
|
--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
|
-
;;
|
|
58
|
+
--help|-h) show_help ;;
|
|
55
59
|
-*)
|
|
56
60
|
eagle_err "Unknown option: $1"
|
|
57
61
|
exit 1
|
package/scripts/tasks.sh
CHANGED
|
@@ -29,6 +29,7 @@ show_help() {
|
|
|
29
29
|
echo -e " ${BOLD}Usage:${RESET}"
|
|
30
30
|
echo -e " eagle-mem tasks ${DIM}# list pending/in-progress tasks${RESET}"
|
|
31
31
|
echo -e " eagle-mem tasks ${CYAN}list${RESET} ${DIM}# list all tasks${RESET}"
|
|
32
|
+
echo -e " eagle-mem tasks ${CYAN}completed${RESET} ${DIM}# list completed tasks${RESET}"
|
|
32
33
|
echo -e " eagle-mem tasks ${CYAN}search${RESET} <query> ${DIM}# search tasks by keyword${RESET}"
|
|
33
34
|
echo ""
|
|
34
35
|
echo -e " ${BOLD}Options:${RESET}"
|
package/scripts/update.sh
CHANGED
|
@@ -12,6 +12,7 @@ LIB_DIR="$SCRIPTS_DIR/../lib"
|
|
|
12
12
|
. "$SCRIPTS_DIR/style.sh"
|
|
13
13
|
. "$LIB_DIR/common.sh"
|
|
14
14
|
. "$LIB_DIR/db.sh"
|
|
15
|
+
. "$LIB_DIR/hooks.sh"
|
|
15
16
|
|
|
16
17
|
SETTINGS="$EAGLE_SETTINGS"
|
|
17
18
|
|
|
@@ -57,37 +58,17 @@ fi
|
|
|
57
58
|
# ─── Re-register hooks (idempotent) ───────────────────────
|
|
58
59
|
|
|
59
60
|
if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
|
|
60
|
-
patch_hook() {
|
|
61
|
-
local event="$1"
|
|
62
|
-
local matcher="$2"
|
|
63
|
-
local command="$3"
|
|
64
|
-
|
|
65
|
-
if jq -e ".hooks.${event}[]? | select(.hooks[]?.command == \"$command\")" "$SETTINGS" &>/dev/null; then
|
|
66
|
-
return
|
|
67
|
-
fi
|
|
68
|
-
|
|
69
|
-
local entry
|
|
70
|
-
if [ -n "$matcher" ]; then
|
|
71
|
-
entry="{\"matcher\": \"$matcher\", \"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
72
|
-
else
|
|
73
|
-
entry="{\"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
local tmp
|
|
77
|
-
tmp=$(mktemp)
|
|
78
|
-
jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
patch_hook "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
|
|
82
|
-
patch_hook "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
83
61
|
# Update PostToolUse matcher if it has the old value (pre-v1.3.0)
|
|
84
62
|
if jq -e '.hooks.PostToolUse[]? | select(.matcher == "Read|Write|Edit|Bash")' "$SETTINGS" &>/dev/null; then
|
|
85
63
|
_tmp=$(mktemp)
|
|
86
64
|
jq '(.hooks.PostToolUse[] | select(.matcher == "Read|Write|Edit|Bash")).matcher = "Read|Write|Edit|Bash|TaskCreate|TaskUpdate"' "$SETTINGS" > "$_tmp" && mv "$_tmp" "$SETTINGS"
|
|
87
65
|
fi
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
66
|
+
|
|
67
|
+
eagle_patch_hook "$SETTINGS" "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
|
|
68
|
+
eagle_patch_hook "$SETTINGS" "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
|
|
69
|
+
eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
|
|
70
|
+
eagle_patch_hook "$SETTINGS" "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
|
|
71
|
+
eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
|
|
91
72
|
|
|
92
73
|
eagle_ok "Hooks registered"
|
|
93
74
|
fi
|
|
@@ -117,5 +98,5 @@ fi
|
|
|
117
98
|
|
|
118
99
|
# ─── Summary ───────────────────────────────────────────────
|
|
119
100
|
|
|
120
|
-
version=$(
|
|
101
|
+
version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
|
|
121
102
|
eagle_footer "Eagle Mem updated to v${version}."
|