eagle-mem 4.8.6 → 4.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -67,7 +67,7 @@ eagle-mem install
67
67
 
68
68
  That's it. Open Claude Code or Codex in any project directory. Eagle Mem activates automatically.
69
69
 
70
- Everything is automatic from here. Eagle Mem scans your codebase, indexes source files, captures session summaries, mirrors Claude's memories and tasks, learns which commands are noisy, and prunes stale data — all in the background via hooks.
70
+ Everything is automatic from here. Eagle Mem scans your codebase, indexes source files, captures session summaries, mirrors Claude's memories and tasks, learns which commands are noisy, prunes stale data, and installs patch bug fixes — all in the background via hooks.
71
71
 
72
72
  For Codex, the installer enables `codex_hooks` in `~/.codex/config.toml`, registers hooks in `~/.codex/hooks.json`, symlinks Eagle Mem skills into `~/.codex/skills`, and patches `~/.codex/AGENTS.md` with the Eagle Mem summary contract. For Claude Code, it keeps using `~/.claude/settings.json`, `CLAUDE.md`, `~/.claude/skills`, and the existing Claude memory/task locations.
73
73
 
@@ -101,6 +101,7 @@ These run automatically via SessionStart — no commands needed:
101
101
  - **Auto-index** — new or stale project triggers FTS5 source indexing
102
102
  - **Auto-prune** — observations over 10K rows trigger cleanup
103
103
  - **Auto-curate** — the self-learning curator analyzes observation data and generates command rules, co-edit patterns, hot file detection, and guardrails (partially requires LLM provider)
104
+ - **Auto-update** — patch releases install automatically by default, then hooks, skills, migrations, and runtime files refresh through `eagle-mem update`
104
105
 
105
106
  ### Token Savings
106
107
 
@@ -138,6 +139,7 @@ Eagle Mem prevents Claude from repeating past mistakes:
138
139
  | `eagle-mem search` | Search past sessions, memories, and code |
139
140
  | `eagle-mem health` | Diagnose pipeline health and background automation |
140
141
  | `eagle-mem config` | View or change LLM provider and token-guard settings |
142
+ | `eagle-mem updates` | View or change auto-update policy |
141
143
  | `eagle-mem guard` | Manage regression guardrails for files |
142
144
  | `eagle-mem overview` | Build or view project overview |
143
145
  | `eagle-mem session` | Save a manual fallback session summary |
@@ -150,6 +152,10 @@ Eagle Mem prevents Claude from repeating past mistakes:
150
152
  | `eagle-mem scan` | Scan codebase and generate overview |
151
153
  | `eagle-mem index` | Index source files for FTS5 code search |
152
154
 
155
+ ### v4.9.0 Patch
156
+
157
+ Eagle Mem now auto-updates by default for patch bug fixes. SessionStart performs a throttled background npm check, applies eligible patch releases with a lock and runtime/database backup, runs `eagle-mem update`, and records a one-time notice for the next session. Minor and major releases stay outside the default auto-apply range unless users opt in with `eagle-mem updates enable minor` or `eagle-mem updates enable major`.
158
+
153
159
  ### v4.8.6 Patch
154
160
 
155
161
  `eagle-mem session save --summary "..."` now exists as a clean manual fallback for agents that need to persist an explicit session note. It writes through the same `sessions` and `summaries` tables used by Stop hooks, keeps Claude Code/Codex source attribution, and is immediately searchable through normal recall.
@@ -186,6 +192,7 @@ eagle-mem search --files # most frequently modified files
186
192
  eagle-mem search --stats # project statistics
187
193
  eagle-mem search --session <id> # full observation trail for one session
188
194
  eagle-mem session save --summary "fixed auth flow" # manual fallback capture
195
+ eagle-mem updates status # auto-update state and policy
189
196
  ```
190
197
 
191
198
  ### Feature Verification
package/bin/eagle-mem CHANGED
@@ -22,6 +22,7 @@ case "$command" in
22
22
  search) bash "$SCRIPTS_DIR/search.sh" "$@" ;;
23
23
  health) bash "$SCRIPTS_DIR/health.sh" "$@" ;;
24
24
  config) bash "$SCRIPTS_DIR/config.sh" "$@" ;;
25
+ updates) bash "$SCRIPTS_DIR/updates.sh" "$@" ;;
25
26
  guard) bash "$SCRIPTS_DIR/guard.sh" "$@" ;;
26
27
  overview) bash "$SCRIPTS_DIR/overview.sh" "$@" ;;
27
28
  session|sessions)
@@ -14,6 +14,7 @@ SCRIPTS_DIR="$SCRIPT_DIR/../scripts"
14
14
  . "$LIB_DIR/common.sh"
15
15
  . "$LIB_DIR/db.sh"
16
16
  . "$LIB_DIR/provider.sh"
17
+ . "$LIB_DIR/updater.sh"
17
18
  . "$LIB_DIR/hooks-sessionstart.sh"
18
19
 
19
20
  eagle_ensure_db
@@ -59,39 +60,16 @@ esac
59
60
  eagle_sessionstart_auto_provision "$project" "$cwd" "$SCRIPTS_DIR"
60
61
  eagle_sessionstart_auto_prune "$project" "$SCRIPTS_DIR" "$(eagle_db "SELECT COUNT(*) FROM observations WHERE session_id IN (SELECT id FROM sessions WHERE project='$p_esc');")"
61
62
  eagle_sessionstart_auto_curate "$project" "$SCRIPTS_DIR"
63
+ nohup bash "$SCRIPTS_DIR/updates.sh" auto >> "$EAGLE_MEM_LOG" 2>&1 &
62
64
 
63
65
  find "$EAGLE_MEM_DIR/read-tracker" -type f -mtime +1 -delete 2>/dev/null &
64
66
  find "$EAGLE_MEM_DIR/mod-tracker" -type f -mtime +1 -delete 2>/dev/null &
65
67
  find "$EAGLE_MEM_DIR/edit-tracker" -type f -mtime +1 -delete 2>/dev/null &
66
68
  find "$EAGLE_MEM_DIR" -name ".turn-counter.*" -mtime +1 -delete 2>/dev/null &
67
69
 
68
- # ─── Version check (non-blocking) ────────────────────────
70
+ # ─── Update notice from previous background run ────────────
69
71
 
70
- update_notice=""
71
- version_file="$EAGLE_MEM_DIR/.version"
72
- latest_file="$EAGLE_MEM_DIR/.latest-version"
73
-
74
- if [ -f "$version_file" ] && [ -s "$version_file" ]; then
75
- installed_version=$(tr -d '[:space:]' < "$version_file")
76
-
77
- if [ -f "$latest_file" ] && [ -s "$latest_file" ]; then
78
- latest_version=$(tr -d '[:space:]' < "$latest_file")
79
- newest=$(printf '%s\n' "$installed_version" "$latest_version" | sort -V | tail -1)
80
- if [ "$newest" != "$installed_version" ]; then
81
- update_notice="Update available: v${installed_version} → v${latest_version} — run: npm update -g eagle-mem && eagle-mem update"
82
- fi
83
- fi
84
-
85
- if [ ! -f "$latest_file" ] || [ -n "$(find "$latest_file" -mtime +0 2>/dev/null)" ]; then
86
- (tmp_latest=$(mktemp)
87
- npm view eagle-mem version 2>/dev/null | tr -d '[:space:]' > "$tmp_latest"
88
- if [ -s "$tmp_latest" ]; then
89
- mv "$tmp_latest" "$latest_file"
90
- else
91
- rm -f "$tmp_latest"
92
- fi) &
93
- fi
94
- fi
72
+ update_notice=$(eagle_update_take_notice)
95
73
 
96
74
  # ─── Gather stats ────────────────────────────────────────
97
75
 
package/lib/provider.sh CHANGED
@@ -189,6 +189,16 @@ model = "gpt-4o-mini"
189
189
  schedule = "auto"
190
190
  min_sessions = 5
191
191
 
192
+ [updates]
193
+ # Eagle Mem is infrastructure: patch fixes auto-apply by default so stale bugs
194
+ # do not keep blocking Claude Code or Codex sessions.
195
+ # mode: "auto" applies eligible updates, "notify" only reports them, "off" disables checks.
196
+ mode = "auto"
197
+ # allow: "patch" auto-applies x.y.Z fixes only; "minor" allows x.Y.z; "major" allows all.
198
+ allow = "patch"
199
+ channel = "latest"
200
+ interval_hours = 24
201
+
192
202
  [token_guard]
193
203
  # rtk: "off" disables RTK help, "auto" uses RTK when found,
194
204
  # "enforce" blocks known raw-output shell commands when RTK is unavailable.
@@ -565,6 +575,13 @@ eagle_show_config() {
565
575
  echo " Codex: $(eagle_config_get "orchestration" "codex_worker_model" "gpt-5.5") / $(eagle_config_get "orchestration" "codex_worker_effort" "xhigh")"
566
576
  echo " Claude: $(eagle_config_get "orchestration" "claude_worker_model" "claude-opus-4-7") / $(eagle_config_get "orchestration" "claude_worker_effort" "xhigh")"
567
577
 
578
+ echo ""
579
+ echo "Updates:"
580
+ echo " Mode: $(eagle_config_get "updates" "mode" "auto")"
581
+ echo " Allow: $(eagle_config_get "updates" "allow" "patch")"
582
+ echo " Channel: $(eagle_config_get "updates" "channel" "latest")"
583
+ echo " Interval: $(eagle_config_get "updates" "interval_hours" "24")h"
584
+
568
585
  echo ""
569
586
  echo "Token guard:"
570
587
  echo " RTK mode: $(eagle_config_get "token_guard" "rtk" "auto")"
package/lib/updater.sh ADDED
@@ -0,0 +1,320 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Auto-update helpers
4
+ # ═══════════════════════════════════════════════════════════
5
+ [ -n "${_EAGLE_UPDATER_LOADED:-}" ] && return 0
6
+ _EAGLE_UPDATER_LOADED=1
7
+
8
+ EAGLE_UPDATE_LATEST_FILE="$EAGLE_MEM_DIR/.latest-version"
9
+ EAGLE_UPDATE_NOTICE_FILE="$EAGLE_MEM_DIR/.update-notice"
10
+ EAGLE_UPDATE_STATE_FILE="$EAGLE_MEM_DIR/.last-update.json"
11
+ EAGLE_UPDATE_LOCK_DIR="$EAGLE_MEM_DIR/.update.lock"
12
+
13
+ eagle_update_config_mode() {
14
+ eagle_config_get "updates" "mode" "auto"
15
+ }
16
+
17
+ eagle_update_config_allow() {
18
+ eagle_config_get "updates" "allow" "patch"
19
+ }
20
+
21
+ eagle_update_config_channel() {
22
+ eagle_config_get "updates" "channel" "latest"
23
+ }
24
+
25
+ eagle_update_config_interval_hours() {
26
+ local hours
27
+ hours=$(eagle_config_get "updates" "interval_hours" "24")
28
+ case "$hours" in
29
+ ''|*[!0-9]*) echo "24" ;;
30
+ *) [ "$hours" -lt 1 ] 2>/dev/null && echo "1" || echo "$hours" ;;
31
+ esac
32
+ }
33
+
34
+ eagle_update_ensure_defaults() {
35
+ if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
36
+ eagle_config_init
37
+ return
38
+ fi
39
+
40
+ if ! grep -q '^\[updates\]' "$EAGLE_CONFIG_FILE" 2>/dev/null; then
41
+ cat >> "$EAGLE_CONFIG_FILE" << 'TOML'
42
+
43
+ [updates]
44
+ # Patch fixes auto-apply by default so stale bugs do not block sessions.
45
+ mode = "auto"
46
+ allow = "patch"
47
+ channel = "latest"
48
+ interval_hours = 24
49
+ TOML
50
+ fi
51
+ }
52
+
53
+ eagle_update_installed_version() {
54
+ local version_file="$EAGLE_MEM_DIR/.version"
55
+ if [ -f "$version_file" ] && [ -s "$version_file" ]; then
56
+ tr -d '[:space:]' < "$version_file"
57
+ return
58
+ fi
59
+
60
+ if command -v eagle-mem >/dev/null 2>&1; then
61
+ eagle-mem version 2>/dev/null | sed -nE 's/.*v([0-9][0-9A-Za-z.+-]*).*/\1/p' | head -1
62
+ return
63
+ fi
64
+
65
+ echo "0.0.0"
66
+ }
67
+
68
+ _eagle_update_file_age_seconds() {
69
+ local path="$1"
70
+ [ -f "$path" ] || { echo 999999999; return; }
71
+ local now mtime
72
+ now=$(date +%s)
73
+ mtime=$(stat -f %m "$path" 2>/dev/null || stat -c %Y "$path" 2>/dev/null || echo 0)
74
+ echo $((now - mtime))
75
+ }
76
+
77
+ eagle_update_latest_version() {
78
+ local force="${1:-0}"
79
+ local channel
80
+ channel=$(eagle_update_config_channel)
81
+ case "$channel" in latest|next) ;; *) channel="latest" ;; esac
82
+
83
+ local interval_seconds age
84
+ interval_seconds=$(( $(eagle_update_config_interval_hours) * 3600 ))
85
+ age=$(_eagle_update_file_age_seconds "$EAGLE_UPDATE_LATEST_FILE")
86
+
87
+ if [ "$force" != "1" ] && [ -f "$EAGLE_UPDATE_LATEST_FILE" ] && [ -s "$EAGLE_UPDATE_LATEST_FILE" ] && [ "$age" -lt "$interval_seconds" ]; then
88
+ tr -d '[:space:]' < "$EAGLE_UPDATE_LATEST_FILE"
89
+ return 0
90
+ fi
91
+
92
+ command -v npm >/dev/null 2>&1 || return 1
93
+
94
+ local latest tmp
95
+ latest=$(npm view "eagle-mem@${channel}" version 2>/dev/null | tr -d '[:space:]')
96
+ [ -n "$latest" ] || return 1
97
+
98
+ mkdir -p "$EAGLE_MEM_DIR" 2>/dev/null
99
+ tmp=$(mktemp "${EAGLE_MEM_DIR}/.latest-version.XXXXXX" 2>/dev/null || mktemp)
100
+ printf '%s\n' "$latest" > "$tmp"
101
+ mv "$tmp" "$EAGLE_UPDATE_LATEST_FILE"
102
+ printf '%s\n' "$latest"
103
+ }
104
+
105
+ _eagle_update_clean_version() {
106
+ printf '%s' "${1:-0.0.0}" | sed -E 's/^v//; s/[^0-9.].*$//'
107
+ }
108
+
109
+ _eagle_update_part() {
110
+ local version part
111
+ version=$(_eagle_update_clean_version "$1")
112
+ part="$2"
113
+ printf '%s' "$version" | awk -F. -v p="$part" '{ v=$p; if (v == "") v=0; gsub(/[^0-9]/, "", v); print v + 0 }'
114
+ }
115
+
116
+ eagle_update_version_gt() {
117
+ local a="$1" b="$2" i av bv
118
+ for i in 1 2 3; do
119
+ av=$(_eagle_update_part "$a" "$i")
120
+ bv=$(_eagle_update_part "$b" "$i")
121
+ [ "$av" -gt "$bv" ] && return 0
122
+ [ "$av" -lt "$bv" ] && return 1
123
+ done
124
+ return 1
125
+ }
126
+
127
+ eagle_update_allowed() {
128
+ local installed="$1" latest="$2" allow="$3"
129
+ eagle_update_version_gt "$latest" "$installed" || return 1
130
+
131
+ local imaj imin lmaj lmin
132
+ imaj=$(_eagle_update_part "$installed" 1)
133
+ imin=$(_eagle_update_part "$installed" 2)
134
+ lmaj=$(_eagle_update_part "$latest" 1)
135
+ lmin=$(_eagle_update_part "$latest" 2)
136
+
137
+ case "$allow" in
138
+ major) return 0 ;;
139
+ minor) [ "$imaj" -eq "$lmaj" ] ;;
140
+ patch|*) [ "$imaj" -eq "$lmaj" ] && [ "$imin" -eq "$lmin" ] ;;
141
+ esac
142
+ }
143
+
144
+ eagle_update_write_state() {
145
+ local status="$1" installed="$2" latest="$3" message="$4"
146
+ mkdir -p "$EAGLE_MEM_DIR" 2>/dev/null
147
+ jq -nc \
148
+ --arg status "$status" \
149
+ --arg installed "$installed" \
150
+ --arg latest "$latest" \
151
+ --arg message "$message" \
152
+ --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
153
+ '{status:$status, installed:$installed, latest:$latest, message:$message, updated_at:$at}' \
154
+ > "$EAGLE_UPDATE_STATE_FILE" 2>/dev/null || true
155
+ }
156
+
157
+ eagle_update_set_notice() {
158
+ local message="$1"
159
+ mkdir -p "$EAGLE_MEM_DIR" 2>/dev/null
160
+ printf '%s\n' "$message" > "$EAGLE_UPDATE_NOTICE_FILE" 2>/dev/null || true
161
+ }
162
+
163
+ eagle_update_take_notice() {
164
+ [ -f "$EAGLE_UPDATE_NOTICE_FILE" ] || return 0
165
+ cat "$EAGLE_UPDATE_NOTICE_FILE" 2>/dev/null
166
+ rm -f "$EAGLE_UPDATE_NOTICE_FILE" 2>/dev/null || true
167
+ }
168
+
169
+ eagle_update_lock() {
170
+ mkdir -p "$EAGLE_MEM_DIR" 2>/dev/null
171
+ mkdir "$EAGLE_UPDATE_LOCK_DIR" 2>/dev/null
172
+ }
173
+
174
+ eagle_update_unlock() {
175
+ rmdir "$EAGLE_UPDATE_LOCK_DIR" 2>/dev/null || true
176
+ }
177
+
178
+ eagle_update_backup_runtime() {
179
+ local backup_dir="$1"
180
+ mkdir -p "$backup_dir"
181
+
182
+ for item in hooks lib db scripts; do
183
+ if [ -d "$EAGLE_MEM_DIR/$item" ]; then
184
+ cp -R "$EAGLE_MEM_DIR/$item" "$backup_dir/$item" 2>/dev/null || true
185
+ fi
186
+ done
187
+
188
+ if [ -f "$EAGLE_MEM_DB" ]; then
189
+ if command -v sqlite3 >/dev/null 2>&1; then
190
+ sqlite3 "$EAGLE_MEM_DB" ".backup '$backup_dir/memory.db'" >/dev/null 2>&1 || cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
191
+ else
192
+ cp "$EAGLE_MEM_DB" "$backup_dir/memory.db" 2>/dev/null || true
193
+ fi
194
+ fi
195
+ }
196
+
197
+ eagle_update_restore_runtime() {
198
+ local backup_dir="$1"
199
+
200
+ for item in hooks lib db scripts; do
201
+ if [ -d "$backup_dir/$item" ]; then
202
+ cp -R "$backup_dir/$item" "$EAGLE_MEM_DIR/" 2>/dev/null || true
203
+ fi
204
+ done
205
+
206
+ if [ -f "$backup_dir/memory.db" ]; then
207
+ cp "$backup_dir/memory.db" "$EAGLE_MEM_DB" 2>/dev/null || true
208
+ fi
209
+ }
210
+
211
+ eagle_update_apply_version() {
212
+ local latest="${1:-}"
213
+ local dry_run="${2:-0}"
214
+ local force="${3:-0}"
215
+
216
+ command -v npm >/dev/null 2>&1 || {
217
+ eagle_update_write_state "failed" "$(eagle_update_installed_version)" "${latest:-unknown}" "npm not found"
218
+ return 1
219
+ }
220
+ command -v eagle-mem >/dev/null 2>&1 || {
221
+ eagle_update_write_state "failed" "$(eagle_update_installed_version)" "${latest:-unknown}" "eagle-mem binary not found on PATH"
222
+ return 1
223
+ }
224
+
225
+ local installed allow
226
+ installed=$(eagle_update_installed_version)
227
+ [ -n "$latest" ] || latest=$(eagle_update_latest_version 1)
228
+ [ -n "$latest" ] || return 1
229
+ allow=$(eagle_update_config_allow)
230
+
231
+ if ! eagle_update_version_gt "$latest" "$installed"; then
232
+ eagle_update_write_state "current" "$installed" "$latest" "already current"
233
+ printf 'current|%s|%s\n' "$installed" "$latest"
234
+ return 0
235
+ fi
236
+
237
+ if [ "$force" != "1" ] && ! eagle_update_allowed "$installed" "$latest" "$allow"; then
238
+ eagle_update_write_state "skipped" "$installed" "$latest" "outside allowed ${allow} update range"
239
+ return 2
240
+ fi
241
+
242
+ if [ "$dry_run" = "1" ]; then
243
+ eagle_update_write_state "dry-run" "$installed" "$latest" "eligible for auto-update"
244
+ printf 'eligible|%s|%s\n' "$installed" "$latest"
245
+ return 0
246
+ fi
247
+
248
+ if ! eagle_update_lock; then
249
+ eagle_log "INFO" "Auto-update skipped: lock already held"
250
+ eagle_update_write_state "locked" "$installed" "$latest" "update already running"
251
+ printf 'locked|%s|%s\n' "$installed" "$latest"
252
+ return 3
253
+ fi
254
+
255
+ local stamp backup_dir update_output update_rc
256
+ stamp=$(date -u +%Y%m%dT%H%M%SZ)
257
+ backup_dir="$EAGLE_MEM_DIR/backups/update-${stamp}-${installed}"
258
+ eagle_update_backup_runtime "$backup_dir"
259
+
260
+ eagle_log "INFO" "Auto-update: installing eagle-mem@$latest from $installed"
261
+ update_output=$(npm install -g "eagle-mem@$latest" 2>&1)
262
+ update_rc=$?
263
+ if [ "$update_rc" -ne 0 ]; then
264
+ eagle_log "ERROR" "Auto-update npm install failed: $update_output"
265
+ eagle_update_write_state "failed" "$installed" "$latest" "npm install failed"
266
+ eagle_update_set_notice "Eagle Mem auto-update failed while installing v${latest}; keeping v${installed}. Run: eagle-mem updates apply"
267
+ eagle_update_unlock
268
+ return 1
269
+ fi
270
+
271
+ update_output=$(EAGLE_MEM_AUTO_UPDATE_ACTIVE=1 eagle-mem update 2>&1)
272
+ update_rc=$?
273
+ if [ "$update_rc" -ne 0 ]; then
274
+ eagle_log "ERROR" "Auto-update runtime update failed: $update_output"
275
+ npm install -g "eagle-mem@$installed" >/dev/null 2>&1 || true
276
+ eagle_update_restore_runtime "$backup_dir"
277
+ printf '%s\n' "$installed" > "$EAGLE_MEM_DIR/.version" 2>/dev/null || true
278
+ eagle_update_write_state "rolled-back" "$installed" "$latest" "runtime update failed; restored backup"
279
+ eagle_update_set_notice "Eagle Mem auto-update to v${latest} failed and was rolled back to v${installed}. Run: eagle-mem updates status"
280
+ eagle_update_unlock
281
+ return 1
282
+ fi
283
+
284
+ printf '%s\n' "$latest" > "$EAGLE_MEM_DIR/.version" 2>/dev/null || true
285
+ eagle_update_write_state "updated" "$installed" "$latest" "auto-update applied"
286
+ eagle_update_set_notice "Eagle Mem auto-updated from v${installed} to v${latest}. Hooks and skills were refreshed automatically."
287
+ eagle_log "INFO" "Auto-update complete: $installed -> $latest"
288
+ eagle_update_unlock
289
+ printf 'updated|%s|%s\n' "$installed" "$latest"
290
+ }
291
+
292
+ eagle_update_auto() {
293
+ [ "${EAGLE_MEM_DISABLE_AUTO_UPDATE:-}" = "1" ] && return 0
294
+ [ "${EAGLE_MEM_AUTO_UPDATE_ACTIVE:-}" = "1" ] && return 0
295
+
296
+ eagle_update_ensure_defaults
297
+
298
+ local mode installed latest allow
299
+ mode=$(eagle_update_config_mode)
300
+ [ "$mode" = "off" ] && return 0
301
+
302
+ installed=$(eagle_update_installed_version)
303
+ latest=$(eagle_update_latest_version 0) || return 0
304
+ [ -n "$latest" ] || return 0
305
+ eagle_update_version_gt "$latest" "$installed" || return 0
306
+
307
+ allow=$(eagle_update_config_allow)
308
+ if ! eagle_update_allowed "$installed" "$latest" "$allow"; then
309
+ eagle_update_write_state "available" "$installed" "$latest" "outside allowed ${allow} update range"
310
+ eagle_update_set_notice "Eagle Mem v${latest} is available, but auto-update is limited to ${allow} releases. Run: eagle-mem updates apply --force"
311
+ return 0
312
+ fi
313
+
314
+ if [ "$mode" = "auto" ]; then
315
+ eagle_update_apply_version "$latest" 0 0 >/dev/null 2>&1 || true
316
+ else
317
+ eagle_update_write_state "available" "$installed" "$latest" "notify mode"
318
+ eagle_update_set_notice "Eagle Mem v${latest} is available. Run: eagle-mem updates apply"
319
+ fi
320
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "4.8.6",
3
+ "version": "4.9.0",
4
4
  "description": "Shared memory, release guardrails, RTK token protection, and worker lanes for Claude Code and Codex",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
package/scripts/config.sh CHANGED
@@ -32,6 +32,8 @@ show_help() {
32
32
  echo -e " eagle-mem config set orchestration.route opposite"
33
33
  echo -e " eagle-mem config set orchestration.codex_worker_model gpt-5.5"
34
34
  echo -e " eagle-mem config set orchestration.claude_worker_model claude-opus-4-7"
35
+ echo -e " eagle-mem config set updates.mode auto"
36
+ echo -e " eagle-mem config set updates.allow patch"
35
37
  echo -e " eagle-mem config set token_guard.rtk enforce"
36
38
  echo -e " eagle-mem config set token_guard.raw_bash block"
37
39
  echo ""
package/scripts/health.sh CHANGED
@@ -12,6 +12,7 @@ LIB_DIR="$SCRIPT_DIR/../lib"
12
12
  . "$SCRIPT_DIR/style.sh"
13
13
  . "$LIB_DIR/db.sh"
14
14
  . "$LIB_DIR/provider.sh"
15
+ . "$LIB_DIR/updater.sh"
15
16
 
16
17
  project=""
17
18
  JSON_OUT=0
@@ -191,6 +192,32 @@ if [ -n "$orch_worktree_root" ]; then
191
192
  eagle_dim " Worktree root: $orch_worktree_root"
192
193
  fi
193
194
 
195
+ # ─── Auto-update visibility (informational) ────────────────
196
+
197
+ updates_mode=$(eagle_update_config_mode)
198
+ updates_allow=$(eagle_update_config_allow)
199
+ updates_channel=$(eagle_update_config_channel)
200
+ updates_interval=$(eagle_update_config_interval_hours)
201
+ updates_latest=$(eagle_update_latest_version 0 2>/dev/null || true)
202
+ updates_installed=$(eagle_update_installed_version)
203
+ updates_status="current"
204
+
205
+ if [ "$updates_mode" = "off" ]; then
206
+ eagle_warn "Updates: disabled"
207
+ issues+=("Auto-updates disabled. Re-enable bug-fix delivery: eagle-mem updates enable patch")
208
+ updates_status="disabled"
209
+ elif [ -n "$updates_latest" ] && eagle_update_version_gt "$updates_latest" "$updates_installed"; then
210
+ if eagle_update_allowed "$updates_installed" "$updates_latest" "$updates_allow"; then
211
+ eagle_warn "Updates: v${updates_latest} available (mode=$updates_mode, allow=$updates_allow)"
212
+ updates_status="available"
213
+ else
214
+ eagle_warn "Updates: v${updates_latest} available, outside $updates_allow range"
215
+ updates_status="outside-range"
216
+ fi
217
+ else
218
+ eagle_ok "Updates: auto/${updates_allow} (${updates_channel}, every ${updates_interval}h)"
219
+ fi
220
+
194
221
  # ─── 5. Data quality (10 pts) ──────────────────────────
195
222
 
196
223
  max_score=$((max_score + 10))
@@ -290,6 +317,13 @@ if [ "$JSON_OUT" -eq 1 ]; then
290
317
  --arg claude_worker_effort "$orch_claude_effort" \
291
318
  --arg codex_bin "${codex_bin:-}" \
292
319
  --arg claude_bin "${claude_bin:-}" \
320
+ --arg updates_mode "$updates_mode" \
321
+ --arg updates_allow "$updates_allow" \
322
+ --arg updates_channel "$updates_channel" \
323
+ --arg updates_interval "$updates_interval" \
324
+ --arg updates_installed "$updates_installed" \
325
+ --arg updates_latest "${updates_latest:-}" \
326
+ --arg updates_status "$updates_status" \
293
327
  --argjson noise_pct "$noise_pct" \
294
328
  --arg last_curated "${last_curated:-never}" \
295
329
  '{project:$project, score:$score, max:$max_score, pct:$pct, grade:$grade,
@@ -304,5 +338,14 @@ if [ "$JSON_OUT" -eq 1 ]; then
304
338
  codex:{model:$codex_worker_model, effort:$codex_worker_effort, cli:$codex_bin},
305
339
  claude_code:{model:$claude_worker_model, effort:$claude_worker_effort, cli:$claude_bin}
306
340
  },
341
+ updates:{
342
+ mode:$updates_mode,
343
+ allow:$updates_allow,
344
+ channel:$updates_channel,
345
+ interval_hours:$updates_interval,
346
+ installed:$updates_installed,
347
+ latest:$updates_latest,
348
+ status:$updates_status
349
+ },
307
350
  noise_pct:$noise_pct, last_curated:$last_curated}' >&3
308
351
  fi
package/scripts/help.sh CHANGED
@@ -22,6 +22,7 @@ echo -e " ${CYAN}update${RESET} Re-deploy hooks and run migrations"
22
22
  echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
23
23
  echo -e " ${CYAN}search${RESET} Search past sessions, memories, and code"
24
24
  echo -e " ${CYAN}health${RESET} Diagnose pipeline health and background automation"
25
+ echo -e " ${CYAN}updates${RESET} Auto-update status and policy"
25
26
  echo -e " ${CYAN}overview${RESET} Build or view project overview"
26
27
  echo -e " ${CYAN}session${RESET} Save a manual session summary"
27
28
  echo -e " ${CYAN}memories${RESET} View/sync agent memories"
@@ -42,6 +43,7 @@ echo -e " ${DIM}Recall${RESET} Project overview, summaries, memories, p
42
43
  echo -e " ${DIM}Guardrails${RESET} Decisions, gotchas, feature verification, stale memory warnings"
43
44
  echo -e " ${DIM}Tokens${RESET} Compact hook recall and optional RTK shell-output routing"
44
45
  echo -e " ${DIM}Lanes${RESET} Cross-agent worker status, worktrees, logs, validation, handoffs"
46
+ echo -e " ${DIM}Updates${RESET} Patch fixes install automatically by default"
45
47
  echo ""
46
48
  echo -e " ${BOLD}Search modes:${RESET}"
47
49
  echo -e " ${DIM}\$${RESET} eagle-mem search \"auth bug\" ${DIM}# keyword search${RESET}"
@@ -316,12 +316,15 @@ fi
316
316
  # ─── Initialize config ────────────────────────────────────
317
317
 
318
318
  . "$LIB_DIR/provider.sh"
319
+ . "$LIB_DIR/updater.sh"
319
320
  if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
320
321
  eagle_config_init
321
322
  eagle_ok "Config created ${DIM}(auto-detected provider)${RESET}"
322
323
  else
324
+ eagle_update_ensure_defaults
323
325
  eagle_ok "Config ${DIM}(already exists)${RESET}"
324
326
  fi
327
+ eagle_ok "Auto-updates ${DIM}(mode=$(eagle_update_config_mode), allow=$(eagle_update_config_allow))${RESET}"
325
328
 
326
329
  # ─── Patch CLAUDE.md with Eagle Mem instructions ─────────
327
330
 
package/scripts/update.sh CHANGED
@@ -12,6 +12,8 @@ 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/provider.sh"
16
+ . "$LIB_DIR/updater.sh"
15
17
  . "$LIB_DIR/hooks.sh"
16
18
  . "$LIB_DIR/codex-hooks.sh"
17
19
 
@@ -150,6 +152,11 @@ else
150
152
  eagle_ok "Project names up to date"
151
153
  fi
152
154
 
155
+ # ─── Ensure auto-update defaults exist ─────────────────────
156
+
157
+ eagle_update_ensure_defaults
158
+ eagle_ok "Auto-updates ${DIM}(mode=$(eagle_update_config_mode), allow=$(eagle_update_config_allow))${RESET}"
159
+
153
160
  # ─── Patch CLAUDE.md with Eagle Mem instructions ─────────
154
161
 
155
162
  if [ "$claude_found" = true ]; then
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Update policy and auto-update CLI
4
+ # ═══════════════════════════════════════════════════════════
5
+ set -euo pipefail
6
+
7
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
8
+ LIB_DIR="$SCRIPTS_DIR/../lib"
9
+
10
+ . "$SCRIPTS_DIR/style.sh"
11
+ . "$LIB_DIR/common.sh"
12
+ . "$LIB_DIR/provider.sh"
13
+ . "$LIB_DIR/updater.sh"
14
+
15
+ show_help() {
16
+ echo -e " ${BOLD}eagle-mem updates${RESET} — Manage automatic Eagle Mem updates"
17
+ echo ""
18
+ echo -e " ${BOLD}Usage:${RESET}"
19
+ echo -e " eagle-mem updates ${CYAN}status${RESET}"
20
+ echo -e " eagle-mem updates ${CYAN}check${RESET}"
21
+ echo -e " eagle-mem updates ${CYAN}apply${RESET} [--force] [--dry-run]"
22
+ echo -e " eagle-mem updates ${CYAN}enable${RESET} [auto|notify|patch|minor|major]"
23
+ echo -e " eagle-mem updates ${CYAN}disable${RESET}"
24
+ echo ""
25
+ echo -e " ${DIM}Default: mode=auto, allow=patch. Patch bug fixes install in the${RESET}"
26
+ echo -e " ${DIM}background from SessionStart so stale bugs do not block agents.${RESET}"
27
+ echo ""
28
+ }
29
+
30
+ updates_status() {
31
+ eagle_update_ensure_defaults
32
+
33
+ local installed latest latest_rc
34
+ installed=$(eagle_update_installed_version)
35
+ latest=$(eagle_update_latest_version 0 2>/dev/null) && latest_rc=0 || latest_rc=$?
36
+
37
+ eagle_header "Updates"
38
+ eagle_kv "Installed:" "v${installed:-unknown}"
39
+ if [ "$latest_rc" -eq 0 ] && [ -n "$latest" ]; then
40
+ eagle_kv "Latest:" "v$latest"
41
+ else
42
+ eagle_kv "Latest:" "unavailable"
43
+ fi
44
+ eagle_kv "Mode:" "$(eagle_update_config_mode)"
45
+ eagle_kv "Allow:" "$(eagle_update_config_allow)"
46
+ eagle_kv "Channel:" "$(eagle_update_config_channel)"
47
+ eagle_kv "Interval:" "$(eagle_update_config_interval_hours)h"
48
+
49
+ if [ -f "$EAGLE_UPDATE_STATE_FILE" ]; then
50
+ status=$(jq -r '.status // "unknown"' "$EAGLE_UPDATE_STATE_FILE" 2>/dev/null || echo "unknown")
51
+ at=$(jq -r '.updated_at // ""' "$EAGLE_UPDATE_STATE_FILE" 2>/dev/null || echo "")
52
+ msg=$(jq -r '.message // ""' "$EAGLE_UPDATE_STATE_FILE" 2>/dev/null || echo "")
53
+ eagle_kv "Last run:" "$status ${at:+($at)}"
54
+ [ -n "$msg" ] && eagle_kv "Message:" "$msg"
55
+ fi
56
+
57
+ if [ -n "$latest" ] && eagle_update_version_gt "$latest" "$installed"; then
58
+ if eagle_update_allowed "$installed" "$latest" "$(eagle_update_config_allow)"; then
59
+ eagle_ok "Eligible update available"
60
+ else
61
+ eagle_warn "Update available but outside allowed range"
62
+ fi
63
+ else
64
+ eagle_ok "Already current"
65
+ fi
66
+ }
67
+
68
+ updates_check() {
69
+ eagle_update_ensure_defaults
70
+ installed=$(eagle_update_installed_version)
71
+ latest=$(eagle_update_latest_version 1)
72
+ if [ -z "$latest" ]; then
73
+ eagle_err "Could not check npm for updates"
74
+ exit 1
75
+ fi
76
+
77
+ if eagle_update_version_gt "$latest" "$installed"; then
78
+ if eagle_update_allowed "$installed" "$latest" "$(eagle_update_config_allow)"; then
79
+ eagle_ok "Update available: v$installed -> v$latest"
80
+ else
81
+ eagle_warn "Update available: v$installed -> v$latest (outside allowed range)"
82
+ fi
83
+ else
84
+ eagle_ok "Already current: v$installed"
85
+ fi
86
+ }
87
+
88
+ updates_apply() {
89
+ eagle_update_ensure_defaults
90
+ local dry_run=0 force=0 latest=""
91
+ while [ $# -gt 0 ]; do
92
+ case "$1" in
93
+ --dry-run) dry_run=1; shift ;;
94
+ --force) force=1; shift ;;
95
+ --latest) latest="${2:-}"; shift 2 ;;
96
+ --help|-h) show_help; exit 0 ;;
97
+ *)
98
+ eagle_err "Unknown apply option: $1"
99
+ exit 1
100
+ ;;
101
+ esac
102
+ done
103
+
104
+ if output=$(eagle_update_apply_version "$latest" "$dry_run" "$force" 2>&1); then
105
+ state=$(printf '%s' "$output" | awk -F'|' 'NR == 1 {print $1}')
106
+ installed=$(printf '%s' "$output" | awk -F'|' 'NR == 1 {print $2}')
107
+ latest=$(printf '%s' "$output" | awk -F'|' 'NR == 1 {print $3}')
108
+ if [ "$state" = "current" ]; then
109
+ eagle_ok "Already current: v$installed"
110
+ elif [ "$dry_run" = "1" ]; then
111
+ eagle_ok "Would update v$installed -> v$latest"
112
+ else
113
+ eagle_ok "Eagle Mem updated from v$installed to v$latest"
114
+ fi
115
+ else
116
+ rc=$?
117
+ if [ "$rc" -eq 2 ]; then
118
+ eagle_warn "Update skipped: outside allowed range"
119
+ eagle_info "Use --force for a manual override"
120
+ exit 0
121
+ fi
122
+ if [ "$rc" -eq 3 ]; then
123
+ eagle_warn "Update already running"
124
+ exit 0
125
+ fi
126
+ eagle_err "Update failed"
127
+ [ -n "$output" ] && eagle_dim "$output"
128
+ exit 1
129
+ fi
130
+ }
131
+
132
+ updates_enable() {
133
+ eagle_update_ensure_defaults
134
+ local value="${1:-auto}"
135
+ case "$value" in
136
+ auto|notify|off)
137
+ eagle_config_set "updates" "mode" "$value"
138
+ ;;
139
+ patch|minor|major)
140
+ eagle_config_set "updates" "mode" "auto"
141
+ eagle_config_set "updates" "allow" "$value"
142
+ ;;
143
+ *)
144
+ eagle_err "Use: eagle-mem updates enable [auto|notify|patch|minor|major]"
145
+ exit 1
146
+ ;;
147
+ esac
148
+ eagle_ok "Updates enabled: mode=$(eagle_update_config_mode), allow=$(eagle_update_config_allow)"
149
+ }
150
+
151
+ updates_disable() {
152
+ eagle_update_ensure_defaults
153
+ eagle_config_set "updates" "mode" "off"
154
+ eagle_ok "Automatic update checks disabled"
155
+ }
156
+
157
+ command="${1:-status}"
158
+ shift 2>/dev/null || true
159
+
160
+ case "$command" in
161
+ status) updates_status "$@" ;;
162
+ check) updates_check "$@" ;;
163
+ apply) updates_apply "$@" ;;
164
+ auto) eagle_update_auto ;;
165
+ enable) updates_enable "$@" ;;
166
+ disable) updates_disable ;;
167
+ help|--help|-h) show_help ;;
168
+ *)
169
+ eagle_err "Unknown updates command: $command"
170
+ echo ""
171
+ show_help
172
+ exit 1
173
+ ;;
174
+ esac