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.
- package/README.md +227 -0
- package/bin/eagle-mem +36 -0
- package/db/002_overviews.sql +16 -0
- package/db/003_code_chunks.sql +46 -0
- package/db/migrate.sh +47 -0
- package/db/schema.sql +154 -0
- package/hooks/post-tool-use.sh +76 -0
- package/hooks/session-end.sh +25 -0
- package/hooks/session-start.sh +161 -0
- package/hooks/stop.sh +147 -0
- package/hooks/user-prompt-submit.sh +102 -0
- package/lib/common.sh +63 -0
- package/lib/db.sh +186 -0
- package/package.json +31 -0
- package/scripts/help.sh +50 -0
- package/scripts/index.sh +196 -0
- package/scripts/install.sh +238 -0
- package/scripts/scan.sh +362 -0
- package/scripts/style.sh +89 -0
- package/scripts/uninstall.sh +63 -0
- package/scripts/update.sh +106 -0
- package/skills/eagle-mem-overview/SKILL.md +103 -0
- package/skills/eagle-mem-search/SKILL.md +126 -0
- package/skills/eagle-mem-tasks/SKILL.md +123 -0
package/scripts/scan.sh
ADDED
|
@@ -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 ""
|
package/scripts/style.sh
ADDED
|
@@ -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}."
|