eagle-mem 1.4.7 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,19 +1,6 @@
1
1
  ```
2
- .~~~~-.
3
- / ,__`)
4
- | \o/|'-.
5
- | / ,\
6
- | ('--./
7
- / \
8
- / , , , \
9
- `--'--'--'--'
10
-
11
- ███████╗░█████╗░░██████╗░██╗░░░░░███████╗ ███╗░░░███╗███████╗███╗░░░███╗
12
- ██╔════╝██╔══██╗██╔════╝░██║░░░░░██╔════╝ ████╗░████║██╔════╝████╗░████║
13
- █████╗░░███████║██║░░██╗░██║░░░░░█████╗░░ ██╔████╔██║█████╗░░██╔████╔██║
14
- ██╔══╝░░██╔══██║██║░░╚██╗██║░░░░░██╔══╝░░ ██║╚██╔╝██║██╔══╝░░██║╚██╔╝██║
15
- ███████╗██║░░██║╚██████╔╝███████╗███████╗ ██║░╚═╝░██║███████╗██║░╚═╝░██║
16
- ╚══════╝╚═╝░░╚═╝░╚═════╝░╚══════╝╚══════╝ ╚═╝░░░░░╚═╝╚══════╝╚═╝░░░░░╚═╝
2
+ █▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
3
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
17
4
  ```
18
5
 
19
6
  # Eagle Mem
@@ -32,14 +19,8 @@ eagle-mem install
32
19
  The installer checks prerequisites and offers to install missing ones:
33
20
 
34
21
  ```
35
- .~~~~-.
36
- / ,__`)
37
- | \o/|'-.
38
- | / ,\
39
- | ('--./
40
- / \
41
- / , , , \
42
- `--'--'--'--'
22
+ █▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
23
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
43
24
 
44
25
  Eagle Mem Install
45
26
  ─────────────────────────────────────
@@ -64,14 +45,12 @@ The installer checks prerequisites and offers to install missing ones:
64
45
  Start a new Claude Code session — Eagle Mem activates automatically and shows:
65
46
 
66
47
  ```
67
- .~~~~-.
68
- / ,__`)
69
- | \o/|'-. Eagle Mem loaded
70
- | / ,\ Project: my-app
71
- | ('--./ Sessions: 5 recent | Memories: 3 | Tasks: 2 pending
72
- / \ Last: Added auth middleware with JWT validation
73
- / , , , \
74
- `--'--'--'--'
48
+ █▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
49
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
50
+
51
+ Project: my-app
52
+ Sessions: 5 recent | Memories: 3 | Tasks: 2 pending
53
+ Last: Added auth middleware with JWT validation
75
54
  ```
76
55
 
77
56
  ## Commands
@@ -213,11 +192,15 @@ Single shared SQLite database at `~/.eagle-mem/memory.db` with a `project` colum
213
192
 
214
193
  ## Skills
215
194
 
216
- Eagle Mem ships with three skills for use inside Claude Code sessions:
195
+ Eagle Mem ships with seven skills for use inside Claude Code sessions:
217
196
 
218
197
  - **eagle-mem-search** — 3-layer search: compact FTS5 search, timeline view, full observations
219
198
  - **eagle-mem-tasks** — TaskAware Compact Loop: create, view, complete, and manage subtasks
220
199
  - **eagle-mem-overview** — Generate and update a persistent project overview
200
+ - **eagle-mem-index** — Index source files for FTS5 code-level search
201
+ - **eagle-mem-scan** — Scan and analyze a project to generate an overview
202
+ - **eagle-mem-memories** — Browse and search Claude Code memories, plans, and tasks
203
+ - **eagle-mem-prune** — Clean up old observations and orphaned chunks
221
204
 
222
205
  ## Architecture
223
206
 
@@ -236,19 +219,24 @@ Package (npm) Runtime (~/.eagle-mem/)
236
219
  │ ├── overview.sh │ └── db.sh
237
220
  │ ├── memories.sh └── db/
238
221
  │ ├── prune.sh ├── migrate.sh
239
- └── help.sh ├── schema.sql
240
- ├── hooks/ Source ├── 002_overviews.sql
241
- ├── lib/ Source ├── 003_code_chunks.sql
242
- ├── common.sh ├── 004_observation_indexes.sql
243
- └── db.sh ├── 005_claude_memories.sql
244
- ├── db/ Source ├── 006_claude_plans.sql
245
- ├── migrate.sh └── 007_claude_tasks.sql
222
+ ├── statusline-em.sh ├── schema.sql
223
+ │ └── help.sh ├── 002_overviews.sql
224
+ ├── hooks/ Source ├── 003_code_chunks.sql
225
+ ├── lib/ Source ├── 004_observation_indexes.sql
226
+ ├── common.sh ├── 005_claude_memories.sql
227
+ │ └── db.sh ├── 006_claude_plans.sql
228
+ ├── db/ Source └── 007_claude_tasks.sql
229
+ │ ├── migrate.sh
246
230
  │ ├── schema.sql
247
231
  │ └── migrations
248
232
  └── skills/ Symlinked → ~/.claude/skills/
249
233
  ├── eagle-mem-search/
250
234
  ├── eagle-mem-tasks/
251
- └── eagle-mem-overview/
235
+ ├── eagle-mem-overview/
236
+ ├── eagle-mem-index/
237
+ ├── eagle-mem-scan/
238
+ ├── eagle-mem-memories/
239
+ └── eagle-mem-prune/
252
240
  ```
253
241
 
254
242
  ## Uninstall
@@ -0,0 +1,57 @@
1
+ -- Migration 008: Enforce one summary per session
2
+ -- Removes duplicate summaries (keeps the latest) and adds UNIQUE constraint.
3
+ -- Recreates FTS triggers since DROP TABLE removes them.
4
+
5
+ -- Delete duplicates, keeping only the row with the highest id per session
6
+ DELETE FROM summaries WHERE id NOT IN (
7
+ SELECT MAX(id) FROM summaries GROUP BY session_id
8
+ );
9
+
10
+ -- Rebuild FTS index to match cleaned data
11
+ INSERT INTO summaries_fts(summaries_fts) VALUES('rebuild');
12
+
13
+ -- Drop old triggers (they'll be gone after table rebuild anyway)
14
+ DROP TRIGGER IF EXISTS summaries_ai;
15
+ DROP TRIGGER IF EXISTS summaries_ad;
16
+ DROP TRIGGER IF EXISTS summaries_au;
17
+
18
+ -- Recreate table with UNIQUE(session_id)
19
+ CREATE TABLE summaries_new (
20
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
21
+ session_id TEXT NOT NULL UNIQUE REFERENCES sessions(id),
22
+ project TEXT NOT NULL,
23
+ request TEXT,
24
+ investigated TEXT,
25
+ learned TEXT,
26
+ completed TEXT,
27
+ next_steps TEXT,
28
+ files_read TEXT DEFAULT '[]',
29
+ files_modified TEXT DEFAULT '[]',
30
+ notes TEXT,
31
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
32
+ );
33
+
34
+ INSERT INTO summaries_new SELECT * FROM summaries;
35
+ DROP TABLE summaries;
36
+ ALTER TABLE summaries_new RENAME TO summaries;
37
+
38
+ CREATE INDEX IF NOT EXISTS idx_summaries_session ON summaries(session_id);
39
+ CREATE INDEX IF NOT EXISTS idx_summaries_project ON summaries(project);
40
+
41
+ -- Recreate FTS content-sync triggers
42
+ CREATE TRIGGER summaries_ai AFTER INSERT ON summaries BEGIN
43
+ INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
44
+ VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
45
+ END;
46
+
47
+ CREATE TRIGGER summaries_ad AFTER DELETE ON summaries BEGIN
48
+ INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
49
+ VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
50
+ END;
51
+
52
+ CREATE TRIGGER summaries_au AFTER UPDATE ON summaries BEGIN
53
+ INSERT INTO summaries_fts(summaries_fts, rowid, request, investigated, learned, completed, next_steps, notes)
54
+ VALUES ('delete', old.id, old.request, old.investigated, old.learned, old.completed, old.next_steps, old.notes);
55
+ INSERT INTO summaries_fts(rowid, request, investigated, learned, completed, next_steps, notes)
56
+ VALUES (new.id, new.request, new.investigated, new.learned, new.completed, new.next_steps, new.notes);
57
+ END;
package/db/migrate.sh CHANGED
@@ -19,8 +19,13 @@ run_migration() {
19
19
  already_applied=$(sqlite3 "$DB" "SELECT COUNT(*) FROM _migrations WHERE name = '$name';" 2>/dev/null || echo "0")
20
20
 
21
21
  if [ "$already_applied" = "0" ]; then
22
- sqlite3 "$DB" < "$file"
23
- sqlite3 "$DB" "INSERT INTO _migrations (name) VALUES ('$name');"
22
+ # Strip PRAGMAs from migration body (they can't run inside transactions
23
+ # and are already set on every connection via lib/db.sh EAGLE_DB_SETUP)
24
+ local body
25
+ body=$(grep -v -E '^[[:space:]]*PRAGMA ' "$file")
26
+
27
+ # Set connection PRAGMAs, then run migration body + tracking insert atomically
28
+ { 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"
24
29
  echo " applied: $name"
25
30
  fi
26
31
  }
@@ -56,4 +61,7 @@ run_migration "006_claude_plans" "$SCRIPT_DIR/006_claude_plans.sql"
56
61
  # ─── Migration 007: Claude Code task mirror ──────────────
57
62
  run_migration "007_claude_tasks" "$SCRIPT_DIR/007_claude_tasks.sql"
58
63
 
64
+ # ─── Migration 008: Summary UPSERT (unique session_id) ───
65
+ run_migration "008_summary_upsert" "$SCRIPT_DIR/008_summary_upsert.sql"
66
+
59
67
  echo " Eagle Mem database ready: $DB"
@@ -60,7 +60,16 @@ case "$tool_name" in
60
60
  -e 's/(password[= :])[^ ]*/\1[REDACTED]/gi' \
61
61
  -e 's/(secret[= :])[^ ]*/\1[REDACTED]/gi' \
62
62
  -e 's/(token[= :])[^ ]*/\1[REDACTED]/gi' \
63
- -e 's/(Authorization: )[^ ]*/\1[REDACTED]/gi')
63
+ -e 's/(Authorization: )[^ ]*/\1[REDACTED]/gi' \
64
+ -e 's/sk_live_[A-Za-z0-9]+/[REDACTED]/g' \
65
+ -e 's/sk_test_[A-Za-z0-9]+/[REDACTED]/g' \
66
+ -e 's/AKIA[A-Z0-9]{16}/[REDACTED]/g' \
67
+ -e 's/ghp_[A-Za-z0-9]{36}/[REDACTED]/g' \
68
+ -e 's/gho_[A-Za-z0-9]{36}/[REDACTED]/g' \
69
+ -e 's/sk-ant-[A-Za-z0-9_-]+/[REDACTED]/g' \
70
+ -e 's/sk-[A-Za-z0-9]{20,}/[REDACTED]/g' \
71
+ -e 's/(ANTHROPIC_API_KEY[= :])[^ ]*/\1[REDACTED]/g' \
72
+ -e 's/(OPENAI_API_KEY[= :])[^ ]*/\1[REDACTED]/g')
64
73
  tool_summary="Bash: $cmd"
65
74
  ;;
66
75
  TaskCreate|TaskUpdate)
@@ -95,15 +104,17 @@ esac
95
104
  # Intercept TaskCreate/TaskUpdate and capture the resulting JSON files
96
105
  case "$tool_name" in
97
106
  TaskCreate|TaskUpdate)
98
- task_dir="$HOME/.claude/tasks/$session_id"
99
- if [ -d "$task_dir" ]; then
100
- task_id=$(echo "$input" | jq -r '.tool_input.id // empty')
101
- if [ -z "$task_id" ]; then
102
- newest=$(ls -t "$task_dir"/*.json 2>/dev/null | head -1)
103
- [ -n "$newest" ] && [ -f "$newest" ] && eagle_capture_claude_task "$newest" "$session_id" "$project"
104
- else
105
- task_json="$task_dir/$task_id.json"
106
- [ -f "$task_json" ] && eagle_capture_claude_task "$task_json" "$session_id" "$project"
107
+ if eagle_validate_session_id "$session_id"; then
108
+ task_dir="$HOME/.claude/tasks/$session_id"
109
+ if [ -d "$task_dir" ]; then
110
+ task_id=$(echo "$input" | jq -r '.tool_input.id // empty')
111
+ if [ -z "$task_id" ]; then
112
+ newest=$(ls -t "$task_dir"/*.json 2>/dev/null | head -1)
113
+ [ -n "$newest" ] && [ -f "$newest" ] && eagle_capture_claude_task "$newest" "$session_id" "$project"
114
+ elif eagle_validate_session_id "$task_id"; then
115
+ task_json="$task_dir/$task_id.json"
116
+ [ -f "$task_json" ] && eagle_capture_claude_task "$task_json" "$session_id" "$project"
117
+ fi
107
118
  fi
108
119
  fi
109
120
  ;;
@@ -24,13 +24,15 @@ project=$(eagle_project_from_cwd "$cwd")
24
24
 
25
25
  # Final sweep: re-capture all task files to catch status changes
26
26
  # Claude Code may update task status without triggering PostToolUse
27
- task_dir="$HOME/.claude/tasks/$session_id"
28
- if [ -d "$task_dir" ]; then
29
- for task_file in "$task_dir"/*.json; do
30
- [ ! -f "$task_file" ] && continue
31
- eagle_capture_claude_task "$task_file" "$session_id" "$project"
32
- done
33
- eagle_log "INFO" "SessionEnd: re-synced tasks from $task_dir"
27
+ if eagle_validate_session_id "$session_id"; then
28
+ task_dir="$HOME/.claude/tasks/$session_id"
29
+ if [ -d "$task_dir" ]; then
30
+ for task_file in "$task_dir"/*.json; do
31
+ [ ! -f "$task_file" ] && continue
32
+ eagle_capture_claude_task "$task_file" "$session_id" "$project"
33
+ done
34
+ eagle_log "INFO" "SessionEnd: re-synced tasks from $task_dir"
35
+ fi
34
36
  fi
35
37
 
36
38
  eagle_end_session "$session_id"
@@ -30,14 +30,15 @@ eagle_log "INFO" "SessionStart: session=$session_id project=$project source=$sou
30
30
 
31
31
  eagle_upsert_session "$session_id" "$project" "$cwd" "$model" "$source_type"
32
32
 
33
+ # ─── Sweep stuck sessions (older than 4 hours) ─────────────
34
+ eagle_db "UPDATE sessions SET status = 'abandoned'
35
+ WHERE status = 'active'
36
+ AND started_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-4 hours');"
37
+
33
38
  # ─── Build context injection ────────────────────────────────
34
39
 
35
- eagle_logo="███████╗░█████╗░░██████╗░██╗░░░░░███████╗ ███╗░░░███╗███████╗███╗░░░███╗
36
- ██╔════╝██╔══██╗██╔════╝░██║░░░░░██╔════╝ ████╗░████║██╔════╝████╗░████║
37
- █████╗░░███████║██║░░██╗░██║░░░░░█████╗░░ ██╔████╔██║█████╗░░██╔████╔██║
38
- ██╔══╝░░██╔══██║██║░░╚██╗██║░░░░░██╔══╝░░ ██║╚██╔╝██║██╔══╝░░██║╚██╔╝██║
39
- ███████╗██║░░██║╚██████╔╝███████╗███████╗ ██║░╚═╝░██║███████╗██║░╚═╝░██║
40
- ╚══════╝╚═╝░░╚═╝░╚═════╝░╚══════╝╚══════╝ ╚═╝░░░░░╚═╝╚══════╝╚═╝░░░░░╚═╝"
40
+ eagle_logo="█▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
41
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █"
41
42
 
42
43
  context="$eagle_logo
43
44
 
@@ -107,8 +108,12 @@ Pending tasks for '$project':
107
108
  active_task=$(eagle_get_active_task "$project")
108
109
 
109
110
  if [ -z "$active_task" ] && [ -n "$first_pending" ]; then
110
- # Auto-activate the next pending task
111
- eagle_activate_task "$first_pending"
111
+ # Auto-activate the next pending task (atomic to prevent races
112
+ # when multiple sessions start simultaneously)
113
+ eagle_db "UPDATE tasks SET status = 'active', started_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
114
+ WHERE id = $first_pending
115
+ AND status = 'pending'
116
+ AND NOT EXISTS (SELECT 1 FROM tasks WHERE project = '$(eagle_sql_escape "$project")' AND status = 'active');"
112
117
  active_task=$(eagle_get_active_task "$project")
113
118
  fi
114
119
 
@@ -160,7 +165,7 @@ fi
160
165
 
161
166
  synced_tasks=$(eagle_db "SELECT subject, status FROM claude_tasks
162
167
  WHERE project = '$(eagle_sql_escape "$project")'
163
- AND status IN ('in_progress', 'pending', 'todo', 'open')
168
+ AND status IN ('in_progress', 'pending')
164
169
  AND updated_at > datetime('now', '-7 days')
165
170
  ORDER BY updated_at DESC LIMIT 5;")
166
171
  if [ -n "$synced_tasks" ]; then
@@ -183,12 +188,8 @@ You have persistent memory powered by Eagle Mem. When you recall context from a
183
188
  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:
184
189
 
185
190
  \`\`\`
186
- ███████╗░█████╗░░██████╗░██╗░░░░░███████╗ ███╗░░░███╗███████╗███╗░░░███╗
187
- ██╔════╝██╔══██╗██╔════╝░██║░░░░░██╔════╝ ████╗░████║██╔════╝████╗░████║
188
- █████╗░░███████║██║░░██╗░██║░░░░░█████╗░░ ██╔████╔██║█████╗░░██╔████╔██║
189
- ██╔══╝░░██╔══██║██║░░╚██╗██║░░░░░██╔══╝░░ ██║╚██╔╝██║██╔══╝░░██║╚██╔╝██║
190
- ███████╗██║░░██║╚██████╔╝███████╗███████╗ ██║░╚═╝░██║███████╗██║░╚═╝░██║
191
- ╚══════╝╚═╝░░╚═╝░╚═════╝░╚══════╝╚══════╝ ╚═╝░░░░░╚═╝╚══════╝╚═╝░░░░░╚═╝
191
+ █▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
192
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
192
193
 
193
194
  Project: <project name>
194
195
  Sessions: N recent | Memories: N | Tasks: N pending
package/hooks/stop.sh CHANGED
@@ -48,8 +48,8 @@ if [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
48
48
  else "" end
49
49
  ' "$transcript_path" 2>/dev/null)
50
50
 
51
- # Strip <private>...</private> blocks before processing
52
- text_content=$(echo "$text_content" | sed '/<private>/,/<\/private>/d')
51
+ # Strip <private>...</private> blocks before processing (case-insensitive, tolerates attributes/whitespace)
52
+ text_content=$(echo "$text_content" | sed -E '/<[Pp][Rr][Ii][Vv][Aa][Tt][Ee][^>]*>/,/<\/[Pp][Rr][Ii][Vv][Aa][Tt][Ee][[:space:]]*>/d')
53
53
 
54
54
  # Parse <eagle-summary> block
55
55
  if [ -n "$text_content" ] && echo "$text_content" | grep -q '<eagle-summary>' 2>/dev/null; then
@@ -108,23 +108,30 @@ fi
108
108
  # ─── Heuristic fallback: extract from tool calls ───────────
109
109
 
110
110
  if [ -z "$request" ] && [ -n "$transcript_path" ] && [ -f "$transcript_path" ]; then
111
- eagle_log "INFO" "Stop: no eagle-summary found, using heuristic fallback"
112
-
113
- # Extract first user prompt as "request"
114
- request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null | head -1 | cut -c1-500)
115
-
116
- # Extract files from Read/Write/Edit tool calls
117
- heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
118
- heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
119
-
120
- if [ -n "$heuristic_reads" ]; then
121
- files_read=$(echo "$heuristic_reads" | jq -Rsc 'split("\n") | map(select(. != ""))')
111
+ # Skip heuristic if we already have a summary for this session.
112
+ # Stop fires every turn -- without this guard, each turn creates a duplicate row.
113
+ existing_count=$(eagle_db "SELECT COUNT(*) FROM summaries WHERE session_id = '$(eagle_sql_escape "$session_id")';")
114
+ if [ "${existing_count:-0}" -gt 0 ]; then
115
+ eagle_log "INFO" "Stop: skipping heuristic — summary already exists for session=$session_id (count=$existing_count)"
116
+ else
117
+ eagle_log "INFO" "Stop: no eagle-summary found, using heuristic fallback"
118
+
119
+ # Extract first user prompt as "request"
120
+ request=$(jq -r 'select(.type == "user") | .message.content | if type == "string" then . elif type == "array" then [.[] | select(.type == "text") | .text] | join(" ") else "" end' "$transcript_path" 2>/dev/null | head -1 | cut -c1-500)
121
+
122
+ # Extract files from Read/Write/Edit tool calls
123
+ heuristic_reads=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Read") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
124
+ heuristic_writes=$(jq -r 'select(.type == "assistant") | .message.content[]? | select(.type == "tool_use") | select(.name == "Write" or .name == "Edit") | .input.file_path // empty' "$transcript_path" 2>/dev/null | sort -u | head -20)
125
+
126
+ if [ -n "$heuristic_reads" ]; then
127
+ files_read=$(echo "$heuristic_reads" | jq -Rsc 'split("\n") | map(select(. != ""))')
128
+ fi
129
+ if [ -n "$heuristic_writes" ]; then
130
+ files_modified=$(echo "$heuristic_writes" | jq -Rsc 'split("\n") | map(select(. != ""))')
131
+ fi
132
+
133
+ completed="(auto-captured from tool usage)"
122
134
  fi
123
- if [ -n "$heuristic_writes" ]; then
124
- files_modified=$(echo "$heuristic_writes" | jq -Rsc 'split("\n") | map(select(. != ""))')
125
- fi
126
-
127
- completed="(auto-captured from tool usage)"
128
135
  fi
129
136
 
130
137
  # ─── Write to database ─────────────────────────────────────
@@ -134,8 +141,8 @@ if [ -n "$request" ] || [ -n "$completed" ] || [ -n "$learned" ]; then
134
141
  eagle_log "INFO" "Stop: summary saved for session=$session_id"
135
142
  fi
136
143
 
137
- # Mark active task as done if eagle-summary mentions completion
138
- if [ -n "$completed" ]; then
144
+ # Mark active task as done only when Claude explicitly provided a summary
145
+ if [ -n "$summary_block" ] && [ -n "$completed" ]; then
139
146
  completed_task_id=$(eagle_complete_active_task "$project")
140
147
  if [ -n "$completed_task_id" ]; then
141
148
  eagle_log "INFO" "Stop: marked task #$completed_task_id as done"
package/lib/common.sh CHANGED
@@ -42,6 +42,13 @@ eagle_fts_sanitize() {
42
42
  printf '%s' "$1" | sed 's/[*"(){}^~:]/ /g' | sed 's/ */ /g; s/^ //; s/ $//'
43
43
  }
44
44
 
45
+ # Validate a session ID is safe for use in file paths (no traversal).
46
+ # Claude Code session IDs are UUIDs or hex strings — reject anything else.
47
+ eagle_validate_session_id() {
48
+ local sid="$1"
49
+ [[ "$sid" =~ ^[A-Za-z0-9_-]+$ ]]
50
+ }
51
+
45
52
  eagle_read_stdin() {
46
53
  local input=""
47
54
  if [ ! -t 0 ]; then
package/lib/db.sh CHANGED
@@ -14,15 +14,15 @@ PRAGMA trusted_schema=ON;
14
14
  .output stdout"
15
15
 
16
16
  eagle_db() {
17
- { echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
17
+ { echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
18
18
  }
19
19
 
20
20
  eagle_db_pipe() {
21
- { echo "$EAGLE_DB_SETUP"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
21
+ { echo "$EAGLE_DB_SETUP"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
22
22
  }
23
23
 
24
24
  eagle_db_json() {
25
- { echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>/dev/null
25
+ { echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
26
26
  }
27
27
 
28
28
  eagle_ensure_db() {
@@ -78,7 +78,7 @@ eagle_insert_summary() {
78
78
  local notes; notes=$(eagle_sql_escape "${10:-}")
79
79
 
80
80
  eagle_db_pipe <<SQL
81
- INSERT INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
81
+ INSERT OR REPLACE INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
82
82
  VALUES (
83
83
  '$session_id',
84
84
  '$project',
@@ -145,19 +145,6 @@ eagle_get_next_task() {
145
145
  LIMIT 1;"
146
146
  }
147
147
 
148
- eagle_get_active_files() {
149
- local project; project=$(eagle_sql_escape "$1")
150
- local limit; limit=$(eagle_sql_int "${2:-20}")
151
-
152
- eagle_db "SELECT json_each.value
153
- FROM observations, json_each(observations.files_modified)
154
- WHERE observations.project = '$project'
155
- AND observations.created_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-30 days')
156
- GROUP BY json_each.value
157
- ORDER BY MAX(observations.created_at) DESC
158
- LIMIT $limit;"
159
- }
160
-
161
148
  eagle_observation_exists() {
162
149
  local session_id; session_id=$(eagle_sql_escape "$1")
163
150
  local tool_name; tool_name=$(eagle_sql_escape "$2")
@@ -329,13 +316,6 @@ eagle_list_claude_memories() {
329
316
  LIMIT $limit;"
330
317
  }
331
318
 
332
- eagle_get_claude_memory() {
333
- local file_path; file_path=$(eagle_sql_escape "$1")
334
- eagle_db "SELECT memory_name, memory_type, description, content, file_path, updated_at, origin_session_id
335
- FROM claude_memories
336
- WHERE file_path = '$file_path';"
337
- }
338
-
339
319
  eagle_capture_claude_plan() {
340
320
  local file_path="$1"
341
321
  local session_id="${2:-}"
@@ -546,16 +526,25 @@ eagle_backfill_projects() {
546
526
  sid_sql=$(eagle_sql_escape "$sid")
547
527
  proj_sql=$(eagle_sql_escape "$project")
548
528
 
549
- local changed
550
- changed=$(eagle_db "UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
529
+ local ch
530
+ ch=$(eagle_db "UPDATE sessions SET project = '$proj_sql' WHERE id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
531
+ SELECT changes();")
532
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
533
+ ch=$(eagle_db "UPDATE claude_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
534
+ SELECT changes();")
535
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
536
+ ch=$(eagle_db "UPDATE claude_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
537
+ SELECT changes();")
538
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
539
+ ch=$(eagle_db "UPDATE claude_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
540
+ SELECT changes();")
541
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
542
+ ch=$(eagle_db "UPDATE summaries SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
551
543
  SELECT changes();")
552
- changed=$(eagle_db "UPDATE claude_tasks SET project = '$proj_sql' WHERE source_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
544
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
545
+ ch=$(eagle_db "UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');
553
546
  SELECT changes();")
554
- [ "${changed:-0}" -gt 0 ] && updated=$((updated + changed))
555
- eagle_db "UPDATE claude_memories SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');"
556
- eagle_db "UPDATE claude_plans SET project = '$proj_sql' WHERE origin_session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');"
557
- eagle_db "UPDATE summaries SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');"
558
- eagle_db "UPDATE observations SET project = '$proj_sql' WHERE session_id = '$sid_sql' AND (project = '' OR project != '$proj_sql');"
547
+ [ "${ch:-0}" -gt 0 ] && updated=$((updated + ch))
559
548
  done <<< "$map"
560
549
 
561
550
  echo "$updated"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "1.4.7",
3
+ "version": "1.5.1",
4
4
  "description": "Lightweight persistent memory for Claude Code — SQLite + FTS5, no daemon, no bloat",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
package/scripts/help.sh CHANGED
@@ -56,9 +56,13 @@ echo -e " ${DOT} Mirrors Claude Code memories, plans, and tasks into FTS5-sea
56
56
  echo -e " ${DOT} Provides task management for complex multi-step work"
57
57
  echo ""
58
58
  echo -e " ${BOLD}Skills${RESET} ${DIM}(available inside Claude Code):${RESET}"
59
- echo -e " ${CYAN}/eagle-mem-search${RESET} Search past sessions and observations"
60
- echo -e " ${CYAN}/eagle-mem-tasks${RESET} Break work into tracked subtasks"
61
- echo -e " ${CYAN}/eagle-mem-overview${RESET} Generate a persistent project summary"
59
+ echo -e " ${CYAN}/eagle-mem-search${RESET} Search past sessions and observations"
60
+ echo -e " ${CYAN}/eagle-mem-tasks${RESET} Break work into tracked subtasks"
61
+ echo -e " ${CYAN}/eagle-mem-overview${RESET} Generate a persistent project summary"
62
+ echo -e " ${CYAN}/eagle-mem-index${RESET} Index source files for code search"
63
+ echo -e " ${CYAN}/eagle-mem-scan${RESET} Scan and analyze a project"
64
+ echo -e " ${CYAN}/eagle-mem-memories${RESET} Browse Claude Code memories, plans, tasks"
65
+ echo -e " ${CYAN}/eagle-mem-prune${RESET} Clean up old observations and chunks"
62
66
  echo ""
63
67
  echo -e " ${DIM}Skills use these CLI commands under the hood — no raw SQL.${RESET}"
64
68
  echo ""
package/scripts/index.sh CHANGED
@@ -151,11 +151,13 @@ while IFS= read -r file; do
151
151
  file_sql=$(eagle_sql_escape "$file")
152
152
  lang_sql=$(eagle_sql_escape "$lang")
153
153
 
154
- eagle_db "DELETE FROM code_chunks WHERE project = '$project_sql' AND file_path = '$file_sql';"
155
-
156
154
  total_lines=$(wc -l < "$full_path" 2>/dev/null | tr -d ' ')
157
155
  [ "$total_lines" -eq 0 ] && continue
158
156
 
157
+ # Build all INSERTs for this file, then run as a single atomic transaction
158
+ txn_sql="BEGIN;
159
+ DELETE FROM code_chunks WHERE project = '$project_sql' AND file_path = '$file_sql';"
160
+
159
161
  start=1
160
162
  while [ "$start" -le "$total_lines" ]; do
161
163
  end=$((start + CHUNK_SIZE - 1))
@@ -164,13 +166,19 @@ while IFS= read -r file; do
164
166
  content=$(sed -n "${start},${end}p" "$full_path")
165
167
  content_sql=$(eagle_sql_escape "$content")
166
168
 
167
- eagle_db "INSERT INTO code_chunks (project, file_path, language, start_line, end_line, content, mtime)
168
- VALUES ('$project_sql', '$file_sql', '$lang_sql', $start, $end, '$content_sql', $current_mtime);"
169
+ txn_sql+="
170
+ INSERT INTO code_chunks (project, file_path, language, start_line, end_line, content, mtime)
171
+ VALUES ('$project_sql', '$file_sql', '$lang_sql', $start, $end, '$content_sql', $current_mtime);"
169
172
 
170
173
  chunk_count=$((chunk_count + 1))
171
174
  start=$((end + 1))
172
175
  done
173
176
 
177
+ txn_sql+="
178
+ COMMIT;"
179
+
180
+ eagle_db_pipe <<< "$txn_sql"
181
+
174
182
  file_count=$((file_count + 1))
175
183
 
176
184
  if [ $((file_count % 10)) -eq 0 ]; then
@@ -135,15 +135,17 @@ echo ""
135
135
  echo -e " ${BOLD}Installing Eagle Mem...${RESET}"
136
136
  echo ""
137
137
 
138
- mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db}
138
+ mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db,scripts}
139
139
 
140
140
  cp "$PACKAGE_DIR"/hooks/*.sh "$EAGLE_MEM_DIR/hooks/"
141
141
  cp "$PACKAGE_DIR"/lib/*.sh "$EAGLE_MEM_DIR/lib/"
142
142
  cp "$PACKAGE_DIR"/db/*.sh "$EAGLE_MEM_DIR/db/"
143
143
  cp "$PACKAGE_DIR"/db/*.sql "$EAGLE_MEM_DIR/db/"
144
+ cp "$PACKAGE_DIR"/scripts/statusline-em.sh "$EAGLE_MEM_DIR/scripts/" 2>/dev/null
144
145
 
145
146
  chmod +x "$EAGLE_MEM_DIR"/hooks/*.sh
146
147
  chmod +x "$EAGLE_MEM_DIR"/db/migrate.sh
148
+ chmod +x "$EAGLE_MEM_DIR"/scripts/*.sh 2>/dev/null
147
149
 
148
150
  eagle_ok "Files copied to $EAGLE_MEM_DIR"
149
151
 
@@ -216,6 +218,53 @@ if [ -d "$PACKAGE_DIR/skills" ]; then
216
218
  done
217
219
  fi
218
220
 
221
+ # ─── Statusline integration ───────────────────────────────
222
+
223
+ EM_STATUSLINE="$EAGLE_MEM_DIR/scripts/statusline-em.sh"
224
+ existing_sl=$(jq -r '.statusLine.command // empty' "$SETTINGS" 2>/dev/null)
225
+
226
+ if [ -z "$existing_sl" ]; then
227
+ # No statusline configured — set up a minimal one that shows Eagle Mem
228
+ wrapper="$EAGLE_MEM_DIR/scripts/statusline-wrapper.sh"
229
+ cat > "$wrapper" << 'WRAPPER'
230
+ #!/usr/bin/env bash
231
+ input=$(cat)
232
+ project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
233
+ source "$HOME/.eagle-mem/scripts/statusline-em.sh"
234
+ eagle_mem_statusline "$project_dir"
235
+ WRAPPER
236
+ chmod +x "$wrapper"
237
+ tmp=$(mktemp)
238
+ jq --arg cmd "sh $wrapper" '.statusLine = {"type": "command", "command": $cmd, "refreshInterval": 30}' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
239
+ eagle_ok "Statusline ${DIM}(new — Eagle Mem indicator)${RESET}"
240
+ elif echo "$existing_sl" | grep -q "eagle-mem"; then
241
+ eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
242
+ else
243
+ # Existing statusline — check if it's a .sh file we can patch
244
+ sl_file=$(echo "$existing_sl" | sed 's/^sh //')
245
+ if [ -f "$sl_file" ] && ! grep -q "eagle-mem" "$sl_file"; then
246
+ eagle_dim " Statusline detected: $sl_file"
247
+ eagle_dim " To add Eagle Mem, add this snippet before your ASSEMBLE section:"
248
+ echo ""
249
+ eagle_dim " # ── EAGLE MEM ──"
250
+ eagle_dim " em_section=\"\""
251
+ eagle_dim " em_db=\"\$HOME/.eagle-mem/memory.db\""
252
+ eagle_dim " if [ -f \"\$em_db\" ]; then"
253
+ eagle_dim " em_proj=\$(basename \"\$project_dir\" | sed \"s/'/''/g\")"
254
+ eagle_dim " em_cnt=\$(echo \".headers off"
255
+ eagle_dim " SELECT COUNT(*) FROM sessions WHERE project = '\${em_proj}';\" | sqlite3 \"\$em_db\" 2>/dev/null | tr -d '[:space:]')"
256
+ eagle_dim " em_mem=\$(echo \".headers off"
257
+ eagle_dim " SELECT COUNT(*) FROM claude_memories WHERE project = '\${em_proj}';\" | sqlite3 \"\$em_db\" 2>/dev/null | tr -d '[:space:]')"
258
+ eagle_dim " em_cnt=\${em_cnt:-0}; em_mem=\${em_mem:-0}"
259
+ eagle_dim " em_section=\$(printf \"%bEagle Mem%b %b%s%b ses %b%s%b mem\" \"\$CYAN\" \"\$R\" \"\$WHT\" \"\$em_cnt\" \"\$DIM\" \"\$WHT\" \"\$em_mem\" \"\$R\")"
260
+ eagle_dim " fi"
261
+ echo ""
262
+ eagle_ok "Statusline ${DIM}(manual patch needed — instructions above)${RESET}"
263
+ else
264
+ eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
265
+ fi
266
+ fi
267
+
219
268
  # ─── Summary ───────────────────────────────────────────────
220
269
 
221
270
  eagle_footer "Eagle Mem installed successfully."
@@ -23,27 +23,29 @@ project=""
23
23
  json_output=false
24
24
  args=()
25
25
 
26
+ show_help() {
27
+ echo -e " ${BOLD}eagle-mem overview${RESET} — Manage project overviews"
28
+ echo ""
29
+ echo -e " ${BOLD}Usage:${RESET}"
30
+ echo -e " eagle-mem overview ${DIM}# show current overview${RESET}"
31
+ echo -e " eagle-mem overview ${CYAN}set${RESET} <text> ${DIM}# set/update overview${RESET}"
32
+ echo -e " eagle-mem overview ${CYAN}delete${RESET} ${DIM}# delete overview${RESET}"
33
+ echo -e " eagle-mem overview ${CYAN}list${RESET} ${DIM}# list all overviews${RESET}"
34
+ echo ""
35
+ echo -e " ${BOLD}Options:${RESET}"
36
+ echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
37
+ echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
38
+ echo ""
39
+ echo -e " ${BOLD}Tip:${RESET} Use ${CYAN}eagle-mem scan${RESET} to auto-generate an overview from code."
40
+ echo ""
41
+ exit 0
42
+ }
43
+
26
44
  while [ $# -gt 0 ]; do
27
45
  case "$1" in
28
46
  --project|-p) project="$2"; shift 2 ;;
29
47
  --json|-j) json_output=true; shift ;;
30
- --help|-h)
31
- echo -e " ${BOLD}eagle-mem overview${RESET} — Manage project overviews"
32
- echo ""
33
- echo -e " ${BOLD}Usage:${RESET}"
34
- echo -e " eagle-mem overview ${DIM}# show current overview${RESET}"
35
- echo -e " eagle-mem overview ${CYAN}set${RESET} <text> ${DIM}# set/update overview${RESET}"
36
- echo -e " eagle-mem overview ${CYAN}delete${RESET} ${DIM}# delete overview${RESET}"
37
- echo -e " eagle-mem overview ${CYAN}list${RESET} ${DIM}# list all overviews${RESET}"
38
- echo ""
39
- echo -e " ${BOLD}Options:${RESET}"
40
- echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
41
- echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
42
- echo ""
43
- echo -e " ${BOLD}Tip:${RESET} Use ${CYAN}eagle-mem scan${RESET} to auto-generate an overview from code."
44
- echo ""
45
- exit 0
46
- ;;
48
+ --help|-h) show_help ;;
47
49
  *) args+=("$1"); shift ;;
48
50
  esac
49
51
  done
@@ -145,23 +147,7 @@ case "$action" in
145
147
  set|update) overview_set ;;
146
148
  delete|rm) overview_delete ;;
147
149
  list|ls) overview_list ;;
148
- --help|-h)
149
- echo -e " ${BOLD}eagle-mem overview${RESET} — Manage project overviews"
150
- echo ""
151
- echo -e " ${BOLD}Usage:${RESET}"
152
- echo -e " eagle-mem overview ${DIM}# show current overview${RESET}"
153
- echo -e " eagle-mem overview ${CYAN}set${RESET} <text> ${DIM}# set/update overview${RESET}"
154
- echo -e " eagle-mem overview ${CYAN}delete${RESET} ${DIM}# delete overview${RESET}"
155
- echo -e " eagle-mem overview ${CYAN}list${RESET} ${DIM}# list all overviews${RESET}"
156
- echo ""
157
- echo -e " ${BOLD}Options:${RESET}"
158
- echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
159
- echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
160
- echo ""
161
- echo -e " ${BOLD}Tip:${RESET} Use ${CYAN}eagle-mem scan${RESET} to auto-generate an overview from code."
162
- echo ""
163
- exit 0
164
- ;;
150
+ --help|-h) show_help ;;
165
151
  *)
166
152
  eagle_err "Unknown action: $action"
167
153
  eagle_dim " Run 'eagle-mem overview --help' for options"
@@ -0,0 +1,34 @@
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"
5
+
6
+ eagle_mem_statusline() {
7
+ local project_dir="${1:-}"
8
+ local em_db="$HOME/.eagle-mem/memory.db"
9
+ [ -f "$em_db" ] || return
10
+
11
+ local proj
12
+ proj=$(basename "$project_dir")
13
+ [ -z "$proj" ] && return
14
+
15
+ # Escape single quotes for safe SQL interpolation
16
+ proj=$(printf '%s' "$proj" | sed "s/'/''/g")
17
+
18
+ local cnt mem
19
+ cnt=$(echo ".headers off
20
+ SELECT COUNT(*) FROM sessions WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
21
+ mem=$(echo ".headers off
22
+ SELECT COUNT(*) FROM claude_memories WHERE project = '${proj}';" | sqlite3 "$em_db" 2>/dev/null | tr -d '[:space:]')
23
+ cnt=${cnt:-0}; mem=${mem:-0}
24
+
25
+ local R='\033[0m' CYAN='\033[96m' WHT='\033[97m' DIM='\033[2m'
26
+ printf "%bEagle Mem%b %b%s%b ses %b%s%b mem" "$CYAN" "$R" "$WHT" "$cnt" "$DIM" "$WHT" "$mem" "$R"
27
+ }
28
+
29
+ # When run directly, read project_dir from stdin JSON (statusline format)
30
+ if [ "${BASH_SOURCE[0]}" = "$0" ]; then
31
+ input=$(cat)
32
+ project_dir=$(echo "$input" | jq -r '.workspace.project_dir // .workspace.current_dir // .cwd // ""' 2>/dev/null)
33
+ eagle_mem_statusline "$project_dir"
34
+ fi
package/scripts/style.sh CHANGED
@@ -32,10 +32,6 @@ eagle_warn() { echo -e " ${YELLOW}!${RESET} $1"; }
32
32
  eagle_err() { echo -e " ${RED}✗${RESET} $1" >&2; }
33
33
  eagle_dim() { echo -e " ${DIM}$1${RESET}"; }
34
34
 
35
- eagle_step() {
36
- echo -e " ${CYAN}$1${RESET} $2"
37
- }
38
-
39
35
  eagle_kv() {
40
36
  printf " ${DIM}%-12s${RESET} %s\n" "$1" "$2"
41
37
  }
@@ -46,28 +42,11 @@ eagle_footer() {
46
42
  echo ""
47
43
  }
48
44
 
49
- eagle_art() {
50
- cat << 'ART'
51
- .~~~~-.
52
- / ,__`)
53
- | \o/|'-.
54
- | / ,\
55
- | ('--./
56
- / \
57
- / , , , \
58
- `--'--'--'--'
59
- ART
60
- }
61
-
62
45
  eagle_banner() {
63
46
  echo -e "${CYAN}"
64
47
  cat << 'BANNER'
65
- ███████╗░█████╗░░██████╗░██╗░░░░░███████╗ ███╗░░░███╗███████╗███╗░░░███╗
66
- ██╔════╝██╔══██╗██╔════╝░██║░░░░░██╔════╝ ████╗░████║██╔════╝████╗░████║
67
- █████╗░░███████║██║░░██╗░██║░░░░░█████╗░░ ██╔████╔██║█████╗░░██╔████╔██║
68
- ██╔══╝░░██╔══██║██║░░╚██╗██║░░░░░██╔══╝░░ ██║╚██╔╝██║██╔══╝░░██║╚██╔╝██║
69
- ███████╗██║░░██║╚██████╔╝███████╗███████╗ ██║░╚═╝░██║███████╗██║░╚═╝░██║
70
- ╚══════╝╚═╝░░╚═╝░╚═════╝░╚══════╝╚══════╝ ╚═╝░░░░░╚═╝╚══════╝╚═╝░░░░░╚═╝
48
+ █▀▀ ▄▀█ █▀▀ █ █▀▀ █▀▄▀█ █▀▀ █▀▄▀█
49
+ ██▄ █▀█ █▄█ █▄▄ ██▄ █ ▀ █ ██▄ █ ▀ █
71
50
  BANNER
72
51
  echo -e "${RESET}"
73
52
  }
package/scripts/tasks.sh CHANGED
@@ -24,28 +24,31 @@ json_output=false
24
24
 
25
25
  # Extract global options from remaining args
26
26
  args=()
27
+
28
+ show_help() {
29
+ echo -e " ${BOLD}eagle-mem tasks${RESET} — Manage tracked tasks"
30
+ echo ""
31
+ echo -e " ${BOLD}Usage:${RESET}"
32
+ echo -e " eagle-mem tasks ${DIM}# list pending tasks${RESET}"
33
+ echo -e " eagle-mem tasks ${CYAN}list${RESET} ${DIM}# list all tasks${RESET}"
34
+ echo -e " eagle-mem tasks ${CYAN}add${RESET} <title> [instructions] ${DIM}# add a task${RESET}"
35
+ echo -e " eagle-mem tasks ${CYAN}done${RESET} <id> ${DIM}# mark task complete${RESET}"
36
+ echo -e " eagle-mem tasks ${CYAN}block${RESET} <id> ${DIM}# mark task blocked${RESET}"
37
+ echo -e " eagle-mem tasks ${CYAN}context${RESET} <id> <snapshot> ${DIM}# set task context${RESET}"
38
+ echo -e " eagle-mem tasks ${CYAN}clear${RESET} ${DIM}# remove all done tasks${RESET}"
39
+ echo ""
40
+ echo -e " ${BOLD}Options:${RESET}"
41
+ echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
42
+ echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
43
+ echo ""
44
+ exit 0
45
+ }
46
+
27
47
  while [ $# -gt 0 ]; do
28
48
  case "$1" in
29
49
  --project|-p) project="$2"; shift 2 ;;
30
50
  --json|-j) json_output=true; shift ;;
31
- --help|-h)
32
- echo -e " ${BOLD}eagle-mem tasks${RESET} — Manage tracked tasks"
33
- echo ""
34
- echo -e " ${BOLD}Usage:${RESET}"
35
- echo -e " eagle-mem tasks ${DIM}# list pending tasks${RESET}"
36
- echo -e " eagle-mem tasks ${CYAN}list${RESET} ${DIM}# list all tasks${RESET}"
37
- echo -e " eagle-mem tasks ${CYAN}add${RESET} <title> [instructions] ${DIM}# add a task${RESET}"
38
- echo -e " eagle-mem tasks ${CYAN}done${RESET} <id> ${DIM}# mark task complete${RESET}"
39
- echo -e " eagle-mem tasks ${CYAN}block${RESET} <id> ${DIM}# mark task blocked${RESET}"
40
- echo -e " eagle-mem tasks ${CYAN}context${RESET} <id> <snapshot> ${DIM}# set task context${RESET}"
41
- echo -e " eagle-mem tasks ${CYAN}clear${RESET} ${DIM}# remove all done tasks${RESET}"
42
- echo ""
43
- echo -e " ${BOLD}Options:${RESET}"
44
- echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
45
- echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
46
- echo ""
47
- exit 0
48
- ;;
51
+ --help|-h) show_help ;;
49
52
  *) args+=("$1"); shift ;;
50
53
  esac
51
54
  done
@@ -265,24 +268,7 @@ case "$action" in
265
268
  block) tasks_block ;;
266
269
  context) tasks_context ;;
267
270
  clear) tasks_clear ;;
268
- --help|-h)
269
- echo -e " ${BOLD}eagle-mem tasks${RESET} — Manage tracked tasks"
270
- echo ""
271
- echo -e " ${BOLD}Usage:${RESET}"
272
- echo -e " eagle-mem tasks ${DIM}# list pending tasks${RESET}"
273
- echo -e " eagle-mem tasks ${CYAN}list${RESET} ${DIM}# list all tasks${RESET}"
274
- echo -e " eagle-mem tasks ${CYAN}add${RESET} <title> [instructions] ${DIM}# add a task${RESET}"
275
- echo -e " eagle-mem tasks ${CYAN}done${RESET} <id> ${DIM}# mark task complete${RESET}"
276
- echo -e " eagle-mem tasks ${CYAN}block${RESET} <id> ${DIM}# mark task blocked${RESET}"
277
- echo -e " eagle-mem tasks ${CYAN}context${RESET} <id> <snapshot> ${DIM}# set task context${RESET}"
278
- echo -e " eagle-mem tasks ${CYAN}clear${RESET} ${DIM}# remove all done tasks${RESET}"
279
- echo ""
280
- echo -e " ${BOLD}Options:${RESET}"
281
- echo -e " ${CYAN}-p, --project${RESET} <name> Project name (default: current dir)"
282
- echo -e " ${CYAN}-j, --json${RESET} Output as JSON"
283
- echo ""
284
- exit 0
285
- ;;
271
+ --help|-h) show_help ;;
286
272
  *)
287
273
  eagle_err "Unknown action: $action"
288
274
  eagle_dim " Run 'eagle-mem tasks --help' for options"
@@ -36,15 +36,10 @@ fi
36
36
  # ─── Remove skill symlinks ────────────────────────────────
37
37
 
38
38
  if [ -d "$EAGLE_SKILLS_DIR" ]; then
39
- for skill in eagle-mem-search eagle-mem-tasks eagle-mem-overview; do
40
- target="$EAGLE_SKILLS_DIR/$skill"
41
- if [ -L "$target" ]; then
42
- rm "$target"
43
- eagle_ok "Skill removed: $skill"
44
- elif [ -d "$target" ]; then
45
- rm -rf "$target"
46
- eagle_ok "Skill removed: $skill"
47
- fi
39
+ for target in "$EAGLE_SKILLS_DIR"/eagle-mem-*; do
40
+ [ -L "$target" ] || [ -d "$target" ] || continue
41
+ rm -rf "$target"
42
+ eagle_ok "Skill removed: $(basename "$target")"
48
43
  done
49
44
  fi
50
45
 
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: eagle-mem-index
3
+ description: >
4
+ Index source files into FTS5-searchable chunks for code-level search. Use when: 'eagle index',
5
+ 'index this project', 'index codebase', 'eagle mem index', 'make code searchable',
6
+ 'update code index'. Uses the eagle-mem CLI.
7
+ ---
8
+
9
+ # Eagle Mem — Index
10
+
11
+ Chunk and index source files into Eagle Mem's FTS5 search index. Enables code-level search across sessions.
12
+
13
+ ## What it does
14
+
15
+ - Walks the project directory for source files
16
+ - Splits each file into chunks (default 80 lines)
17
+ - Stores chunks in the `code_chunks` table with FTS5 indexing
18
+ - Incremental: only re-indexes files modified since the last run (via mtime)
19
+
20
+ ## Commands
21
+
22
+ ### Index current project
23
+
24
+ ```bash
25
+ eagle-mem index .
26
+ ```
27
+
28
+ ### Index a specific directory
29
+
30
+ ```bash
31
+ eagle-mem index /path/to/project
32
+ ```
33
+
34
+ ## When to use
35
+
36
+ - After initial project setup to make code searchable
37
+ - After pulling significant changes
38
+ - When `/eagle-mem-search` isn't finding code you know exists
39
+
40
+ ## How it integrates
41
+
42
+ Once indexed, the UserPromptSubmit hook can surface relevant code chunks when you ask questions. The `/eagle-mem-search` skill also searches indexed code.
43
+
44
+ ## Notes
45
+
46
+ - Respects .gitignore — only indexes tracked/untracked source files
47
+ - Skips binary files and files over 1MB
48
+ - Chunk size is configurable via `EAGLE_MEM_CHUNK_SIZE` env var (default: 80 lines)
49
+ - Re-running is safe — unchanged files are skipped automatically
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: eagle-mem-memories
3
+ description: >
4
+ View and sync Claude Code auto-memories, plans, and tasks mirrored in Eagle Mem. Use when:
5
+ 'eagle memories', 'show memories', 'list memories', 'sync memories', 'eagle mem memories',
6
+ 'what does claude remember', 'show plans', 'show tasks'. Uses the eagle-mem CLI.
7
+ ---
8
+
9
+ # Eagle Mem — Memories
10
+
11
+ View, search, and sync Claude Code's auto-memories, plans, and tasks that are mirrored into Eagle Mem's SQLite + FTS5 database.
12
+
13
+ ## Commands
14
+
15
+ ### List mirrored memories
16
+
17
+ ```bash
18
+ eagle-mem memories
19
+ eagle-mem memories list
20
+ ```
21
+
22
+ ### Search memories
23
+
24
+ ```bash
25
+ eagle-mem memories search "auth middleware"
26
+ ```
27
+
28
+ ### Show a specific memory
29
+
30
+ ```bash
31
+ eagle-mem memories show <name>
32
+ ```
33
+
34
+ ### List mirrored plans
35
+
36
+ ```bash
37
+ eagle-mem memories plans
38
+ ```
39
+
40
+ ### List mirrored tasks
41
+
42
+ ```bash
43
+ eagle-mem memories tasks
44
+ ```
45
+
46
+ ### Sync all from Claude Code
47
+
48
+ Backfill all memories, plans, and tasks from Claude Code's filesystem into Eagle Mem:
49
+
50
+ ```bash
51
+ eagle-mem memories sync
52
+ ```
53
+
54
+ ## Options
55
+
56
+ | Flag | Description |
57
+ |------|-------------|
58
+ | `-p, --project <name>` | Target a specific project (default: current directory) |
59
+ | `-n, --limit <N>` | Max results (default: 20) |
60
+ | `-j, --json` | Output as JSON |
61
+
62
+ ## How it works
63
+
64
+ Eagle Mem automatically mirrors Claude Code writes via the PostToolUse hook:
65
+ - **Memories** from `~/.claude/projects/*/memory/*.md`
66
+ - **Plans** from `~/.claude/plans/*.md`
67
+ - **Tasks** from Claude Code's TaskCreate/TaskUpdate calls
68
+
69
+ The `sync` command does a full backfill for anything the hooks missed.
70
+
71
+ ## When to use
72
+
73
+ - To check what Claude Code has auto-saved about this project
74
+ - To search across memories from multiple projects
75
+ - After installing Eagle Mem on an existing project (run `sync` to backfill)
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: eagle-mem-prune
3
+ description: >
4
+ Clean up old observations and orphaned data to keep Eagle Mem's database lean. Use when:
5
+ 'eagle prune', 'clean up memory', 'prune database', 'eagle mem prune', 'database too large',
6
+ 'remove old data'. Uses the eagle-mem CLI.
7
+ ---
8
+
9
+ # Eagle Mem — Prune
10
+
11
+ Remove old observations, orphaned code chunks, and stale data from Eagle Mem's database.
12
+
13
+ ## Commands
14
+
15
+ ### Prune with defaults (90 days)
16
+
17
+ ```bash
18
+ eagle-mem prune
19
+ ```
20
+
21
+ ### Prune older than N days
22
+
23
+ ```bash
24
+ eagle-mem prune --days 30
25
+ ```
26
+
27
+ ### Dry run (see what would be removed)
28
+
29
+ ```bash
30
+ eagle-mem prune --dry-run
31
+ ```
32
+
33
+ ### Prune a specific project
34
+
35
+ ```bash
36
+ eagle-mem prune --project my-app
37
+ ```
38
+
39
+ ## Options
40
+
41
+ | Flag | Description |
42
+ |------|-------------|
43
+ | `-d, --days <N>` | Remove observations older than N days (default: 90) |
44
+ | `-p, --project <name>` | Target a specific project |
45
+ | `-n, --dry-run` | Show what would be pruned without deleting |
46
+
47
+ ## What gets pruned
48
+
49
+ - **Old observations** — tool-use records older than the threshold
50
+ - **Orphaned code chunks** — chunks for files that no longer exist
51
+
52
+ ## What is preserved
53
+
54
+ - Session records (never deleted)
55
+ - All summaries
56
+ - Claude Code mirrored memories and plans
57
+ - Task records
58
+
59
+ ## When to use
60
+
61
+ - Database feels slow or is growing large
62
+ - After removing a project or major refactor
63
+ - Periodic maintenance (monthly is fine for most users)
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: eagle-mem-scan
3
+ description: >
4
+ Scan and analyze a project's codebase to generate a persistent overview. Use when: 'eagle scan',
5
+ 'scan this project', 'analyze codebase', 'eagle mem scan', 'generate overview from code',
6
+ 'what does this project look like'. Uses the eagle-mem CLI.
7
+ ---
8
+
9
+ # Eagle Mem — Scan
10
+
11
+ Analyze the current project's codebase and generate a concise overview that Eagle Mem injects at the start of every session.
12
+
13
+ ## What it does
14
+
15
+ Scans the project directory to collect:
16
+ - File count, directory structure, languages used
17
+ - Entry points, config files, test presence
18
+ - Framework/library detection from package.json, go.mod, etc.
19
+
20
+ Then saves a compact overview to the database.
21
+
22
+ ## Commands
23
+
24
+ ### Scan current project
25
+
26
+ ```bash
27
+ eagle-mem scan .
28
+ ```
29
+
30
+ ### Scan a specific directory
31
+
32
+ ```bash
33
+ eagle-mem scan /path/to/project
34
+ ```
35
+
36
+ ## When to use
37
+
38
+ - First time opening a project with Eagle Mem
39
+ - After major restructuring (new packages, renamed dirs)
40
+ - When the current overview feels stale or wrong
41
+
42
+ The overview is automatically injected by the SessionStart hook, so every fresh session starts with project context.
43
+
44
+ ## Notes
45
+
46
+ - Scans respect .gitignore — only tracked/untracked files are analyzed
47
+ - The overview is stored once per project and updated in place
48
+ - Keep the generated overview factual — it supplements session summaries