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 +1 -1
- package/scripts/hook-toast.sh +41 -88
- package/src/agents.js +24 -6
package/package.json
CHANGED
package/scripts/hook-toast.sh
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
# hook-toast.sh — tmux
|
|
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> #
|
|
6
|
-
# hook-toast.sh <system> --status <id> # start a multi-stage toast (
|
|
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> #
|
|
11
|
+
# hook-toast.sh --close <id> # show final message, then clear
|
|
9
12
|
#
|
|
10
13
|
# Built-in systems: fathom, memento
|
|
11
|
-
# fathom → 📝
|
|
12
|
-
# memento → 🧠
|
|
13
|
-
#
|
|
14
|
-
# Any other system name works too — gets a default icon and grey border.
|
|
14
|
+
# fathom → 📝
|
|
15
|
+
# memento → 🧠
|
|
15
16
|
#
|
|
16
|
-
#
|
|
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
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
121
|
-
local
|
|
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
|
-
|
|
76
|
+
# Save system info for --update to use
|
|
77
|
+
echo "$icon|$title" > "$meta_file"
|
|
127
78
|
|
|
128
|
-
|
|
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
|
-
|
|
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
|
|
99
|
+
# Close a running toast — show final message briefly, then clear
|
|
152
100
|
toast_close() {
|
|
153
101
|
local id="$1"
|
|
154
|
-
|
|
155
|
-
|
|
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 --
|
|
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
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
return files.some((f) => f.endsWith(".jsonl"));
|
|
108
|
+
candidates.add(fs.realpathSync(projectDir));
|
|
104
109
|
} catch {
|
|
105
|
-
|
|
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) {
|