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,349 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Curator
4
+ # Self-learning engine that analyzes sessions and generates:
5
+ # 1. Promoted gotchas → persistent memories
6
+ # 2. Superseded decision detection
7
+ # 3. Command compression rules
8
+ # 4. Feature auto-discovery
9
+ # ═══════════════════════════════════════════════════════════
10
+ set -euo pipefail
11
+
12
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ LIB_DIR="$SCRIPT_DIR/../lib"
14
+
15
+ . "$LIB_DIR/common.sh"
16
+ . "$SCRIPT_DIR/style.sh"
17
+ . "$LIB_DIR/db.sh"
18
+ . "$LIB_DIR/provider.sh"
19
+
20
+ eagle_header "Curator"
21
+
22
+ DRY_RUN=0
23
+ FULL=0
24
+ project=""
25
+
26
+ while [ $# -gt 0 ]; do
27
+ case "$1" in
28
+ --dry-run) DRY_RUN=1; shift ;;
29
+ --full) FULL=1; shift ;;
30
+ -p|--project) project="$2"; shift 2 ;;
31
+ *) shift ;;
32
+ esac
33
+ done
34
+
35
+ if [ -z "$project" ]; then
36
+ project=$(eagle_project_from_cwd "$(pwd)")
37
+ fi
38
+
39
+ p_esc=$(eagle_sql_escape "$project")
40
+
41
+ # Verify provider is configured
42
+ provider=$(eagle_config_get "provider" "type" "none")
43
+ if [ "$provider" = "none" ]; then
44
+ eagle_err "No LLM provider configured. Run: eagle-mem config init"
45
+ exit 1
46
+ fi
47
+ eagle_info "Provider: $provider ($(eagle_config_get "$provider" "model" "unknown"))"
48
+ eagle_info "Project: $project"
49
+ [ "$DRY_RUN" -eq 1 ] && eagle_info "Dry run — no changes will be made"
50
+ echo ""
51
+
52
+ # ─── 1. Analyze gotchas for promotion ─────────────────────
53
+
54
+ eagle_info "Analyzing gotchas for promotion..."
55
+
56
+ recent_gotchas=$(eagle_db "SELECT gotchas, created_at
57
+ FROM summaries
58
+ WHERE project = '$p_esc'
59
+ AND gotchas IS NOT NULL AND gotchas != ''
60
+ ORDER BY created_at DESC
61
+ LIMIT 20;")
62
+
63
+ if [ -n "$recent_gotchas" ]; then
64
+ gotcha_prompt="Analyze these gotchas from recent development sessions on project '$project'. Identify any that appear multiple times or are important enough to become permanent project knowledge.
65
+
66
+ GOTCHAS:
67
+ $recent_gotchas
68
+
69
+ For each gotcha worth promoting, output EXACTLY this format (one per line):
70
+ PROMOTE: <one-line gotcha summary>
71
+
72
+ Only promote gotchas that are:
73
+ 1. Repeated across multiple sessions (same mistake happening again)
74
+ 2. Non-obvious and likely to be forgotten
75
+ 3. Specific enough to be actionable
76
+
77
+ If none qualify, output: NONE"
78
+
79
+ gotcha_result=$(eagle_llm_call "$gotcha_prompt" "You analyze software development patterns. Be concise. Only output PROMOTE lines or NONE." 512)
80
+
81
+ if [ -n "$gotcha_result" ] && ! echo "$gotcha_result" | grep -q "^NONE$"; then
82
+ promoted=0
83
+ while IFS= read -r line; do
84
+ case "$line" in
85
+ PROMOTE:*)
86
+ gotcha_text=$(echo "$line" | sed 's/^PROMOTE:[[:space:]]*//')
87
+ if [ "$DRY_RUN" -eq 1 ]; then
88
+ eagle_info " Would promote: $gotcha_text"
89
+ else
90
+ eagle_log "INFO" "Curator: promoting gotcha: $gotcha_text"
91
+ fi
92
+ promoted=$((promoted + 1))
93
+ ;;
94
+ esac
95
+ done <<< "$gotcha_result"
96
+ eagle_ok "Found $promoted gotchas to promote"
97
+ else
98
+ eagle_ok "No gotchas need promotion"
99
+ fi
100
+ else
101
+ eagle_dim " No gotchas to analyze"
102
+ fi
103
+
104
+ # ─── 2. Detect superseded decisions ───────────���───────────
105
+
106
+ eagle_info "Checking for superseded decisions..."
107
+
108
+ recent_decisions=$(eagle_db "SELECT decisions, key_files, created_at
109
+ FROM summaries
110
+ WHERE project = '$p_esc'
111
+ AND decisions IS NOT NULL AND decisions != ''
112
+ ORDER BY created_at DESC
113
+ LIMIT 20;")
114
+
115
+ if [ -n "$recent_decisions" ]; then
116
+ decision_prompt="Analyze these decisions from recent sessions on project '$project'. Identify any that CONTRADICT or SUPERSEDE earlier decisions (e.g., session 5 decided to use approach A, but session 8 switched to approach B).
117
+
118
+ DECISIONS (newest first):
119
+ $recent_decisions
120
+
121
+ For each superseded decision, output EXACTLY:
122
+ SUPERSEDED: <old decision> → <new decision> | file: <affected file if known>
123
+
124
+ If none are superseded, output: NONE"
125
+
126
+ decision_result=$(eagle_llm_call "$decision_prompt" "You detect contradicting software decisions. Be precise." 512)
127
+
128
+ if [ -n "$decision_result" ] && ! echo "$decision_result" | grep -q "^NONE$"; then
129
+ superseded=0
130
+ while IFS= read -r line; do
131
+ case "$line" in
132
+ SUPERSEDED:*)
133
+ if [ "$DRY_RUN" -eq 1 ]; then
134
+ eagle_info " $line"
135
+ else
136
+ eagle_log "INFO" "Curator: $line"
137
+ fi
138
+ superseded=$((superseded + 1))
139
+ ;;
140
+ esac
141
+ done <<< "$decision_result"
142
+ eagle_ok "Found $superseded superseded decisions"
143
+ else
144
+ eagle_ok "No superseded decisions found"
145
+ fi
146
+ else
147
+ eagle_dim " No decisions to analyze"
148
+ fi
149
+
150
+ # ─── 3. Generate command compression rules ────────────────
151
+
152
+ eagle_info "Analyzing command patterns..."
153
+
154
+ command_stats=$(eagle_db "SELECT command_category,
155
+ COUNT(*) as count,
156
+ AVG(output_bytes) as avg_bytes,
157
+ MAX(output_bytes) as max_bytes,
158
+ AVG(output_lines) as avg_lines
159
+ FROM observations
160
+ WHERE project = '$p_esc'
161
+ AND tool_name = 'Bash'
162
+ AND command_category IS NOT NULL
163
+ AND output_bytes IS NOT NULL
164
+ AND output_bytes > 0
165
+ GROUP BY command_category
166
+ HAVING count > 5
167
+ ORDER BY avg_bytes DESC
168
+ LIMIT 10;")
169
+
170
+ if [ -n "$command_stats" ]; then
171
+ # Get specific noisy commands
172
+ noisy_commands=$(eagle_db "SELECT tool_input_summary,
173
+ COUNT(*) as count,
174
+ CAST(AVG(output_bytes) AS INTEGER) as avg_bytes,
175
+ CAST(AVG(output_lines) AS INTEGER) as avg_lines
176
+ FROM observations
177
+ WHERE project = '$p_esc'
178
+ AND tool_name = 'Bash'
179
+ AND output_bytes > 1000
180
+ GROUP BY tool_input_summary
181
+ HAVING count >= 3
182
+ ORDER BY avg_bytes DESC
183
+ LIMIT 15;")
184
+
185
+ if [ -n "$noisy_commands" ]; then
186
+ cmd_prompt="Analyze these frequently-run commands and their output sizes for project '$project'. Suggest compression rules for commands that produce consistently large output.
187
+
188
+ COMMAND STATS (command | times_run | avg_output_bytes | avg_output_lines):
189
+ $noisy_commands
190
+
191
+ For each command that deserves a compression rule, output EXACTLY:
192
+ RULE: <pattern> | <strategy: summary or truncate> | <max_lines or -> | <reason>
193
+
194
+ Where:
195
+ - pattern: the base command to match (e.g., 'npm', 'git log', 'pnpm test')
196
+ - strategy: 'summary' (only show result) or 'truncate' (keep first N lines)
197
+ - max_lines: for truncate strategy, how many lines to keep; '-' for summary
198
+ - reason: one-line explanation
199
+
200
+ If no rules needed, output: NONE"
201
+
202
+ cmd_result=$(eagle_llm_call "$cmd_prompt" "You optimize CLI output for AI assistants. Be conservative — only suggest rules for genuinely noisy commands." 512)
203
+
204
+ if [ -n "$cmd_result" ] && ! echo "$cmd_result" | grep -q "^NONE$"; then
205
+ rules_count=0
206
+ while IFS= read -r line; do
207
+ case "$line" in
208
+ RULE:*)
209
+ rule_data=$(echo "$line" | sed 's/^RULE:[[:space:]]*//')
210
+ IFS='|' read -r pattern strategy max_lines reason <<< "$rule_data"
211
+ pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
212
+ strategy=$(echo "$strategy" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
213
+ max_lines=$(echo "$max_lines" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
214
+ reason=$(echo "$reason" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
215
+
216
+ [ "$max_lines" = "-" ] && max_lines=""
217
+
218
+ if [ "$DRY_RUN" -eq 1 ]; then
219
+ eagle_info " Rule: $pattern → $strategy ($reason)"
220
+ else
221
+ pattern_esc=$(eagle_sql_escape "$pattern")
222
+ reason_esc=$(eagle_sql_escape "$reason")
223
+ ml_val="${max_lines:-NULL}"
224
+ [ "$ml_val" != "NULL" ] && ml_val=$(eagle_sql_int "$ml_val")
225
+
226
+ eagle_db "INSERT INTO command_rules (project, pattern, strategy, max_lines, reason)
227
+ VALUES ('$p_esc', '$pattern_esc', '$strategy', $ml_val, '$reason_esc')
228
+ ON CONFLICT(project, pattern) DO UPDATE SET
229
+ strategy = excluded.strategy,
230
+ max_lines = excluded.max_lines,
231
+ reason = excluded.reason,
232
+ updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now');"
233
+ eagle_log "INFO" "Curator: added command rule: $pattern → $strategy"
234
+ fi
235
+ rules_count=$((rules_count + 1))
236
+ ;;
237
+ esac
238
+ done <<< "$cmd_result"
239
+ eagle_ok "$rules_count command rules generated"
240
+ else
241
+ eagle_ok "No command rules needed"
242
+ fi
243
+ else
244
+ eagle_dim " Not enough command data yet"
245
+ fi
246
+ else
247
+ eagle_dim " No command metrics collected yet (need more sessions)"
248
+ fi
249
+
250
+ # ─── 4. Feature auto-discovery ────────────────────────────
251
+
252
+ eagle_info "Discovering features from session data..."
253
+
254
+ feature_data=$(eagle_db "SELECT s.request, s.key_files, s.decisions, s.completed
255
+ FROM summaries s
256
+ WHERE s.project = '$p_esc'
257
+ AND (s.key_files IS NOT NULL AND s.key_files != ''
258
+ OR s.decisions IS NOT NULL AND s.decisions != '')
259
+ ORDER BY s.created_at DESC
260
+ LIMIT 15;")
261
+
262
+ existing_features=$(eagle_db "SELECT name FROM features WHERE project = '$p_esc' AND status = 'active';")
263
+
264
+ if [ -n "$feature_data" ]; then
265
+ feature_prompt="Analyze these session summaries from project '$project' and identify distinct FEATURES (user-facing capabilities, not implementation details).
266
+
267
+ SESSION DATA (request | key_files | decisions | completed):
268
+ $feature_data
269
+
270
+ EXISTING FEATURES (already tracked):
271
+ ${existing_features:-none}
272
+
273
+ For each NEW feature discovered (not already in existing list), output EXACTLY:
274
+ FEATURE: <name> | <one-line description> | <comma-separated key files>
275
+
276
+ Rules:
277
+ - Feature names should be short, kebab-case (e.g., 'title-generation', 'auth-middleware')
278
+ - Only discover features with clear file evidence (at least 2 related files)
279
+ - Don't re-discover existing features
280
+ - If no new features found, output: NONE"
281
+
282
+ feature_result=$(eagle_llm_call "$feature_prompt" "You identify software features from development session data. Be specific and evidence-based." 512)
283
+
284
+ if [ -n "$feature_result" ] && ! echo "$feature_result" | grep -q "^NONE$"; then
285
+ features_count=0
286
+ while IFS= read -r line; do
287
+ case "$line" in
288
+ FEATURE:*)
289
+ feat_data=$(echo "$line" | sed 's/^FEATURE:[[:space:]]*//')
290
+ IFS='|' read -r fname fdesc ffiles <<< "$feat_data"
291
+ fname=$(echo "$fname" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
292
+ fdesc=$(echo "$fdesc" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
293
+ ffiles=$(echo "$ffiles" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
294
+
295
+ if [ "$DRY_RUN" -eq 1 ]; then
296
+ eagle_info " Feature: $fname — $fdesc"
297
+ eagle_info " Files: $ffiles"
298
+ else
299
+ eagle_upsert_feature "$project" "$fname" "$fdesc"
300
+ fid=$(eagle_get_feature_id "$project" "$fname")
301
+ if [ -n "$fid" ] && [ -n "$ffiles" ]; then
302
+ IFS=',' read -ra file_arr <<< "$ffiles"
303
+ for f in "${file_arr[@]}"; do
304
+ f=$(echo "$f" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
305
+ [ -n "$f" ] && eagle_add_feature_file "$fid" "$f" ""
306
+ done
307
+ fi
308
+ eagle_log "INFO" "Curator: discovered feature: $fname"
309
+ fi
310
+ features_count=$((features_count + 1))
311
+ ;;
312
+ esac
313
+ done <<< "$feature_result"
314
+ eagle_ok "$features_count features discovered"
315
+ else
316
+ eagle_ok "No new features discovered"
317
+ fi
318
+ else
319
+ eagle_dim " Not enough session data for feature discovery"
320
+ fi
321
+
322
+ # ─── 5. Session compression (--full only) ─────────────────
323
+
324
+ if [ "$FULL" -eq 1 ]; then
325
+ eagle_info "Compressing old sessions..."
326
+
327
+ old_count=$(eagle_db "SELECT COUNT(*) FROM summaries
328
+ WHERE project = '$p_esc'
329
+ AND created_at < strftime('%Y-%m-%dT%H:%M:%fZ', 'now', '-30 days');")
330
+
331
+ if [ "${old_count:-0}" -gt 10 ]; then
332
+ if [ "$DRY_RUN" -eq 1 ]; then
333
+ eagle_info " Would analyze $old_count old sessions for compression"
334
+ else
335
+ eagle_info " $old_count sessions older than 30 days (compression not yet implemented)"
336
+ fi
337
+ else
338
+ eagle_dim " Not enough old sessions to compress"
339
+ fi
340
+ fi
341
+
342
+ # ──��� Summary ──────────────────────────────────────────────
343
+
344
+ echo ""
345
+ if [ "$DRY_RUN" -eq 1 ]; then
346
+ eagle_footer "Dry run complete. Run without --dry-run to apply changes."
347
+ else
348
+ eagle_footer "Curation complete for '$project'."
349
+ fi
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env bash
2
+ # ═══════════════════════════════════════════════════════════
3
+ # Eagle Mem — Feature management
4
+ # eagle-mem feature [list|show|verify|add]
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/db.sh"
14
+
15
+ eagle_ensure_db
16
+ eagle_header "Features"
17
+
18
+ project=$(eagle_project_from_cwd "$(pwd)")
19
+ subcommand="${1:-list}"
20
+ shift 2>/dev/null || true
21
+
22
+ case "$subcommand" in
23
+ list|ls)
24
+ results=$(eagle_list_features "$project")
25
+ if [ -z "$results" ]; then
26
+ eagle_dim "No features tracked for '$project'"
27
+ eagle_dim "Run 'eagle-mem curate' to auto-discover features"
28
+ exit 0
29
+ fi
30
+
31
+ while IFS='|' read -r name desc status verified dep_count file_count test_count; do
32
+ [ -z "$name" ] && continue
33
+ verified_label=""
34
+ if [ -n "$verified" ]; then
35
+ verified_label=" ${DIM}(verified: ${verified})${RESET}"
36
+ else
37
+ verified_label=" ${DIM}(never verified)${RESET}"
38
+ fi
39
+ echo -e " ${CYAN}${name}${RESET}${verified_label}"
40
+ [ -n "$desc" ] && echo -e " ${desc}"
41
+ echo -e " ${DIM}${file_count} files, ${dep_count} deps, ${test_count} smoke tests${RESET}"
42
+ done <<< "$results"
43
+ ;;
44
+
45
+ show)
46
+ name="${1:-}"
47
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature show <name>"; exit 1; }
48
+ eagle_show_feature "$project" "$name"
49
+ ;;
50
+
51
+ verify)
52
+ name="${1:-}"
53
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature verify <name> [--notes <text>]"; exit 1; }
54
+ shift
55
+ notes=""
56
+ while [ $# -gt 0 ]; do
57
+ case "$1" in
58
+ --notes) notes="$2"; shift 2 ;;
59
+ *) notes="$1"; shift ;;
60
+ esac
61
+ done
62
+ eagle_verify_feature "$project" "$name" "$notes"
63
+ eagle_ok "Feature '$name' marked as verified"
64
+ ;;
65
+
66
+ add)
67
+ name="${1:-}"
68
+ [ -z "$name" ] && { eagle_err "Usage: eagle-mem feature add <name> [--desc <text>] [--file <path>] [--requires <target:name>] [--smoke <command>]"; exit 1; }
69
+ shift
70
+ desc=""
71
+ files=()
72
+ deps=()
73
+ smokes=()
74
+
75
+ while [ $# -gt 0 ]; do
76
+ case "$1" in
77
+ --desc|-d) desc="$2"; shift 2 ;;
78
+ --file|-f) files+=("$2"); shift 2 ;;
79
+ --requires|-r) deps+=("$2"); shift 2 ;;
80
+ --smoke|-s) smokes+=("$2"); shift 2 ;;
81
+ *) shift ;;
82
+ esac
83
+ done
84
+
85
+ eagle_upsert_feature "$project" "$name" "$desc"
86
+ fid=$(eagle_get_feature_id "$project" "$name")
87
+
88
+ for f in "${files[@]+"${files[@]}"}"; do
89
+ eagle_add_feature_file "$fid" "$f" ""
90
+ done
91
+
92
+ for d in "${deps[@]+"${deps[@]}"}"; do
93
+ target="${d%%:*}"
94
+ dep_name="${d#*:}"
95
+ eagle_add_feature_dependency "$fid" "env_var" "$target" "$dep_name" ""
96
+ done
97
+
98
+ for s in "${smokes[@]+"${smokes[@]}"}"; do
99
+ eagle_add_feature_smoke_test "$fid" "$s" ""
100
+ done
101
+
102
+ eagle_ok "Feature '$name' created"
103
+ ;;
104
+
105
+ *)
106
+ eagle_err "Unknown feature command: $subcommand"
107
+ eagle_info "Usage: eagle-mem feature [list|show|verify|add]"
108
+ exit 1
109
+ ;;
110
+ esac
package/scripts/help.sh CHANGED
@@ -24,6 +24,9 @@ echo -e " ${CYAN}memories${RESET} View/sync mirrored Claude Code memories"
24
24
  echo -e " ${CYAN}tasks${RESET} View mirrored Claude Code tasks"
25
25
  echo -e " ${CYAN}refresh${RESET} Full project sync: scan (if needed) + index + memories"
26
26
  echo -e " ${CYAN}prune${RESET} Remove old observations and orphaned chunks"
27
+ echo -e " ${CYAN}config${RESET} View or change LLM provider settings"
28
+ echo -e " ${CYAN}curate${RESET} Run the self-learning curator (LLM-powered analysis)"
29
+ echo -e " ${CYAN}feature${RESET} Manage feature graph (list/show/verify/add)"
27
30
  echo -e " ${CYAN}install${RESET} First-time setup: hooks, database, skills"
28
31
  echo -e " ${CYAN}update${RESET} Re-deploy hooks and run migrations"
29
32
  echo -e " ${CYAN}uninstall${RESET} Remove hooks and optionally delete data"
package/scripts/index.sh CHANGED
@@ -162,7 +162,7 @@ DELETE FROM code_chunks WHERE project = '$project_sql' AND file_path = '$file_sq
162
162
  end=$((start + CHUNK_SIZE - 1))
163
163
  [ "$end" -gt "$total_lines" ] && end="$total_lines"
164
164
 
165
- content=$(sed -n "${start},${end}p" "$full_path")
165
+ content=$(sed -n "${start},${end}p" "$full_path" | eagle_redact)
166
166
  content_sql=$(eagle_sql_escape "$content")
167
167
 
168
168
  txn_sql+="
@@ -181,6 +181,10 @@ eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" \
181
181
  "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh" \
182
182
  "UserPromptSubmit hook"
183
183
 
184
+ eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" \
185
+ "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh" \
186
+ "PreToolUse hook"
187
+
184
188
  # ─── Install skills ────────────────────────────────────────
185
189
 
186
190
  if [ -d "$PACKAGE_DIR/skills" ]; then
@@ -242,6 +246,16 @@ else
242
246
  fi
243
247
  fi
244
248
 
249
+ # ─── Initialize config ────────────────────────────────────
250
+
251
+ . "$LIB_DIR/provider.sh"
252
+ if [ ! -f "$EAGLE_CONFIG_FILE" ]; then
253
+ eagle_config_init
254
+ eagle_ok "Config created ${DIM}(auto-detected provider)${RESET}"
255
+ else
256
+ eagle_ok "Config ${DIM}(already exists)${RESET}"
257
+ fi
258
+
245
259
  # ─── Save installed version ───────────────────────────────
246
260
 
247
261
  version=$(jq -r .version "$PACKAGE_DIR/package.json" 2>/dev/null || echo "unknown")
package/scripts/update.sh CHANGED
@@ -69,6 +69,7 @@ if [ -f "$SETTINGS" ] && command -v jq &>/dev/null; then
69
69
  eagle_patch_hook "$SETTINGS" "PostToolUse" "Read|Write|Edit|Bash|TaskCreate|TaskUpdate" "$EAGLE_MEM_DIR/hooks/post-tool-use.sh"
70
70
  eagle_patch_hook "$SETTINGS" "SessionEnd" "" "$EAGLE_MEM_DIR/hooks/session-end.sh"
71
71
  eagle_patch_hook "$SETTINGS" "UserPromptSubmit" "" "$EAGLE_MEM_DIR/hooks/user-prompt-submit.sh"
72
+ eagle_patch_hook "$SETTINGS" "PreToolUse" "Bash" "$EAGLE_MEM_DIR/hooks/pre-tool-use.sh"
72
73
 
73
74
  eagle_ok "Hooks registered"
74
75
  fi