fathom-mcp 0.5.17 → 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/src/cli.js +25 -6
- package/src/server-client.js +12 -0
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) {
|
package/src/cli.js
CHANGED
|
@@ -576,17 +576,36 @@ async function runInit(flags = {}) {
|
|
|
576
576
|
appendToGitignore(cwd, [".fathom.json"]);
|
|
577
577
|
console.log(" ✓ .gitignore");
|
|
578
578
|
|
|
579
|
-
// Register with server
|
|
579
|
+
// Register with server — use provision endpoint to get API key + create settings.json entry
|
|
580
580
|
if (serverReachable) {
|
|
581
|
-
const
|
|
581
|
+
const provResult = await regClient.provisionWorkspace(workspace, cwd, {
|
|
582
582
|
vault,
|
|
583
583
|
agents: selectedAgents,
|
|
584
|
-
type:
|
|
584
|
+
type: vaultMode,
|
|
585
|
+
execution: "local",
|
|
585
586
|
});
|
|
586
|
-
if (
|
|
587
|
+
if (provResult.ok) {
|
|
587
588
|
console.log(` ✓ Registered workspace "${workspace}" with server`);
|
|
588
|
-
|
|
589
|
-
|
|
589
|
+
// Use the server-provisioned API key (overwrites any manually entered one)
|
|
590
|
+
if (provResult.api_key) {
|
|
591
|
+
apiKey = provResult.api_key;
|
|
592
|
+
configData.apiKey = apiKey;
|
|
593
|
+
writeConfig(cwd, configData);
|
|
594
|
+
console.log(` ✓ API key provisioned by server`);
|
|
595
|
+
}
|
|
596
|
+
} else if (provResult.error) {
|
|
597
|
+
console.log(` · Server registration: ${provResult.error}`);
|
|
598
|
+
// Fall back to old registration endpoint (server may be older version)
|
|
599
|
+
const regResult = await regClient.registerWorkspace(workspace, cwd, {
|
|
600
|
+
vault,
|
|
601
|
+
agents: selectedAgents,
|
|
602
|
+
type: vaultMode,
|
|
603
|
+
});
|
|
604
|
+
if (regResult.ok) {
|
|
605
|
+
console.log(` ✓ Registered workspace "${workspace}" with server (legacy)`);
|
|
606
|
+
} else if (regResult.error) {
|
|
607
|
+
console.log(` · Server registration: ${regResult.error}`);
|
|
608
|
+
}
|
|
590
609
|
}
|
|
591
610
|
}
|
|
592
611
|
|
package/src/server-client.js
CHANGED
|
@@ -129,6 +129,17 @@ export function createClient(config) {
|
|
|
129
129
|
return request("POST", "/api/workspaces", { body });
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
async function provisionWorkspace(name, projectPath, { vault, description, agents, type, execution, ssh } = {}) {
|
|
133
|
+
const body = { name, path: projectPath };
|
|
134
|
+
if (vault) body.vault = vault;
|
|
135
|
+
if (description) body.description = description;
|
|
136
|
+
if (agents && agents.length > 0) body.agents = agents;
|
|
137
|
+
if (type) body.type = type;
|
|
138
|
+
if (execution) body.execution = execution;
|
|
139
|
+
if (ssh) body.ssh = ssh;
|
|
140
|
+
return request("POST", "/api/workspaces/provision", { body });
|
|
141
|
+
}
|
|
142
|
+
|
|
132
143
|
// --- Access tracking -------------------------------------------------------
|
|
133
144
|
|
|
134
145
|
async function notifyAccess(filePath, ws) {
|
|
@@ -345,6 +356,7 @@ export function createClient(config) {
|
|
|
345
356
|
sendToWorkspace,
|
|
346
357
|
listWorkspaces,
|
|
347
358
|
registerWorkspace,
|
|
359
|
+
provisionWorkspace,
|
|
348
360
|
notifyAccess,
|
|
349
361
|
vaultList,
|
|
350
362
|
vaultFolder,
|