eagle-mem 2.0.7 → 3.0.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.
@@ -0,0 +1,365 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Curator
4
+ # Self-learning engine that analyzes sessions and generates:
5
+ # 1. Promoted gotchas → persistent memories
6
+ # 2. Superseded decision detection
7
+ # 3. Command compression rules
8
+ # 4. Feature auto-discovery
9
+ # ═══════════════════════════════════════════════════════════
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ LIB_DIR="$SCRIPT_DIR/../lib"
14
+
15
+ . "$LIB_DIR/common.sh"
16
+ . "$SCRIPT_DIR/style.sh"
17
+ . "$LIB_DIR/db.sh"
18
+ . "$LIB_DIR/provider.sh"
19
+
20
+ eagle_header "Curator"
21
+
22
+ DRY_RUN=0
23
+ FULL=0
24
+ project=""
25
+
26
+ while [ $# -gt 0 ]; do
27
+ case "$1" in
28
+ --dry-run) DRY_RUN=1; shift ;;
29
+ --full) FULL=1; shift ;;
30
+ -p|--project) project="$2"; shift 2 ;;
31
+ *) shift ;;
32
+ esac
33
+ done
34
+
35
+ if [ -z "$project" ]; then
36
+ project=$(eagle_project_from_cwd "$(pwd)")
37
+ fi
38
+
39
+ p_esc=$(eagle_sql_escape "$project")
40
+
41
+ # Verify provider is configured
42
+ provider=$(eagle_config_get "provider" "type" "none")
43
+ if [ "$provider" = "none" ]; then
44
+ eagle_err "No LLM provider configured. Run: eagle-mem config init"
45
+ exit 1
46
+ fi
47
+ eagle_info "Provider: $provider ($(eagle_config_get "$provider" "model" "unknown"))"
48
+ eagle_info "Project: $project"
49
+ [ "$DRY_RUN" -eq 1 ] && eagle_info "Dry run — no changes will be made"
50
+ echo ""
51
+
52
+ # ─── 1. Analyze gotchas for promotion ─────────────────────
53
+
54
+ eagle_info "Analyzing gotchas for promotion..."
55
+
56
+ recent_gotchas=$(eagle_db "SELECT gotchas, created_at
57
+ FROM summaries
58
+ WHERE project = '$p_esc'
59
+ AND gotchas IS NOT NULL AND gotchas != ''
60
+ ORDER BY created_at DESC
61
+ LIMIT 20;")
62
+
63
+ if [ -n "$recent_gotchas" ]; then
64
+ gotcha_prompt="Analyze these gotchas from recent development sessions on project '$project'. Identify any that appear multiple times or are important enough to become permanent project knowledge.
65
+
66
+ GOTCHAS:
67
+ $recent_gotchas
68
+
69
+ For each gotcha worth promoting, output EXACTLY this format (one per line):
70
+ PROMOTE: <one-line gotcha summary>
71
+
72
+ Only promote gotchas that are:
73
+ 1. Repeated across multiple sessions (same mistake happening again)
74
+ 2. Non-obvious and likely to be forgotten
75
+ 3. Specific enough to be actionable
76
+
77
+ If none qualify, output: NONE"
78
+
79
+ gotcha_result=$(eagle_llm_call "$gotcha_prompt" "You analyze software development patterns. Be concise. Only output PROMOTE lines or NONE." 512)
80
+
81
+ if [ -n "$gotcha_result" ] && ! echo "$gotcha_result" | grep -q "^NONE$"; then
82
+ promoted=0
83
+ while IFS= read -r line; do
84
+ case "$line" in
85
+ PROMOTE:*)
86
+ gotcha_text=$(echo "$line" | sed 's/^PROMOTE:[[:space:]]*//')
87
+ if [ "$DRY_RUN" -eq 1 ]; then
88
+ eagle_info " Would promote: $gotcha_text"
89
+ else
90
+ eagle_log "INFO" "Curator: promoting gotcha: $gotcha_text"
91
+ fi
92
+ promoted=$((promoted + 1))
93
+ ;;
94
+ esac
95
+ done <<< "$gotcha_result"
96
+ eagle_ok "Found $promoted gotchas to promote"
97
+ else
98
+ eagle_ok "No gotchas need promotion"
99
+ fi
100
+ else
101
+ eagle_dim " No gotchas to analyze"
102
+ fi
103
+
104
+ # ─── 2. Detect superseded decisions ───────────���───────────
105
+
106
+ eagle_info "Checking for superseded decisions..."
107
+
108
+ recent_decisions=$(eagle_db "SELECT decisions, key_files, created_at
109
+ FROM summaries
110
+ WHERE project = '$p_esc'
111
+ AND decisions IS NOT NULL AND decisions != ''
112
+ ORDER BY created_at DESC
113
+ LIMIT 20;")
114
+
115
+ if [ -n "$recent_decisions" ]; then
116
+ decision_prompt="Analyze these decisions from recent sessions on project '$project'. Identify any that CONTRADICT or SUPERSEDE earlier decisions (e.g., session 5 decided to use approach A, but session 8 switched to approach B).
117
+
118
+ DECISIONS (newest first):
119
+ $recent_decisions
120
+
121
+ For each superseded decision, output EXACTLY:
122
+ SUPERSEDED: <old decision> → <new decision> | file: <affected file if known>
123
+
124
+ If none are superseded, output: NONE"
125
+
126
+ decision_result=$(eagle_llm_call "$decision_prompt" "You detect contradicting software decisions. Be precise." 512)
127
+
128
+ if [ -n "$decision_result" ] && ! echo "$decision_result" | grep -q "^NONE$"; then
129
+ superseded=0
130
+ while IFS= read -r line; do
131
+ case "$line" in
132
+ SUPERSEDED:*)
133
+ if [ "$DRY_RUN" -eq 1 ]; then
134
+ eagle_info " $line"
135
+ else
136
+ eagle_log "INFO" "Curator: $line"
137
+ fi
138
+ superseded=$((superseded + 1))
139
+ ;;
140
+ esac
141
+ done <<< "$decision_result"
142
+ eagle_ok "Found $superseded superseded decisions"
143
+ else
144
+ eagle_ok "No superseded decisions found"
145
+ fi
146
+ else
147
+ eagle_dim " No decisions to analyze"
148
+ fi
149
+
150
+ # ─── 3. Generate command compression rules ────────────────
151
+
152
+ eagle_info "Analyzing command patterns..."
153
+
154
+ command_stats=$(eagle_db "SELECT command_category,
155
+ COUNT(*) as count,
156
+ AVG(output_bytes) as avg_bytes,
157
+ MAX(output_bytes) as max_bytes,
158
+ AVG(output_lines) as avg_lines
159
+ FROM observations
160
+ WHERE project = '$p_esc'
161
+ AND tool_name = 'Bash'
162
+ AND command_category IS NOT NULL
163
+ AND output_bytes IS NOT NULL
164
+ AND output_bytes > 0
165
+ GROUP BY command_category
166
+ HAVING count > 5
167
+ ORDER BY avg_bytes DESC
168
+ LIMIT 10;")
169
+
170
+ if [ -n "$command_stats" ]; then
171
+ # Get specific noisy commands
172
+ noisy_commands=$(eagle_db "SELECT tool_input_summary,
173
+ COUNT(*) as count,
174
+ CAST(AVG(output_bytes) AS INTEGER) as avg_bytes,
175
+ CAST(AVG(output_lines) AS INTEGER) as avg_lines
176
+ FROM observations
177
+ WHERE project = '$p_esc'
178
+ AND tool_name = 'Bash'
179
+ AND output_bytes > 1000
180
+ GROUP BY tool_input_summary
181
+ HAVING count >= 3
182
+ ORDER BY avg_bytes DESC
183
+ LIMIT 15;")
184
+
185
+ if [ -n "$noisy_commands" ]; then
186
+ cmd_prompt="Analyze these frequently-run commands and their output sizes for project '$project'. Suggest compression rules for commands that produce consistently large output.
187
+
188
+ COMMAND STATS (command | times_run | avg_output_bytes | avg_output_lines):
189
+ $noisy_commands
190
+
191
+ For each command that deserves a compression rule, output EXACTLY:
192
+ RULE: <pattern> | <strategy: summary or truncate> | <max_lines or -> | <reason>
193
+
194
+ Where:
195
+ - pattern: the base command to match (e.g., 'npm', 'git log', 'pnpm test')
196
+ - strategy: 'summary' (only show result) or 'truncate' (keep first N lines)
197
+ - max_lines: for truncate strategy, how many lines to keep; '-' for summary
198
+ - reason: one-line explanation
199
+
200
+ If no rules needed, output: NONE"
201
+
202
+ cmd_result=$(eagle_llm_call "$cmd_prompt" "You optimize CLI output for AI assistants. Be conservative — only suggest rules for genuinely noisy commands." 512)
203
+
204
+ if [ -n "$cmd_result" ] && ! echo "$cmd_result" | grep -q "^NONE$"; then
205
+ rules_count=0
206
+ while IFS= read -r line; do
207
+ case "$line" in
208
+ RULE:*)
209
+ rule_data=$(echo "$line" | sed 's/^RULE:[[:space:]]*//')
210
+ IFS='|' read -r pattern strategy max_lines reason <<< "$rule_data"
211
+ pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
212
+ strategy=$(echo "$strategy" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
213
+ max_lines=$(echo "$max_lines" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
214
+ reason=$(echo "$reason" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
215
+
216
+ # Guard: skip malformed lines missing required fields
217
+ if [ -z "$pattern" ] || [ -z "$strategy" ]; then
218
+ eagle_log "WARN" "Curator: skipping malformed RULE line: $line"
219
+ continue
220
+ fi
221
+ case "$strategy" in summary|truncate) ;; *)
222
+ eagle_log "WARN" "Curator: skipping RULE with invalid strategy '$strategy'"
223
+ continue
224
+ ;; esac
225
+
226
+ [ "$max_lines" = "-" ] && max_lines=""
227
+
228
+ if [ "$DRY_RUN" -eq 1 ]; then
229
+ eagle_info " Rule: $pattern → $strategy ($reason)"
230
+ else
231
+ pattern_esc=$(eagle_sql_escape "$pattern")
232
+ reason_esc=$(eagle_sql_escape "$reason")
233
+ ml_val="${max_lines:-NULL}"
234
+ [ "$ml_val" != "NULL" ] && ml_val=$(eagle_sql_int "$ml_val")
235
+
236
+ eagle_db "INSERT INTO command_rules (project, pattern, strategy, max_lines, reason)
237
+ VALUES ('$p_esc', '$pattern_esc', '$strategy', $ml_val, '$reason_esc')
238
+ ON CONFLICT(project, pattern) DO UPDATE SET
239
+ strategy = excluded.strategy,
240
+ max_lines = excluded.max_lines,
241
+ reason = excluded.reason,
242
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
243
+ eagle_log "INFO" "Curator: added command rule: $pattern → $strategy"
244
+ fi
245
+ rules_count=$((rules_count + 1))
246
+ ;;
247
+ esac
248
+ done <<< "$cmd_result"
249
+ eagle_ok "$rules_count command rules generated"
250
+ else
251
+ eagle_ok "No command rules needed"
252
+ fi
253
+ else
254
+ eagle_dim " Not enough command data yet"
255
+ fi
256
+ else
257
+ eagle_dim " No command metrics collected yet (need more sessions)"
258
+ fi
259
+
260
+ # ─── 4. Feature auto-discovery ────────────────────────────
261
+
262
+ eagle_info "Discovering features from session data..."
263
+
264
+ feature_data=$(eagle_db "SELECT s.request, s.key_files, s.decisions, s.completed
265
+ FROM summaries s
266
+ WHERE s.project = '$p_esc'
267
+ AND (s.key_files IS NOT NULL AND s.key_files != ''
268
+ OR s.decisions IS NOT NULL AND s.decisions != '')
269
+ ORDER BY s.created_at DESC
270
+ LIMIT 15;")
271
+
272
+ existing_features=$(eagle_db "SELECT name FROM features WHERE project = '$p_esc' AND status = 'active';")
273
+
274
+ if [ -n "$feature_data" ]; then
275
+ feature_prompt="Analyze these session summaries from project '$project' and identify distinct FEATURES (user-facing capabilities, not implementation details).
276
+
277
+ SESSION DATA (request | key_files | decisions | completed):
278
+ $feature_data
279
+
280
+ EXISTING FEATURES (already tracked):
281
+ ${existing_features:-none}
282
+
283
+ For each NEW feature discovered (not already in existing list), output EXACTLY:
284
+ FEATURE: <name> | <one-line description> | <comma-separated key files>
285
+
286
+ Rules:
287
+ - Feature names should be short, kebab-case (e.g., 'title-generation', 'auth-middleware')
288
+ - Only discover features with clear file evidence (at least 2 related files)
289
+ - Don't re-discover existing features
290
+ - If no new features found, output: NONE"
291
+
292
+ feature_result=$(eagle_llm_call "$feature_prompt" "You identify software features from development session data. Be specific and evidence-based." 512)
293
+
294
+ if [ -n "$feature_result" ] && ! echo "$feature_result" | grep -q "^NONE$"; then
295
+ features_count=0
296
+ while IFS= read -r line; do
297
+ case "$line" in
298
+ FEATURE:*)
299
+ feat_data=$(echo "$line" | sed 's/^FEATURE:[[:space:]]*//')
300
+ IFS='|' read -r fname fdesc ffiles <<< "$feat_data"
301
+ fname=$(echo "$fname" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
302
+ fdesc=$(echo "$fdesc" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
303
+ ffiles=$(echo "$ffiles" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
304
+
305
+ # Guard: skip malformed lines missing required name
306
+ if [ -z "$fname" ]; then
307
+ eagle_log "WARN" "Curator: skipping malformed FEATURE line: $line"
308
+ continue
309
+ fi
310
+
311
+ if [ "$DRY_RUN" -eq 1 ]; then
312
+ eagle_info " Feature: $fname — $fdesc"
313
+ eagle_info " Files: $ffiles"
314
+ else
315
+ eagle_upsert_feature "$project" "$fname" "$fdesc"
316
+ fid=$(eagle_get_feature_id "$project" "$fname")
317
+ if [ -n "$fid" ] && [ -n "$ffiles" ]; then
318
+ IFS=',' read -ra file_arr <<< "$ffiles"
319
+ for f in "${file_arr[@]}"; do
320
+ f=$(echo "$f" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
321
+ [ -n "$f" ] && eagle_add_feature_file "$fid" "$f" ""
322
+ done
323
+ fi
324
+ eagle_log "INFO" "Curator: discovered feature: $fname"
325
+ fi
326
+ features_count=$((features_count + 1))
327
+ ;;
328
+ esac
329
+ done <<< "$feature_result"
330
+ eagle_ok "$features_count features discovered"
331
+ else
332
+ eagle_ok "No new features discovered"
333
+ fi
334
+ else
335
+ eagle_dim " Not enough session data for feature discovery"
336
+ fi
337
+
338
+ # ─── 5. Session compression (--full only) ─────────────────
339
+
340
+ if [ "$FULL" -eq 1 ]; then
341
+ eagle_info "Compressing old sessions..."
342
+
343
+ old_count=$(eagle_db "SELECT COUNT(*) FROM summaries
344
+ WHERE project = '$p_esc'
345
+ AND created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-30 days');")
346
+
347
+ if [ "${old_count:-0}" -gt 10 ]; then
348
+ if [ "$DRY_RUN" -eq 1 ]; then
349
+ eagle_info " Would analyze $old_count old sessions for compression"
350
+ else
351
+ eagle_info " $old_count sessions older than 30 days (compression not yet implemented)"
352
+ fi
353
+ else
354
+ eagle_dim " Not enough old sessions to compress"
355
+ fi
356
+ fi
357
+
358
+ # ──��� Summary ──────────────────────────────────────────────
359
+
360
+ echo ""
361
+ if [ "$DRY_RUN" -eq 1 ]; then
362
+ eagle_footer "Dry run complete. Run without --dry-run to apply changes."
363
+ else
364
+ eagle_footer "Curation complete for '$project'."
365
+ fi
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Feature management
4
+ # eagle-mem feature [list|show|verify|add]
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+ LIB_DIR="$SCRIPT_DIR/../lib"
10
+
11
+ . "$LIB_DIR/common.sh"
12
+ . "$SCRIPT_DIR/style.sh"
13
+ . "$LIB_DIR/db.sh"
14
+
15
+ eagle_ensure_db
16
+ eagle_header "Features"
17
+
18
+ project=$(eagle_project_from_cwd "$(pwd)")
19
+ subcommand="${1:-list}"
20
+ shift 2>/dev/null || true
21
+
22
+ case "$subcommand" in
23
+ list|ls)
24
+ results=$(eagle_list_features "$project")
25
+ if [ -z "$results" ]; then
26
+ eagle_dim "No features tracked for '$project'"
27
+ eagle_dim "Run 'eagle-mem curate' to auto-discover features"
28
+ exit 0
29
+ fi
30
+
31
+ while IFS='|' read -r name desc status verified dep_count file_count test_count; do
32
+ [ -z "$name" ] && continue
33
+ verified_label=""
34
+ if [ -n "$verified" ]; then
35
+ verified_label=" ${DIM}(verified: ${verified})${RESET}"
36
+ else
37
+ verified_label=" ${DIM}(never verified)${RESET}"
38
+ fi
39
+ echo -e " ${CYAN}${name}${RESET}${verified_label}"
40
+ [ -n "$desc" ] && echo -e " ${desc}"
41
+ echo -e " ${DIM}${file_count} files, ${dep_count} deps, ${test_count} smoke tests${RESET}"
42
+ done <<< "$results"
43
+ ;;
44
+
45
+ show)
46
+ name="${1:-}"
47
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature show <name>"; exit 1; }
48
+ eagle_show_feature "$project" "$name"
49
+ ;;
50
+
51
+ verify)
52
+ name="${1:-}"
53
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature verify <name> [--notes <text>]"; exit 1; }
54
+ shift
55
+ notes=""
56
+ while [ $# -gt 0 ]; do
57
+ case "$1" in
58
+ --notes) notes="$2"; shift 2 ;;
59
+ *) notes="$1"; shift ;;
60
+ esac
61
+ done
62
+ eagle_verify_feature "$project" "$name" "$notes"
63
+ eagle_ok "Feature '$name' marked as verified"
64
+ ;;
65
+
66
+ add)
67
+ name="${1:-}"
68
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature add <name> [--desc <text>] [--file <path>] [--requires <target:name>] [--smoke <command>]"; exit 1; }
69
+ shift
70
+ desc=""
71
+ files=()
72
+ deps=()
73
+ smokes=()
74
+
75
+ while [ $# -gt 0 ]; do
76
+ case "$1" in
77
+ --desc|-d) desc="$2"; shift 2 ;;
78
+ --file|-f) files+=("$2"); shift 2 ;;
79
+ --requires|-r) deps+=("$2"); shift 2 ;;
80
+ --smoke|-s) smokes+=("$2"); shift 2 ;;
81
+ *) shift ;;
82
+ esac
83
+ done
84
+
85
+ eagle_upsert_feature "$project" "$name" "$desc"
86
+ fid=$(eagle_get_feature_id "$project" "$name")
87
+
88
+ for f in "${files[@]+"${files[@]}"}"; do
89
+ eagle_add_feature_file "$fid" "$f" ""
90
+ done
91
+
92
+ for d in "${deps[@]+"${deps[@]}"}"; do
93
+ target="${d%%:*}"
94
+ dep_name="${d#*:}"
95
+ eagle_add_feature_dependency "$fid" "env_var" "$target" "$dep_name" ""
96
+ done
97
+
98
+ for s in "${smokes[@]+"${smokes[@]}"}"; do
99
+ eagle_add_feature_smoke_test "$fid" "$s" ""
100
+ done
101
+
102
+ eagle_ok "Feature '$name' created"
103
+ ;;
104
+
105
+ *)
106
+ eagle_err "Unknown feature command: $subcommand"
107
+ eagle_info "Usage: eagle-mem feature [list|show|verify|add]"
108
+ exit 1
109
+ ;;
110
+ esac
package/scripts/help.sh CHANGED
@@ -24,6 +24,9 @@ echo -e " ${CYAN}memories${RESET} View/sync mirrored Claude Code memories"
24
24
  echo -e " ${CYAN}tasks${RESET} View mirrored Claude Code tasks"
25
25
  echo -e " ${CYAN}refresh${RESET} Full project sync: scan (if needed) + index + memories"
26
26
  echo -e " ${CYAN}prune${RESET} Remove old observations and orphaned chunks"
27
+ echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
28
+ echo -e " ${CYAN}curate${RESET} Run the self-learning curator (LLM-powered analysis)"
29
+ echo -e " ${CYAN}feature${RESET} Manage feature graph (list/show/verify/add)"
27
30
  echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
28
31
  echo -e " ${CYAN}update${RESET} Re-deploy hooks and run migrations"
29
32
  echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
package/scripts/index.sh CHANGED
@@ -162,7 +162,7 @@ DELETE FROM code_chunks WHERE project = '$project_sql' AND file_path = '$file_sq
162
162
  end=$((start + CHUNK_SIZE - 1))
163
163
  [ "$end" -gt "$total_lines" ] && end="$total_lines"
164
164
 
165
- content=$(sed -n "${start},${end}p" "$full_path")
165
+ content=$(sed -n "${start},${end}p" "$full_path" | eagle_redact)
166
166
  content_sql=$(eagle_sql_escape "$content")
167
167
 
168
168
  txn_sql+="
@@ -152,7 +152,10 @@ eagle_ok "Files copied to $EAGLE_MEM_DIR"
152
152
 
153
153
  # ─── Run migrations ────────────────────────────────────────
154
154
 
155
- "$EAGLE_MEM_DIR/db/migrate.sh" 2>/dev/null | grep -v -E '^(wal|5000|Eagle Mem database)$' > /dev/null
155
+ if ! "$EAGLE_MEM_DIR/db/migrate.sh" 2>/dev/null; then
156
+ eagle_err "Database migration failed"
157
+ exit 1
158
+ fi
156
159
  eagle_ok "Database ready"
157
160
 
158
161
  # ─── Patch settings.json ───────────────────────────────────
@@ -181,6 +184,10 @@ eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" \
181
184
  "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh" \
182
185
  "UserPromptSubmit hook"
183
186
 
187
+ eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" \
188
+ "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
189
+ "PreToolUse hook"
190
+
184
191
  # ─── Install skills ────────────────────────────────────────
185
192
 
186
193
  if [ -d "$PACKAGE_DIR/skills" ]; then
@@ -238,10 +245,20 @@ else
238
245
  echo ""
239
246
  eagle_ok "Statusline ${DIM}(manual patch needed — instructions above)${RESET}"
240
247
  else
241
- eagle_ok "Statusline ${DIM}(already has Eagle Mem)${RESET}"
248
+ eagle_ok "Statusline ${DIM}(existing cannot auto-patch; add Eagle Mem manually)${RESET}"
242
249
  fi
243
250
  fi
244
251
 
252
+ # ─── Initialize config ────────────────────────────────────
253
+
254
+ . "$LIB_DIR/provider.sh"
255
+ if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
256
+ eagle_config_init
257
+ eagle_ok "Config created ${DIM}(auto-detected provider)${RESET}"
258
+ else
259
+ eagle_ok "Config ${DIM}(already exists)${RESET}"
260
+ fi
261
+
245
262
  # ─── Save installed version ───────────────────────────────
246
263
 
247
264
  version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
@@ -18,7 +18,7 @@ eagle_header "Uninstall"
18
18
  # ─── Remove hooks from settings.json ──────────────────────
19
19
 
20
20
  if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
21
- for event in SessionStart Stop PostToolUse SessionEnd UserPromptSubmit; do
21
+ for event in SessionStart Stop PostToolUse PreToolUse SessionEnd UserPromptSubmit; do
22
22
  if jq -e ".hooks.${event}" "$SETTINGS" &>/dev/null; then
23
23
  tmp=$(mktemp)
24
24
  jq ".hooks.${event} = [.hooks.${event}[]? | select(any(.hooks[]?; .command | contains(\"eagle-mem\")) | not)]" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
package/scripts/update.sh CHANGED
@@ -46,7 +46,11 @@ eagle_ok "Files updated"
46
46
 
47
47
  # ─── Run pending migrations ────────────────────────────────
48
48
 
49
- migration_output=$("$EAGLE_MEM_DIR/db/migrate.sh" 2>/dev/null | grep -v -E '^(wal|5000)$')
49
+ migration_output=$("$EAGLE_MEM_DIR/db/migrate.sh" 2>&1) || {
50
+ eagle_err "Database migration failed"
51
+ eagle_err "$migration_output"
52
+ exit 1
53
+ }
50
54
  if echo "$migration_output" | grep -q "applied:"; then
51
55
  echo "$migration_output" | grep "applied:" | while read -r line; do
52
56
  eagle_ok "Migration: ${line#*applied: }"
@@ -69,6 +73,7 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
69
73
  eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
70
74
  eagle_patch_hook "$SETTINGS" "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
71
75
  eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
76
+ eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
72
77
 
73
78
  eagle_ok "Hooks registered"
74
79
  fi