fathom-mcp 0.5.18 → 0.5.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fathom-mcp",
3
- "version": "0.5.18",
3
+ "version": "0.5.19",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,22 +1,20 @@
1
1
  #!/bin/bash
2
- # hook-toast.sh — tmux popup notifications for agent hooks.
2
+ # hook-toast.sh — tmux status-line notifications for agent hooks.
3
+ #
4
+ # Uses tmux display-message — non-modal, no keyboard stealing.
5
+ # Each toast targets the originating session via -t.
3
6
  #
4
7
  # Usage:
5
- # hook-toast.sh <system> <message> # queued toast (shows for 2s each)
6
- # hook-toast.sh <system> --status <id> # start a multi-stage toast (poll mode)
8
+ # hook-toast.sh <system> <message> # show toast for 3s
9
+ # hook-toast.sh <system> --status <id> # start a multi-stage toast (persistent)
7
10
  # hook-toast.sh --update <id> <message> # update a running toast
8
- # hook-toast.sh --close <id> # close a running toast (after delay)
11
+ # hook-toast.sh --close <id> # show final message, then clear
9
12
  #
10
13
  # Built-in systems: fathom, memento
11
- # fathom → 📝 purple (colour141)
12
- # memento → 🧠 teal (colour37)
13
- #
14
- # Any other system name works too — gets a default icon and grey border.
14
+ # fathom → 📝
15
+ # memento → 🧠
15
16
  #
16
- # One-shot toasts are queued per-sessionmultiple hooks can fire toasts
17
- # without clobbering each other. A single popup reads from the queue,
18
- # showing each message for 2s before advancing. Popups target the session
19
- # that spawned them via -t, so they stay in the right terminal.
17
+ # Any other system name works too gets a default 📌 icon.
20
18
  #
21
19
  # Multi-stage example (PreCompact):
22
20
  # hook-toast.sh memento --status precompact
@@ -30,25 +28,15 @@ if ! tmux info &>/dev/null; then
30
28
  exit 0
31
29
  fi
32
30
 
33
- # Derive session name — used for per-session queue isolation and -t targeting
31
+ # Derive session name — used for -t targeting
34
32
  SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null)
35
33
  if [ -z "$SESSION" ]; then
36
34
  exit 0
37
35
  fi
38
36
 
39
37
  TOAST_DIR="/tmp/hook-toast/${SESSION}"
40
- QUEUE_FILE="$TOAST_DIR/queue"
41
- READER_PID_FILE="$TOAST_DIR/reader.pid"
42
38
  mkdir -p "$TOAST_DIR"
43
39
 
44
- get_style() {
45
- case "$1" in
46
- memento) echo "colour37" ;;
47
- fathom) echo "colour141" ;;
48
- *) echo "colour245" ;;
49
- esac
50
- }
51
-
52
40
  get_icon() {
53
41
  case "$1" in
54
42
  memento) echo "🧠" ;;
@@ -65,94 +53,59 @@ get_title() {
65
53
  esac
66
54
  }
67
55
 
68
- # Check if the queue reader popup is still alive
69
- reader_alive() {
70
- local pid
71
- pid=$(cat "$READER_PID_FILE" 2>/dev/null) || return 1
72
- [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null
73
- }
74
-
75
- # Queued one-shot toast — append to queue, spawn reader if needed
56
+ # One-shot toast display in status line for 3 seconds
76
57
  toast_oneshot() {
77
58
  local system="$1"
78
59
  local message="$2"
79
- local icon
60
+ local icon title
80
61
  icon=$(get_icon "$system")
62
+ title=$(get_title "$system")
81
63
 
82
- # Append to queue: icon|colour|title|message
83
- echo "${icon}|$(get_style "$system")|$(get_title "$system")|$message" >> "$QUEUE_FILE"
84
-
85
- # If reader is already running, it will pick up the new entry
86
- if reader_alive; then
87
- return
88
- fi
89
-
90
- # Spawn a reader popup targeting this session
91
- (tmux display-popup -t "$SESSION" -x R -y 0 -w 42 -h 3 \
92
- -s "fg=colour245" -E "
93
- echo \$\$ > '$READER_PID_FILE'
94
- while true; do
95
- LINE=\$(head -1 '$QUEUE_FILE' 2>/dev/null)
96
- if [ -z \"\$LINE\" ]; then
97
- rm -f '$READER_PID_FILE'
98
- break
99
- fi
100
- TAIL=\$(tail -n +2 '$QUEUE_FILE' 2>/dev/null)
101
- if [ -n \"\$TAIL\" ]; then
102
- echo \"\$TAIL\" > '$QUEUE_FILE'
103
- else
104
- > '$QUEUE_FILE'
105
- fi
106
- ICON=\$(echo \"\$LINE\" | cut -d'|' -f1)
107
- MSG=\$(echo \"\$LINE\" | cut -d'|' -f4-)
108
- clear
109
- echo \" \$ICON \$MSG\"
110
- sleep 2
111
- done
112
- rm -f '$QUEUE_FILE' '$READER_PID_FILE'
113
- " &>/dev/null &)
64
+ tmux display-message -t "$SESSION" -d 3000 "$icon $title: $message"
114
65
  }
115
66
 
116
- # Start a multi-stage toast (polling status file) targets this session
67
+ # Start a multi-stage toast — persistent until --close
117
68
  toast_start() {
118
69
  local system="$1"
119
70
  local id="$2"
120
- local status_file="$TOAST_DIR/$id"
121
- local colour icon title
122
- colour=$(get_style "$system")
71
+ local meta_file="$TOAST_DIR/$id"
72
+ local icon title
123
73
  icon=$(get_icon "$system")
124
74
  title=$(get_title "$system")
125
75
 
126
- echo "⏳ Starting..." > "$status_file"
76
+ # Save system info for --update to use
77
+ echo "$icon|$title" > "$meta_file"
127
78
 
128
- (tmux display-popup -t "$SESSION" -x R -y 0 -w 42 -h 3 \
129
- -s "fg=$colour" -T " $icon $title " -E "
130
- LAST=''
131
- while [ -f '$status_file' ]; do
132
- MSG=\$(cat '$status_file' 2>/dev/null)
133
- if [ \"\$MSG\" != \"\$LAST\" ]; then
134
- clear
135
- echo \" \$MSG\"
136
- LAST=\"\$MSG\"
137
- fi
138
- case \"\$MSG\" in ✓*|✗*) sleep 2; break ;; esac
139
- sleep 0.2
140
- done
141
- " &>/dev/null &)
79
+ tmux display-message -t "$SESSION" -d 0 "$icon $title: Starting..."
142
80
  }
143
81
 
144
- # Update a running toast
82
+ # Update a running toast — replace the status line message
145
83
  toast_update() {
146
84
  local id="$1"
147
85
  local message="$2"
148
- echo "$message" > "$TOAST_DIR/$id"
86
+ local meta_file="$TOAST_DIR/$id"
87
+
88
+ if [ -f "$meta_file" ]; then
89
+ local icon title
90
+ icon=$(cut -d'|' -f1 "$meta_file")
91
+ title=$(cut -d'|' -f2 "$meta_file")
92
+ tmux display-message -t "$SESSION" -d 0 "$icon $title: $message"
93
+ else
94
+ # Fallback — no meta file, just show raw message
95
+ tmux display-message -t "$SESSION" -d 0 "📌 $message"
96
+ fi
149
97
  }
150
98
 
151
- # Close a running toast (remove status file popup exits on next poll)
99
+ # Close a running toast show final message briefly, then clear
152
100
  toast_close() {
153
101
  local id="$1"
154
- sleep 2
155
- rm -f "$TOAST_DIR/$id"
102
+ local meta_file="$TOAST_DIR/$id"
103
+
104
+ # Let the last --update message linger for 3s, then clean up
105
+ sleep 3
106
+ rm -f "$meta_file"
107
+ # Clear the display-message by showing empty briefly
108
+ tmux display-message -t "$SESSION" -d 1 ""
156
109
  }
157
110
 
158
111
  # --- Argument parsing ---
package/src/agents.js CHANGED
@@ -83,7 +83,7 @@ export function isAgentRunning(name, entry) {
83
83
  */
84
84
  export function defaultCommand(agentType) {
85
85
  const cmds = {
86
- "claude-code": "claude --model opus --permission-mode bypassPermissions",
86
+ "claude-code": "claude --continue",
87
87
  codex: "codex",
88
88
  gemini: "gemini",
89
89
  opencode: "opencode",
@@ -94,16 +94,34 @@ export function defaultCommand(agentType) {
94
94
  /**
95
95
  * Check if a Claude Code project has any previous conversations to --continue from.
96
96
  * Claude stores conversations as .jsonl files in ~/.claude/projects/<encoded-path>/.
97
+ *
98
+ * The encoded directory name uses the *resolved* path that Claude sees at runtime,
99
+ * which may differ from agents.json projectDir (e.g., container bind-mounts).
100
+ * We resolve the real path and also check the literal path as a fallback.
97
101
  */
98
102
  function hasPreviousConversation(projectDir) {
99
- const encoded = projectDir.replace(/^\//, "").replace(/\//g, "-");
100
- const convDir = path.join(process.env.HOME || "/tmp", ".claude", "projects", encoded);
103
+ const claudeProjectsDir = path.join(process.env.HOME || "/tmp", ".claude", "projects");
104
+
105
+ // Try both the literal path and the real (resolved symlink/bind-mount) path
106
+ const candidates = new Set([projectDir]);
101
107
  try {
102
- const files = fs.readdirSync(convDir);
103
- return files.some((f) => f.endsWith(".jsonl"));
108
+ candidates.add(fs.realpathSync(projectDir));
104
109
  } catch {
105
- return false;
110
+ // projectDir might not exist on this machine (e.g., host path checked from container)
111
+ }
112
+
113
+ for (const dir of candidates) {
114
+ const encoded = dir.replace(/\//g, "-");
115
+ const convDir = path.join(claudeProjectsDir, encoded);
116
+ try {
117
+ const files = fs.readdirSync(convDir);
118
+ if (files.some((f) => f.endsWith(".jsonl"))) return true;
119
+ } catch {
120
+ // continue to next candidate
121
+ }
106
122
  }
123
+
124
+ return false;
107
125
  }
108
126
 
109
127
  export function startAgent(name, entry) {