eagle-mem 2.0.7 → 3.0.1

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/lib/db.sh CHANGED
@@ -14,15 +14,45 @@ PRAGMA trusted_schema=ON;
14
14
  .output stdout"
15
15
 
16
16
  eagle_db() {
17
- { echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
17
+ local _eagle_db_err
18
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_err.$$")
19
+ local _eagle_db_out
20
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
21
+ local _eagle_db_rc=$?
22
+ if [ -s "$_eagle_db_err" ]; then
23
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
24
+ fi
25
+ rm -f "$_eagle_db_err" 2>/dev/null
26
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
27
+ return $_eagle_db_rc
18
28
  }
19
29
 
20
30
  eagle_db_pipe() {
21
- { echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
31
+ local _eagle_db_err
32
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_pipe_err.$$")
33
+ local _eagle_db_out
34
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".bail on"; cat; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
35
+ local _eagle_db_rc=$?
36
+ if [ -s "$_eagle_db_err" ]; then
37
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
38
+ fi
39
+ rm -f "$_eagle_db_err" 2>/dev/null
40
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
41
+ return $_eagle_db_rc
22
42
  }
23
43
 
24
44
  eagle_db_json() {
25
- { echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>>"$EAGLE_MEM_LOG"
45
+ local _eagle_db_err
46
+ _eagle_db_err=$(mktemp 2>/dev/null || echo "/tmp/_eagle_db_json_err.$$")
47
+ local _eagle_db_out
48
+ _eagle_db_out=$({ echo "$EAGLE_DB_SETUP"; echo ".mode json"; echo "$*"; } | sqlite3 "$EAGLE_MEM_DB" 2>"$_eagle_db_err")
49
+ local _eagle_db_rc=$?
50
+ if [ -s "$_eagle_db_err" ]; then
51
+ cat "$_eagle_db_err" >> "$EAGLE_MEM_LOG" 2>/dev/null
52
+ fi
53
+ rm -f "$_eagle_db_err" 2>/dev/null
54
+ [ -n "$_eagle_db_out" ] && printf '%s\n' "$_eagle_db_out"
55
+ return $_eagle_db_rc
26
56
  }
27
57
 
28
58
  eagle_ensure_db() {
@@ -62,9 +92,19 @@ eagle_insert_observation() {
62
92
  local tool_input_summary; tool_input_summary=$(eagle_sql_escape "$4")
63
93
  local files_read; files_read=$(eagle_sql_escape "$5")
64
94
  local files_modified; files_modified=$(eagle_sql_escape "$6")
95
+ local output_bytes="${7:-}"
96
+ local output_lines="${8:-}"
97
+ local command_category; command_category=$(eagle_sql_escape "${9:-}")
98
+
99
+ local extra_cols=""
100
+ local extra_vals=""
101
+ if [ -n "$output_bytes" ]; then
102
+ extra_cols=", output_bytes, output_lines, command_category"
103
+ extra_vals=", $(eagle_sql_int "$output_bytes"), $(eagle_sql_int "$output_lines"), '$command_category'"
104
+ fi
65
105
 
66
- eagle_db "INSERT INTO observations (session_id, project, tool_name, tool_input_summary, files_read, files_modified)
67
- SELECT '$session_id', '$project', '$tool_name', '$tool_input_summary', '$files_read', '$files_modified'
106
+ eagle_db "INSERT INTO observations (session_id, project, tool_name, tool_input_summary, files_read, files_modified${extra_cols})
107
+ SELECT '$session_id', '$project', '$tool_name', '$tool_input_summary', '$files_read', '$files_modified'${extra_vals}
68
108
  WHERE NOT EXISTS (
69
109
  SELECT 1 FROM observations
70
110
  WHERE session_id = '$session_id'
@@ -565,3 +605,136 @@ COMMIT;"
565
605
  fi
566
606
  echo "$removed"
567
607
  }
608
+
609
+ # ─── Feature graph helpers ─────��───────────────────────────
610
+
611
+ eagle_upsert_feature() {
612
+ local project; project=$(eagle_sql_escape "$1")
613
+ local name; name=$(eagle_sql_escape "$2")
614
+ local description; description=$(eagle_sql_escape "${3:-}")
615
+
616
+ eagle_db "INSERT INTO features (project, name, description)
617
+ VALUES ('$project', '$name', '$description')
618
+ ON CONFLICT(project, name) DO UPDATE SET
619
+ description = COALESCE(NULLIF('$description', ''), features.description),
620
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
621
+ }
622
+
623
+ eagle_add_feature_dependency() {
624
+ local feature_id; feature_id=$(eagle_sql_int "$1")
625
+ local kind; kind=$(eagle_sql_escape "$2")
626
+ local target; target=$(eagle_sql_escape "$3")
627
+ local name; name=$(eagle_sql_escape "$4")
628
+ local notes; notes=$(eagle_sql_escape "${5:-}")
629
+
630
+ eagle_db "INSERT OR IGNORE INTO feature_dependencies (feature_id, kind, target, name, notes)
631
+ VALUES ($feature_id, '$kind', '$target', '$name', '$notes');"
632
+ }
633
+
634
+ eagle_add_feature_file() {
635
+ local feature_id; feature_id=$(eagle_sql_int "$1")
636
+ local file_path; file_path=$(eagle_sql_escape "$2")
637
+ local role; role=$(eagle_sql_escape "${3:-}")
638
+
639
+ eagle_db "INSERT OR IGNORE INTO feature_files (feature_id, file_path, role)
640
+ VALUES ($feature_id, '$file_path', '$role');"
641
+ }
642
+
643
+ eagle_add_feature_smoke_test() {
644
+ local feature_id; feature_id=$(eagle_sql_int "$1")
645
+ local command; command=$(eagle_sql_escape "$2")
646
+ local description; description=$(eagle_sql_escape "${3:-}")
647
+
648
+ eagle_db "INSERT OR IGNORE INTO feature_smoke_tests (feature_id, command, description)
649
+ VALUES ($feature_id, '$command', '$description');"
650
+ }
651
+
652
+ eagle_verify_feature() {
653
+ local project; project=$(eagle_sql_escape "$1")
654
+ local name; name=$(eagle_sql_escape "$2")
655
+ local notes; notes=$(eagle_sql_escape "${3:-}")
656
+
657
+ eagle_db "UPDATE features SET
658
+ last_verified_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now'),
659
+ last_verified_notes = '$notes',
660
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
661
+ WHERE project = '$project' AND name = '$name';"
662
+ }
663
+
664
+ eagle_get_feature_id() {
665
+ local project; project=$(eagle_sql_escape "$1")
666
+ local name; name=$(eagle_sql_escape "$2")
667
+ eagle_db "SELECT id FROM features WHERE project = '$project' AND name = '$name';"
668
+ }
669
+
670
+ eagle_list_features() {
671
+ local project; project=$(eagle_sql_escape "$1")
672
+ local limit; limit=$(eagle_sql_int "${2:-20}")
673
+
674
+ eagle_db "SELECT f.name, f.description, f.status, f.last_verified_at,
675
+ (SELECT COUNT(*) FROM feature_dependencies WHERE feature_id = f.id) as dep_count,
676
+ (SELECT COUNT(*) FROM feature_files WHERE feature_id = f.id) as file_count,
677
+ (SELECT COUNT(*) FROM feature_smoke_tests WHERE feature_id = f.id) as test_count
678
+ FROM features f
679
+ WHERE f.project = '$project' AND f.status = 'active'
680
+ ORDER BY f.updated_at DESC
681
+ LIMIT $limit;"
682
+ }
683
+
684
+ eagle_show_feature() {
685
+ local project; project=$(eagle_sql_escape "$1")
686
+ local name; name=$(eagle_sql_escape "$2")
687
+
688
+ local feature_id
689
+ feature_id=$(eagle_get_feature_id "$1" "$2")
690
+ [ -z "$feature_id" ] && return 1
691
+
692
+ echo "=== Feature: $2 ==="
693
+ eagle_db "SELECT name, description, status, last_verified_at, last_verified_notes
694
+ FROM features WHERE id = $feature_id;"
695
+
696
+ local deps
697
+ deps=$(eagle_db "SELECT kind, target, name, notes FROM feature_dependencies WHERE feature_id = $feature_id;")
698
+ if [ -n "$deps" ]; then
699
+ echo "--- Dependencies ---"
700
+ echo "$deps"
701
+ fi
702
+
703
+ local files
704
+ files=$(eagle_db "SELECT file_path, role FROM feature_files WHERE feature_id = $feature_id;")
705
+ if [ -n "$files" ]; then
706
+ echo "--- Files ---"
707
+ echo "$files"
708
+ fi
709
+
710
+ local tests
711
+ tests=$(eagle_db "SELECT command, description FROM feature_smoke_tests WHERE feature_id = $feature_id;")
712
+ if [ -n "$tests" ]; then
713
+ echo "--- Smoke Tests ---"
714
+ echo "$tests"
715
+ fi
716
+ }
717
+
718
+ eagle_find_features_for_file() {
719
+ local project; project=$(eagle_sql_escape "$1")
720
+ local file_path="$2"
721
+ local fname; fname=$(basename "$file_path")
722
+ local fname_esc; fname_esc=$(eagle_sql_escape "$fname")
723
+ local fname_like; fname_like=$(eagle_like_escape "$fname_esc")
724
+
725
+ eagle_db "SELECT f.name, f.description, f.last_verified_at,
726
+ ff.role,
727
+ (SELECT GROUP_CONCAT(fd.target || ':' || fd.name, ', ')
728
+ FROM feature_dependencies fd WHERE fd.feature_id = f.id) as deps,
729
+ (SELECT GROUP_CONCAT(ff2.file_path, ', ')
730
+ FROM feature_files ff2 WHERE ff2.feature_id = f.id AND ff2.file_path != ff.file_path) as other_files,
731
+ (SELECT GROUP_CONCAT(fst.command, ', ')
732
+ FROM feature_smoke_tests fst WHERE fst.feature_id = f.id) as smoke_tests
733
+ FROM features f
734
+ JOIN feature_files ff ON ff.feature_id = f.id
735
+ WHERE f.project = '$project'
736
+ AND f.status = 'active'
737
+ AND (ff.file_path LIKE '%$fname_like' ESCAPE '\\' OR ff.file_path LIKE '%$fname_like%' ESCAPE '\\')
738
+ ORDER BY f.updated_at DESC
739
+ LIMIT 3;"
740
+ }
@@ -0,0 +1,346 @@
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
+ # Validate section/key are alphanumeric+underscore (safe for grep/sed patterns)
54
+ if [[ ! "$section" =~ ^[A-Za-z0-9_-]+$ ]] || [[ ! "$key" =~ ^[A-Za-z0-9_-]+$ ]]; then
55
+ eagle_log "ERROR" "config_set: invalid section/key: [$section] $key"
56
+ return 1
57
+ fi
58
+
59
+ if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
60
+ eagle_config_init
61
+ fi
62
+
63
+ # Escape sed metacharacters in value to prevent injection via |, &, \, /
64
+ local safe_value
65
+ safe_value=$(printf '%s' "$value" | sed 's/[|&/\]/\\&/g')
66
+
67
+ if grep -q "^\[${section}\]" "$EAGLE_CONFIG_FILE" 2>/dev/null; then
68
+ if grep -q "^[[:space:]]*${key}[[:space:]]*=" "$EAGLE_CONFIG_FILE" 2>/dev/null; then
69
+ sed -i '' "s|^[[:space:]]*${key}[[:space:]]*=.*|${key} = \"${safe_value}\"|" "$EAGLE_CONFIG_FILE"
70
+ else
71
+ sed -i '' "/^\[${section}\]/a\\
72
+ ${key} = \"${safe_value}\"
73
+ " "$EAGLE_CONFIG_FILE"
74
+ fi
75
+ else
76
+ # printf is safe — no sed interpolation needed for append
77
+ printf '\n[%s]\n%s = "%s"\n' "$section" "$key" "$value" >> "$EAGLE_CONFIG_FILE"
78
+ fi
79
+ }
80
+
81
+ # ─── Ollama detection ──────────────────────────────────────
82
+
83
+ eagle_detect_ollama() {
84
+ local url="${1:-$EAGLE_DEFAULT_OLLAMA_URL}"
85
+ curl -sf "${url}/api/tags" --connect-timeout 2 --max-time 3 2>/dev/null
86
+ }
87
+
88
+ eagle_ollama_models() {
89
+ local url="${1:-$EAGLE_DEFAULT_OLLAMA_URL}"
90
+ eagle_detect_ollama "$url" | jq -r '.models[].name' 2>/dev/null
91
+ }
92
+
93
+ eagle_ollama_best_model() {
94
+ local models
95
+ models=$(eagle_ollama_models "$1")
96
+ [ -z "$models" ] && return 1
97
+
98
+ local preferred="mistral qwen3-coder gemma4 llama3 phi3 deepseek-coder"
99
+ for pref in $preferred; do
100
+ if echo "$models" | grep -qi "$pref"; then
101
+ echo "$models" | grep -i "$pref" | head -1
102
+ return 0
103
+ fi
104
+ done
105
+
106
+ echo "$models" | head -1
107
+ }
108
+
109
+ # ─── Config initialization ─────────────────────────────────
110
+
111
+ eagle_config_init() {
112
+ local ollama_url="$EAGLE_DEFAULT_OLLAMA_URL"
113
+ local provider="none"
114
+ local model=""
115
+
116
+ local ollama_response
117
+ ollama_response=$(eagle_detect_ollama "$ollama_url")
118
+ if [ -n "$ollama_response" ]; then
119
+ provider="ollama"
120
+ model=$(eagle_ollama_best_model "$ollama_url")
121
+ elif [ -n "${ANTHROPIC_API_KEY:-}" ]; then
122
+ provider="anthropic"
123
+ model="claude-haiku-4-5-20251001"
124
+ elif [ -n "${OPENAI_API_KEY:-}" ]; then
125
+ provider="openai"
126
+ model="gpt-4o-mini"
127
+ fi
128
+
129
+ # Create config with restrictive permissions from the start (no TOCTOU window)
130
+ (
131
+ umask 077
132
+ cat > "$EAGLE_CONFIG_FILE" << TOML
133
+ # Eagle Mem configuration
134
+ # Docs: https://github.com/eagleisbatman/eagle-mem
135
+
136
+ [provider]
137
+ # Which LLM provider to use for the curator and analysis features
138
+ # Options: "ollama" (free, local), "anthropic", "openai"
139
+ type = "$provider"
140
+
141
+ [ollama]
142
+ url = "$ollama_url"
143
+ model = "${model:-mistral}"
144
+
145
+ [anthropic]
146
+ # Uses ANTHROPIC_API_KEY env var for authentication
147
+ model = "claude-haiku-4-5-20251001"
148
+
149
+ [openai]
150
+ # Uses OPENAI_API_KEY env var for authentication
151
+ model = "gpt-4o-mini"
152
+
153
+ [curator]
154
+ # How often the curator runs: "manual", "daily", "weekly"
155
+ schedule = "manual"
156
+
157
+ [redaction]
158
+ # Additional secret patterns (regex) beyond built-in defaults
159
+ # extra_patterns = ["MY_CUSTOM_SECRET_.*"]
160
+ TOML
161
+ )
162
+ eagle_log "INFO" "Config initialized: provider=$provider model=$model"
163
+ }
164
+
165
+ # ─── Unified LLM call ─────────────────────────────────────
166
+
167
+ eagle_llm_call() {
168
+ local prompt="$1"
169
+ local system_prompt="${2:-You are a helpful assistant that analyzes software development sessions.}"
170
+ local max_tokens="${3:-1024}"
171
+
172
+ local provider
173
+ provider=$(eagle_config_get "provider" "type" "none")
174
+
175
+ case "$provider" in
176
+ ollama) _eagle_call_ollama "$prompt" "$system_prompt" "$max_tokens" ;;
177
+ anthropic) _eagle_call_anthropic "$prompt" "$system_prompt" "$max_tokens" ;;
178
+ openai) _eagle_call_openai "$prompt" "$system_prompt" "$max_tokens" ;;
179
+ none)
180
+ eagle_log "ERROR" "No LLM provider configured. Run: eagle-mem config"
181
+ return 1
182
+ ;;
183
+ *)
184
+ eagle_log "ERROR" "Unknown provider: $provider"
185
+ return 1
186
+ ;;
187
+ esac
188
+ }
189
+
190
+ _eagle_call_ollama() {
191
+ local prompt="$1" system="$2" max_tokens="$3"
192
+ local url model
193
+
194
+ url=$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")
195
+ model=$(eagle_config_get "ollama" "model" "mistral")
196
+
197
+ local body
198
+ body=$(jq -nc \
199
+ --arg model "$model" \
200
+ --arg system "$system" \
201
+ --arg prompt "$prompt" \
202
+ --argjson tokens "$max_tokens" \
203
+ '{
204
+ model: $model,
205
+ messages: [
206
+ {role: "system", content: $system},
207
+ {role: "user", content: $prompt}
208
+ ],
209
+ stream: false,
210
+ options: {num_predict: $tokens}
211
+ }')
212
+
213
+ local response
214
+ response=$(curl -sf "${url}/api/chat" \
215
+ --connect-timeout 5 \
216
+ --max-time 120 \
217
+ -H "Content-Type: application/json" \
218
+ -d "$body" 2>/dev/null)
219
+
220
+ if [ $? -ne 0 ] || [ -z "$response" ]; then
221
+ eagle_log "ERROR" "Ollama call failed: model=$model url=$url"
222
+ return 1
223
+ fi
224
+
225
+ echo "$response" | jq -r '.message.content // empty'
226
+ }
227
+
228
+ _eagle_call_anthropic() {
229
+ local prompt="$1" system="$2" max_tokens="$3"
230
+ local model api_key
231
+
232
+ model=$(eagle_config_get "anthropic" "model" "claude-haiku-4-5-20251001")
233
+ api_key="${ANTHROPIC_API_KEY:-}"
234
+
235
+ if [ -z "$api_key" ]; then
236
+ eagle_log "ERROR" "ANTHROPIC_API_KEY not set"
237
+ return 1
238
+ fi
239
+
240
+ local body
241
+ body=$(jq -nc \
242
+ --arg model "$model" \
243
+ --arg system "$system" \
244
+ --arg prompt "$prompt" \
245
+ --argjson tokens "$max_tokens" \
246
+ '{
247
+ model: $model,
248
+ max_tokens: $tokens,
249
+ system: $system,
250
+ messages: [{role: "user", content: $prompt}]
251
+ }')
252
+
253
+ # Pass API key via config stdin to avoid exposing it in process list (ps aux)
254
+ local response
255
+ response=$(curl -sf "https://api.anthropic.com/v1/messages" \
256
+ --connect-timeout 5 \
257
+ --max-time 120 \
258
+ -K <(printf 'header = "x-api-key: %s"' "$api_key") \
259
+ -H "anthropic-version: 2023-06-01" \
260
+ -H "content-type: application/json" \
261
+ -d "$body" 2>/dev/null)
262
+
263
+ if [ $? -ne 0 ] || [ -z "$response" ]; then
264
+ eagle_log "ERROR" "Anthropic call failed: model=$model"
265
+ return 1
266
+ fi
267
+
268
+ echo "$response" | jq -r '.content[0].text // empty'
269
+ }
270
+
271
+ _eagle_call_openai() {
272
+ local prompt="$1" system="$2" max_tokens="$3"
273
+ local model api_key
274
+
275
+ model=$(eagle_config_get "openai" "model" "gpt-4o-mini")
276
+ api_key="${OPENAI_API_KEY:-}"
277
+
278
+ if [ -z "$api_key" ]; then
279
+ eagle_log "ERROR" "OPENAI_API_KEY not set"
280
+ return 1
281
+ fi
282
+
283
+ local body
284
+ body=$(jq -nc \
285
+ --arg model "$model" \
286
+ --arg system "$system" \
287
+ --arg prompt "$prompt" \
288
+ --argjson tokens "$max_tokens" \
289
+ '{
290
+ model: $model,
291
+ max_tokens: $tokens,
292
+ messages: [
293
+ {role: "system", content: $system},
294
+ {role: "user", content: $prompt}
295
+ ]
296
+ }')
297
+
298
+ # Pass API key via config stdin to avoid exposing it in process list (ps aux)
299
+ local response
300
+ response=$(curl -sf "https://api.openai.com/v1/chat/completions" \
301
+ --connect-timeout 5 \
302
+ --max-time 120 \
303
+ -K <(printf 'header = "Authorization: Bearer %s"' "$api_key") \
304
+ -H "content-type: application/json" \
305
+ -d "$body" 2>/dev/null)
306
+
307
+ if [ $? -ne 0 ] || [ -z "$response" ]; then
308
+ eagle_log "ERROR" "OpenAI call failed: model=$model"
309
+ return 1
310
+ fi
311
+
312
+ echo "$response" | jq -r '.choices[0].message.content // empty'
313
+ }
314
+
315
+ # ─── Config CLI helpers ────────────────────────────────────
316
+
317
+ eagle_show_config() {
318
+ if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
319
+ echo "No config file found. Run: eagle-mem config init"
320
+ return 1
321
+ fi
322
+
323
+ local provider model
324
+ provider=$(eagle_config_get "provider" "type" "none")
325
+ model=$(eagle_config_get "$provider" "model" "unknown")
326
+
327
+ echo "Provider: $provider"
328
+ echo "Model: $model"
329
+
330
+ if [ "$provider" = "ollama" ]; then
331
+ local url
332
+ url=$(eagle_config_get "ollama" "url" "$EAGLE_DEFAULT_OLLAMA_URL")
333
+ echo "URL: $url"
334
+ local running
335
+ running=$(eagle_detect_ollama "$url")
336
+ if [ -n "$running" ]; then
337
+ echo "Status: running"
338
+ echo "Models: $(eagle_ollama_models "$url" | tr '\n' ', ' | sed 's/,$//')"
339
+ else
340
+ echo "Status: not running"
341
+ fi
342
+ fi
343
+
344
+ echo ""
345
+ echo "Config: $EAGLE_CONFIG_FILE"
346
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eagle-mem",
3
- "version": "2.0.7",
3
+ "version": "3.0.1",
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