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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fathom-mcp",
3
- "version": "0.5.14",
3
+ "version": "0.5.16",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
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 mode = entry.executionMode || "tmux";
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
- const files = fs.readdirSync(pidsDir);
86
- for (const file of files) {
87
- try {
88
- const pid = parseInt(file, 10);
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
- // pids dir doesn't exist
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, executionMode) {
84
+ export function defaultCommand(agentType) {
110
85
  const cmds = {
111
- "claude-code": {
112
- tmux: "claude --model opus --permission-mode bypassPermissions",
113
- headless:
114
- "claude -p --permission-mode bypassPermissions --output-format stream-json",
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
- const forType = cmds[agentType] || cmds["claude-code"];
126
- return forType[executionMode] || forType.tmux;
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
- const mode = entry.executionMode || "tmux";
131
- const command = entry.command || defaultCommand(entry.agentType || "claude-code", mode);
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
- executionMode,
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
- function runAgentHeadless(agentKey, prompt) {
184
- const cmdBuilder = HEADLESS_CMDS[agentKey];
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/", "claude-sdk": ".claude/", "gemini": ".gemini/" };
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") || selectedAgents.includes("claude-sdk");
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 cmdParts = HEADLESS_CMDS[primaryAgent]?.(prompt);
627
+ const cmdBuilder = AGENT_PROMPT_CMDS[primaryAgent];
637
628
 
638
629
  if (nonInteractive) {
639
- if (cmdParts) {
630
+ if (cmdBuilder) {
640
631
  console.log(`\n Integrating instructions via ${AGENTS[primaryAgent].name}...`);
641
- const result = runAgentHeadless(primaryAgent, prompt);
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 (cmdParts) {
653
- const [cmd, ...args] = cmdParts;
654
- const flagArgs = args.slice(0, -1).join(" ");
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 = runAgentHeadless(primaryAgent, prompt);
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" && k !== "claude-sdk");
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 / Claude SDK
772
- const hasClaude = agents.includes("claude-code") || agents.includes("claude-sdk")
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, mode: 11, status: 10 };
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|claude-sdk|gemini|manual)", defaults.agentType);
963
- const executionMode = await ask(rl, " Execution mode (tmux|headless)", defaults.executionMode);
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,
@@ -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