eagle-mem 1.0.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.
@@ -0,0 +1,362 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Scan
4
+ # Analyzes a project's codebase and generates a brief overview
5
+ # Stored in the overviews table, auto-injected at session start
6
+ # ═══════════════════════════════════════════════════════════
7
+ set -euo pipefail
8
+
9
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+ LIB_DIR="$SCRIPTS_DIR/../lib"
11
+
12
+ . "$SCRIPTS_DIR/style.sh"
13
+ . "$LIB_DIR/common.sh"
14
+ . "$LIB_DIR/db.sh"
15
+
16
+ eagle_ensure_db
17
+
18
+ TARGET_DIR="${1:-.}"
19
+ TARGET_DIR="$(cd "$TARGET_DIR" && pwd)"
20
+ PROJECT=$(eagle_project_from_cwd "$TARGET_DIR")
21
+
22
+ eagle_header "Scan"
23
+ eagle_info "Scanning ${BOLD}$PROJECT${RESET} at $TARGET_DIR"
24
+ echo ""
25
+
26
+ # ─── Collect files ─────────────────────────────────────────
27
+
28
+ is_git=false
29
+ if git -C "$TARGET_DIR" rev-parse --is-inside-work-tree &>/dev/null; then
30
+ is_git=true
31
+ fi
32
+
33
+ TMPFILE=$(mktemp)
34
+ trap 'rm -f "$TMPFILE"' EXIT
35
+
36
+ eagle_collect_files "$TARGET_DIR" "$TMPFILE"
37
+
38
+ total_files=$(wc -l < "$TMPFILE" | tr -d ' ')
39
+
40
+ if [ "$total_files" -eq 0 ]; then
41
+ eagle_fail "No files found in $TARGET_DIR"
42
+ exit 1
43
+ fi
44
+
45
+ eagle_ok "$total_files files found"
46
+
47
+ # ─── Language breakdown (bash 3 compatible — no assoc arrays) ──
48
+
49
+ while IFS= read -r file; do
50
+ ext="${file##*.}"
51
+ [ "$ext" = "$file" ] && continue
52
+ if [ -f "$TARGET_DIR/$file" ]; then
53
+ lines=$(wc -l < "$TARGET_DIR/$file" 2>/dev/null | tr -d ' ')
54
+ else
55
+ lines=0
56
+ fi
57
+ echo "$ext $lines"
58
+ done < "$TMPFILE" | awk '
59
+ BEGIN {
60
+ m["sh"]="Bash"; m["bash"]="Bash"; m["zsh"]="Bash"
61
+ m["js"]="JavaScript"; m["jsx"]="JavaScript"; m["mjs"]="JavaScript"; m["cjs"]="JavaScript"
62
+ m["ts"]="TypeScript"; m["tsx"]="TypeScript"; m["mts"]="TypeScript"
63
+ m["py"]="Python"; m["rb"]="Ruby"; m["go"]="Go"; m["rs"]="Rust"
64
+ m["java"]="Java"; m["kt"]="Kotlin"; m["kts"]="Kotlin"; m["swift"]="Swift"
65
+ m["c"]="C"; m["h"]="C"; m["cpp"]="C++"; m["cc"]="C++"; m["hpp"]="C++"
66
+ m["cs"]="C#"; m["php"]="PHP"; m["sql"]="SQL"
67
+ m["html"]="HTML"; m["htm"]="HTML"
68
+ m["css"]="CSS"; m["scss"]="CSS"; m["sass"]="CSS"; m["less"]="CSS"
69
+ m["json"]="JSON"; m["yaml"]="YAML"; m["yml"]="YAML"; m["toml"]="TOML"
70
+ m["md"]="Markdown"; m["vue"]="Vue"; m["svelte"]="Svelte"; m["dart"]="Dart"
71
+ m["ex"]="Elixir"; m["exs"]="Elixir"; m["zig"]="Zig"; m["lua"]="Lua"
72
+ m["r"]="R"; m["scala"]="Scala"
73
+ }
74
+ {
75
+ ext = tolower($1); lines = $2
76
+ if (ext in m) {
77
+ lang = m[ext]
78
+ counts[lang]++
79
+ llines[lang] += lines
80
+ }
81
+ }
82
+ END {
83
+ total = 0
84
+ for (lang in llines) total += llines[lang]
85
+ printf "TOTAL_LINES=%d\n", total
86
+
87
+ n = 0
88
+ for (lang in llines) { order[n++] = lang }
89
+ for (i = 0; i < n-1; i++)
90
+ for (j = i+1; j < n; j++)
91
+ if (llines[order[j]] > llines[order[i]]) {
92
+ tmp = order[i]; order[i] = order[j]; order[j] = tmp
93
+ }
94
+
95
+ top = (n < 5) ? n : 5
96
+ for (i = 0; i < top; i++) {
97
+ lang = order[i]
98
+ if (llines[lang] >= 1000)
99
+ printf "LANG=%s (%dk lines, %d files)\n", lang, int(llines[lang]/1000), counts[lang]
100
+ else
101
+ printf "LANG=%s (%d lines, %d files)\n", lang, llines[lang], counts[lang]
102
+ }
103
+ }
104
+ ' > "${TMPFILE}.analysis"
105
+
106
+ total_lines=$(grep '^TOTAL_LINES=' "${TMPFILE}.analysis" | cut -d= -f2)
107
+ top_langs=$(grep '^LANG=' "${TMPFILE}.analysis" | sed 's/^LANG=//' | while read -r line; do printf "%s" "${sep:-}${line}"; sep=", "; done)
108
+ rm -f "${TMPFILE}.analysis"
109
+
110
+ [ -n "$top_langs" ] && eagle_ok "Languages: $top_langs"
111
+
112
+ # ─── Total lines ───────────────────────────────────────────
113
+
114
+ total_lines="${total_lines:-0}"
115
+
116
+ if [ "$total_lines" -ge 1000000 ]; then
117
+ scale="$(( total_lines / 1000000 )).$(( (total_lines % 1000000) / 100000 ))M"
118
+ elif [ "$total_lines" -ge 1000 ]; then
119
+ scale="$(( total_lines / 1000 ))k"
120
+ else
121
+ scale="$total_lines"
122
+ fi
123
+
124
+ # ─── Framework detection ──────────────────────────────────
125
+
126
+ frameworks=""
127
+
128
+ detect_framework() {
129
+ local file="$1" indicator="$2"
130
+ if [ -f "$TARGET_DIR/$file" ]; then
131
+ frameworks="${frameworks:+$frameworks, }$indicator"
132
+ return 0
133
+ fi
134
+ return 1
135
+ }
136
+
137
+ detect_dep() {
138
+ local file="$1" dep="$2" indicator="$3"
139
+ if [ -f "$TARGET_DIR/$file" ] && grep -q "\"$dep\"" "$TARGET_DIR/$file" 2>/dev/null; then
140
+ frameworks="${frameworks:+$frameworks, }$indicator"
141
+ return 0
142
+ fi
143
+ return 1
144
+ }
145
+
146
+ # Node.js ecosystem
147
+ if [ -f "$TARGET_DIR/package.json" ]; then
148
+ detect_dep "package.json" "next" "Next.js" || true
149
+ detect_dep "package.json" "react" "React" || true
150
+ detect_dep "package.json" "vue" "Vue" || true
151
+ detect_dep "package.json" "svelte" "Svelte" || true
152
+ detect_dep "package.json" "express" "Express" || true
153
+ detect_dep "package.json" "fastify" "Fastify" || true
154
+ detect_dep "package.json" "hono" "Hono" || true
155
+ detect_dep "package.json" "nestjs" "NestJS" || true
156
+ detect_dep "package.json" "@anthropic-ai/sdk" "Claude SDK" || true
157
+ detect_dep "package.json" "prisma" "Prisma" || true
158
+ detect_dep "package.json" "drizzle-orm" "Drizzle" || true
159
+ detect_dep "package.json" "tailwindcss" "Tailwind" || true
160
+ if [ -z "$frameworks" ]; then
161
+ frameworks="Node.js"
162
+ fi
163
+ fi
164
+
165
+ # Python
166
+ detect_framework "requirements.txt" "Python" || true
167
+ detect_framework "pyproject.toml" "Python" || true
168
+ detect_framework "setup.py" "Python" || true
169
+ if [ -f "$TARGET_DIR/requirements.txt" ] || [ -f "$TARGET_DIR/pyproject.toml" ]; then
170
+ grep -ql "django" "$TARGET_DIR"/requirements.txt "$TARGET_DIR"/pyproject.toml 2>/dev/null && frameworks="${frameworks:+$frameworks, }Django"
171
+ grep -ql "flask" "$TARGET_DIR"/requirements.txt "$TARGET_DIR"/pyproject.toml 2>/dev/null && frameworks="${frameworks:+$frameworks, }Flask"
172
+ grep -ql "fastapi" "$TARGET_DIR"/requirements.txt "$TARGET_DIR"/pyproject.toml 2>/dev/null && frameworks="${frameworks:+$frameworks, }FastAPI"
173
+ fi
174
+
175
+ # Other ecosystems
176
+ detect_framework "Cargo.toml" "Rust/Cargo" || true
177
+ detect_framework "go.mod" "Go" || true
178
+ detect_framework "Gemfile" "Ruby" || true
179
+ detect_framework "build.gradle" "Gradle" || true
180
+ detect_framework "build.gradle.kts" "Gradle (Kotlin)" || true
181
+ detect_framework "pom.xml" "Maven" || true
182
+ detect_framework "Package.swift" "Swift" || true
183
+ detect_framework "pubspec.yaml" "Dart/Flutter" || true
184
+ detect_framework "mix.exs" "Elixir/Mix" || true
185
+
186
+ [ -n "$frameworks" ] && eagle_ok "Frameworks: $frameworks"
187
+
188
+ # ─── Structure analysis ───────────────────────────────────
189
+
190
+ top_dirs=""
191
+ if [ "$is_git" = true ]; then
192
+ git -C "$TARGET_DIR" ls-files --cached --others --exclude-standard | cut -d/ -f1 | sort -u | while read -r item; do
193
+ if [ -d "$TARGET_DIR/$item" ]; then
194
+ count=$(git -C "$TARGET_DIR" ls-files --cached --others --exclude-standard "$item/" 2>/dev/null | wc -l | tr -d ' ')
195
+ echo "$item/ ($count)"
196
+ fi
197
+ done > "${TMPFILE}.dirs"
198
+ else
199
+ find "$TARGET_DIR" -maxdepth 1 -type d -not -name '.*' -not -name 'node_modules' -not -name 'dist' -not -name 'build' -not -name '__pycache__' -not -path "$TARGET_DIR" | sort | while read -r dir; do
200
+ name=$(basename "$dir")
201
+ count=$(find "$dir" -type f 2>/dev/null | wc -l | tr -d ' ')
202
+ echo "$name/ ($count)"
203
+ done > "${TMPFILE}.dirs"
204
+ fi
205
+ top_dirs=$(head -10 "${TMPFILE}.dirs" | while read -r line; do printf "%s" "${sep:-}${line}"; sep=", "; done)
206
+ rm -f "${TMPFILE}.dirs"
207
+
208
+ [ -n "$top_dirs" ] && eagle_ok "Structure: $top_dirs"
209
+
210
+ # ─── Entry points ─────────────────────────────────────────
211
+
212
+ entries=""
213
+
214
+ for candidate in "bin/"* "src/index."* "src/main."* "src/app."* "index."* "main."* "app."* "server."* "cli."*; do
215
+ match=$(grep "^${candidate}$\|^${candidate}" "$TMPFILE" 2>/dev/null | head -1 || true)
216
+ if [ -n "$match" ]; then
217
+ entries="${entries:+$entries, }$match"
218
+ fi
219
+ done
220
+
221
+ [ -n "$entries" ] && eagle_ok "Entry points: $entries"
222
+
223
+ # ─── Test detection ───────────────────────────────────────
224
+
225
+ tests=""
226
+ test_count=0
227
+
228
+ test_count=$(grep -cE '(^test/|^tests/|^__tests__/|\.test\.|\.spec\.|_test\.)' "$TMPFILE" || true)
229
+ if [ "$test_count" -gt 0 ]; then
230
+ tests="$test_count test files"
231
+ if [ -f "$TARGET_DIR/package.json" ]; then
232
+ grep -q "jest" "$TARGET_DIR/package.json" 2>/dev/null && tests="$tests (Jest)"
233
+ grep -q "vitest" "$TARGET_DIR/package.json" 2>/dev/null && tests="$tests (Vitest)"
234
+ grep -q "mocha" "$TARGET_DIR/package.json" 2>/dev/null && tests="$tests (Mocha)"
235
+ fi
236
+ grep -q "pytest" "$TARGET_DIR/requirements.txt" "$TARGET_DIR/pyproject.toml" 2>/dev/null && tests="$tests (pytest)"
237
+ eagle_ok "Tests: $tests"
238
+ else
239
+ eagle_dim "Tests: none detected"
240
+ fi
241
+
242
+ # ─── Key config files ─────────────────────────────────────
243
+
244
+ configs=""
245
+ for cfg in .env.example .env.local Dockerfile docker-compose.yml docker-compose.yaml \
246
+ Makefile Justfile railway.json railway.toml vercel.json netlify.toml \
247
+ tsconfig.json .eslintrc* biome.json .prettierrc* tailwind.config.* \
248
+ vite.config.* next.config.* webpack.config.* rollup.config.* \
249
+ CLAUDE.md .cursorrules; do
250
+ match=$(grep "^${cfg}$" "$TMPFILE" 2>/dev/null | head -1 || true)
251
+ if [ -n "$match" ]; then
252
+ configs="${configs:+$configs, }$match"
253
+ fi
254
+ done
255
+
256
+ [ -n "$configs" ] && eagle_ok "Config: $configs"
257
+
258
+ # ─── Dependency count ─────────────────────────────────────
259
+
260
+ dep_info=""
261
+ if [ -f "$TARGET_DIR/package.json" ]; then
262
+ deps=$(jq '(.dependencies // {}) | length' "$TARGET_DIR/package.json" 2>/dev/null || echo "0")
263
+ dev_deps=$(jq '(.devDependencies // {}) | length' "$TARGET_DIR/package.json" 2>/dev/null || echo "0")
264
+ [ "$deps" -gt 0 ] || [ "$dev_deps" -gt 0 ] && dep_info="npm: ${deps} deps, ${dev_deps} devDeps"
265
+ fi
266
+
267
+ if [ -f "$TARGET_DIR/go.mod" ]; then
268
+ go_deps=$(grep -c "^\t" "$TARGET_DIR/go.mod" 2>/dev/null || echo "0")
269
+ dep_info="${dep_info:+$dep_info; }go: $go_deps modules"
270
+ fi
271
+
272
+ [ -n "$dep_info" ] && eagle_ok "Dependencies: $dep_info"
273
+
274
+ # ─── Monorepo detection ──────────────────────────────────
275
+
276
+ monorepo=""
277
+ if [ -f "$TARGET_DIR/package.json" ]; then
278
+ if jq -e '.workspaces' "$TARGET_DIR/package.json" &>/dev/null; then
279
+ workspace_count=$(jq '.workspaces | if type == "array" then length elif type == "object" then (.packages // []) | length else 0 end' "$TARGET_DIR/package.json" 2>/dev/null || echo "?")
280
+ monorepo="npm workspaces ($workspace_count patterns)"
281
+ fi
282
+ fi
283
+
284
+ if [ -f "$TARGET_DIR/pnpm-workspace.yaml" ]; then
285
+ monorepo="pnpm workspace"
286
+ fi
287
+
288
+ if [ -f "$TARGET_DIR/lerna.json" ]; then
289
+ monorepo="${monorepo:+$monorepo + }Lerna"
290
+ fi
291
+
292
+ if [ -f "$TARGET_DIR/turbo.json" ]; then
293
+ monorepo="${monorepo:+$monorepo + }Turborepo"
294
+ fi
295
+
296
+ [ -n "$monorepo" ] && eagle_ok "Monorepo: $monorepo"
297
+
298
+ # ─── Generate overview text ────────────────────────────────
299
+
300
+ echo ""
301
+ eagle_info "Generating overview..."
302
+
303
+ overview="$PROJECT: "
304
+
305
+ # Primary language/framework
306
+ if [ -n "$frameworks" ]; then
307
+ overview+="$frameworks project"
308
+ else
309
+ primary_lang=$(echo "$top_langs" | cut -d'(' -f1 | tr -d ' ')
310
+ overview+="${primary_lang:-unknown} project"
311
+ fi
312
+
313
+ overview+=" ($total_files files, ~${scale} lines)"
314
+
315
+ [ -n "$monorepo" ] && overview+=". Monorepo: $monorepo"
316
+
317
+ overview+="."
318
+
319
+ # Structure
320
+ if [ -n "$top_dirs" ]; then
321
+ overview+=" Structure: $top_dirs."
322
+ fi
323
+
324
+ # Entry points
325
+ if [ -n "$entries" ]; then
326
+ overview+=" Entry: $entries."
327
+ fi
328
+
329
+ # Tests
330
+ if [ "$test_count" -gt 0 ]; then
331
+ overview+=" Tests: $tests."
332
+ else
333
+ overview+=" No tests detected."
334
+ fi
335
+
336
+ # Dependencies
337
+ if [ -n "$dep_info" ]; then
338
+ overview+=" Dependencies: $dep_info."
339
+ fi
340
+
341
+ # Config highlights
342
+ if [ -n "$configs" ]; then
343
+ overview+=" Config: $configs."
344
+ fi
345
+
346
+ # Store in database
347
+ eagle_upsert_overview "$PROJECT" "$overview"
348
+
349
+ eagle_ok "Overview saved for project '$PROJECT'"
350
+ echo ""
351
+
352
+ echo -e " ${BOLD}Generated overview:${RESET}"
353
+ echo ""
354
+ echo -e " ${DIM}$overview${RESET}"
355
+ echo ""
356
+
357
+ eagle_footer "Scan complete."
358
+ eagle_kv "Project:" "$PROJECT"
359
+ eagle_kv "Files:" "$total_files"
360
+ eagle_kv "Lines:" "~$scale"
361
+ eagle_kv "Database:" "$EAGLE_MEM_DB"
362
+ echo ""
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — CLI styling helpers
4
+ # Source this in all scripts: . "$(dirname "$0")/style.sh"
5
+ # ═══════════════════════════════════════════════════════════
6
+
7
+ RED='\033[0;31m'
8
+ GREEN='\033[0;32m'
9
+ YELLOW='\033[0;33m'
10
+ BLUE='\033[0;34m'
11
+ CYAN='\033[0;36m'
12
+ BOLD='\033[1m'
13
+ DIM='\033[2m'
14
+ RESET='\033[0m'
15
+
16
+ TICK="${GREEN}✓${RESET}"
17
+ CROSS="${RED}✗${RESET}"
18
+ ARROW="${CYAN}→${RESET}"
19
+ DOT="${DIM}·${RESET}"
20
+
21
+ eagle_header() {
22
+ echo ""
23
+ echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}$1${RESET}"
24
+ echo -e " ${DIM}─────────────────────────────────────${RESET}"
25
+ echo ""
26
+ }
27
+
28
+ eagle_ok() { echo -e " ${TICK} $1"; }
29
+ eagle_fail() { echo -e " ${CROSS} $1"; }
30
+ eagle_info() { echo -e " ${ARROW} $1"; }
31
+ eagle_warn() { echo -e " ${YELLOW}!${RESET} $1"; }
32
+ eagle_err() { echo -e " ${RED}✗${RESET} $1" >&2; }
33
+ eagle_dim() { echo -e " ${DIM}$1${RESET}"; }
34
+
35
+ eagle_step() {
36
+ echo -e " ${CYAN}$1${RESET} $2"
37
+ }
38
+
39
+ eagle_kv() {
40
+ printf " ${DIM}%-12s${RESET} %s\n" "$1" "$2"
41
+ }
42
+
43
+ eagle_footer() {
44
+ echo ""
45
+ echo -e " ${GREEN}${BOLD}$1${RESET}"
46
+ echo ""
47
+ }
48
+
49
+ eagle_banner() {
50
+ echo -e "${CYAN}"
51
+ cat << 'BANNER'
52
+ ███████╗ █████╗ ██████╗ ██╗ ███████╗
53
+ ██╔════╝██╔══██╗██╔════╝ ██║ ██╔════╝
54
+ █████╗ ███████║██║ ██╗ ██║ █████╗
55
+ ██╔══╝ ██╔══██║██║ ╚██╗██║ ██╔══╝
56
+ ███████╗██║ ██║╚██████╔╝███████╗███████╗
57
+ ╚══════╝╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝
58
+ ███╗ ███╗███████╗███╗ ███╗
59
+ ████╗ ████║██╔════╝████╗ ████║
60
+ ██╔████╔██║█████╗ ██╔████╔██║
61
+ ██║╚██╔╝██║██╔══╝ ██║╚██╔╝██║
62
+ ██║ ╚═╝ ██║███████╗██║ ╚═╝ ██║
63
+ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
64
+ BANNER
65
+ echo -e "${RESET}"
66
+ }
67
+
68
+ eagle_is_tty() {
69
+ [ -t 0 ] && [ -t 1 ]
70
+ }
71
+
72
+ eagle_confirm() {
73
+ local prompt="$1"
74
+ local default="${2:-n}"
75
+
76
+ if ! eagle_is_tty; then
77
+ [ "$default" = "y" ] && return 0 || return 1
78
+ fi
79
+
80
+ local hint
81
+ [ "$default" = "y" ] && hint="Y/n" || hint="y/N"
82
+
83
+ echo -ne " ${YELLOW}?${RESET} ${prompt} [${hint}] "
84
+ read -r -n 1 reply
85
+ echo ""
86
+
87
+ reply="${reply:-$default}"
88
+ [[ "$reply" =~ ^[Yy]$ ]]
89
+ }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Uninstall
4
+ # Removes hooks from settings.json and optionally wipes data
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
9
+
10
+ . "$SCRIPTS_DIR/style.sh"
11
+
12
+ EAGLE_MEM_DIR="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}"
13
+ SETTINGS="$HOME/.claude/settings.json"
14
+ SKILLS_DIR="$HOME/.claude/skills"
15
+
16
+ eagle_header "Uninstall"
17
+
18
+ # ─── Remove hooks from settings.json ──────────────────────
19
+
20
+ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
21
+ for event in SessionStart Stop PostToolUse SessionEnd UserPromptSubmit; do
22
+ if jq -e ".hooks.${event}" "$SETTINGS" &>/dev/null; then
23
+ tmp=$(mktemp)
24
+ jq ".hooks.${event} = [.hooks.${event}[]? | select(any(.hooks[]?; .command | contains(\"eagle-mem\")) | not)]" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
25
+ tmp=$(mktemp)
26
+ jq "if .hooks.${event} == [] then del(.hooks.${event}) else . end" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
27
+ fi
28
+ done
29
+ tmp=$(mktemp)
30
+ jq 'if .hooks == {} then del(.hooks) else . end' "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
31
+ eagle_ok "Hooks removed from settings.json"
32
+ else
33
+ eagle_warn "Could not patch settings.json (jq not found or file missing)"
34
+ fi
35
+
36
+ # ─── Remove skill symlinks ────────────────────────────────
37
+
38
+ if [ -d "$SKILLS_DIR" ]; then
39
+ for skill in eagle-mem-search eagle-mem-tasks eagle-mem-overview; do
40
+ target="$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
48
+ done
49
+ fi
50
+
51
+ # ─── Optionally wipe data ─────────────────────────────────
52
+
53
+ if [ -d "$EAGLE_MEM_DIR" ]; then
54
+ echo ""
55
+ if eagle_confirm "Delete Eagle Mem data? (${DIM}$EAGLE_MEM_DIR${RESET})"; then
56
+ rm -rf "$EAGLE_MEM_DIR"
57
+ eagle_ok "Data deleted"
58
+ else
59
+ eagle_info "Data preserved at $EAGLE_MEM_DIR"
60
+ fi
61
+ fi
62
+
63
+ eagle_footer "Eagle Mem uninstalled."
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Update
4
+ # Re-deploys hooks/lib/db files and runs pending migrations
5
+ # ═══════════════════════════════════════════════════════════
6
+ set -euo pipefail
7
+
8
+ PACKAGE_DIR="${1:-.}"
9
+ SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
10
+
11
+ . "$SCRIPTS_DIR/style.sh"
12
+
13
+ EAGLE_MEM_DIR="${EAGLE_MEM_DIR:-$HOME/.eagle-mem}"
14
+ SETTINGS="$HOME/.claude/settings.json"
15
+
16
+ eagle_header "Update"
17
+
18
+ # ─── Verify existing installation ──────────────────────────
19
+
20
+ if [ ! -d "$EAGLE_MEM_DIR" ]; then
21
+ eagle_fail "Eagle Mem is not installed ($EAGLE_MEM_DIR not found)"
22
+ eagle_info "Run ${BOLD}eagle-mem install${RESET} first"
23
+ exit 1
24
+ fi
25
+
26
+ if [ ! -f "$EAGLE_MEM_DIR/memory.db" ]; then
27
+ eagle_warn "Database not found — will be created"
28
+ fi
29
+
30
+ # ─── Update files ──────────────────────────────────────────
31
+
32
+ mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db}
33
+
34
+ cp "$PACKAGE_DIR"/hooks/*.sh "$EAGLE_MEM_DIR/hooks/"
35
+ cp "$PACKAGE_DIR"/lib/*.sh "$EAGLE_MEM_DIR/lib/"
36
+ cp "$PACKAGE_DIR"/db/*.sh "$EAGLE_MEM_DIR/db/"
37
+ cp "$PACKAGE_DIR"/db/*.sql "$EAGLE_MEM_DIR/db/"
38
+
39
+ chmod +x "$EAGLE_MEM_DIR"/hooks/*.sh
40
+ chmod +x "$EAGLE_MEM_DIR"/db/migrate.sh
41
+
42
+ eagle_ok "Files updated"
43
+
44
+ # ─── Run pending migrations ────────────────────────────────
45
+
46
+ migration_output=$("$EAGLE_MEM_DIR/db/migrate.sh" 2>/dev/null | grep -v -E '^(wal|5000)$')
47
+ if echo "$migration_output" | grep -q "applied:"; then
48
+ echo "$migration_output" | grep "applied:" | while read -r line; do
49
+ eagle_ok "Migration: ${line#*applied: }"
50
+ done
51
+ else
52
+ eagle_ok "Database up to date"
53
+ fi
54
+
55
+ # ─── Re-register hooks (idempotent) ───────────────────────
56
+
57
+ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
58
+ patch_hook() {
59
+ local event="$1"
60
+ local matcher="$2"
61
+ local command="$3"
62
+
63
+ if jq -e ".hooks.${event}[]? | select(.hooks[]?.command == \"$command\")" "$SETTINGS" &>/dev/null; then
64
+ return
65
+ fi
66
+
67
+ local entry
68
+ if [ -n "$matcher" ]; then
69
+ entry="{\"matcher\": \"$matcher\", \"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
70
+ else
71
+ entry="{\"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
72
+ fi
73
+
74
+ local tmp
75
+ tmp=$(mktemp)
76
+ jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
77
+ }
78
+
79
+ patch_hook "SessionStart" "" "$EAGLE_MEM_DIR/hooks/session-start.sh"
80
+ patch_hook "Stop" "" "$EAGLE_MEM_DIR/hooks/stop.sh"
81
+ patch_hook "PostToolUse" "Read|Write|Edit|Bash" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
82
+ patch_hook "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
83
+ patch_hook "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
84
+
85
+ eagle_ok "Hooks registered"
86
+ fi
87
+
88
+ # ─── Update skill symlinks ────────────────────────────────
89
+
90
+ SKILLS_DIR="$HOME/.claude/skills"
91
+ if [ -d "$PACKAGE_DIR/skills" ]; then
92
+ mkdir -p "$SKILLS_DIR"
93
+ for skill_dir in "$PACKAGE_DIR"/skills/*/; do
94
+ [ ! -d "$skill_dir" ] && continue
95
+ skill_name=$(basename "$skill_dir")
96
+ dst="$SKILLS_DIR/$skill_name"
97
+ [ -L "$dst" ] && rm "$dst"
98
+ ln -sf "$skill_dir" "$dst"
99
+ done
100
+ eagle_ok "Skills updated"
101
+ fi
102
+
103
+ # ─── Summary ───────────────────────────────────────────────
104
+
105
+ version=$(node -e "console.log(require('$PACKAGE_DIR/package.json').version)" 2>/dev/null || echo "unknown")
106
+ eagle_footer "Eagle Mem updated to v${version}."