fathom-mcp 0.5.14 → 0.5.16
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/src/agents.js +35 -90
- package/src/cli.js +23 -37
- package/src/ws-connection.js +0 -7
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -65,40 +65,15 @@ export function removeAgent(name) {
|
|
|
65
65
|
export function isAgentRunning(name, entry) {
|
|
66
66
|
if (entry.ssh) return "ssh";
|
|
67
67
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
if (mode === "tmux") {
|
|
71
|
-
const session = `${name}_fathom-session`;
|
|
72
|
-
try {
|
|
73
|
-
execSync(`tmux has-session -t ${JSON.stringify(session)} 2>/dev/null`, {
|
|
74
|
-
stdio: "pipe",
|
|
75
|
-
});
|
|
76
|
-
return "running";
|
|
77
|
-
} catch {
|
|
78
|
-
return "stopped";
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// headless — check pids directory for any live processes
|
|
83
|
-
const pidsDir = path.join(entry.projectDir, ".fathom", "pids");
|
|
68
|
+
const session = `${name}_fathom-session`;
|
|
84
69
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (!isNaN(pid)) {
|
|
90
|
-
process.kill(pid, 0); // signal 0 = existence check
|
|
91
|
-
return "running";
|
|
92
|
-
}
|
|
93
|
-
} catch {
|
|
94
|
-
// Dead process — clean up stale PID file
|
|
95
|
-
try { fs.unlinkSync(path.join(pidsDir, file)); } catch { /* */ }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
70
|
+
execSync(`tmux has-session -t ${JSON.stringify(session)} 2>/dev/null`, {
|
|
71
|
+
stdio: "pipe",
|
|
72
|
+
});
|
|
73
|
+
return "running";
|
|
98
74
|
} catch {
|
|
99
|
-
|
|
75
|
+
return "stopped";
|
|
100
76
|
}
|
|
101
|
-
return "stopped";
|
|
102
77
|
}
|
|
103
78
|
|
|
104
79
|
// ── Start / Stop ────────────────────────────────────────────────────────────
|
|
@@ -106,29 +81,39 @@ export function isAgentRunning(name, entry) {
|
|
|
106
81
|
/**
|
|
107
82
|
* Default command per agent type + execution mode.
|
|
108
83
|
*/
|
|
109
|
-
export function defaultCommand(agentType
|
|
84
|
+
export function defaultCommand(agentType) {
|
|
110
85
|
const cmds = {
|
|
111
|
-
"claude-code":
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
},
|
|
116
|
-
"claude-sdk": {
|
|
117
|
-
tmux: "claude --model opus --permission-mode bypassPermissions",
|
|
118
|
-
headless:
|
|
119
|
-
"claude -p --permission-mode bypassPermissions --output-format stream-json",
|
|
120
|
-
},
|
|
121
|
-
codex: { tmux: "codex", headless: "codex" },
|
|
122
|
-
gemini: { tmux: "gemini", headless: "gemini" },
|
|
123
|
-
opencode: { tmux: "opencode", headless: "opencode" },
|
|
86
|
+
"claude-code": "claude --model opus --permission-mode bypassPermissions",
|
|
87
|
+
codex: "codex",
|
|
88
|
+
gemini: "gemini",
|
|
89
|
+
opencode: "opencode",
|
|
124
90
|
};
|
|
125
|
-
|
|
126
|
-
|
|
91
|
+
return cmds[agentType] || cmds["claude-code"];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Check if a Claude Code project has any previous conversations to --continue from.
|
|
96
|
+
* Claude stores conversations as .jsonl files in ~/.claude/projects/<encoded-path>/.
|
|
97
|
+
*/
|
|
98
|
+
function hasPreviousConversation(projectDir) {
|
|
99
|
+
const encoded = projectDir.replace(/^\//, "").replace(/\//g, "-");
|
|
100
|
+
const convDir = path.join(process.env.HOME || "/tmp", ".claude", "projects", encoded);
|
|
101
|
+
try {
|
|
102
|
+
const files = fs.readdirSync(convDir);
|
|
103
|
+
return files.some((f) => f.endsWith(".jsonl"));
|
|
104
|
+
} catch {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
127
107
|
}
|
|
128
108
|
|
|
129
109
|
export function startAgent(name, entry) {
|
|
130
|
-
|
|
131
|
-
|
|
110
|
+
let command = entry.command || defaultCommand(entry.agentType || "claude-code");
|
|
111
|
+
|
|
112
|
+
// --continue crashes if there's no previous conversation. Strip it for new agents.
|
|
113
|
+
if (command.includes("--continue") && !hasPreviousConversation(entry.projectDir)) {
|
|
114
|
+
command = command.replace(/\s*--continue\b/g, "");
|
|
115
|
+
}
|
|
116
|
+
|
|
132
117
|
const session = `${name}_fathom-session`;
|
|
133
118
|
|
|
134
119
|
// Build env
|
|
@@ -144,10 +129,6 @@ export function startAgent(name, entry) {
|
|
|
144
129
|
return startAgentSSH(name, entry, command, env);
|
|
145
130
|
}
|
|
146
131
|
|
|
147
|
-
if (mode === "headless") {
|
|
148
|
-
return startAgentHeadless(name, entry, command, env);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
132
|
return startAgentTmux(name, entry, command, session, env);
|
|
152
133
|
}
|
|
153
134
|
|
|
@@ -199,12 +180,6 @@ function savePaneId(name, session) {
|
|
|
199
180
|
}
|
|
200
181
|
}
|
|
201
182
|
|
|
202
|
-
function startAgentHeadless(name, entry, command, env) {
|
|
203
|
-
// Headless agents are per-invocation — no persistent process to start.
|
|
204
|
-
// The server spawns fresh claude -p processes on demand.
|
|
205
|
-
return { ok: true, message: `Headless agent "${name}" ready (per-invocation mode)` };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
183
|
function startAgentSSH(name, entry, command, env) {
|
|
209
184
|
const { host, user, key } = entry.ssh;
|
|
210
185
|
const sshArgs = [];
|
|
@@ -248,33 +223,6 @@ export function stopAgent(name, entry) {
|
|
|
248
223
|
// No tmux session
|
|
249
224
|
}
|
|
250
225
|
|
|
251
|
-
// Kill headless processes (per-invocation pids directory)
|
|
252
|
-
const fathomDir = path.join(entry.projectDir, ".fathom");
|
|
253
|
-
const pidsDir = path.join(fathomDir, "pids");
|
|
254
|
-
try {
|
|
255
|
-
const files = fs.readdirSync(pidsDir);
|
|
256
|
-
for (const file of files) {
|
|
257
|
-
try {
|
|
258
|
-
const pid = parseInt(file, 10);
|
|
259
|
-
if (!isNaN(pid)) {
|
|
260
|
-
process.kill(pid);
|
|
261
|
-
messages.push(`Killed headless process: PID ${pid}`);
|
|
262
|
-
stopped = true;
|
|
263
|
-
}
|
|
264
|
-
} catch {
|
|
265
|
-
// Already dead
|
|
266
|
-
}
|
|
267
|
-
try { fs.unlinkSync(path.join(pidsDir, file)); } catch { /* */ }
|
|
268
|
-
}
|
|
269
|
-
} catch {
|
|
270
|
-
// pids dir doesn't exist
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Clean up legacy files
|
|
274
|
-
for (const f of ["agent.pid", "agent-keeper.pid", "agent.pipe"]) {
|
|
275
|
-
try { fs.unlinkSync(path.join(fathomDir, f)); } catch { /* */ }
|
|
276
|
-
}
|
|
277
|
-
|
|
278
226
|
if (!stopped) {
|
|
279
227
|
return { ok: false, message: `No running session found for: ${name}` };
|
|
280
228
|
}
|
|
@@ -289,13 +237,10 @@ export function stopAgent(name, entry) {
|
|
|
289
237
|
*/
|
|
290
238
|
export function buildEntryFromConfig(projectDir, fathomConfig) {
|
|
291
239
|
const agentType = fathomConfig.agents?.[0] || "claude-code";
|
|
292
|
-
const isHeadless = agentType === "claude-sdk";
|
|
293
|
-
const executionMode = isHeadless ? "headless" : "tmux";
|
|
294
240
|
return {
|
|
295
241
|
projectDir,
|
|
296
242
|
agentType,
|
|
297
|
-
|
|
298
|
-
command: defaultCommand(agentType, executionMode),
|
|
243
|
+
command: defaultCommand(agentType),
|
|
299
244
|
server: fathomConfig.server || "http://localhost:4243",
|
|
300
245
|
apiKey: fathomConfig.apiKey || "",
|
|
301
246
|
vault: fathomConfig.vault || "vault",
|
package/src/cli.js
CHANGED
|
@@ -144,14 +144,6 @@ function copyScripts(targetDir) {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
// --- Headless agent integration ----------------------------------------------
|
|
148
|
-
|
|
149
|
-
const HEADLESS_CMDS = {
|
|
150
|
-
"claude-code": (prompt) => ["claude", "-p", "--dangerously-skip-permissions", prompt],
|
|
151
|
-
"claude-sdk": (prompt) => ["claude", "-p", "--dangerously-skip-permissions", prompt],
|
|
152
|
-
"gemini": (prompt) => ["gemini", prompt],
|
|
153
|
-
};
|
|
154
|
-
|
|
155
147
|
function buildIntegrationPrompt(blob) {
|
|
156
148
|
return [
|
|
157
149
|
"The following instructions were generated by fathom-mcp init for this project.",
|
|
@@ -180,8 +172,14 @@ function detectFathomServer() {
|
|
|
180
172
|
}
|
|
181
173
|
}
|
|
182
174
|
|
|
183
|
-
|
|
184
|
-
|
|
175
|
+
// Agent commands for non-interactive prompt integration (init --non-interactive)
|
|
176
|
+
const AGENT_PROMPT_CMDS = {
|
|
177
|
+
"claude-code": (prompt) => ["claude", "-p", "--dangerously-skip-permissions", prompt],
|
|
178
|
+
"gemini": (prompt) => ["gemini", prompt],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
function runAgentPrompt(agentKey, prompt) {
|
|
182
|
+
const cmdBuilder = AGENT_PROMPT_CMDS[agentKey];
|
|
185
183
|
if (!cmdBuilder) return null;
|
|
186
184
|
const [cmd, ...args] = cmdBuilder(prompt);
|
|
187
185
|
try {
|
|
@@ -265,13 +263,6 @@ const AGENTS = {
|
|
|
265
263
|
hasHooks: true,
|
|
266
264
|
nextSteps: 'Add to CLAUDE.md: `ToolSearch query="+fathom" max_results=20`',
|
|
267
265
|
},
|
|
268
|
-
"claude-sdk": {
|
|
269
|
-
name: "Claude SDK (headless)",
|
|
270
|
-
detect: (cwd) => fs.existsSync(path.join(cwd, ".claude")),
|
|
271
|
-
configWriter: writeMcpJson,
|
|
272
|
-
hasHooks: true,
|
|
273
|
-
nextSteps: "Headless Claude Code — no TUI, structured I/O. Start with: npx fathom-mcp start",
|
|
274
|
-
},
|
|
275
266
|
"gemini": {
|
|
276
267
|
name: "Gemini CLI",
|
|
277
268
|
detect: (cwd) => fs.existsSync(path.join(cwd, ".gemini")),
|
|
@@ -358,7 +349,7 @@ async function runInit(flags = {}) {
|
|
|
358
349
|
const agent = AGENTS[key];
|
|
359
350
|
const isDetected = detected.includes(key);
|
|
360
351
|
const mark = isDetected ? "✓" : " ";
|
|
361
|
-
const markers = { "claude-code": ".claude/", "
|
|
352
|
+
const markers = { "claude-code": ".claude/", "gemini": ".gemini/" };
|
|
362
353
|
const hint = isDetected ? ` (${markers[key] || key} found)` : "";
|
|
363
354
|
console.log(` ${mark} ${agent.name}${hint}`);
|
|
364
355
|
}
|
|
@@ -458,7 +449,7 @@ async function runInit(flags = {}) {
|
|
|
458
449
|
: "vault";
|
|
459
450
|
|
|
460
451
|
// 9. Hooks — ask if any hook-supporting agent is selected (Claude Code, Claude SDK, Gemini CLI)
|
|
461
|
-
const hasClaude = selectedAgents.includes("claude-code")
|
|
452
|
+
const hasClaude = selectedAgents.includes("claude-code");
|
|
462
453
|
const hasGemini = selectedAgents.includes("gemini");
|
|
463
454
|
const hasHookAgent = hasClaude || hasGemini;
|
|
464
455
|
let enableRecallHook = false;
|
|
@@ -633,12 +624,12 @@ async function runInit(flags = {}) {
|
|
|
633
624
|
|
|
634
625
|
if (instructionsBlob) {
|
|
635
626
|
const prompt = buildIntegrationPrompt(instructionsBlob);
|
|
636
|
-
const
|
|
627
|
+
const cmdBuilder = AGENT_PROMPT_CMDS[primaryAgent];
|
|
637
628
|
|
|
638
629
|
if (nonInteractive) {
|
|
639
|
-
if (
|
|
630
|
+
if (cmdBuilder) {
|
|
640
631
|
console.log(`\n Integrating instructions via ${AGENTS[primaryAgent].name}...`);
|
|
641
|
-
const result =
|
|
632
|
+
const result = runAgentPrompt(primaryAgent, prompt);
|
|
642
633
|
if (result !== null) {
|
|
643
634
|
console.log(result);
|
|
644
635
|
} else {
|
|
@@ -649,9 +640,9 @@ async function runInit(flags = {}) {
|
|
|
649
640
|
printInstructionsFallback(agentMdPath, selectedAgents);
|
|
650
641
|
}
|
|
651
642
|
} else {
|
|
652
|
-
if (
|
|
653
|
-
const [cmd, ...
|
|
654
|
-
const flagArgs =
|
|
643
|
+
if (cmdBuilder) {
|
|
644
|
+
const [cmd, ...sampleArgs] = cmdBuilder("<prompt>");
|
|
645
|
+
const flagArgs = sampleArgs.slice(0, -1).join(" ");
|
|
655
646
|
const displayCmd = `${cmd} ${flagArgs} <prompt>`;
|
|
656
647
|
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
657
648
|
console.log("\n" + "─".repeat(60));
|
|
@@ -664,7 +655,7 @@ async function runInit(flags = {}) {
|
|
|
664
655
|
|
|
665
656
|
if (integrate) {
|
|
666
657
|
console.log(`\n Running ${AGENTS[primaryAgent].name}...`);
|
|
667
|
-
const result =
|
|
658
|
+
const result = runAgentPrompt(primaryAgent, prompt);
|
|
668
659
|
if (result !== null) {
|
|
669
660
|
console.log(result);
|
|
670
661
|
} else {
|
|
@@ -684,7 +675,7 @@ async function runInit(flags = {}) {
|
|
|
684
675
|
}
|
|
685
676
|
|
|
686
677
|
function printInstructionsFallback(agentMdPath, selectedAgents) {
|
|
687
|
-
const hasNonClaude = selectedAgents.some((k) => k !== "claude-code"
|
|
678
|
+
const hasNonClaude = selectedAgents.some((k) => k !== "claude-code");
|
|
688
679
|
const docTarget = hasNonClaude
|
|
689
680
|
? "your CLAUDE.md, AGENTS.md, or equivalent"
|
|
690
681
|
: "your CLAUDE.md";
|
|
@@ -768,8 +759,8 @@ async function runUpdate() {
|
|
|
768
759
|
const sessionStartCmd = "bash ~/.config/fathom-mcp/scripts/fathom-sessionstart.sh";
|
|
769
760
|
const registeredHooks = [];
|
|
770
761
|
|
|
771
|
-
// Claude Code
|
|
772
|
-
const hasClaude = agents.includes("claude-code")
|
|
762
|
+
// Claude Code
|
|
763
|
+
const hasClaude = agents.includes("claude-code")
|
|
773
764
|
|| fs.existsSync(path.join(projectDir, ".claude"));
|
|
774
765
|
if (hasClaude) {
|
|
775
766
|
const settingsPath = path.join(projectDir, ".claude", "settings.local.json");
|
|
@@ -861,12 +852,11 @@ function runList() {
|
|
|
861
852
|
}
|
|
862
853
|
|
|
863
854
|
// Header
|
|
864
|
-
const cols = { name: 16, type: 13,
|
|
855
|
+
const cols = { name: 16, type: 13, status: 10 };
|
|
865
856
|
console.log(
|
|
866
857
|
"\n " +
|
|
867
858
|
"NAME".padEnd(cols.name) +
|
|
868
859
|
"TYPE".padEnd(cols.type) +
|
|
869
|
-
"MODE".padEnd(cols.mode) +
|
|
870
860
|
"STATUS".padEnd(cols.status) +
|
|
871
861
|
"DIR",
|
|
872
862
|
);
|
|
@@ -874,7 +864,6 @@ function runList() {
|
|
|
874
864
|
for (const name of names) {
|
|
875
865
|
const entry = agents[name];
|
|
876
866
|
const type = entry.agentType || "claude-code";
|
|
877
|
-
const mode = entry.executionMode || "tmux";
|
|
878
867
|
const status = entry.ssh ? "[ssh]" : isAgentRunning(name, entry);
|
|
879
868
|
const dir = entry.projectDir.replace(process.env.HOME, "~");
|
|
880
869
|
|
|
@@ -882,7 +871,6 @@ function runList() {
|
|
|
882
871
|
" " +
|
|
883
872
|
name.padEnd(cols.name) +
|
|
884
873
|
type.padEnd(cols.type) +
|
|
885
|
-
mode.padEnd(cols.mode) +
|
|
886
874
|
status.padEnd(cols.status) +
|
|
887
875
|
dir,
|
|
888
876
|
);
|
|
@@ -959,9 +947,8 @@ async function runAdd(argv) {
|
|
|
959
947
|
|
|
960
948
|
const name = await ask(rl, " Agent name", nameArg || defaultName);
|
|
961
949
|
const agentProjectDir = await ask(rl, " Project directory", defaults.projectDir);
|
|
962
|
-
const agentType = await ask(rl, " Agent type (claude-code|
|
|
963
|
-
const
|
|
964
|
-
const command = await ask(rl, " Command", defaultCommand(agentType, executionMode));
|
|
950
|
+
const agentType = await ask(rl, " Agent type (claude-code|gemini|manual)", defaults.agentType);
|
|
951
|
+
const command = await ask(rl, " Command", defaultCommand(agentType));
|
|
965
952
|
const server = await ask(rl, " Server URL", defaults.server);
|
|
966
953
|
const apiKey = await ask(rl, " API key", defaults.apiKey);
|
|
967
954
|
const vault = await ask(rl, " Vault subdirectory", defaults.vault || "vault");
|
|
@@ -973,7 +960,6 @@ async function runAdd(argv) {
|
|
|
973
960
|
const entry = {
|
|
974
961
|
projectDir: path.resolve(agentProjectDir),
|
|
975
962
|
agentType,
|
|
976
|
-
executionMode,
|
|
977
963
|
command,
|
|
978
964
|
server,
|
|
979
965
|
apiKey,
|
package/src/ws-connection.js
CHANGED
|
@@ -164,13 +164,6 @@ export function createWSConnection(config) {
|
|
|
164
164
|
|
|
165
165
|
function injectMessage(text) {
|
|
166
166
|
if (!text) return;
|
|
167
|
-
|
|
168
|
-
if (agent === "claude-sdk") {
|
|
169
|
-
// Headless agents are per-invocation — injection handled server-side.
|
|
170
|
-
// Nothing to do client-side.
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
167
|
injectToTmux(text);
|
|
175
168
|
}
|
|
176
169
|
|