eagle-mem 2.0.7 → 3.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/bin/eagle-mem +3 -0
- package/db/013_features.sql +69 -0
- package/db/014_command_intelligence.sql +25 -0
- package/hooks/post-tool-use.sh +97 -18
- package/hooks/pre-tool-use.sh +124 -0
- package/hooks/session-start.sh +4 -0
- package/hooks/stop.sh +11 -0
- package/lib/common.sh +22 -0
- package/lib/db.sh +144 -2
- package/lib/provider.sh +330 -0
- package/package.json +1 -1
- package/scripts/config.sh +72 -0
- package/scripts/curate.sh +349 -0
- package/scripts/feature.sh +110 -0
- package/scripts/help.sh +3 -0
- package/scripts/index.sh +1 -1
- package/scripts/install.sh +14 -0
- package/scripts/update.sh +1 -0
package/lib/provider.sh
ADDED
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — LLM Provider Abstraction
|
|
4
|
+
# Config parsing + unified eagle_llm_call for Ollama/Anthropic/OpenAI
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
|
|
7
|
+
EAGLE_CONFIG_FILE="${EAGLE_MEM_DIR}/config.toml"
|
|
8
|
+
EAGLE_DEFAULT_OLLAMA_URL="http://localhost:11434"
|
|
9
|
+
|
|
10
|
+
# ─── Config parsing ────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
eagle_config_get() {
|
|
13
|
+
local section="$1"
|
|
14
|
+
local key="$2"
|
|
15
|
+
local default="${3:-}"
|
|
16
|
+
|
|
17
|
+
if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
|
|
18
|
+
echo "$default"
|
|
19
|
+
return
|
|
20
|
+
fi
|
|
21
|
+
|
|
22
|
+
local value
|
|
23
|
+
value=$(awk -v section="$section" -v key="$key" '
|
|
24
|
+
/^[[:space:]]*\[/ {
|
|
25
|
+
gsub(/[\[\][:space:]]/, "")
|
|
26
|
+
current = $0
|
|
27
|
+
}
|
|
28
|
+
current == section && /^[[:space:]]*[^#\[]/ {
|
|
29
|
+
split($0, parts, "=")
|
|
30
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", parts[1])
|
|
31
|
+
if (parts[1] == key) {
|
|
32
|
+
val = substr($0, index($0, "=") + 1)
|
|
33
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
|
|
34
|
+
gsub(/^["'"'"']|["'"'"']$/, "", val)
|
|
35
|
+
print val
|
|
36
|
+
exit
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
' "$EAGLE_CONFIG_FILE")
|
|
40
|
+
|
|
41
|
+
if [ -n "$value" ]; then
|
|
42
|
+
echo "$value"
|
|
43
|
+
else
|
|
44
|
+
echo "$default"
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
eagle_config_set() {
|
|
49
|
+
local section="$1"
|
|
50
|
+
local key="$2"
|
|
51
|
+
local value="$3"
|
|
52
|
+
|
|
53
|
+
if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
|
|
54
|
+
eagle_config_init
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
if grep -q "^\[${section}\]" "$EAGLE_CONFIG_FILE" 2>/dev/null; then
|
|
58
|
+
if grep -q "^[[:space:]]*${key}[[:space:]]*=" "$EAGLE_CONFIG_FILE" 2>/dev/null; then
|
|
59
|
+
sed -i '' "s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = \"${value}\"|" "$EAGLE_CONFIG_FILE"
|
|
60
|
+
else
|
|
61
|
+
sed -i '' "/^\[${section}\]/a\\
|
|
62
|
+
${key} = \"${value}\"
|
|
63
|
+
" "$EAGLE_CONFIG_FILE"
|
|
64
|
+
fi
|
|
65
|
+
else
|
|
66
|
+
printf '\n[%s]\n%s = "%s"\n' "$section" "$key" "$value" >> "$EAGLE_CONFIG_FILE"
|
|
67
|
+
fi
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# ─── Ollama detection ──────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
eagle_detect_ollama() {
|
|
73
|
+
local url="${1:-$EAGLE_DEFAULT_OLLAMA_URL}"
|
|
74
|
+
curl -sf "${url}/api/tags" --connect-timeout 2 --max-time 3 2>/dev/null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
eagle_ollama_models() {
|
|
78
|
+
local url="${1:-$EAGLE_DEFAULT_OLLAMA_URL}"
|
|
79
|
+
eagle_detect_ollama "$url" | jq -r '.models[].name' 2>/dev/null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
eagle_ollama_best_model() {
|
|
83
|
+
local models
|
|
84
|
+
models=$(eagle_ollama_models "$1")
|
|
85
|
+
[ -z "$models" ] && return 1
|
|
86
|
+
|
|
87
|
+
local preferred="mistral qwen3-coder gemma4 llama3 phi3 deepseek-coder"
|
|
88
|
+
for pref in $preferred; do
|
|
89
|
+
if echo "$models" | grep -qi "$pref"; then
|
|
90
|
+
echo "$models" | grep -i "$pref" | head -1
|
|
91
|
+
return 0
|
|
92
|
+
fi
|
|
93
|
+
done
|
|
94
|
+
|
|
95
|
+
echo "$models" | head -1
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
# ─── Config initialization ─────────────────────────────────
|
|
99
|
+
|
|
100
|
+
eagle_config_init() {
|
|
101
|
+
local ollama_url="$EAGLE_DEFAULT_OLLAMA_URL"
|
|
102
|
+
local provider="none"
|
|
103
|
+
local model=""
|
|
104
|
+
|
|
105
|
+
local ollama_response
|
|
106
|
+
ollama_response=$(eagle_detect_ollama "$ollama_url")
|
|
107
|
+
if [ -n "$ollama_response" ]; then
|
|
108
|
+
provider="ollama"
|
|
109
|
+
model=$(eagle_ollama_best_model "$ollama_url")
|
|
110
|
+
elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then
|
|
111
|
+
provider="anthropic"
|
|
112
|
+
model="claude-haiku-4-5-20251001"
|
|
113
|
+
elif [ -n "${OPENAI_API_KEY:-}" ]; then
|
|
114
|
+
provider="openai"
|
|
115
|
+
model="gpt-4o-mini"
|
|
116
|
+
fi
|
|
117
|
+
|
|
118
|
+
cat > "$EAGLE_CONFIG_FILE" << TOML
|
|
119
|
+
# Eagle Mem configuration
|
|
120
|
+
# Docs: https://github.com/eagleisbatman/eagle-mem
|
|
121
|
+
|
|
122
|
+
[provider]
|
|
123
|
+
# Which LLM provider to use for the curator and analysis features
|
|
124
|
+
# Options: "ollama" (free, local), "anthropic", "openai"
|
|
125
|
+
type = "$provider"
|
|
126
|
+
|
|
127
|
+
[ollama]
|
|
128
|
+
url = "$ollama_url"
|
|
129
|
+
model = "${model:-mistral}"
|
|
130
|
+
|
|
131
|
+
[anthropic]
|
|
132
|
+
# Uses ANTHROPIC_API_KEY env var for authentication
|
|
133
|
+
model = "claude-haiku-4-5-20251001"
|
|
134
|
+
|
|
135
|
+
[openai]
|
|
136
|
+
# Uses OPENAI_API_KEY env var for authentication
|
|
137
|
+
model = "gpt-4o-mini"
|
|
138
|
+
|
|
139
|
+
[curator]
|
|
140
|
+
# How often the curator runs: "manual", "daily", "weekly"
|
|
141
|
+
schedule = "manual"
|
|
142
|
+
|
|
143
|
+
[redaction]
|
|
144
|
+
# Additional secret patterns (regex) beyond built-in defaults
|
|
145
|
+
# extra_patterns = ["MY_CUSTOM_SECRET_.*"]
|
|
146
|
+
TOML
|
|
147
|
+
|
|
148
|
+
eagle_log "INFO" "Config initialized: provider=$provider model=$model"
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# ─── Unified LLM call ─────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
eagle_llm_call() {
|
|
154
|
+
local prompt="$1"
|
|
155
|
+
local system_prompt="${2:-You are a helpful assistant that analyzes software development sessions.}"
|
|
156
|
+
local max_tokens="${3:-1024}"
|
|
157
|
+
|
|
158
|
+
local provider
|
|
159
|
+
provider=$(eagle_config_get "provider" "type" "none")
|
|
160
|
+
|
|
161
|
+
case "$provider" in
|
|
162
|
+
ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
|
|
163
|
+
anthropic) _eagle_call_anthropic "$prompt" "$system_prompt" "$max_tokens" ;;
|
|
164
|
+
openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
|
|
165
|
+
none)
|
|
166
|
+
eagle_log "ERROR" "No LLM provider configured. Run: eagle-mem config"
|
|
167
|
+
return 1
|
|
168
|
+
;;
|
|
169
|
+
*)
|
|
170
|
+
eagle_log "ERROR" "Unknown provider: $provider"
|
|
171
|
+
return 1
|
|
172
|
+
;;
|
|
173
|
+
esac
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
_eagle_call_ollama() {
|
|
177
|
+
local prompt="$1" system="$2" max_tokens="$3"
|
|
178
|
+
local url model
|
|
179
|
+
|
|
180
|
+
url=$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")
|
|
181
|
+
model=$(eagle_config_get "ollama" "model" "mistral")
|
|
182
|
+
|
|
183
|
+
local body
|
|
184
|
+
body=$(jq -nc \
|
|
185
|
+
--arg model "$model" \
|
|
186
|
+
--arg system "$system" \
|
|
187
|
+
--arg prompt "$prompt" \
|
|
188
|
+
--argjson tokens "$max_tokens" \
|
|
189
|
+
'{
|
|
190
|
+
model: $model,
|
|
191
|
+
messages: [
|
|
192
|
+
{role: "system", content: $system},
|
|
193
|
+
{role: "user", content: $prompt}
|
|
194
|
+
],
|
|
195
|
+
stream: false,
|
|
196
|
+
options: {num_predict: $tokens}
|
|
197
|
+
}')
|
|
198
|
+
|
|
199
|
+
local response
|
|
200
|
+
response=$(curl -sf "${url}/api/chat" \
|
|
201
|
+
--connect-timeout 5 \
|
|
202
|
+
--max-time 120 \
|
|
203
|
+
-H "Content-Type: application/json" \
|
|
204
|
+
-d "$body" 2>/dev/null)
|
|
205
|
+
|
|
206
|
+
if [ $? -ne 0 ] || [ -z "$response" ]; then
|
|
207
|
+
eagle_log "ERROR" "Ollama call failed: model=$model url=$url"
|
|
208
|
+
return 1
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
echo "$response" | jq -r '.message.content // empty'
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_eagle_call_anthropic() {
|
|
215
|
+
local prompt="$1" system="$2" max_tokens="$3"
|
|
216
|
+
local model api_key
|
|
217
|
+
|
|
218
|
+
model=$(eagle_config_get "anthropic" "model" "claude-haiku-4-5-20251001")
|
|
219
|
+
api_key="${ANTHROPIC_API_KEY:-}"
|
|
220
|
+
|
|
221
|
+
if [ -z "$api_key" ]; then
|
|
222
|
+
eagle_log "ERROR" "ANTHROPIC_API_KEY not set"
|
|
223
|
+
return 1
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
local body
|
|
227
|
+
body=$(jq -nc \
|
|
228
|
+
--arg model "$model" \
|
|
229
|
+
--arg system "$system" \
|
|
230
|
+
--arg prompt "$prompt" \
|
|
231
|
+
--argjson tokens "$max_tokens" \
|
|
232
|
+
'{
|
|
233
|
+
model: $model,
|
|
234
|
+
max_tokens: $tokens,
|
|
235
|
+
system: $system,
|
|
236
|
+
messages: [{role: "user", content: $prompt}]
|
|
237
|
+
}')
|
|
238
|
+
|
|
239
|
+
local response
|
|
240
|
+
response=$(curl -sf "https://api.anthropic.com/v1/messages" \
|
|
241
|
+
--connect-timeout 5 \
|
|
242
|
+
--max-time 120 \
|
|
243
|
+
-H "x-api-key: ${api_key}" \
|
|
244
|
+
-H "anthropic-version: 2023-06-01" \
|
|
245
|
+
-H "content-type: application/json" \
|
|
246
|
+
-d "$body" 2>/dev/null)
|
|
247
|
+
|
|
248
|
+
if [ $? -ne 0 ] || [ -z "$response" ]; then
|
|
249
|
+
eagle_log "ERROR" "Anthropic call failed: model=$model"
|
|
250
|
+
return 1
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
echo "$response" | jq -r '.content[0].text // empty'
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
_eagle_call_openai() {
|
|
257
|
+
local prompt="$1" system="$2" max_tokens="$3"
|
|
258
|
+
local model api_key
|
|
259
|
+
|
|
260
|
+
model=$(eagle_config_get "openai" "model" "gpt-4o-mini")
|
|
261
|
+
api_key="${OPENAI_API_KEY:-}"
|
|
262
|
+
|
|
263
|
+
if [ -z "$api_key" ]; then
|
|
264
|
+
eagle_log "ERROR" "OPENAI_API_KEY not set"
|
|
265
|
+
return 1
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
local body
|
|
269
|
+
body=$(jq -nc \
|
|
270
|
+
--arg model "$model" \
|
|
271
|
+
--arg system "$system" \
|
|
272
|
+
--arg prompt "$prompt" \
|
|
273
|
+
--argjson tokens "$max_tokens" \
|
|
274
|
+
'{
|
|
275
|
+
model: $model,
|
|
276
|
+
max_tokens: $tokens,
|
|
277
|
+
messages: [
|
|
278
|
+
{role: "system", content: $system},
|
|
279
|
+
{role: "user", content: $prompt}
|
|
280
|
+
]
|
|
281
|
+
}')
|
|
282
|
+
|
|
283
|
+
local response
|
|
284
|
+
response=$(curl -sf "https://api.openai.com/v1/chat/completions" \
|
|
285
|
+
--connect-timeout 5 \
|
|
286
|
+
--max-time 120 \
|
|
287
|
+
-H "Authorization: Bearer ${api_key}" \
|
|
288
|
+
-H "content-type: application/json" \
|
|
289
|
+
-d "$body" 2>/dev/null)
|
|
290
|
+
|
|
291
|
+
if [ $? -ne 0 ] || [ -z "$response" ]; then
|
|
292
|
+
eagle_log "ERROR" "OpenAI call failed: model=$model"
|
|
293
|
+
return 1
|
|
294
|
+
fi
|
|
295
|
+
|
|
296
|
+
echo "$response" | jq -r '.choices[0].message.content // empty'
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
# ─── Config CLI helpers ────────────────────────────────────
|
|
300
|
+
|
|
301
|
+
eagle_show_config() {
|
|
302
|
+
if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
|
|
303
|
+
echo "No config file found. Run: eagle-mem config init"
|
|
304
|
+
return 1
|
|
305
|
+
fi
|
|
306
|
+
|
|
307
|
+
local provider model
|
|
308
|
+
provider=$(eagle_config_get "provider" "type" "none")
|
|
309
|
+
model=$(eagle_config_get "$provider" "model" "unknown")
|
|
310
|
+
|
|
311
|
+
echo "Provider: $provider"
|
|
312
|
+
echo "Model: $model"
|
|
313
|
+
|
|
314
|
+
if [ "$provider" = "ollama" ]; then
|
|
315
|
+
local url
|
|
316
|
+
url=$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")
|
|
317
|
+
echo "URL: $url"
|
|
318
|
+
local running
|
|
319
|
+
running=$(eagle_detect_ollama "$url")
|
|
320
|
+
if [ -n "$running" ]; then
|
|
321
|
+
echo "Status: running"
|
|
322
|
+
echo "Models: $(eagle_ollama_models "$url" | tr '\n' ', ' | sed 's/,$//')"
|
|
323
|
+
else
|
|
324
|
+
echo "Status: not running"
|
|
325
|
+
fi
|
|
326
|
+
fi
|
|
327
|
+
|
|
328
|
+
echo ""
|
|
329
|
+
echo "Config: $EAGLE_CONFIG_FILE"
|
|
330
|
+
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# ═══════════════════════════════════════════════════════════
|
|
3
|
+
# Eagle Mem — Config management
|
|
4
|
+
# eagle-mem config [init|show|set|test]
|
|
5
|
+
# ═══════════════════════════════════════════════════════════
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
9
|
+
LIB_DIR="$SCRIPT_DIR/../lib"
|
|
10
|
+
|
|
11
|
+
. "$LIB_DIR/common.sh"
|
|
12
|
+
. "$SCRIPT_DIR/style.sh"
|
|
13
|
+
. "$LIB_DIR/provider.sh"
|
|
14
|
+
|
|
15
|
+
eagle_header "Config"
|
|
16
|
+
|
|
17
|
+
subcommand="${1:-show}"
|
|
18
|
+
shift 2>/dev/null || true
|
|
19
|
+
|
|
20
|
+
case "$subcommand" in
|
|
21
|
+
init)
|
|
22
|
+
eagle_config_init
|
|
23
|
+
eagle_ok "Config created: $EAGLE_CONFIG_FILE"
|
|
24
|
+
echo ""
|
|
25
|
+
eagle_show_config
|
|
26
|
+
;;
|
|
27
|
+
|
|
28
|
+
show|status)
|
|
29
|
+
eagle_show_config
|
|
30
|
+
;;
|
|
31
|
+
|
|
32
|
+
set)
|
|
33
|
+
key="${1:-}"
|
|
34
|
+
value="${2:-}"
|
|
35
|
+
if [ -z "$key" ] || [ -z "$value" ]; then
|
|
36
|
+
eagle_err "Usage: eagle-mem config set <section.key> <value>"
|
|
37
|
+
eagle_info "Examples:"
|
|
38
|
+
eagle_info " eagle-mem config set provider.type ollama"
|
|
39
|
+
eagle_info " eagle-mem config set ollama.model mistral"
|
|
40
|
+
eagle_info " eagle-mem config set anthropic.model claude-haiku-4-5-20251001"
|
|
41
|
+
exit 1
|
|
42
|
+
fi
|
|
43
|
+
section="${key%%.*}"
|
|
44
|
+
config_key="${key#*.}"
|
|
45
|
+
eagle_config_set "$section" "$config_key" "$value"
|
|
46
|
+
eagle_ok "Set [$section] $config_key = $value"
|
|
47
|
+
;;
|
|
48
|
+
|
|
49
|
+
test)
|
|
50
|
+
provider=$(eagle_config_get "provider" "type" "none")
|
|
51
|
+
if [ "$provider" = "none" ]; then
|
|
52
|
+
eagle_err "No provider configured. Run: eagle-mem config init"
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
|
|
56
|
+
eagle_info "Testing $provider provider..."
|
|
57
|
+
result=$(eagle_llm_call "Respond with exactly: Eagle Mem provider test successful" "You are a test assistant. Follow instructions exactly." 50)
|
|
58
|
+
if [ -n "$result" ]; then
|
|
59
|
+
eagle_ok "Provider working"
|
|
60
|
+
echo " Response: $result"
|
|
61
|
+
else
|
|
62
|
+
eagle_err "Provider call failed. Check logs: $EAGLE_MEM_LOG"
|
|
63
|
+
exit 1
|
|
64
|
+
fi
|
|
65
|
+
;;
|
|
66
|
+
|
|
67
|
+
*)
|
|
68
|
+
eagle_err "Unknown config command: $subcommand"
|
|
69
|
+
eagle_info "Usage: eagle-mem config [init|show|set|test]"
|
|
70
|
+
exit 1
|
|
71
|
+
;;
|
|
72
|
+
esac
|