fathom-mcp 0.4.8 → 0.4.10
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/cli.js +79 -76
- package/src/index.js +67 -0
- package/src/server-client.js +16 -0
package/package.json
CHANGED
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 —
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
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
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
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
|
|
788
|
-
// Detect by
|
|
789
|
-
const
|
|
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
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
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 (
|
|
821
|
-
console.log("\n Registered hooks:");
|
|
822
|
-
|
|
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`);
|
package/src/index.js
CHANGED
|
@@ -291,6 +291,55 @@ const tools = [
|
|
|
291
291
|
required: ["workspace", "message"],
|
|
292
292
|
},
|
|
293
293
|
},
|
|
294
|
+
{
|
|
295
|
+
name: "fathom_routine_create",
|
|
296
|
+
description:
|
|
297
|
+
"Create a new ping routine for a workspace. Routines fire on an interval and inject " +
|
|
298
|
+
"context into the persistent session. Use single_fire for one-shot routines that " +
|
|
299
|
+
"auto-disable after firing once. All parameters are optional with sensible defaults.",
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: "object",
|
|
302
|
+
properties: {
|
|
303
|
+
name: { type: "string", description: "Routine name. Default: 'New Routine'." },
|
|
304
|
+
enabled: { type: "boolean", description: "Start enabled. Default: false." },
|
|
305
|
+
interval_minutes: { type: "integer", description: "Minutes between pings. Default: 60.", minimum: 1 },
|
|
306
|
+
single_fire: { type: "boolean", description: "Auto-disable after firing once. Default: false." },
|
|
307
|
+
workspace: WORKSPACE_PROP,
|
|
308
|
+
context_sources: {
|
|
309
|
+
type: "object",
|
|
310
|
+
description: "What to inject on each ping.",
|
|
311
|
+
properties: {
|
|
312
|
+
time: { type: "boolean", description: "Include current time/date. Default: true." },
|
|
313
|
+
scripts: {
|
|
314
|
+
type: "array",
|
|
315
|
+
description: "Shell commands to run and inject output.",
|
|
316
|
+
items: {
|
|
317
|
+
type: "object",
|
|
318
|
+
properties: {
|
|
319
|
+
label: { type: "string" },
|
|
320
|
+
command: { type: "string" },
|
|
321
|
+
enabled: { type: "boolean" },
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
texts: {
|
|
326
|
+
type: "array",
|
|
327
|
+
description: "Static text blocks to inject.",
|
|
328
|
+
items: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
label: { type: "string" },
|
|
332
|
+
content: { type: "string" },
|
|
333
|
+
enabled: { type: "boolean" },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
},
|
|
340
|
+
required: [],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
294
343
|
];
|
|
295
344
|
|
|
296
345
|
// --- Vault routing by mode ---------------------------------------------------
|
|
@@ -480,6 +529,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
480
529
|
case "fathom_send":
|
|
481
530
|
result = await client.sendToWorkspace(args.workspace, args.message, config.workspace);
|
|
482
531
|
break;
|
|
532
|
+
case "fathom_routine_create": {
|
|
533
|
+
const routineParams = {};
|
|
534
|
+
if (args.name != null) routineParams.name = args.name;
|
|
535
|
+
if (args.enabled != null) routineParams.enabled = args.enabled;
|
|
536
|
+
if (args.interval_minutes != null) routineParams.intervalMinutes = args.interval_minutes;
|
|
537
|
+
if (args.single_fire != null) routineParams.singleFire = args.single_fire;
|
|
538
|
+
if (args.context_sources != null) routineParams.contextSources = args.context_sources;
|
|
539
|
+
result = await client.createRoutine(routineParams, args.workspace || config.workspace);
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
483
542
|
default:
|
|
484
543
|
result = { error: `Unknown tool: ${name}` };
|
|
485
544
|
}
|
|
@@ -539,6 +598,13 @@ async function startupSync() {
|
|
|
539
598
|
await client.pushFile(config.workspace, filePath, content);
|
|
540
599
|
}
|
|
541
600
|
}
|
|
601
|
+
|
|
602
|
+
// Delete server files that no longer exist locally (local is source of truth)
|
|
603
|
+
if (diff.deleted?.length) {
|
|
604
|
+
for (const filePath of diff.deleted) {
|
|
605
|
+
await client.deleteFile(config.workspace, filePath);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
542
608
|
} catch {
|
|
543
609
|
// Sync failure is non-fatal — local vault is source of truth
|
|
544
610
|
}
|
|
@@ -551,6 +617,7 @@ async function main() {
|
|
|
551
617
|
vault: config._rawVault,
|
|
552
618
|
description: config.description,
|
|
553
619
|
agents: config.agents,
|
|
620
|
+
type: config.vaultMode,
|
|
554
621
|
}).catch(() => {});
|
|
555
622
|
}
|
|
556
623
|
|
package/src/server-client.js
CHANGED
|
@@ -190,6 +190,21 @@ export function createClient(config) {
|
|
|
190
190
|
});
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
// --- Activation / Routines -------------------------------------------------
|
|
194
|
+
|
|
195
|
+
async function createRoutine(params, ws) {
|
|
196
|
+
const body = {};
|
|
197
|
+
if (params.name != null) body.name = params.name;
|
|
198
|
+
if (params.enabled != null) body.enabled = params.enabled;
|
|
199
|
+
if (params.intervalMinutes != null) body.intervalMinutes = params.intervalMinutes;
|
|
200
|
+
if (params.singleFire != null) body.singleFire = params.singleFire;
|
|
201
|
+
if (params.contextSources != null) body.contextSources = params.contextSources;
|
|
202
|
+
return request("POST", "/api/activation/ping/routines", {
|
|
203
|
+
params: { workspace: ws },
|
|
204
|
+
body,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
193
208
|
// --- Auth ------------------------------------------------------------------
|
|
194
209
|
|
|
195
210
|
async function getApiKey() {
|
|
@@ -229,6 +244,7 @@ export function createClient(config) {
|
|
|
229
244
|
listFiles,
|
|
230
245
|
pushFile,
|
|
231
246
|
syncManifest,
|
|
247
|
+
createRoutine,
|
|
232
248
|
getApiKey,
|
|
233
249
|
healthCheck,
|
|
234
250
|
};
|