fathom-mcp 0.6.4 → 2.0.1

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/src/config.js DELETED
@@ -1,137 +0,0 @@
1
- /**
2
- * Config resolution for fathom-mcp.
3
- *
4
- * Precedence (highest wins):
5
- * 1. Environment variables (FATHOM_SERVER_URL, FATHOM_API_KEY, FATHOM_WORKSPACE, FATHOM_VAULT_DIR)
6
- * 2. .fathom.json (walked up from cwd to filesystem root)
7
- * 3. Built-in defaults
8
- */
9
-
10
- import fs from "fs";
11
- import path from "path";
12
-
13
- const CONFIG_FILENAME = ".fathom.json";
14
-
15
- const VALID_VAULT_MODES = new Set(["synced", "local", "none"]);
16
-
17
- const DEFAULTS = {
18
- workspace: "",
19
- vault: "vault",
20
- vaultMode: "local", // hosted | synced | local | none
21
- server: "http://127.0.0.1:4243",
22
- apiKey: "",
23
- description: "",
24
- agents: [],
25
- hooks: {
26
- "context-inject": { enabled: true },
27
- "precompact-snapshot": { enabled: true },
28
- },
29
- };
30
-
31
- /**
32
- * Walk up from startDir looking for .fathom.json.
33
- * Returns the parsed config and the directory it was found in, or null.
34
- */
35
- export function findConfigFile(startDir = process.cwd()) {
36
- let dir = path.resolve(startDir);
37
- const root = path.parse(dir).root;
38
-
39
- while (true) {
40
- const candidate = path.join(dir, CONFIG_FILENAME);
41
- try {
42
- const content = fs.readFileSync(candidate, "utf-8");
43
- const config = JSON.parse(content);
44
- return { config, dir, path: candidate };
45
- } catch {
46
- // Not found or invalid — keep walking up
47
- }
48
-
49
- const parent = path.dirname(dir);
50
- if (parent === dir || dir === root) break;
51
- dir = parent;
52
- }
53
-
54
- return null;
55
- }
56
-
57
- /**
58
- * Apply fields from a source config object onto a result object.
59
- */
60
- function applyConfig(result, config) {
61
- if (config.workspace) result.workspace = config.workspace;
62
- if (config.vault) result.vault = config.vault;
63
- if (config.vaultMode && VALID_VAULT_MODES.has(config.vaultMode)) {
64
- result.vaultMode = config.vaultMode;
65
- }
66
- if (config.server) result.server = config.server;
67
- if (config.apiKey) result.apiKey = config.apiKey;
68
- if (config.agents && Array.isArray(config.agents)) {
69
- result.agents = config.agents;
70
- }
71
- if (config.hooks) {
72
- result.hooks = { ...result.hooks, ...config.hooks };
73
- }
74
- }
75
-
76
- /**
77
- * Resolve final config by merging: defaults → .fathom.json → env vars.
78
- */
79
- export function resolveConfig(startDir = process.cwd()) {
80
- const result = { ...DEFAULTS, hooks: { ...DEFAULTS.hooks } };
81
- let projectDir = startDir;
82
-
83
- // Layer 2: .fathom.json
84
- const found = findConfigFile(startDir);
85
- if (found) {
86
- projectDir = found.dir;
87
- applyConfig(result, found.config);
88
- }
89
-
90
- // Layer 1: Environment variables (highest priority)
91
- if (process.env.FATHOM_SERVER_URL) result.server = process.env.FATHOM_SERVER_URL;
92
- if (process.env.FATHOM_API_KEY) result.apiKey = process.env.FATHOM_API_KEY;
93
- if (process.env.FATHOM_WORKSPACE) result.workspace = process.env.FATHOM_WORKSPACE;
94
- if (process.env.FATHOM_VAULT_DIR) result.vault = process.env.FATHOM_VAULT_DIR;
95
- if (process.env.FATHOM_VAULT_MODE && VALID_VAULT_MODES.has(process.env.FATHOM_VAULT_MODE)) {
96
- result.vaultMode = process.env.FATHOM_VAULT_MODE;
97
- }
98
-
99
- // Derive workspace name from directory if still empty
100
- if (!result.workspace) {
101
- result.workspace = path.basename(projectDir);
102
- }
103
-
104
- // Preserve raw vault name before resolving to absolute (for registration)
105
- result._rawVault = result.vault;
106
-
107
- // Resolve vault to absolute path
108
- if (!path.isAbsolute(result.vault)) {
109
- result.vault = path.join(projectDir, result.vault);
110
- }
111
-
112
- // Normalize server URL — strip trailing slash
113
- result.server = result.server.replace(/\/+$/, "");
114
-
115
- result._projectDir = projectDir;
116
- result._configPath = found?.path || null;
117
-
118
- return result;
119
- }
120
-
121
- /**
122
- * Write a .fathom.json config file.
123
- */
124
- export function writeConfig(dir, config) {
125
- const filePath = path.join(dir, CONFIG_FILENAME);
126
- const data = {
127
- workspace: config.workspace,
128
- vaultMode: config.vaultMode || "local",
129
- vault: config.vault || "vault",
130
- server: config.server || DEFAULTS.server,
131
- apiKey: config.apiKey || "",
132
- agents: config.agents || [],
133
- hooks: config.hooks || DEFAULTS.hooks,
134
- };
135
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + "\n");
136
- return filePath;
137
- }
@@ -1,77 +0,0 @@
1
- /**
2
- * Shared frontmatter parsing and validation for vault files.
3
- *
4
- * Used by the vault-frontmatter-lint hook script and any other
5
- * code that needs to validate vault file frontmatter.
6
- */
7
-
8
- // --- Constants ---------------------------------------------------------------
9
-
10
- export const VALID_STATUSES = new Set(["draft", "published", "archived"]);
11
-
12
- export const VAULT_SCHEMA = {
13
- title: { required: true, type: "string" },
14
- date: { required: true, type: "string" },
15
- tags: { required: false, type: "array" },
16
- status: { required: false, type: "string" },
17
- project: { required: false, type: "string" },
18
- aliases: { required: false, type: "array" },
19
- };
20
-
21
- // --- Frontmatter -------------------------------------------------------------
22
-
23
- export function parseFrontmatter(content) {
24
- if (!content.startsWith("---")) return { fm: {}, body: content };
25
- const lines = content.split("\n");
26
- let endIdx = null;
27
- for (let i = 1; i < lines.length; i++) {
28
- if (lines[i].trim() === "---") { endIdx = i; break; }
29
- }
30
- if (endIdx === null) return { fm: {}, body: content };
31
- try {
32
- const fmLines = lines.slice(1, endIdx);
33
- const fm = {};
34
- let currentKey = null;
35
- for (const line of fmLines) {
36
- const listMatch = line.match(/^[ ]{2}- (.+)$/);
37
- const kvMatch = line.match(/^(\w+):\s*(.*)$/);
38
- if (listMatch && currentKey) {
39
- fm[currentKey].push(listMatch[1].trim());
40
- } else if (kvMatch) {
41
- currentKey = kvMatch[1];
42
- const val = kvMatch[2].trim();
43
- if (val === "") {
44
- fm[currentKey] = [];
45
- } else {
46
- fm[currentKey] = val;
47
- }
48
- }
49
- }
50
- const body = lines.slice(endIdx + 1).join("\n");
51
- return { fm, body };
52
- } catch {
53
- return { fm: {}, body: content };
54
- }
55
- }
56
-
57
- export function validateFrontmatter(fm) {
58
- const errors = [];
59
- for (const [field, spec] of Object.entries(VAULT_SCHEMA)) {
60
- const val = fm[field];
61
- if (spec.required && val == null) {
62
- errors.push(`Missing required field: '${field}'`);
63
- continue;
64
- }
65
- if (val != null) {
66
- const actualType = Array.isArray(val) ? "array" : typeof val;
67
- if (actualType !== spec.type) {
68
- errors.push(`Field '${field}' must be ${spec.type}, got ${actualType}`);
69
- }
70
- }
71
- }
72
- const status = fm["status"];
73
- if (status != null && !VALID_STATUSES.has(status)) {
74
- errors.push(`Field 'status' must be one of [${[...VALID_STATUSES].join(", ")}], got '${status}'`);
75
- }
76
- return errors;
77
- }