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 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=$(node -e "console.log(require('$PACKAGE_DIR/package.json').version)" 2>/dev/null || echo "unknown")
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
- { 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"
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
- # ─── Migration 002: Project overviews ──────────────────────
47
- run_migration "002_overviews" "$SCRIPT_DIR/002_overviews.sql"
48
-
49
- # ─── Migration 003: Code chunks ───────────────────────────
50
- run_migration "003_code_chunks" "$SCRIPT_DIR/003_code_chunks.sql"
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;
@@ -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
@@ -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 > datetime('now', '-7 days')
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 OR REPLACE INTO summaries (session_id, project, request, investigated, learned, completed, next_steps, files_read, files_modified, notes)
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=$(eagle_sql_escape "$1")
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=$(eagle_sql_escape "$1")
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "1.6.0",
3
+ "version": "1.6.2",
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
@@ -8,7 +8,7 @@ PACKAGE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
8
8
 
9
9
  . "$SCRIPTS_DIR/style.sh"
10
10
 
11
- version=$(node -e "console.log(require('$PACKAGE_DIR/package.json').version)" 2>/dev/null || echo "unknown")
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
@@ -104,7 +104,6 @@ fi
104
104
  project_sql=$(eagle_sql_escape "$PROJECT")
105
105
  NEEDS_INDEX="$TMPDIR_IDX/needs_index"
106
106
 
107
- indexed_count=0
108
107
  skipped_count=0
109
108
 
110
109
  while IFS= read -r file; do
@@ -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
- patch_hook() {
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
- patch_hook "Stop" "" \
168
+ eagle_patch_hook "$SETTINGS" "Stop" "" \
192
169
  "$EAGLE_MEM_DIR/hooks/stop.sh" \
193
170
  "Stop hook"
194
171
 
195
- patch_hook "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" \
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
- patch_hook "SessionEnd" "" \
176
+ eagle_patch_hook "$SETTINGS" "SessionEnd" "" \
200
177
  "$EAGLE_MEM_DIR/hooks/session-end.sh" \
201
178
  "SessionEnd hook"
202
179
 
203
- patch_hook "UserPromptSubmit" "" \
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
- patch_hook "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
89
- patch_hook "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
90
- patch_hook "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
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=$(node -e "console.log(require('$PACKAGE_DIR/package.json').version)" 2>/dev/null || echo "unknown")
101
+ version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
121
102
  eagle_footer "Eagle Mem updated to v${version}."