fathom-mcp 0.6.3 → 2.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.
@@ -1,194 +0,0 @@
1
- #!/bin/bash
2
- # Fathom Vault recall — BM25 keyword + cached semantic search on every user message.
3
- # JSON output: systemMessage (user sees count) + additionalContext (model sees details).
4
- #
5
- # Search strategy:
6
- # - BM25 keyword search runs synchronously (<2s) for immediate results
7
- # - Vector semantic search runs asynchronously in the background
8
- # - Cached vsearch results from the previous query are shown alongside BM25
9
- # - This gives us the best of both: fast keywords + deep semantics (one message behind)
10
-
11
- set -o pipefail
12
-
13
- HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
14
- TOAST="$HOOK_DIR/hook-toast.sh"
15
- VSEARCH_CACHE="/tmp/fathom-vsearch-cache.json"
16
- VSEARCH_LOCK="/tmp/fathom-vsearch.lock"
17
- STALE_LOCK_SECONDS=180 # 3 minutes — lock older than this is considered stale
18
- CACHE_TTL_SECONDS=300 # 5 minutes — cached results older than this are ignored
19
-
20
- # Walk up to find .fathom.json
21
- find_config() {
22
- local dir="$PWD"
23
- while [ "$dir" != "/" ]; do
24
- if [ -f "$dir/.fathom.json" ]; then
25
- echo "$dir/.fathom.json"
26
- return 0
27
- fi
28
- dir="$(dirname "$dir")"
29
- done
30
- return 1
31
- }
32
-
33
- CONFIG_FILE=$(find_config 2>/dev/null) || exit 0
34
-
35
- WORKSPACE=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('workspace',''))" 2>/dev/null || echo "")
36
- SERVER=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('server','http://localhost:4243'))" 2>/dev/null || echo "http://localhost:4243")
37
- API_KEY=$(python3 -c "import json; print(json.load(open('$CONFIG_FILE')).get('apiKey',''))" 2>/dev/null || echo "")
38
-
39
- AUTH_HEADER=""
40
- if [ -n "$API_KEY" ]; then
41
- AUTH_HEADER="Authorization: Bearer $API_KEY"
42
- fi
43
-
44
- # Compact parser: converts verbose qmd output to one-line-per-result format
45
- compact_results() {
46
- python3 -c "
47
- import sys, re
48
- lines = sys.stdin.read().strip().split('\n')
49
- current = {}
50
- for line in lines:
51
- m = re.match(r'qmd://[^/]+/(.+?)(?::\d+)?\s+#\w+', line)
52
- if m:
53
- if current and current.get('path'):
54
- score = current.get('score', '?')
55
- title = current.get('title', '(untitled)')
56
- print(f\" {current['path']} ({score}) — {title}\")
57
- current = {'path': m.group(1)}
58
- elif line.startswith('Title: '):
59
- current['title'] = line[7:]
60
- elif line.startswith('Score:'):
61
- parts = line.split()
62
- current['score'] = parts[-1] if parts else '?'
63
- if current and current.get('path'):
64
- score = current.get('score', '?')
65
- title = current.get('title', '(untitled)')
66
- print(f\" {current['path']} ({score}) — {title}\")
67
- " 2>/dev/null
68
- }
69
-
70
- INPUT=$(cat)
71
- USER_MESSAGE=$(echo "$INPUT" | jq -r '.prompt // empty' 2>/dev/null)
72
-
73
- if [ -z "$USER_MESSAGE" ] || [ ${#USER_MESSAGE} -lt 10 ]; then
74
- exit 0
75
- fi
76
-
77
- QUERY="${USER_MESSAGE:0:500}"
78
-
79
- # Toast: start retrieving
80
- "$TOAST" fathom "⏳ Retrieving docs..." &>/dev/null
81
-
82
- # --- Phase 1: Read cached vsearch results from previous query ---
83
- CACHED_VSEARCH=""
84
- if [ -f "$VSEARCH_CACHE" ]; then
85
- CACHE_AGE=$(( $(date +%s) - $(stat -c %Y "$VSEARCH_CACHE" 2>/dev/null || echo 0) ))
86
- if [ "$CACHE_AGE" -lt "$CACHE_TTL_SECONDS" ]; then
87
- RAW_VSEARCH=$(python3 -c "
88
- import json, sys
89
- try:
90
- with open(sys.argv[1]) as f:
91
- data = json.load(f)
92
- if data.get('results'):
93
- print(data['results'])
94
- except Exception:
95
- pass
96
- " "$VSEARCH_CACHE" 2>/dev/null)
97
- if [ -n "$RAW_VSEARCH" ]; then
98
- CACHED_VSEARCH=$(echo "$RAW_VSEARCH" | compact_results)
99
- fi
100
- fi
101
- fi
102
-
103
- # --- Phase 2: Run search via API ---
104
- ENCODED_Q=$(python3 -c "import urllib.parse, sys; print(urllib.parse.quote(sys.argv[1]))" "$QUERY")
105
-
106
- CURL_ARGS=(-sf)
107
- [ -n "$AUTH_HEADER" ] && CURL_ARGS+=(-H "$AUTH_HEADER")
108
-
109
- API_RESPONSE=$(timeout 5 curl "${CURL_ARGS[@]}" "${SERVER}/api/search?q=${ENCODED_Q}&n=5&mode=bm25&workspace=${WORKSPACE}" 2>/dev/null)
110
- BM25_RESULTS=""
111
-
112
- if [ -n "$API_RESPONSE" ]; then
113
- BM25_RESULTS=$(echo "$API_RESPONSE" | python3 -c "
114
- import json, sys
115
- try:
116
- data = json.load(sys.stdin)
117
- for r in data.get('results', []):
118
- score = str(r.get('score', '?')) + '%'
119
- title = r.get('title', '(untitled)')
120
- path = r.get('file', '')
121
- print(f' {path} ({score}) — {title}')
122
- except Exception:
123
- pass
124
- " 2>/dev/null)
125
- fi
126
-
127
- # Combine vault results
128
- ALL_RESULTS=""
129
- [ -n "$BM25_RESULTS" ] && ALL_RESULTS="$BM25_RESULTS"
130
- if [ -n "$CACHED_VSEARCH" ]; then
131
- if [ -n "$ALL_RESULTS" ]; then
132
- ALL_RESULTS="$ALL_RESULTS"$'\n'"$CACHED_VSEARCH"
133
- else
134
- ALL_RESULTS="$CACHED_VSEARCH"
135
- fi
136
- fi
137
-
138
- # --- Output ---
139
- if [ -n "$BM25_RESULTS" ] || [ -n "$CACHED_VSEARCH" ]; then
140
- VAULT_COUNT=0
141
- [ -n "$BM25_RESULTS" ] && VAULT_COUNT=$(echo "$BM25_RESULTS" | grep -c '^\s' || true)
142
- [ -n "$CACHED_VSEARCH" ] && VAULT_COUNT=$((VAULT_COUNT + $(echo "$CACHED_VSEARCH" | grep -c '^\s' || true)))
143
-
144
- DETAIL_TEXT="Fathom Vault: ${VAULT_COUNT} results"
145
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'
146
- if [ -n "$BM25_RESULTS" ] && [ -n "$CACHED_VSEARCH" ]; then
147
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'"Vault (keyword):"
148
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$BM25_RESULTS"
149
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'$'\n'"Vault (semantic, previous query):"
150
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$CACHED_VSEARCH"
151
- elif [ -n "$BM25_RESULTS" ]; then
152
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$BM25_RESULTS"
153
- elif [ -n "$CACHED_VSEARCH" ]; then
154
- DETAIL_TEXT="$DETAIL_TEXT"$'\n'"$CACHED_VSEARCH"
155
- fi
156
-
157
- SUMMARY="Fathom Vault: ${VAULT_COUNT} memories"
158
-
159
- # Toast: result
160
- "$TOAST" fathom "✓ ${VAULT_COUNT} docs recalled" &>/dev/null
161
-
162
- python3 -c "
163
- import json, sys
164
- summary = sys.argv[1]
165
- detail = sys.argv[2]
166
- print(json.dumps({
167
- 'systemMessage': summary,
168
- 'hookSpecificOutput': {
169
- 'hookEventName': 'UserPromptSubmit',
170
- 'additionalContext': detail
171
- }
172
- }))
173
- " "$SUMMARY" "$DETAIL_TEXT"
174
- else
175
- # Toast: no results
176
- "$TOAST" fathom "✓ No docs matched" &>/dev/null
177
- fi
178
-
179
- # --- Phase 3: Launch background vsearch for current query ---
180
- SHOULD_LAUNCH=true
181
-
182
- if [ -f "$VSEARCH_LOCK" ]; then
183
- LOCK_AGE=$(( $(date +%s) - $(stat -c %Y "$VSEARCH_LOCK" 2>/dev/null || echo 0) ))
184
- if [ "$LOCK_AGE" -lt "$STALE_LOCK_SECONDS" ]; then
185
- SHOULD_LAUNCH=false
186
- else
187
- rm -f "$VSEARCH_LOCK"
188
- fi
189
- fi
190
-
191
- if [ "$SHOULD_LAUNCH" = true ]; then
192
- nohup "$HOOK_DIR/fathom-vsearch-background.sh" "$QUERY" "$WORKSPACE" >/dev/null 2>&1 &
193
- disown
194
- fi
@@ -1,72 +0,0 @@
1
- #!/bin/bash
2
- # Fathom SessionStart hook — version check on session startup.
3
- # Compares local .fathom/version against npm registry and injects update notice.
4
- #
5
- # Output: JSON with hookSpecificOutput.additionalContext (update notice or empty).
6
- # Graceful failure: if npm unreachable or version file missing, skip silently.
7
-
8
- set -o pipefail
9
-
10
- HOOK_DIR="$(cd "$(dirname "$0")" && pwd)"
11
- TOAST="$HOOK_DIR/hook-toast.sh"
12
-
13
- # Consume stdin (SessionStart sends JSON we don't need)
14
- cat > /dev/null
15
-
16
- # Walk up to find .fathom.json
17
- find_config() {
18
- local dir="$PWD"
19
- while [ "$dir" != "/" ]; do
20
- if [ -f "$dir/.fathom.json" ]; then
21
- echo "$dir"
22
- return 0
23
- fi
24
- dir="$(dirname "$dir")"
25
- done
26
- return 1
27
- }
28
-
29
- PROJECT_DIR=$(find_config 2>/dev/null) || exit 0
30
-
31
- VERSION_FILE="$PROJECT_DIR/.fathom/version"
32
-
33
- # If no version file, skip silently — init hasn't been run with this version yet
34
- if [ ! -f "$VERSION_FILE" ]; then
35
- exit 0
36
- fi
37
-
38
- LOCAL_VERSION=$(cat "$VERSION_FILE" 2>/dev/null | tr -d '[:space:]')
39
- if [ -z "$LOCAL_VERSION" ]; then
40
- exit 0
41
- fi
42
-
43
- # Check npm registry for latest version (2s timeout)
44
- LATEST_VERSION=$(curl -s --max-time 2 "https://registry.npmjs.org/fathom-mcp/latest" 2>/dev/null \
45
- | python3 -c "import json,sys; print(json.load(sys.stdin).get('version',''))" 2>/dev/null || echo "")
46
-
47
- # If npm check failed, skip silently
48
- if [ -z "$LATEST_VERSION" ]; then
49
- "$TOAST" fathom "✓ Fathom v${LOCAL_VERSION}" &>/dev/null
50
- exit 0
51
- fi
52
-
53
- # Compare versions
54
- if [ "$LOCAL_VERSION" != "$LATEST_VERSION" ]; then
55
- # Update available — inject notice and show toast
56
- "$TOAST" fathom "⬆ Fathom v${LATEST_VERSION} available" &>/dev/null
57
-
58
- python3 -c "
59
- import json
60
- print(json.dumps({
61
- 'hookSpecificOutput': {
62
- 'hookEventName': 'SessionStart',
63
- 'additionalContext': 'Fathom update available: v${LOCAL_VERSION} → v${LATEST_VERSION}. Run: npx fathom-mcp update'
64
- }
65
- }))
66
- "
67
- else
68
- # Up to date — toast only, no context injection
69
- "$TOAST" fathom "✓ Fathom v${LOCAL_VERSION}" &>/dev/null
70
- fi
71
-
72
- exit 0
@@ -1,366 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # fathom-start.sh — Launch an agent session.
4
- #
5
- # Reads .fathom.json for workspace name and agent. For interactive agents
6
- # (claude-code, codex, gemini, opencode), wraps in a tmux session. For
7
- # headless agents (claude-sdk), spawns a direct process with PID tracking.
8
- #
9
- # Claude agents require an explicit mode flag:
10
- # fathom-start.sh --claude-code-w-tmux Interactive Claude Code in tmux
11
- # fathom-start.sh --claude-sdk Headless Claude SDK, no tmux
12
- #
13
- # Other usage:
14
- # fathom-start.sh Start agent (non-claude agents auto-detect)
15
- # fathom-start.sh --detach Start agent, don't attach to tmux
16
- # fathom-start.sh --agent X Override agent
17
- # fathom-start.sh --kill Kill existing session
18
- # fathom-start.sh --status Show session status
19
-
20
- set -euo pipefail
21
-
22
- # ── Defaults ──────────────────────────────────────────────────────────────────
23
-
24
- ATTACH=true
25
- AGENT_OVERRIDE=""
26
- ACTION="start"
27
- MODE_FLAG=""
28
- USE_TMUX=true
29
-
30
- # ── Parse flags ───────────────────────────────────────────────────────────────
31
-
32
- while [[ $# -gt 0 ]]; do
33
- case "$1" in
34
- --claude-code-w-tmux) MODE_FLAG="claude-code-w-tmux"; shift ;;
35
- --claude-sdk) MODE_FLAG="claude-sdk"; shift ;;
36
- --detach) ATTACH=false; shift ;;
37
- --attach) ATTACH=true; shift ;;
38
- --agent) AGENT_OVERRIDE="$2"; shift 2 ;;
39
- --kill) ACTION="kill"; shift ;;
40
- --status) ACTION="status"; shift ;;
41
- -h|--help)
42
- echo "Usage: fathom-start.sh [FLAGS]"
43
- echo ""
44
- echo "Mode flags (required for Claude agents):"
45
- echo " --claude-code-w-tmux Interactive Claude Code in tmux"
46
- echo " --claude-sdk Headless Claude SDK, no tmux"
47
- echo ""
48
- echo "Other flags:"
49
- echo " --detach Start but don't attach to tmux"
50
- echo " --agent X Override agent: claude-code, claude-sdk, codex, gemini, opencode"
51
- echo " --kill Kill existing session"
52
- echo " --status Show if session is running"
53
- exit 0
54
- ;;
55
- *)
56
- echo "Unknown flag: $1" >&2
57
- exit 1
58
- ;;
59
- esac
60
- done
61
-
62
- # ── Find .fathom.json ────────────────────────────────────────────────────────
63
-
64
- find_config() {
65
- local dir="$PWD"
66
- while [[ "$dir" != "/" ]]; do
67
- if [[ -f "$dir/.fathom.json" ]]; then
68
- echo "$dir/.fathom.json"
69
- return 0
70
- fi
71
- dir="$(dirname "$dir")"
72
- done
73
- return 1
74
- }
75
-
76
- CONFIG_FILE=$(find_config) || {
77
- echo "Error: No .fathom.json found (searched from $PWD to /)" >&2
78
- echo "Run 'npx fathom-mcp init' first." >&2
79
- exit 1
80
- }
81
-
82
- PROJECT_DIR="$(dirname "$CONFIG_FILE")"
83
-
84
- # ── Parse config ──────────────────────────────────────────────────────────────
85
-
86
- read_json_field() {
87
- local file="$1" field="$2"
88
- if command -v jq &>/dev/null; then
89
- jq -r ".$field // empty" "$file" 2>/dev/null
90
- else
91
- # Fallback: simple grep/sed for flat string fields
92
- sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" "$file" | head -1
93
- fi
94
- }
95
-
96
- read_json_array_first() {
97
- local file="$1" field="$2"
98
- if command -v jq &>/dev/null; then
99
- jq -r ".$field[0] // empty" "$file" 2>/dev/null
100
- else
101
- # Fallback: grab first quoted string after the array field
102
- sed -n "/\"$field\"/,/\]/{ s/.*\"\([^\"]*\)\".*/\1/p; }" "$file" | head -1
103
- fi
104
- }
105
-
106
- WORKSPACE=$(read_json_field "$CONFIG_FILE" "workspace")
107
- if [[ -z "$WORKSPACE" ]]; then
108
- WORKSPACE="$(basename "$PROJECT_DIR")"
109
- fi
110
-
111
- SESSION="${WORKSPACE}_fathom-session"
112
- PANE_DIR="$HOME/.config/fathom-mcp"
113
- PANE_FILE="$PANE_DIR/${WORKSPACE}-pane-id"
114
- PID_FILE="$PROJECT_DIR/.fathom/agent.pid"
115
- LOG_FILE="$PROJECT_DIR/.fathom/agent.log"
116
-
117
- # ── Resolve agent type ────────────────────────────────────────────────────────
118
-
119
- resolve_agent() {
120
- local agent="${AGENT_OVERRIDE:-}"
121
- if [[ -z "$agent" ]]; then
122
- agent=$(read_json_array_first "$CONFIG_FILE" "agents")
123
- fi
124
- if [[ -z "$agent" ]]; then
125
- agent="claude-code"
126
- fi
127
- echo "$agent"
128
- }
129
-
130
- AGENT=$(resolve_agent)
131
-
132
- # ── Determine execution mode (tmux vs headless) ──────────────────────────────
133
- # Must happen in the main shell, not inside $(...) subshell.
134
-
135
- case "$AGENT" in
136
- claude-code)
137
- if [[ "$MODE_FLAG" == "claude-sdk" ]]; then
138
- USE_TMUX=false
139
- elif [[ -z "$MODE_FLAG" ]]; then
140
- echo "Error: Claude Code requires an explicit mode flag:" >&2
141
- echo " fathom-start.sh --claude-code-w-tmux Interactive Claude in tmux" >&2
142
- echo " fathom-start.sh --claude-sdk Headless Claude SDK" >&2
143
- echo "" >&2
144
- echo "For local agents managed by fathom-server, use the dashboard restart button instead." >&2
145
- exit 1
146
- fi
147
- ;;
148
- claude-sdk)
149
- if [[ "$MODE_FLAG" == "claude-code-w-tmux" ]]; then
150
- USE_TMUX=true
151
- else
152
- USE_TMUX=false
153
- fi
154
- ;;
155
- esac
156
-
157
- # ── Resolve agent command ─────────────────────────────────────────────────────
158
-
159
- resolve_agent_cmd() {
160
- case "$AGENT" in
161
- claude-code)
162
- if [[ "$USE_TMUX" == false ]]; then
163
- echo "claude -p --permission-mode bypassPermissions --output-format stream-json"
164
- else
165
- echo "claude --model opus --permission-mode bypassPermissions"
166
- fi
167
- ;;
168
- claude-sdk)
169
- if [[ "$USE_TMUX" == true ]]; then
170
- echo "claude --model opus --permission-mode bypassPermissions"
171
- else
172
- echo "claude -p --permission-mode bypassPermissions --output-format stream-json"
173
- fi
174
- ;;
175
- codex)
176
- echo "codex"
177
- ;;
178
- gemini)
179
- echo "gemini"
180
- ;;
181
- opencode)
182
- echo "opencode"
183
- ;;
184
- *)
185
- echo "Warning: Unknown agent '$AGENT', falling back to claude" >&2
186
- echo "claude"
187
- ;;
188
- esac
189
- }
190
-
191
- # ── Save pane ID ──────────────────────────────────────────────────────────────
192
-
193
- save_pane_id() {
194
- local pane_id
195
- pane_id=$(tmux list-panes -t "$SESSION" -F '#{pane_id}' 2>/dev/null | head -1)
196
- if [[ -n "$pane_id" ]]; then
197
- mkdir -p "$PANE_DIR"
198
- echo "$pane_id" > "$PANE_FILE"
199
- echo "Pane ID: $pane_id → $PANE_FILE"
200
- fi
201
- }
202
-
203
- # ── Session/process check ────────────────────────────────────────────────────
204
-
205
- session_exists() {
206
- tmux has-session -t "$SESSION" 2>/dev/null
207
- }
208
-
209
- headless_is_running() {
210
- if [[ -f "$PID_FILE" ]]; then
211
- local pid
212
- pid=$(cat "$PID_FILE" 2>/dev/null)
213
- if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
214
- return 0
215
- fi
216
- fi
217
- return 1
218
- }
219
-
220
- # ── Actions ───────────────────────────────────────────────────────────────────
221
-
222
- do_status() {
223
- echo "Workspace: $WORKSPACE"
224
- echo "Session: $SESSION"
225
- if session_exists; then
226
- echo "Status: running (tmux)"
227
- if [[ -f "$PANE_FILE" ]]; then
228
- echo "Pane ID: $(cat "$PANE_FILE")"
229
- fi
230
- elif headless_is_running; then
231
- echo "Status: running (headless)"
232
- echo "PID: $(cat "$PID_FILE")"
233
- echo "Log: $LOG_FILE"
234
- if [[ -p "$PROJECT_DIR/.fathom/agent.pipe" ]]; then
235
- echo "Pipe: $PROJECT_DIR/.fathom/agent.pipe"
236
- fi
237
- else
238
- echo "Status: not running"
239
- fi
240
- }
241
-
242
- do_kill() {
243
- local killed=false
244
- if session_exists; then
245
- tmux kill-session -t "$SESSION"
246
- rm -f "$PANE_FILE"
247
- echo "Killed tmux session: $SESSION"
248
- killed=true
249
- fi
250
- if headless_is_running; then
251
- local pid
252
- pid=$(cat "$PID_FILE")
253
- kill "$pid" 2>/dev/null || true
254
- rm -f "$PID_FILE"
255
- echo "Killed headless process: PID $pid"
256
- killed=true
257
- fi
258
- # Clean up keeper process and FIFO
259
- local keeper_pid_file="$PROJECT_DIR/.fathom/agent-keeper.pid"
260
- if [[ -f "$keeper_pid_file" ]]; then
261
- local keeper_pid
262
- keeper_pid=$(cat "$keeper_pid_file")
263
- kill "$keeper_pid" 2>/dev/null || true
264
- rm -f "$keeper_pid_file"
265
- fi
266
- rm -f "$PROJECT_DIR/.fathom/agent.pipe"
267
- if [[ "$killed" == false ]]; then
268
- echo "No running session found for: $WORKSPACE"
269
- fi
270
- }
271
-
272
- do_start() {
273
- local agent_cmd
274
- agent_cmd=$(resolve_agent_cmd)
275
-
276
- if [[ "$USE_TMUX" == false ]]; then
277
- # ── Headless mode — FIFO-based stdin ──
278
- if headless_is_running; then
279
- echo "Headless agent already running (PID $(cat "$PID_FILE"))"
280
- echo "Log: $LOG_FILE"
281
- return 0
282
- fi
283
-
284
- echo "Starting headless: $agent_cmd"
285
- echo "Dir: $PROJECT_DIR"
286
- echo "Logs: $LOG_FILE"
287
-
288
- cd "$PROJECT_DIR"
289
- unset CLAUDECODE 2>/dev/null || true
290
- # Ensure common binary locations are on PATH for backgrounded processes
291
- export PATH="$HOME/.local/bin:$HOME/.claude/local/bin:$PATH"
292
- mkdir -p .fathom
293
-
294
- local pipe_file="$PROJECT_DIR/.fathom/agent.pipe"
295
-
296
- # Clean up stale pipe
297
- rm -f "$pipe_file"
298
- mkfifo "$pipe_file"
299
-
300
- # Keep a writer FD open so the pipe never sends EOF to the reader.
301
- # Without this, the agent reads EOF and exits when no writer is connected.
302
- sleep infinity > "$pipe_file" &
303
- local keeper_pid=$!
304
-
305
- # Start agent reading from the FIFO, logging stdout/stderr
306
- $agent_cmd < "$pipe_file" >> "$LOG_FILE" 2>&1 &
307
- local pid=$!
308
-
309
- echo "$pid" > "$PID_FILE"
310
- echo "$keeper_pid" > "$PROJECT_DIR/.fathom/agent-keeper.pid"
311
-
312
- echo "PID: $pid"
313
- echo "Pipe: $pipe_file"
314
- echo "Started. View logs: tail -f $LOG_FILE"
315
- echo "Inject: echo 'your message' > $pipe_file"
316
- return 0
317
- fi
318
-
319
- # ── Interactive mode — tmux session ──
320
- if ! command -v tmux &>/dev/null; then
321
- echo "Error: tmux is not installed. Install it first:" >&2
322
- echo " apt install tmux | brew install tmux | dnf install tmux" >&2
323
- exit 1
324
- fi
325
-
326
- if session_exists; then
327
- echo "Session already running: $SESSION"
328
- save_pane_id
329
- if [[ "$ATTACH" == true ]]; then
330
- exec tmux attach-session -t "$SESSION"
331
- fi
332
- return 0
333
- fi
334
-
335
- echo "Starting: $SESSION"
336
- echo "Agent: $agent_cmd"
337
- echo "Dir: $PROJECT_DIR"
338
-
339
- # Unset CLAUDECODE to avoid nested session detection
340
- unset CLAUDECODE 2>/dev/null || true
341
-
342
- # Create detached tmux session running the agent
343
- tmux new-session -d -s "$SESSION" -c "$PROJECT_DIR" $agent_cmd
344
-
345
- # Wait briefly for session to stabilize
346
- sleep 2
347
-
348
- if session_exists; then
349
- save_pane_id
350
- echo "Session started."
351
- if [[ "$ATTACH" == true ]]; then
352
- exec tmux attach-session -t "$SESSION"
353
- fi
354
- else
355
- echo "Error: Session failed to start" >&2
356
- exit 1
357
- fi
358
- }
359
-
360
- # ── Main ──────────────────────────────────────────────────────────────────────
361
-
362
- case "$ACTION" in
363
- status) do_status ;;
364
- kill) do_kill ;;
365
- start) do_start ;;
366
- esac
@@ -1,46 +0,0 @@
1
- #!/bin/bash
2
- # Background vector search worker for fathom-recall hook.
3
- # Launched via nohup/disown — runs asynchronously after the hook returns.
4
- #
5
- # Takes a search query as $1 and workspace as $2, runs qmd vsearch,
6
- # and writes results as JSON to /tmp/fathom-vsearch-cache.json.
7
-
8
- set -o pipefail
9
-
10
- LOCK_FILE="/tmp/fathom-vsearch.lock"
11
- CACHE_FILE="/tmp/fathom-vsearch-cache.json"
12
-
13
- cleanup() {
14
- rm -f "$LOCK_FILE"
15
- }
16
- trap cleanup EXIT
17
-
18
- if [ -z "$1" ]; then
19
- exit 1
20
- fi
21
-
22
- WORKSPACE="${2:-fathom}"
23
-
24
- echo $$ > "$LOCK_FILE"
25
-
26
- VSEARCH_QUERY="${1:0:200}"
27
-
28
- # Run vsearch with 180-second hard timeout
29
- RESULTS=$(timeout 180 qmd vsearch "$VSEARCH_QUERY" -n 5 -c "$WORKSPACE" --min-score 0.5 2>/dev/null)
30
-
31
- TMPFILE=$(mktemp /tmp/fathom-vsearch-cache.XXXXXX)
32
-
33
- python3 -c "
34
- import json, sys, time
35
- query = sys.argv[1][:200]
36
- results = sys.stdin.read().strip()
37
- has_results = bool(results) and 'No results found' not in results
38
- with open(sys.argv[2], 'w') as f:
39
- json.dump({
40
- 'query': query,
41
- 'timestamp': int(time.time()),
42
- 'results': results if has_results else None
43
- }, f)
44
- " "$VSEARCH_QUERY" "$TMPFILE" <<< "$RESULTS"
45
-
46
- mv "$TMPFILE" "$CACHE_FILE"