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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "2.0.7",
3
+ "version": "3.0.0",
4
4
  "description": "Persistent memory for Claude Code — SQLite + FTS5, no daemon, no bloat",
5
5
  "bin": {
6
6
  "eagle-mem": "bin/eagle-mem"
@@ -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