fathom-mcp 0.4.8 → 0.4.9

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.js +79 -76
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fathom-mcp",
3
- "version": "0.4.8",
3
+ "version": "0.4.9",
4
4
  "description": "MCP server for Fathom — vault operations, search, rooms, and cross-workspace communication",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.js CHANGED
@@ -91,6 +91,25 @@ function appendToGitignore(dir, patterns) {
91
91
  }
92
92
  }
93
93
 
94
+ /**
95
+ * Idempotently register a hook in a settings object.
96
+ * Works for both Claude Code and Gemini CLI (same JSON structure).
97
+ * Returns true if a new hook was added, false if already present.
98
+ */
99
+ function ensureHook(settings, eventName, command, timeout) {
100
+ const existing = settings.hooks?.[eventName] || [];
101
+ const alreadyRegistered = existing.some((entry) =>
102
+ entry.hooks?.some((h) => h.command === command)
103
+ );
104
+ if (alreadyRegistered) return false;
105
+ if (!settings.hooks) settings.hooks = {};
106
+ settings.hooks[eventName] = [
107
+ ...existing,
108
+ { hooks: [{ type: "command", command, timeout }] },
109
+ ];
110
+ return true;
111
+ }
112
+
94
113
  function copyScripts(targetDir) {
95
114
  fs.mkdirSync(targetDir, { recursive: true });
96
115
  try {
@@ -446,17 +465,19 @@ async function runInit(flags = {}) {
446
465
  ? (nonInteractive ? "vault" : await ask(rl, " Vault subdirectory", "vault"))
447
466
  : "vault";
448
467
 
449
- // 9. Hooks — only ask if Claude Code is selected
468
+ // 9. Hooks — ask if any hook-supporting agent is selected (Claude Code, Gemini CLI)
450
469
  const hasClaude = selectedAgents.includes("claude-code");
470
+ const hasGemini = selectedAgents.includes("gemini");
471
+ const hasHookAgent = hasClaude || hasGemini;
451
472
  let enableRecallHook = false;
452
473
  let enablePrecompactHook = false;
453
- if (hasClaude) {
474
+ if (hasHookAgent) {
454
475
  if (nonInteractive) {
455
476
  enableRecallHook = true;
456
477
  enablePrecompactHook = true;
457
478
  } else {
458
479
  console.log();
459
- enableRecallHook = await askYesNo(rl, " Enable vault recall on every message (UserPromptSubmit)?", true);
480
+ enableRecallHook = await askYesNo(rl, " Enable vault recall on every message?", true);
460
481
  enablePrecompactHook = await askYesNo(rl, " Enable PreCompact vault snapshot hook?", true);
461
482
  }
462
483
  }
@@ -531,60 +552,34 @@ async function runInit(flags = {}) {
531
552
  console.log(` ✓ ${result}`);
532
553
  }
533
554
 
534
- // Claude Code hooks only if claude-code is selected
535
- if (hasClaude) {
536
- const claudeSettingsPath = path.join(cwd, ".claude", "settings.local.json");
537
- const claudeSettings = readJsonFile(claudeSettingsPath) || {};
538
-
539
- const hooks = {};
540
-
541
- // SessionStart — always registered (version check should always be on)
542
- hooks["SessionStart"] = [
543
- ...(claudeSettings.hooks?.["SessionStart"] || []),
544
- ];
545
- const sessionStartCmd = "bash .fathom/scripts/fathom-sessionstart.sh";
546
- const hasFathomSessionStart = hooks["SessionStart"].some((entry) =>
547
- entry.hooks?.some((h) => h.command === sessionStartCmd)
548
- );
549
- if (!hasFathomSessionStart) {
550
- hooks["SessionStart"].push({
551
- hooks: [{ type: "command", command: sessionStartCmd, timeout: 10000 }],
552
- });
553
- }
555
+ // Hook scripts (shared across agents)
556
+ const sessionStartCmd = "bash .fathom/scripts/fathom-sessionstart.sh";
557
+ const recallCmd = "bash .fathom/scripts/fathom-recall.sh";
558
+ const precompactCmd = "bash .fathom/scripts/fathom-precompact.sh";
554
559
 
555
- if (enableRecallHook) {
556
- hooks["UserPromptSubmit"] = [
557
- ...(claudeSettings.hooks?.["UserPromptSubmit"] || []),
558
- ];
559
- const recallCmd = "bash .fathom/scripts/fathom-recall.sh";
560
- const hasFathomRecall = hooks["UserPromptSubmit"].some((entry) =>
561
- entry.hooks?.some((h) => h.command === recallCmd)
562
- );
563
- if (!hasFathomRecall) {
564
- hooks["UserPromptSubmit"].push({
565
- hooks: [{ type: "command", command: recallCmd, timeout: 10000 }],
566
- });
567
- }
568
- }
569
- if (enablePrecompactHook) {
570
- hooks["PreCompact"] = [
571
- ...(claudeSettings.hooks?.["PreCompact"] || []),
572
- ];
573
- const precompactCmd = "bash .fathom/scripts/fathom-precompact.sh";
574
- const hasFathomPrecompact = hooks["PreCompact"].some((entry) =>
575
- entry.hooks?.some((h) => h.command === precompactCmd)
576
- );
577
- if (!hasFathomPrecompact) {
578
- hooks["PreCompact"].push({
579
- hooks: [{ type: "command", command: precompactCmd, timeout: 30000 }],
580
- });
581
- }
560
+ // Claude Code hooks
561
+ if (hasClaude) {
562
+ const settingsPath = path.join(cwd, ".claude", "settings.local.json");
563
+ const settings = readJsonFile(settingsPath) || {};
564
+ let changed = ensureHook(settings, "SessionStart", sessionStartCmd, 10000);
565
+ if (enableRecallHook) changed = ensureHook(settings, "UserPromptSubmit", recallCmd, 10000) || changed;
566
+ if (enablePrecompactHook) changed = ensureHook(settings, "PreCompact", precompactCmd, 30000) || changed;
567
+ if (changed) {
568
+ writeJsonFile(settingsPath, settings);
569
+ console.log(".claude/settings.local.json (hooks)");
582
570
  }
571
+ }
583
572
 
584
- if (Object.keys(hooks).length > 0) {
585
- claudeSettings.hooks = { ...(claudeSettings.hooks || {}), ...hooks };
586
- writeJsonFile(claudeSettingsPath, claudeSettings);
587
- console.log(" ✓ .claude/settings.local.json (hooks)");
573
+ // Gemini CLI hooks
574
+ if (hasGemini) {
575
+ const settingsPath = path.join(cwd, ".gemini", "settings.json");
576
+ const settings = readJsonFile(settingsPath) || {};
577
+ let changed = ensureHook(settings, "SessionStart", sessionStartCmd, 10000);
578
+ if (enableRecallHook) changed = ensureHook(settings, "BeforeAgent", recallCmd, 10000) || changed;
579
+ if (enablePrecompactHook) changed = ensureHook(settings, "PreCompress", precompactCmd, 30000) || changed;
580
+ if (changed) {
581
+ writeJsonFile(settingsPath, settings);
582
+ console.log(" ✓ .gemini/settings.json (hooks)");
588
583
  }
589
584
  }
590
585
 
@@ -784,27 +779,33 @@ async function runUpdate() {
784
779
  fs.mkdirSync(fathomDir, { recursive: true });
785
780
  fs.writeFileSync(path.join(fathomDir, "version"), packageVersion + "\n");
786
781
 
787
- // Ensure SessionStart hook is registered for Claude Code workspaces
788
- // Detect by .claude/ dir (older configs may not have agents field)
789
- const hasClaude = (found.config.agents || []).includes("claude-code")
782
+ // Ensure SessionStart hook is registered for agents that support hooks
783
+ // Detect by config agents field or directory presence (older configs may lack agents)
784
+ const agents = found.config.agents || [];
785
+ const sessionStartCmd = "bash .fathom/scripts/fathom-sessionstart.sh";
786
+ const registeredHooks = [];
787
+
788
+ // Claude Code
789
+ const hasClaude = agents.includes("claude-code")
790
790
  || fs.existsSync(path.join(projectDir, ".claude"));
791
- let hooksUpdated = false;
792
791
  if (hasClaude) {
793
- const claudeSettingsPath = path.join(projectDir, ".claude", "settings.local.json");
794
- const claudeSettings = readJsonFile(claudeSettingsPath) || {};
795
- const sessionStartCmd = "bash .fathom/scripts/fathom-sessionstart.sh";
796
- const existingHooks = claudeSettings.hooks?.["SessionStart"] || [];
797
- const hasSessionStart = existingHooks.some((entry) =>
798
- entry.hooks?.some((h) => h.command === sessionStartCmd)
799
- );
800
- if (!hasSessionStart) {
801
- claudeSettings.hooks = claudeSettings.hooks || {};
802
- claudeSettings.hooks["SessionStart"] = [
803
- ...existingHooks,
804
- { hooks: [{ type: "command", command: sessionStartCmd, timeout: 10000 }] },
805
- ];
806
- writeJsonFile(claudeSettingsPath, claudeSettings);
807
- hooksUpdated = true;
792
+ const settingsPath = path.join(projectDir, ".claude", "settings.local.json");
793
+ const settings = readJsonFile(settingsPath) || {};
794
+ if (ensureHook(settings, "SessionStart", sessionStartCmd, 10000)) {
795
+ writeJsonFile(settingsPath, settings);
796
+ registeredHooks.push("Claude Code .claude/settings.local.json");
797
+ }
798
+ }
799
+
800
+ // Gemini CLI
801
+ const hasGemini = agents.includes("gemini")
802
+ || fs.existsSync(path.join(projectDir, ".gemini"));
803
+ if (hasGemini) {
804
+ const settingsPath = path.join(projectDir, ".gemini", "settings.json");
805
+ const settings = readJsonFile(settingsPath) || {};
806
+ if (ensureHook(settings, "SessionStart", sessionStartCmd, 10000)) {
807
+ writeJsonFile(settingsPath, settings);
808
+ registeredHooks.push("Gemini CLI → .gemini/settings.json");
808
809
  }
809
810
  }
810
811
 
@@ -817,9 +818,11 @@ async function runUpdate() {
817
818
  }
818
819
  }
819
820
 
820
- if (hooksUpdated) {
821
- console.log("\n Registered hooks:");
822
- console.log(" SessionStart fathom-sessionstart.sh (version check)");
821
+ if (registeredHooks.length > 0) {
822
+ console.log("\n Registered SessionStart hooks:");
823
+ for (const hook of registeredHooks) {
824
+ console.log(` ${hook}`);
825
+ }
823
826
  }
824
827
 
825
828
  console.log(`\n Version written to .fathom/version`);