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/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "eagle-mem",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Lightweight persistent memory for Claude Code — SQLite + FTS5, no daemon, no bloat",
|
|
5
|
+
"bin": {
|
|
6
|
+
"eagle-mem": "bin/eagle-mem"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin/",
|
|
10
|
+
"scripts/",
|
|
11
|
+
"hooks/",
|
|
12
|
+
"lib/",
|
|
13
|
+
"db/",
|
|
14
|
+
"skills/"
|
|
15
|
+
],
|
|
16
|
+
"keywords": [
|
|
17
|
+
"claude-code",
|
|
18
|
+
"memory",
|
|
19
|
+
"sqlite",
|
|
20
|
+
"fts5",
|
|
21
|
+
"hooks",
|
|
22
|
+
"persistent-memory"
|
|
23
|
+
],
|
|
24
|
+
"author": "eagleisbatman",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/eagleisbatman/eagle-mem.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/eagleisbatman/eagle-mem#readme"
|
|
31
|
+
}
|
package/scripts/help.sh
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Help
|
|
4
|
+
# ═══════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
7
|
+
PACKAGE_DIR="$(cd "$SCRIPTS_DIR/.." && pwd)"
|
|
8
|
+
|
|
9
|
+
. "$SCRIPTS_DIR/style.sh"
|
|
10
|
+
|
|
11
|
+
version=$(node -e "console.log(require('$PACKAGE_DIR/package.json').version)" 2>/dev/null || echo "unknown")
|
|
12
|
+
|
|
13
|
+
eagle_banner
|
|
14
|
+
|
|
15
|
+
echo -e " ${BOLD}Eagle Mem${RESET} ${DIM}v${version}${RESET}"
|
|
16
|
+
echo -e " ${DIM}Lightweight persistent memory for Claude Code${RESET}"
|
|
17
|
+
echo ""
|
|
18
|
+
echo -e " ${BOLD}Usage:${RESET}"
|
|
19
|
+
echo -e " eagle-mem ${CYAN}<command>${RESET}"
|
|
20
|
+
echo ""
|
|
21
|
+
echo -e " ${BOLD}Commands:${RESET}"
|
|
22
|
+
echo -e " ${CYAN}install${RESET} Set up hooks, database, and skills"
|
|
23
|
+
echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
|
|
24
|
+
echo -e " ${CYAN}update${RESET} Re-deploy hooks and run new migrations"
|
|
25
|
+
echo -e " ${CYAN}scan${RESET} Analyze a project and generate an overview"
|
|
26
|
+
echo -e " ${CYAN}index${RESET} Index source files for code-level search"
|
|
27
|
+
echo -e " ${CYAN}help${RESET} Show this help message"
|
|
28
|
+
echo -e " ${CYAN}version${RESET} Show version number"
|
|
29
|
+
echo ""
|
|
30
|
+
echo -e " ${BOLD}Examples:${RESET}"
|
|
31
|
+
echo -e " ${DIM}\$${RESET} eagle-mem install ${DIM}# First-time setup${RESET}"
|
|
32
|
+
echo -e " ${DIM}\$${RESET} eagle-mem update ${DIM}# After npm update${RESET}"
|
|
33
|
+
echo -e " ${DIM}\$${RESET} eagle-mem scan . ${DIM}# Scan current project${RESET}"
|
|
34
|
+
echo -e " ${DIM}\$${RESET} eagle-mem index . ${DIM}# Index source files${RESET}"
|
|
35
|
+
echo -e " ${DIM}\$${RESET} eagle-mem uninstall ${DIM}# Clean removal${RESET}"
|
|
36
|
+
echo ""
|
|
37
|
+
echo -e " ${BOLD}What it does:${RESET}"
|
|
38
|
+
echo -e " ${DOT} Saves session summaries to a shared SQLite database"
|
|
39
|
+
echo -e " ${DOT} Injects relevant memory at session start"
|
|
40
|
+
echo -e " ${DOT} Searches past sessions when you ask related questions"
|
|
41
|
+
echo -e " ${DOT} Tracks file operations across sessions"
|
|
42
|
+
echo -e " ${DOT} Provides task management for complex multi-step work"
|
|
43
|
+
echo ""
|
|
44
|
+
echo -e " ${BOLD}Skills${RESET} ${DIM}(available inside Claude Code):${RESET}"
|
|
45
|
+
echo -e " ${CYAN}/eagle-mem-search${RESET} Search past sessions and observations"
|
|
46
|
+
echo -e " ${CYAN}/eagle-mem-tasks${RESET} Break work into tracked subtasks"
|
|
47
|
+
echo -e " ${CYAN}/eagle-mem-overview${RESET} Generate a persistent project summary"
|
|
48
|
+
echo ""
|
|
49
|
+
echo -e " ${DIM}https://github.com/eagleisbatman/eagle-mem${RESET}"
|
|
50
|
+
echo ""
|
package/scripts/index.sh
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Index
|
|
4
|
+
# Chunks source files and indexes them for FTS5 code search
|
|
5
|
+
# Incremental: only re-indexes files that changed since last run
|
|
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
|
+
CHUNK_SIZE="${EAGLE_MEM_CHUNK_SIZE:-80}"
|
|
23
|
+
if ! [[ "$CHUNK_SIZE" =~ ^[0-9]+$ ]] || [ "$CHUNK_SIZE" -lt 1 ]; then
|
|
24
|
+
CHUNK_SIZE=80
|
|
25
|
+
fi
|
|
26
|
+
MAX_FILE_SIZE=1048576 # 1MB
|
|
27
|
+
|
|
28
|
+
eagle_header "Index"
|
|
29
|
+
eagle_info "Indexing ${BOLD}$PROJECT${RESET} at $TARGET_DIR"
|
|
30
|
+
echo ""
|
|
31
|
+
|
|
32
|
+
# ─── Source file extensions to index ───────────────────────
|
|
33
|
+
|
|
34
|
+
SOURCE_EXTS="sh|bash|zsh|js|jsx|mjs|cjs|ts|tsx|mts|py|rb|go|rs|java|kt|kts|swift|c|h|cpp|cc|cxx|hpp|cs|php|sql|html|htm|css|scss|vue|svelte|dart|ex|exs|zig|lua|r|scala|yaml|yml|toml|json|md"
|
|
35
|
+
|
|
36
|
+
ext_to_lang() {
|
|
37
|
+
case "$1" in
|
|
38
|
+
sh|bash|zsh) echo "Bash" ;;
|
|
39
|
+
js|jsx|mjs|cjs) echo "JavaScript" ;;
|
|
40
|
+
ts|tsx|mts) echo "TypeScript" ;;
|
|
41
|
+
py) echo "Python" ;;
|
|
42
|
+
rb) echo "Ruby" ;;
|
|
43
|
+
go) echo "Go" ;;
|
|
44
|
+
rs) echo "Rust" ;;
|
|
45
|
+
java) echo "Java" ;;
|
|
46
|
+
kt|kts) echo "Kotlin" ;;
|
|
47
|
+
swift) echo "Swift" ;;
|
|
48
|
+
c|h) echo "C" ;;
|
|
49
|
+
cpp|cc|cxx|hpp) echo "C++" ;;
|
|
50
|
+
cs) echo "C#" ;;
|
|
51
|
+
php) echo "PHP" ;;
|
|
52
|
+
sql) echo "SQL" ;;
|
|
53
|
+
html|htm) echo "HTML" ;;
|
|
54
|
+
css|scss|sass|less) echo "CSS" ;;
|
|
55
|
+
vue) echo "Vue" ;;
|
|
56
|
+
svelte) echo "Svelte" ;;
|
|
57
|
+
dart) echo "Dart" ;;
|
|
58
|
+
ex|exs) echo "Elixir" ;;
|
|
59
|
+
zig) echo "Zig" ;;
|
|
60
|
+
lua) echo "Lua" ;;
|
|
61
|
+
r) echo "R" ;;
|
|
62
|
+
scala) echo "Scala" ;;
|
|
63
|
+
yaml|yml) echo "YAML" ;;
|
|
64
|
+
toml) echo "TOML" ;;
|
|
65
|
+
json) echo "JSON" ;;
|
|
66
|
+
md) echo "Markdown" ;;
|
|
67
|
+
*) echo "" ;;
|
|
68
|
+
esac
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
# ─── Collect files ─────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
TMPDIR_IDX=$(mktemp -d)
|
|
74
|
+
trap 'rm -rf "$TMPDIR_IDX"' EXIT
|
|
75
|
+
|
|
76
|
+
ALL_FILES="$TMPDIR_IDX/all_files"
|
|
77
|
+
|
|
78
|
+
eagle_collect_files "$TARGET_DIR" "$ALL_FILES"
|
|
79
|
+
|
|
80
|
+
# Filter to source files only, skip large files
|
|
81
|
+
SOURCE_FILES="$TMPDIR_IDX/source_files"
|
|
82
|
+
while IFS= read -r file; do
|
|
83
|
+
ext="${file##*.}"
|
|
84
|
+
[ "$ext" = "$file" ] && continue
|
|
85
|
+
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
|
86
|
+
echo "$ext" | grep -qE "^($SOURCE_EXTS)$" || continue
|
|
87
|
+
full_path="$TARGET_DIR/$file"
|
|
88
|
+
[ ! -f "$full_path" ] && continue
|
|
89
|
+
file_size=$(wc -c < "$full_path" 2>/dev/null | tr -d ' ')
|
|
90
|
+
[ "$file_size" -gt "$MAX_FILE_SIZE" ] && continue
|
|
91
|
+
echo "$file"
|
|
92
|
+
done < "$ALL_FILES" > "$SOURCE_FILES"
|
|
93
|
+
|
|
94
|
+
total_source=$(wc -l < "$SOURCE_FILES" | tr -d ' ')
|
|
95
|
+
eagle_ok "$total_source source files found"
|
|
96
|
+
|
|
97
|
+
if [ "$total_source" -eq 0 ]; then
|
|
98
|
+
eagle_info "Nothing to index"
|
|
99
|
+
exit 0
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# ─── Check which files need re-indexing ────────────────────
|
|
103
|
+
|
|
104
|
+
project_sql=$(eagle_sql_escape "$PROJECT")
|
|
105
|
+
NEEDS_INDEX="$TMPDIR_IDX/needs_index"
|
|
106
|
+
|
|
107
|
+
indexed_count=0
|
|
108
|
+
skipped_count=0
|
|
109
|
+
|
|
110
|
+
while IFS= read -r file; do
|
|
111
|
+
full_path="$TARGET_DIR/$file"
|
|
112
|
+
current_mtime=$(stat -f '%m' "$full_path" 2>/dev/null || stat -c '%Y' "$full_path" 2>/dev/null || echo "0")
|
|
113
|
+
|
|
114
|
+
stored_mtime=$(eagle_db "SELECT MAX(mtime) FROM code_chunks WHERE project = '$project_sql' AND file_path = '$(eagle_sql_escape "$file")';")
|
|
115
|
+
|
|
116
|
+
if [ -n "$stored_mtime" ] && [ "$stored_mtime" = "$current_mtime" ]; then
|
|
117
|
+
skipped_count=$((skipped_count + 1))
|
|
118
|
+
continue
|
|
119
|
+
fi
|
|
120
|
+
|
|
121
|
+
echo "$file"
|
|
122
|
+
done < "$SOURCE_FILES" > "$NEEDS_INDEX"
|
|
123
|
+
|
|
124
|
+
needs_count=$(wc -l < "$NEEDS_INDEX" | tr -d ' ')
|
|
125
|
+
|
|
126
|
+
if [ "$skipped_count" -gt 0 ]; then
|
|
127
|
+
eagle_ok "$skipped_count files unchanged (skipped)"
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
if [ "$needs_count" -eq 0 ]; then
|
|
131
|
+
eagle_ok "Index is up to date"
|
|
132
|
+
eagle_footer "Nothing to index."
|
|
133
|
+
exit 0
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
eagle_info "$needs_count files to index"
|
|
137
|
+
|
|
138
|
+
# ─── Chunk and index files ─────────────────────────────────
|
|
139
|
+
|
|
140
|
+
echo ""
|
|
141
|
+
|
|
142
|
+
chunk_count=0
|
|
143
|
+
file_count=0
|
|
144
|
+
|
|
145
|
+
while IFS= read -r file; do
|
|
146
|
+
full_path="$TARGET_DIR/$file"
|
|
147
|
+
ext="${file##*.}"
|
|
148
|
+
ext=$(echo "$ext" | tr '[:upper:]' '[:lower:]')
|
|
149
|
+
lang=$(ext_to_lang "$ext")
|
|
150
|
+
current_mtime=$(stat -f '%m' "$full_path" 2>/dev/null || stat -c '%Y' "$full_path" 2>/dev/null || echo "0")
|
|
151
|
+
file_sql=$(eagle_sql_escape "$file")
|
|
152
|
+
lang_sql=$(eagle_sql_escape "$lang")
|
|
153
|
+
|
|
154
|
+
eagle_db "DELETE FROM code_chunks WHERE project = '$project_sql' AND file_path = '$file_sql';"
|
|
155
|
+
|
|
156
|
+
total_lines=$(wc -l < "$full_path" 2>/dev/null | tr -d ' ')
|
|
157
|
+
[ "$total_lines" -eq 0 ] && continue
|
|
158
|
+
|
|
159
|
+
start=1
|
|
160
|
+
while [ "$start" -le "$total_lines" ]; do
|
|
161
|
+
end=$((start + CHUNK_SIZE - 1))
|
|
162
|
+
[ "$end" -gt "$total_lines" ] && end="$total_lines"
|
|
163
|
+
|
|
164
|
+
content=$(sed -n "${start},${end}p" "$full_path")
|
|
165
|
+
content_sql=$(eagle_sql_escape "$content")
|
|
166
|
+
|
|
167
|
+
eagle_db "INSERT INTO code_chunks (project, file_path, language, start_line, end_line, content, mtime)
|
|
168
|
+
VALUES ('$project_sql', '$file_sql', '$lang_sql', $start, $end, '$content_sql', $current_mtime);"
|
|
169
|
+
|
|
170
|
+
chunk_count=$((chunk_count + 1))
|
|
171
|
+
start=$((end + 1))
|
|
172
|
+
done
|
|
173
|
+
|
|
174
|
+
file_count=$((file_count + 1))
|
|
175
|
+
|
|
176
|
+
if [ $((file_count % 10)) -eq 0 ]; then
|
|
177
|
+
printf " \r ${ARROW} Indexed %d / %d files (%d chunks)..." "$file_count" "$needs_count" "$chunk_count"
|
|
178
|
+
fi
|
|
179
|
+
done < "$NEEDS_INDEX"
|
|
180
|
+
|
|
181
|
+
echo ""
|
|
182
|
+
eagle_ok "Indexed $file_count files ($chunk_count chunks)"
|
|
183
|
+
|
|
184
|
+
# ─── Summary ──────────────────────────────────────────────
|
|
185
|
+
|
|
186
|
+
total_chunks=$(eagle_db "SELECT COUNT(*) FROM code_chunks WHERE project = '$project_sql';")
|
|
187
|
+
total_indexed=$(eagle_db "SELECT COUNT(DISTINCT file_path) FROM code_chunks WHERE project = '$project_sql';")
|
|
188
|
+
|
|
189
|
+
echo ""
|
|
190
|
+
eagle_footer "Index complete."
|
|
191
|
+
eagle_kv "Project:" "$PROJECT"
|
|
192
|
+
eagle_kv "Files indexed:" "$total_indexed"
|
|
193
|
+
eagle_kv "Total chunks:" "$total_chunks"
|
|
194
|
+
eagle_kv "Chunk size:" "$CHUNK_SIZE lines"
|
|
195
|
+
eagle_kv "Database:" "$EAGLE_MEM_DB"
|
|
196
|
+
echo ""
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Install
|
|
4
|
+
# Sets up hooks, database, and skills for Claude Code
|
|
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_banner
|
|
17
|
+
eagle_header "Install"
|
|
18
|
+
|
|
19
|
+
# ─── Detect platform ───────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
detect_pkg_manager() {
|
|
22
|
+
if command -v brew &>/dev/null; then
|
|
23
|
+
echo "brew"
|
|
24
|
+
elif command -v apt-get &>/dev/null; then
|
|
25
|
+
echo "apt"
|
|
26
|
+
elif command -v dnf &>/dev/null; then
|
|
27
|
+
echo "dnf"
|
|
28
|
+
elif command -v pacman &>/dev/null; then
|
|
29
|
+
echo "pacman"
|
|
30
|
+
else
|
|
31
|
+
echo ""
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
install_package() {
|
|
36
|
+
local pkg="$1"
|
|
37
|
+
local mgr
|
|
38
|
+
mgr=$(detect_pkg_manager)
|
|
39
|
+
|
|
40
|
+
case "$mgr" in
|
|
41
|
+
brew) eagle_info "Running: brew install $pkg"; brew install "$pkg" ;;
|
|
42
|
+
apt) eagle_info "Running: sudo apt-get install -y $pkg"; sudo apt-get install -y "$pkg" ;;
|
|
43
|
+
dnf) eagle_info "Running: sudo dnf install -y $pkg"; sudo dnf install -y "$pkg" ;;
|
|
44
|
+
pacman) eagle_info "Running: sudo pacman -S --noconfirm $pkg"; sudo pacman -S --noconfirm "$pkg" ;;
|
|
45
|
+
*)
|
|
46
|
+
eagle_fail "No package manager found. Install $pkg manually and re-run."
|
|
47
|
+
exit 1
|
|
48
|
+
;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ─── Check prerequisites ───────────────────────────────────
|
|
53
|
+
|
|
54
|
+
echo -e " ${BOLD}Checking prerequisites...${RESET}"
|
|
55
|
+
echo ""
|
|
56
|
+
|
|
57
|
+
prereqs_ok=true
|
|
58
|
+
|
|
59
|
+
# sqlite3
|
|
60
|
+
if command -v sqlite3 &>/dev/null; then
|
|
61
|
+
sqlite_version=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
|
|
62
|
+
eagle_ok "sqlite3 ${DIM}($sqlite_version)${RESET}"
|
|
63
|
+
else
|
|
64
|
+
eagle_fail "sqlite3 not found"
|
|
65
|
+
if eagle_confirm "Install sqlite3?"; then
|
|
66
|
+
install_package sqlite3
|
|
67
|
+
if command -v sqlite3 &>/dev/null; then
|
|
68
|
+
sqlite_version=$(sqlite3 --version 2>/dev/null | awk '{print $1}')
|
|
69
|
+
eagle_ok "sqlite3 installed ${DIM}($sqlite_version)${RESET}"
|
|
70
|
+
else
|
|
71
|
+
eagle_fail "sqlite3 installation failed"
|
|
72
|
+
prereqs_ok=false
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
prereqs_ok=false
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# FTS5 support
|
|
80
|
+
if command -v sqlite3 &>/dev/null; then
|
|
81
|
+
if sqlite3 :memory: "SELECT sqlite_compileoption_used('ENABLE_FTS5');" 2>/dev/null | grep -q "1"; then
|
|
82
|
+
eagle_ok "FTS5 support"
|
|
83
|
+
else
|
|
84
|
+
eagle_fail "SQLite was compiled without FTS5 support"
|
|
85
|
+
eagle_dim "On macOS: brew install sqlite3 (system sqlite3 includes FTS5)"
|
|
86
|
+
eagle_dim "On Linux: install libsqlite3-dev or rebuild with --enable-fts5"
|
|
87
|
+
prereqs_ok=false
|
|
88
|
+
fi
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
# jq
|
|
92
|
+
if command -v jq &>/dev/null; then
|
|
93
|
+
jq_version=$(jq --version 2>/dev/null | sed 's/jq-//')
|
|
94
|
+
eagle_ok "jq ${DIM}($jq_version)${RESET}"
|
|
95
|
+
else
|
|
96
|
+
eagle_fail "jq not found"
|
|
97
|
+
if eagle_confirm "Install jq?"; then
|
|
98
|
+
install_package jq
|
|
99
|
+
if command -v jq &>/dev/null; then
|
|
100
|
+
jq_version=$(jq --version 2>/dev/null | sed 's/jq-//')
|
|
101
|
+
eagle_ok "jq installed ${DIM}($jq_version)${RESET}"
|
|
102
|
+
else
|
|
103
|
+
eagle_fail "jq installation failed"
|
|
104
|
+
prereqs_ok=false
|
|
105
|
+
fi
|
|
106
|
+
else
|
|
107
|
+
prereqs_ok=false
|
|
108
|
+
fi
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
# Claude Code
|
|
112
|
+
if [ -d "$HOME/.claude" ]; then
|
|
113
|
+
eagle_ok "Claude Code ${DIM}(~/.claude/)${RESET}"
|
|
114
|
+
else
|
|
115
|
+
eagle_fail "Claude Code not found (~/.claude/ does not exist)"
|
|
116
|
+
echo ""
|
|
117
|
+
eagle_dim "Install Claude Code first:"
|
|
118
|
+
eagle_dim " npm install -g @anthropic-ai/claude-code"
|
|
119
|
+
eagle_dim " https://docs.anthropic.com/en/docs/claude-code"
|
|
120
|
+
echo ""
|
|
121
|
+
exit 1
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [ "$prereqs_ok" = false ]; then
|
|
125
|
+
echo ""
|
|
126
|
+
eagle_err "Prerequisites not met. Fix the issues above and re-run."
|
|
127
|
+
exit 1
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
echo ""
|
|
131
|
+
|
|
132
|
+
# ─── Copy files to ~/.eagle-mem/ ───────────────────────────
|
|
133
|
+
|
|
134
|
+
echo -e " ${BOLD}Installing Eagle Mem...${RESET}"
|
|
135
|
+
echo ""
|
|
136
|
+
|
|
137
|
+
mkdir -p "$EAGLE_MEM_DIR"/{hooks,lib,db}
|
|
138
|
+
|
|
139
|
+
cp "$PACKAGE_DIR"/hooks/*.sh "$EAGLE_MEM_DIR/hooks/"
|
|
140
|
+
cp "$PACKAGE_DIR"/lib/*.sh "$EAGLE_MEM_DIR/lib/"
|
|
141
|
+
cp "$PACKAGE_DIR"/db/*.sh "$EAGLE_MEM_DIR/db/"
|
|
142
|
+
cp "$PACKAGE_DIR"/db/*.sql "$EAGLE_MEM_DIR/db/"
|
|
143
|
+
|
|
144
|
+
chmod +x "$EAGLE_MEM_DIR"/hooks/*.sh
|
|
145
|
+
chmod +x "$EAGLE_MEM_DIR"/db/migrate.sh
|
|
146
|
+
|
|
147
|
+
eagle_ok "Files copied to $EAGLE_MEM_DIR"
|
|
148
|
+
|
|
149
|
+
# ─── Run migrations ────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
"$EAGLE_MEM_DIR/db/migrate.sh" 2>/dev/null | grep -v -E '^(wal|5000|Eagle Mem database)$' > /dev/null
|
|
152
|
+
eagle_ok "Database ready"
|
|
153
|
+
|
|
154
|
+
# ─── Patch settings.json ───────────────────────────────────
|
|
155
|
+
|
|
156
|
+
if [ ! -f "$SETTINGS" ]; then
|
|
157
|
+
echo '{}' > "$SETTINGS"
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
patch_hook() {
|
|
161
|
+
local event="$1"
|
|
162
|
+
local matcher="$2"
|
|
163
|
+
local command="$3"
|
|
164
|
+
local description="$4"
|
|
165
|
+
|
|
166
|
+
if jq -e ".hooks.${event}[]? | select(.hooks[]?.command == \"$command\")" "$SETTINGS" &>/dev/null; then
|
|
167
|
+
eagle_ok "$description ${DIM}(already registered)${RESET}"
|
|
168
|
+
return
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
local entry
|
|
172
|
+
if [ -n "$matcher" ]; then
|
|
173
|
+
entry="{\"matcher\": \"$matcher\", \"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
174
|
+
else
|
|
175
|
+
entry="{\"hooks\": [{\"type\": \"command\", \"command\": \"$command\"}]}"
|
|
176
|
+
fi
|
|
177
|
+
|
|
178
|
+
local tmp
|
|
179
|
+
tmp=$(mktemp)
|
|
180
|
+
jq --argjson entry "$entry" ".hooks.${event} = ((.hooks.${event} // []) + [\$entry])" "$SETTINGS" > "$tmp" && mv "$tmp" "$SETTINGS"
|
|
181
|
+
eagle_ok "$description"
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
patch_hook "SessionStart" "" \
|
|
185
|
+
"$EAGLE_MEM_DIR/hooks/session-start.sh" \
|
|
186
|
+
"SessionStart hook"
|
|
187
|
+
|
|
188
|
+
patch_hook "Stop" "" \
|
|
189
|
+
"$EAGLE_MEM_DIR/hooks/stop.sh" \
|
|
190
|
+
"Stop hook"
|
|
191
|
+
|
|
192
|
+
patch_hook "PostToolUse" "Read|Write|Edit|Bash" \
|
|
193
|
+
"$EAGLE_MEM_DIR/hooks/post-tool-use.sh" \
|
|
194
|
+
"PostToolUse hook"
|
|
195
|
+
|
|
196
|
+
patch_hook "SessionEnd" "" \
|
|
197
|
+
"$EAGLE_MEM_DIR/hooks/session-end.sh" \
|
|
198
|
+
"SessionEnd hook"
|
|
199
|
+
|
|
200
|
+
patch_hook "UserPromptSubmit" "" \
|
|
201
|
+
"$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh" \
|
|
202
|
+
"UserPromptSubmit hook"
|
|
203
|
+
|
|
204
|
+
# ─── Install skills ────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
SKILLS_DIR="$HOME/.claude/skills"
|
|
207
|
+
if [ -d "$SKILLS_DIR" ] && [ -d "$PACKAGE_DIR/skills" ]; then
|
|
208
|
+
for skill_dir in "$PACKAGE_DIR"/skills/*/; do
|
|
209
|
+
[ ! -d "$skill_dir" ] && continue
|
|
210
|
+
skill_name=$(basename "$skill_dir")
|
|
211
|
+
dst="$SKILLS_DIR/$skill_name"
|
|
212
|
+
[ -L "$dst" ] && rm "$dst"
|
|
213
|
+
ln -sf "$skill_dir" "$dst"
|
|
214
|
+
eagle_ok "Skill: $skill_name"
|
|
215
|
+
done
|
|
216
|
+
elif [ -d "$PACKAGE_DIR/skills" ]; then
|
|
217
|
+
mkdir -p "$SKILLS_DIR"
|
|
218
|
+
for skill_dir in "$PACKAGE_DIR"/skills/*/; do
|
|
219
|
+
[ ! -d "$skill_dir" ] && continue
|
|
220
|
+
skill_name=$(basename "$skill_dir")
|
|
221
|
+
dst="$SKILLS_DIR/$skill_name"
|
|
222
|
+
[ -L "$dst" ] && rm "$dst"
|
|
223
|
+
ln -sf "$skill_dir" "$dst"
|
|
224
|
+
eagle_ok "Skill: $skill_name"
|
|
225
|
+
done
|
|
226
|
+
fi
|
|
227
|
+
|
|
228
|
+
# ─── Summary ───────────────────────────────────────────────
|
|
229
|
+
|
|
230
|
+
eagle_footer "Eagle Mem installed successfully."
|
|
231
|
+
|
|
232
|
+
eagle_kv "Database:" "$EAGLE_MEM_DIR/memory.db"
|
|
233
|
+
eagle_kv "Hooks:" "$EAGLE_MEM_DIR/hooks/"
|
|
234
|
+
eagle_kv "Settings:" "$SETTINGS"
|
|
235
|
+
|
|
236
|
+
echo ""
|
|
237
|
+
eagle_dim "Start a new Claude Code session — Eagle Mem will activate automatically."
|
|
238
|
+
echo ""
|