@blacksmithers/vaultforge 1.0.0
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/LICENSE +21 -0
- package/README.md +464 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +705 -0
- package/dist/index.js.map +1 -0
- package/dist/tool-handlers.d.ts +79 -0
- package/dist/tool-handlers.d.ts.map +1 -0
- package/dist/tool-handlers.js +326 -0
- package/dist/tool-handlers.js.map +1 -0
- package/dist/tools/canvas/canvas-create.d.ts +4 -0
- package/dist/tools/canvas/canvas-create.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-create.js +243 -0
- package/dist/tools/canvas/canvas-create.js.map +1 -0
- package/dist/tools/canvas/canvas-patch.d.ts +4 -0
- package/dist/tools/canvas/canvas-patch.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-patch.js +225 -0
- package/dist/tools/canvas/canvas-patch.js.map +1 -0
- package/dist/tools/canvas/canvas-read.d.ts +4 -0
- package/dist/tools/canvas/canvas-read.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-read.js +97 -0
- package/dist/tools/canvas/canvas-read.js.map +1 -0
- package/dist/tools/canvas/canvas-relayout.d.ts +4 -0
- package/dist/tools/canvas/canvas-relayout.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-relayout.js +152 -0
- package/dist/tools/canvas/canvas-relayout.js.map +1 -0
- package/dist/tools/canvas/canvas-utils.d.ts +40 -0
- package/dist/tools/canvas/canvas-utils.d.ts.map +1 -0
- package/dist/tools/canvas/canvas-utils.js +141 -0
- package/dist/tools/canvas/canvas-utils.js.map +1 -0
- package/dist/tools/canvas/layout-engine.d.ts +34 -0
- package/dist/tools/canvas/layout-engine.d.ts.map +1 -0
- package/dist/tools/canvas/layout-engine.js +75 -0
- package/dist/tools/canvas/layout-engine.js.map +1 -0
- package/dist/tools/canvas/types.d.ts +71 -0
- package/dist/tools/canvas/types.d.ts.map +1 -0
- package/dist/tools/canvas/types.js +2 -0
- package/dist/tools/canvas/types.js.map +1 -0
- package/dist/tools/files/batch-rename.d.ts +4 -0
- package/dist/tools/files/batch-rename.d.ts.map +1 -0
- package/dist/tools/files/batch-rename.js +140 -0
- package/dist/tools/files/batch-rename.js.map +1 -0
- package/dist/tools/files/delete-folder.d.ts +17 -0
- package/dist/tools/files/delete-folder.d.ts.map +1 -0
- package/dist/tools/files/delete-folder.js +100 -0
- package/dist/tools/files/delete-folder.js.map +1 -0
- package/dist/tools/files/prune-empty-dirs.d.ts +17 -0
- package/dist/tools/files/prune-empty-dirs.d.ts.map +1 -0
- package/dist/tools/files/prune-empty-dirs.js +106 -0
- package/dist/tools/files/prune-empty-dirs.js.map +1 -0
- package/dist/tools/intelligence/clustering.d.ts +15 -0
- package/dist/tools/intelligence/clustering.d.ts.map +1 -0
- package/dist/tools/intelligence/clustering.js +93 -0
- package/dist/tools/intelligence/clustering.js.map +1 -0
- package/dist/tools/intelligence/tfidf.d.ts +19 -0
- package/dist/tools/intelligence/tfidf.d.ts.map +1 -0
- package/dist/tools/intelligence/tfidf.js +68 -0
- package/dist/tools/intelligence/tfidf.js.map +1 -0
- package/dist/tools/intelligence/vault-suggest.d.ts +7 -0
- package/dist/tools/intelligence/vault-suggest.d.ts.map +1 -0
- package/dist/tools/intelligence/vault-suggest.js +307 -0
- package/dist/tools/intelligence/vault-suggest.js.map +1 -0
- package/dist/tools/intelligence/vault-themes.d.ts +30 -0
- package/dist/tools/intelligence/vault-themes.d.ts.map +1 -0
- package/dist/tools/intelligence/vault-themes.js +88 -0
- package/dist/tools/intelligence/vault-themes.js.map +1 -0
- package/dist/tools/links/backlinks.d.ts +4 -0
- package/dist/tools/links/backlinks.d.ts.map +1 -0
- package/dist/tools/links/backlinks.js +50 -0
- package/dist/tools/links/backlinks.js.map +1 -0
- package/dist/tools/links/link-utils.d.ts +23 -0
- package/dist/tools/links/link-utils.d.ts.map +1 -0
- package/dist/tools/links/link-utils.js +63 -0
- package/dist/tools/links/link-utils.js.map +1 -0
- package/dist/tools/links/update-links.d.ts +16 -0
- package/dist/tools/links/update-links.d.ts.map +1 -0
- package/dist/tools/links/update-links.js +88 -0
- package/dist/tools/links/update-links.js.map +1 -0
- package/dist/tools/metadata/frontmatter.d.ts +12 -0
- package/dist/tools/metadata/frontmatter.d.ts.map +1 -0
- package/dist/tools/metadata/frontmatter.js +190 -0
- package/dist/tools/metadata/frontmatter.js.map +1 -0
- package/dist/tools/notes/edit-regex.d.ts +4 -0
- package/dist/tools/notes/edit-regex.d.ts.map +1 -0
- package/dist/tools/notes/edit-regex.js +142 -0
- package/dist/tools/notes/edit-regex.js.map +1 -0
- package/dist/tools/search/markdown-parser.d.ts +12 -0
- package/dist/tools/search/markdown-parser.d.ts.map +1 -0
- package/dist/tools/search/markdown-parser.js +64 -0
- package/dist/tools/search/markdown-parser.js.map +1 -0
- package/dist/tools/search/orama-engine.d.ts +76 -0
- package/dist/tools/search/orama-engine.d.ts.map +1 -0
- package/dist/tools/search/orama-engine.js +152 -0
- package/dist/tools/search/orama-engine.js.map +1 -0
- package/dist/tools/search/search-reindex.d.ts +7 -0
- package/dist/tools/search/search-reindex.d.ts.map +1 -0
- package/dist/tools/search/search-reindex.js +34 -0
- package/dist/tools/search/search-reindex.js.map +1 -0
- package/dist/tools/search/smart-search.d.ts +7 -0
- package/dist/tools/search/smart-search.d.ts.map +1 -0
- package/dist/tools/search/smart-search.js +102 -0
- package/dist/tools/search/smart-search.js.map +1 -0
- package/dist/vault-index.d.ts +95 -0
- package/dist/vault-index.d.ts.map +1 -0
- package/dist/vault-index.js +432 -0
- package/dist/vault-index.js.map +1 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { readFile, writeFile, mkdir, unlink, appendFile, rename } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { VaultIndex } from "./vault-index.js";
|
|
8
|
+
import { registerCanvasCreate } from "./tools/canvas/canvas-create.js";
|
|
9
|
+
import { registerCanvasRead } from "./tools/canvas/canvas-read.js";
|
|
10
|
+
import { registerCanvasPatch } from "./tools/canvas/canvas-patch.js";
|
|
11
|
+
import { registerCanvasRelayout } from "./tools/canvas/canvas-relayout.js";
|
|
12
|
+
import { registerSmartSearch } from "./tools/search/smart-search.js";
|
|
13
|
+
import { registerSearchReindex } from "./tools/search/search-reindex.js";
|
|
14
|
+
import { registerVaultThemes } from "./tools/intelligence/vault-themes.js";
|
|
15
|
+
import { registerVaultSuggest } from "./tools/intelligence/vault-suggest.js";
|
|
16
|
+
import { registerEditRegex } from "./tools/notes/edit-regex.js";
|
|
17
|
+
import { registerBatchRename } from "./tools/files/batch-rename.js";
|
|
18
|
+
import { registerDeleteFolder } from "./tools/files/delete-folder.js";
|
|
19
|
+
import { registerPruneEmptyDirs } from "./tools/files/prune-empty-dirs.js";
|
|
20
|
+
import { registerUpdateLinks, updateWikilinks } from "./tools/links/update-links.js";
|
|
21
|
+
import { registerBacklinks } from "./tools/links/backlinks.js";
|
|
22
|
+
import { registerFrontmatter, parseFrontmatter, serializeFrontmatter } from "./tools/metadata/frontmatter.js";
|
|
23
|
+
// ── Config ─────────────────────────────────────────────────────────
|
|
24
|
+
const VAULT_PATH = process.env.OBSIDIAN_VAULT_PATH || process.argv[2];
|
|
25
|
+
if (!VAULT_PATH) {
|
|
26
|
+
console.error("ERROR: Provide vault path as argument or set OBSIDIAN_VAULT_PATH environment variable.\nUsage: vaultforge /path/to/vault");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const vault = new VaultIndex(VAULT_PATH);
|
|
30
|
+
const server = new McpServer({
|
|
31
|
+
name: "vaultforge",
|
|
32
|
+
version: "1.0.0",
|
|
33
|
+
});
|
|
34
|
+
// ── Helpers ─────────────────────────────────────────────────────────
|
|
35
|
+
function abs(relPath) {
|
|
36
|
+
const normalized = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
37
|
+
return path.join(VAULT_PATH, normalized);
|
|
38
|
+
}
|
|
39
|
+
function ensureMd(relPath) {
|
|
40
|
+
if (!path.extname(relPath))
|
|
41
|
+
return relPath + ".md";
|
|
42
|
+
return relPath;
|
|
43
|
+
}
|
|
44
|
+
async function ensureDir(filePath) {
|
|
45
|
+
const dir = path.dirname(filePath);
|
|
46
|
+
await mkdir(dir, { recursive: true });
|
|
47
|
+
}
|
|
48
|
+
function resolveOrFail(input) {
|
|
49
|
+
const entry = vault.resolve(input);
|
|
50
|
+
if (entry)
|
|
51
|
+
return { abs: entry.abs, rel: entry.rel };
|
|
52
|
+
// If not indexed yet, treat as literal path
|
|
53
|
+
const rel = ensureMd(input.replace(/\\/g, "/").replace(/^\/+/, ""));
|
|
54
|
+
return { abs: abs(rel), rel };
|
|
55
|
+
}
|
|
56
|
+
function timestamp() {
|
|
57
|
+
return new Date().toISOString().slice(0, 19).replace("T", " ");
|
|
58
|
+
}
|
|
59
|
+
// ── Tools ───────────────────────────────────────────────────────────
|
|
60
|
+
// 1. VAULT STATUS
|
|
61
|
+
server.tool("vault_status", "Get vault index stats: total files, directories, file type breakdown, and index health.", {}, async () => {
|
|
62
|
+
await vault.waitReady();
|
|
63
|
+
const stats = vault.stats();
|
|
64
|
+
return {
|
|
65
|
+
content: [
|
|
66
|
+
{
|
|
67
|
+
type: "text",
|
|
68
|
+
text: JSON.stringify({
|
|
69
|
+
vaultPath: VAULT_PATH,
|
|
70
|
+
...stats,
|
|
71
|
+
topExtensions: Object.entries(stats.extensions)
|
|
72
|
+
.sort((a, b) => b[1] - a[1])
|
|
73
|
+
.slice(0, 10),
|
|
74
|
+
}, null, 2),
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
});
|
|
79
|
+
// 2. READ NOTE
|
|
80
|
+
server.tool("read_note", "Read a note from the vault. Supports fuzzy path resolution: exact path, stem name, or partial path. Returns file content and metadata.", { path: z.string().describe("Relative path, filename, or stem (e.g. '01-Daily/2025-03-04' or just '2025-03-04')") }, async ({ path: notePath }) => {
|
|
81
|
+
await vault.waitReady();
|
|
82
|
+
const resolved = resolveOrFail(notePath);
|
|
83
|
+
try {
|
|
84
|
+
const content = await readFile(resolved.abs, "utf-8");
|
|
85
|
+
const entry = vault.get(resolved.rel);
|
|
86
|
+
return {
|
|
87
|
+
content: [
|
|
88
|
+
{
|
|
89
|
+
type: "text",
|
|
90
|
+
text: JSON.stringify({
|
|
91
|
+
path: resolved.rel,
|
|
92
|
+
size: entry?.size ?? content.length,
|
|
93
|
+
mtime: entry?.mtime ? new Date(entry.mtime).toISOString() : null,
|
|
94
|
+
content,
|
|
95
|
+
}),
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return { content: [{ type: "text", text: `ERROR: File not found: ${notePath} (resolved to: ${resolved.rel})` }], isError: true };
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
// 3. WRITE NOTE
|
|
105
|
+
server.tool("write_note", "Create or overwrite a note. Creates parent directories automatically. Use for new files or full replacements.", {
|
|
106
|
+
path: z.string().describe("Relative path for the note (e.g. '00-Inbox/new-idea.md')"),
|
|
107
|
+
content: z.string().describe("Full content to write"),
|
|
108
|
+
overwrite: z.boolean().default(false).describe("Set true to overwrite existing files. Safety guard."),
|
|
109
|
+
}, async ({ path: notePath, content, overwrite }) => {
|
|
110
|
+
await vault.waitReady();
|
|
111
|
+
const rel = ensureMd(notePath.replace(/\\/g, "/").replace(/^\/+/, ""));
|
|
112
|
+
const absPath = abs(rel);
|
|
113
|
+
if (!overwrite && vault.has(rel)) {
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: "text", text: `ERROR: File already exists: ${rel}. Set overwrite=true to replace.` }],
|
|
116
|
+
isError: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
await ensureDir(absPath);
|
|
120
|
+
await writeFile(absPath, content, "utf-8");
|
|
121
|
+
return { content: [{ type: "text", text: `OK: Written ${rel} (${content.length} bytes)` }] };
|
|
122
|
+
});
|
|
123
|
+
// 4. APPEND NOTE
|
|
124
|
+
server.tool("append_note", "Append content to an existing note or create it if it doesn't exist. Perfect for daily notes, inbox capture, and incremental logging.", {
|
|
125
|
+
path: z.string().describe("Relative path or stem of the note"),
|
|
126
|
+
content: z.string().describe("Content to append"),
|
|
127
|
+
separator: z.string().default("\n").describe("Separator before appended content (default: newline)"),
|
|
128
|
+
create_if_missing: z.boolean().default(true).describe("Create the file if it doesn't exist"),
|
|
129
|
+
add_timestamp: z.boolean().default(false).describe("Prepend a timestamp to the appended block"),
|
|
130
|
+
}, async ({ path: notePath, content, separator, create_if_missing, add_timestamp }) => {
|
|
131
|
+
await vault.waitReady();
|
|
132
|
+
const resolved = resolveOrFail(notePath);
|
|
133
|
+
const prefix = add_timestamp ? `\n<!-- ${timestamp()} -->\n` : "";
|
|
134
|
+
const payload = separator + prefix + content;
|
|
135
|
+
try {
|
|
136
|
+
await appendFile(resolved.abs, payload, "utf-8");
|
|
137
|
+
return { content: [{ type: "text", text: `OK: Appended ${payload.length} bytes to ${resolved.rel}` }] };
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
if (create_if_missing) {
|
|
141
|
+
await ensureDir(resolved.abs);
|
|
142
|
+
await writeFile(resolved.abs, prefix + content, "utf-8");
|
|
143
|
+
return { content: [{ type: "text", text: `OK: Created ${resolved.rel} with ${content.length} bytes` }] };
|
|
144
|
+
}
|
|
145
|
+
return { content: [{ type: "text", text: `ERROR: File not found: ${resolved.rel}` }], isError: true };
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
// 5. EDIT NOTE (str_replace style)
|
|
149
|
+
server.tool("edit_note", "In-place edit: find and replace a unique string in a note. The old_str must appear exactly once. Atomic read-modify-write.", {
|
|
150
|
+
path: z.string().describe("Relative path or stem of the note"),
|
|
151
|
+
old_str: z.string().describe("Exact string to find (must be unique in the file)"),
|
|
152
|
+
new_str: z.string().describe("Replacement string (empty string to delete)"),
|
|
153
|
+
}, async ({ path: notePath, old_str, new_str }) => {
|
|
154
|
+
await vault.waitReady();
|
|
155
|
+
const resolved = resolveOrFail(notePath);
|
|
156
|
+
let content;
|
|
157
|
+
try {
|
|
158
|
+
content = await readFile(resolved.abs, "utf-8");
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return { content: [{ type: "text", text: `ERROR: File not found: ${resolved.rel}` }], isError: true };
|
|
162
|
+
}
|
|
163
|
+
const occurrences = content.split(old_str).length - 1;
|
|
164
|
+
if (occurrences === 0) {
|
|
165
|
+
return { content: [{ type: "text", text: `ERROR: String not found in ${resolved.rel}. Check exact spacing/newlines.` }], isError: true };
|
|
166
|
+
}
|
|
167
|
+
if (occurrences > 1) {
|
|
168
|
+
return {
|
|
169
|
+
content: [{ type: "text", text: `ERROR: String found ${occurrences} times in ${resolved.rel}. Must be unique. Add surrounding context to disambiguate.` }],
|
|
170
|
+
isError: true,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
const updated = content.replace(old_str, new_str);
|
|
174
|
+
await writeFile(resolved.abs, updated, "utf-8");
|
|
175
|
+
return { content: [{ type: "text", text: `OK: Edited ${resolved.rel} (replaced ${old_str.length} → ${new_str.length} chars)` }] };
|
|
176
|
+
});
|
|
177
|
+
// 6. DELETE NOTE
|
|
178
|
+
server.tool("delete_note", "Delete a note from the vault. Moves to .trash by default for safety. With cleanup_empty_parents, removes empty parent directories after deletion.", {
|
|
179
|
+
path: z.string().describe("Relative path or stem of the note"),
|
|
180
|
+
permanent: z.boolean().default(false).describe("Skip trash and delete permanently"),
|
|
181
|
+
cleanup_empty_parents: z.boolean().default(false).describe("After deleting, walk up and remove empty parent directories until hitting a non-empty parent or vault root"),
|
|
182
|
+
}, async ({ path: notePath, permanent, cleanup_empty_parents: cleanupParents }) => {
|
|
183
|
+
await vault.waitReady();
|
|
184
|
+
const resolved = resolveOrFail(notePath);
|
|
185
|
+
try {
|
|
186
|
+
if (permanent) {
|
|
187
|
+
await unlink(resolved.abs);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
const trashDir = path.join(VAULT_PATH, ".trash");
|
|
191
|
+
await mkdir(trashDir, { recursive: true });
|
|
192
|
+
const trashPath = path.join(trashDir, path.basename(resolved.abs));
|
|
193
|
+
const content = await readFile(resolved.abs, "utf-8");
|
|
194
|
+
await writeFile(trashPath, content, "utf-8");
|
|
195
|
+
await unlink(resolved.abs);
|
|
196
|
+
}
|
|
197
|
+
let detail = `OK: Deleted ${resolved.rel}${permanent ? " (permanent)" : " (moved to .trash)"}`;
|
|
198
|
+
if (cleanupParents) {
|
|
199
|
+
const { cleanupEmptyParents } = await import("./tool-handlers.js");
|
|
200
|
+
const fileDir = path.dirname(resolved.rel).replace(/\\/g, "/");
|
|
201
|
+
const cleaned = await cleanupEmptyParents(vault, VAULT_PATH, fileDir);
|
|
202
|
+
if (cleaned.length > 0) {
|
|
203
|
+
detail += ` | Cleaned ${cleaned.length} empty parent(s): ${cleaned.join(", ")}`;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return { content: [{ type: "text", text: detail }] };
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return { content: [{ type: "text", text: `ERROR: Could not delete: ${resolved.rel}` }], isError: true };
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
// 7. LIST DIRECTORY
|
|
213
|
+
server.tool("list_dir", "List files and directories in a vault directory. Returns indexed metadata with created/modified timestamps. Sort by name, date, or size. Directories listed separately with child counts.", {
|
|
214
|
+
path: z.string().default(".").describe("Relative directory path (default: vault root)"),
|
|
215
|
+
recursive: z.boolean().default(false).describe("Include subdirectories recursively"),
|
|
216
|
+
pattern: z.string().optional().describe("Glob pattern to filter files (e.g. '*.md', '**/*.canvas'). Does not filter directories."),
|
|
217
|
+
sort_by: z.enum(["name", "created", "modified", "size"]).default("name").describe("Sort field (default: name)"),
|
|
218
|
+
sort_order: z.enum(["asc", "desc"]).default("asc").describe("Sort order (default: asc)"),
|
|
219
|
+
include_dirs: z.boolean().default(true).describe("Include subdirectories in response (default: true)"),
|
|
220
|
+
}, async ({ path: dirPath, recursive, pattern, sort_by, sort_order, include_dirs }) => {
|
|
221
|
+
await vault.waitReady();
|
|
222
|
+
let files;
|
|
223
|
+
if (recursive && pattern) {
|
|
224
|
+
const fullPattern = dirPath === "." ? pattern : `${dirPath}/${pattern}`;
|
|
225
|
+
files = vault.glob(fullPattern);
|
|
226
|
+
}
|
|
227
|
+
else if (recursive) {
|
|
228
|
+
files = vault.searchPaths(dirPath === "." ? "" : dirPath);
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
files = vault.listDir(dirPath);
|
|
232
|
+
}
|
|
233
|
+
let fileListing = files.map((f) => ({
|
|
234
|
+
path: f.rel,
|
|
235
|
+
ext: f.ext,
|
|
236
|
+
size: f.size,
|
|
237
|
+
created: new Date(f.ctime).toISOString(),
|
|
238
|
+
modified: new Date(f.mtime).toISOString(),
|
|
239
|
+
}));
|
|
240
|
+
// Sort files
|
|
241
|
+
fileListing.sort((a, b) => {
|
|
242
|
+
switch (sort_by) {
|
|
243
|
+
case "name": return a.path.localeCompare(b.path);
|
|
244
|
+
case "created": return new Date(a.created).getTime() - new Date(b.created).getTime();
|
|
245
|
+
case "modified": return new Date(a.modified).getTime() - new Date(b.modified).getTime();
|
|
246
|
+
case "size": return a.size - b.size;
|
|
247
|
+
default: return 0;
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
if (sort_order === "desc")
|
|
251
|
+
fileListing.reverse();
|
|
252
|
+
// Build directories listing
|
|
253
|
+
let dirListing = [];
|
|
254
|
+
if (include_dirs && !recursive) {
|
|
255
|
+
const dirs = await vault.listDirEntries(dirPath);
|
|
256
|
+
dirListing = dirs.map((d) => ({
|
|
257
|
+
path: d.rel,
|
|
258
|
+
children_count: d.children_count,
|
|
259
|
+
created: d.ctime ? new Date(d.ctime).toISOString() : "",
|
|
260
|
+
modified: d.mtime ? new Date(d.mtime).toISOString() : "",
|
|
261
|
+
}));
|
|
262
|
+
// Sort directories
|
|
263
|
+
dirListing.sort((a, b) => {
|
|
264
|
+
switch (sort_by) {
|
|
265
|
+
case "name": return a.path.localeCompare(b.path);
|
|
266
|
+
case "created": return new Date(a.created || 0).getTime() - new Date(b.created || 0).getTime();
|
|
267
|
+
case "modified": return new Date(a.modified || 0).getTime() - new Date(b.modified || 0).getTime();
|
|
268
|
+
default: return 0;
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
if (sort_order === "desc")
|
|
272
|
+
dirListing.reverse();
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
content: [
|
|
276
|
+
{
|
|
277
|
+
type: "text",
|
|
278
|
+
text: JSON.stringify({
|
|
279
|
+
directory: dirPath,
|
|
280
|
+
count: fileListing.length + dirListing.length,
|
|
281
|
+
directories: dirListing,
|
|
282
|
+
files: fileListing,
|
|
283
|
+
}, null, 2),
|
|
284
|
+
},
|
|
285
|
+
],
|
|
286
|
+
};
|
|
287
|
+
});
|
|
288
|
+
// 8. SEARCH VAULT (path-based, instant from index)
|
|
289
|
+
server.tool("search_vault", "Search the vault index by filename or path. Instant — uses in-memory index, no disk scan.", {
|
|
290
|
+
query: z.string().describe("Search query (matches against file paths)"),
|
|
291
|
+
limit: z.number().default(20).describe("Max results to return"),
|
|
292
|
+
}, async ({ query, limit }) => {
|
|
293
|
+
await vault.waitReady();
|
|
294
|
+
const results = vault.searchPaths(query).slice(0, limit);
|
|
295
|
+
return {
|
|
296
|
+
content: [
|
|
297
|
+
{
|
|
298
|
+
type: "text",
|
|
299
|
+
text: JSON.stringify({
|
|
300
|
+
query,
|
|
301
|
+
count: results.length,
|
|
302
|
+
results: results.map((f) => ({
|
|
303
|
+
path: f.rel,
|
|
304
|
+
size: f.size,
|
|
305
|
+
modified: new Date(f.mtime).toISOString(),
|
|
306
|
+
})),
|
|
307
|
+
}, null, 2),
|
|
308
|
+
},
|
|
309
|
+
],
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
// 9. SEARCH CONTENT (grep-like, reads files from disk)
|
|
313
|
+
server.tool("search_content", "Full-text content search across vault files. Reads files from disk. Results sorted by match density (most matches first). Use search_vault for faster path-only search.", {
|
|
314
|
+
query: z.string().describe("Text to search for (case-insensitive)"),
|
|
315
|
+
extensions: z.array(z.string()).default([".md"]).describe("File extensions to search (default: ['.md'])"),
|
|
316
|
+
limit: z.number().default(10).describe("Max files to return"),
|
|
317
|
+
context_lines: z.number().default(2).describe("Lines of context around each match"),
|
|
318
|
+
}, async ({ query, extensions, limit, context_lines }) => {
|
|
319
|
+
await vault.waitReady();
|
|
320
|
+
const lower = query.toLowerCase();
|
|
321
|
+
const candidates = vault.allFiles().filter((f) => extensions.includes(f.ext));
|
|
322
|
+
const allResults = [];
|
|
323
|
+
for (const file of candidates) {
|
|
324
|
+
try {
|
|
325
|
+
const content = await readFile(file.abs, "utf-8");
|
|
326
|
+
if (!content.toLowerCase().includes(lower))
|
|
327
|
+
continue;
|
|
328
|
+
// Count total occurrences
|
|
329
|
+
let matchCount = 0;
|
|
330
|
+
let searchPos = 0;
|
|
331
|
+
const contentLower = content.toLowerCase();
|
|
332
|
+
while (true) {
|
|
333
|
+
const idx = contentLower.indexOf(lower, searchPos);
|
|
334
|
+
if (idx === -1)
|
|
335
|
+
break;
|
|
336
|
+
matchCount++;
|
|
337
|
+
searchPos = idx + 1;
|
|
338
|
+
}
|
|
339
|
+
const lines = content.split("\n");
|
|
340
|
+
const matchLines = [];
|
|
341
|
+
for (let i = 0; i < lines.length; i++) {
|
|
342
|
+
if (lines[i].toLowerCase().includes(lower)) {
|
|
343
|
+
const start = Math.max(0, i - context_lines);
|
|
344
|
+
const end = Math.min(lines.length - 1, i + context_lines);
|
|
345
|
+
const snippet = lines
|
|
346
|
+
.slice(start, end + 1)
|
|
347
|
+
.map((l, idx) => `${start + idx + 1}: ${l}`)
|
|
348
|
+
.join("\n");
|
|
349
|
+
matchLines.push(snippet);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
allResults.push({ path: file.rel, match_count: matchCount, matches: matchLines });
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
// Skip unreadable files
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
// Sort by match_count descending, then limit
|
|
359
|
+
allResults.sort((a, b) => b.match_count - a.match_count);
|
|
360
|
+
const results = allResults.slice(0, limit);
|
|
361
|
+
return {
|
|
362
|
+
content: [
|
|
363
|
+
{
|
|
364
|
+
type: "text",
|
|
365
|
+
text: JSON.stringify({ query, count: results.length, results }, null, 2),
|
|
366
|
+
},
|
|
367
|
+
],
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
// 10. RECENT NOTES
|
|
371
|
+
server.tool("recent_notes", "Get most recently modified notes. Instant from index — no disk reads.", {
|
|
372
|
+
limit: z.number().default(15).describe("Number of recent files to return"),
|
|
373
|
+
extension: z.string().optional().describe("Filter by extension (e.g. '.md')"),
|
|
374
|
+
}, async ({ limit, extension }) => {
|
|
375
|
+
await vault.waitReady();
|
|
376
|
+
let files = vault.recentFiles(limit * 2);
|
|
377
|
+
if (extension)
|
|
378
|
+
files = files.filter((f) => f.ext === extension);
|
|
379
|
+
files = files.slice(0, limit);
|
|
380
|
+
return {
|
|
381
|
+
content: [
|
|
382
|
+
{
|
|
383
|
+
type: "text",
|
|
384
|
+
text: JSON.stringify({
|
|
385
|
+
count: files.length,
|
|
386
|
+
files: files.map((f) => ({
|
|
387
|
+
path: f.rel,
|
|
388
|
+
modified: new Date(f.mtime).toISOString(),
|
|
389
|
+
size: f.size,
|
|
390
|
+
})),
|
|
391
|
+
}, null, 2),
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
};
|
|
395
|
+
});
|
|
396
|
+
// 11. BATCH OPERATIONS
|
|
397
|
+
const BatchOpSchema = z.discriminatedUnion("op", [
|
|
398
|
+
z.object({
|
|
399
|
+
op: z.literal("read"),
|
|
400
|
+
path: z.string(),
|
|
401
|
+
}),
|
|
402
|
+
z.object({
|
|
403
|
+
op: z.literal("write"),
|
|
404
|
+
path: z.string(),
|
|
405
|
+
content: z.string(),
|
|
406
|
+
overwrite: z.boolean().default(false),
|
|
407
|
+
}),
|
|
408
|
+
z.object({
|
|
409
|
+
op: z.literal("append"),
|
|
410
|
+
path: z.string(),
|
|
411
|
+
content: z.string(),
|
|
412
|
+
separator: z.string().default("\n"),
|
|
413
|
+
add_timestamp: z.boolean().default(false),
|
|
414
|
+
}),
|
|
415
|
+
z.object({
|
|
416
|
+
op: z.literal("edit"),
|
|
417
|
+
path: z.string(),
|
|
418
|
+
old_str: z.string(),
|
|
419
|
+
new_str: z.string(),
|
|
420
|
+
}),
|
|
421
|
+
z.object({
|
|
422
|
+
op: z.literal("delete"),
|
|
423
|
+
path: z.string(),
|
|
424
|
+
permanent: z.boolean().default(false),
|
|
425
|
+
cleanup_empty_parents: z.boolean().default(false),
|
|
426
|
+
}),
|
|
427
|
+
z.object({
|
|
428
|
+
op: z.literal("rename"),
|
|
429
|
+
from: z.string(),
|
|
430
|
+
to: z.string(),
|
|
431
|
+
update_links: z.boolean().default(true),
|
|
432
|
+
}),
|
|
433
|
+
z.object({
|
|
434
|
+
op: z.literal("edit_regex"),
|
|
435
|
+
path: z.string(),
|
|
436
|
+
match: z.string(),
|
|
437
|
+
replace: z.string(),
|
|
438
|
+
flags: z.string().default("g"),
|
|
439
|
+
}),
|
|
440
|
+
z.object({
|
|
441
|
+
op: z.literal("frontmatter"),
|
|
442
|
+
path: z.string(),
|
|
443
|
+
action: z.enum(["read", "set", "merge", "delete_keys"]),
|
|
444
|
+
data: z.record(z.string(), z.any()).optional(),
|
|
445
|
+
keys: z.array(z.string()).optional(),
|
|
446
|
+
}),
|
|
447
|
+
]);
|
|
448
|
+
server.tool("batch", "Execute multiple vault operations in a single call. Supports: read, write, append, edit, delete, rename, edit_regex, frontmatter. Sequential execution. Returns results array.", {
|
|
449
|
+
operations: z.array(BatchOpSchema).min(1).max(50).describe("Array of operations to execute"),
|
|
450
|
+
}, async ({ operations }) => {
|
|
451
|
+
await vault.waitReady();
|
|
452
|
+
const results = [];
|
|
453
|
+
for (let i = 0; i < operations.length; i++) {
|
|
454
|
+
const op = operations[i];
|
|
455
|
+
try {
|
|
456
|
+
switch (op.op) {
|
|
457
|
+
case "read": {
|
|
458
|
+
const resolved = resolveOrFail(op.path);
|
|
459
|
+
const content = await readFile(resolved.abs, "utf-8");
|
|
460
|
+
results.push({ index: i, op: "read", status: "ok", detail: resolved.rel, content });
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "write": {
|
|
464
|
+
const rel = ensureMd(op.path.replace(/\\/g, "/").replace(/^\/+/, ""));
|
|
465
|
+
const absPath = abs(rel);
|
|
466
|
+
if (!op.overwrite && vault.has(rel)) {
|
|
467
|
+
results.push({ index: i, op: "write", status: "error", detail: `Already exists: ${rel}` });
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
|
+
await ensureDir(absPath);
|
|
471
|
+
await writeFile(absPath, op.content, "utf-8");
|
|
472
|
+
results.push({ index: i, op: "write", status: "ok", detail: `Written: ${rel}` });
|
|
473
|
+
}
|
|
474
|
+
break;
|
|
475
|
+
}
|
|
476
|
+
case "append": {
|
|
477
|
+
const resolved = resolveOrFail(op.path);
|
|
478
|
+
const prefix = op.add_timestamp ? `\n<!-- ${timestamp()} -->\n` : "";
|
|
479
|
+
const payload = op.separator + prefix + op.content;
|
|
480
|
+
try {
|
|
481
|
+
await appendFile(resolved.abs, payload, "utf-8");
|
|
482
|
+
results.push({ index: i, op: "append", status: "ok", detail: `Appended to: ${resolved.rel}` });
|
|
483
|
+
}
|
|
484
|
+
catch {
|
|
485
|
+
await ensureDir(resolved.abs);
|
|
486
|
+
await writeFile(resolved.abs, prefix + op.content, "utf-8");
|
|
487
|
+
results.push({ index: i, op: "append", status: "ok", detail: `Created: ${resolved.rel}` });
|
|
488
|
+
}
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case "edit": {
|
|
492
|
+
const resolved = resolveOrFail(op.path);
|
|
493
|
+
const content = await readFile(resolved.abs, "utf-8");
|
|
494
|
+
const count = content.split(op.old_str).length - 1;
|
|
495
|
+
if (count !== 1) {
|
|
496
|
+
results.push({
|
|
497
|
+
index: i,
|
|
498
|
+
op: "edit",
|
|
499
|
+
status: "error",
|
|
500
|
+
detail: count === 0 ? `String not found in ${resolved.rel}` : `String found ${count} times in ${resolved.rel}`,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
await writeFile(resolved.abs, content.replace(op.old_str, op.new_str), "utf-8");
|
|
505
|
+
results.push({ index: i, op: "edit", status: "ok", detail: `Edited: ${resolved.rel}` });
|
|
506
|
+
}
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
case "delete": {
|
|
510
|
+
const resolved = resolveOrFail(op.path);
|
|
511
|
+
if (op.permanent) {
|
|
512
|
+
await unlink(resolved.abs);
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
const trashDir = path.join(VAULT_PATH, ".trash");
|
|
516
|
+
await mkdir(trashDir, { recursive: true });
|
|
517
|
+
const c = await readFile(resolved.abs, "utf-8");
|
|
518
|
+
await writeFile(path.join(trashDir, path.basename(resolved.abs)), c, "utf-8");
|
|
519
|
+
await unlink(resolved.abs);
|
|
520
|
+
}
|
|
521
|
+
let deleteDetail = `Deleted: ${resolved.rel}`;
|
|
522
|
+
if (op.cleanup_empty_parents) {
|
|
523
|
+
const { cleanupEmptyParents } = await import("./tool-handlers.js");
|
|
524
|
+
const fileDir = path.dirname(resolved.rel).replace(/\\/g, "/");
|
|
525
|
+
const cleaned = await cleanupEmptyParents(vault, VAULT_PATH, fileDir);
|
|
526
|
+
if (cleaned.length > 0) {
|
|
527
|
+
deleteDetail += ` | Cleaned ${cleaned.length} empty parent(s)`;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
results.push({ index: i, op: "delete", status: "ok", detail: deleteDetail });
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
case "rename": {
|
|
534
|
+
const fromResolved = vault.resolve(op.from);
|
|
535
|
+
const fromRel = fromResolved?.rel ?? op.from;
|
|
536
|
+
const fromAbs = path.join(VAULT_PATH, fromRel);
|
|
537
|
+
const toAbs = path.join(VAULT_PATH, op.to);
|
|
538
|
+
await mkdir(path.dirname(toAbs), { recursive: true });
|
|
539
|
+
await rename(fromAbs, toAbs);
|
|
540
|
+
let linksUpdated = 0;
|
|
541
|
+
if (op.update_links) {
|
|
542
|
+
const linkResult = await updateWikilinks(VAULT_PATH, vault, fromRel, op.to, false);
|
|
543
|
+
linksUpdated = linkResult.totalLinks;
|
|
544
|
+
}
|
|
545
|
+
results.push({ index: i, op: "rename", status: "ok", detail: `Renamed: ${fromRel} → ${op.to} (${linksUpdated} links updated)` });
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
case "edit_regex": {
|
|
549
|
+
const resolved = resolveOrFail(op.path);
|
|
550
|
+
const content = await readFile(resolved.abs, "utf-8");
|
|
551
|
+
let regex;
|
|
552
|
+
try {
|
|
553
|
+
regex = new RegExp(op.match, op.flags);
|
|
554
|
+
}
|
|
555
|
+
catch (err) {
|
|
556
|
+
results.push({ index: i, op: "edit_regex", status: "error", detail: `Invalid regex: ${err.message}` });
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
const newContent = content.replace(regex, op.replace);
|
|
560
|
+
if (newContent === content) {
|
|
561
|
+
results.push({ index: i, op: "edit_regex", status: "ok", detail: `No matches in ${resolved.rel}` });
|
|
562
|
+
}
|
|
563
|
+
else {
|
|
564
|
+
await writeFile(resolved.abs, newContent, "utf-8");
|
|
565
|
+
results.push({ index: i, op: "edit_regex", status: "ok", detail: `Regex applied to ${resolved.rel}` });
|
|
566
|
+
}
|
|
567
|
+
break;
|
|
568
|
+
}
|
|
569
|
+
case "frontmatter": {
|
|
570
|
+
const resolved = resolveOrFail(op.path);
|
|
571
|
+
let content;
|
|
572
|
+
try {
|
|
573
|
+
content = await readFile(resolved.abs, "utf-8");
|
|
574
|
+
}
|
|
575
|
+
catch {
|
|
576
|
+
content = "";
|
|
577
|
+
}
|
|
578
|
+
const parsed = parseFrontmatter(content);
|
|
579
|
+
switch (op.action) {
|
|
580
|
+
case "read":
|
|
581
|
+
results.push({ index: i, op: "frontmatter", status: "ok", detail: resolved.rel, content: JSON.stringify(parsed.frontmatter) });
|
|
582
|
+
break;
|
|
583
|
+
case "set":
|
|
584
|
+
if (op.data) {
|
|
585
|
+
await writeFile(resolved.abs, serializeFrontmatter(op.data) + "\n" + parsed.body, "utf-8");
|
|
586
|
+
results.push({ index: i, op: "frontmatter", status: "ok", detail: `Set frontmatter: ${resolved.rel}` });
|
|
587
|
+
}
|
|
588
|
+
break;
|
|
589
|
+
case "merge":
|
|
590
|
+
if (op.data) {
|
|
591
|
+
const merged = { ...parsed.frontmatter, ...op.data };
|
|
592
|
+
await writeFile(resolved.abs, serializeFrontmatter(merged) + "\n" + parsed.body, "utf-8");
|
|
593
|
+
results.push({ index: i, op: "frontmatter", status: "ok", detail: `Merged frontmatter: ${resolved.rel}` });
|
|
594
|
+
}
|
|
595
|
+
break;
|
|
596
|
+
case "delete_keys":
|
|
597
|
+
if (op.keys) {
|
|
598
|
+
const updated = { ...parsed.frontmatter };
|
|
599
|
+
for (const key of op.keys)
|
|
600
|
+
delete updated[key];
|
|
601
|
+
const newFm = Object.keys(updated).length > 0 ? serializeFrontmatter(updated) + "\n" + parsed.body : parsed.body;
|
|
602
|
+
await writeFile(resolved.abs, newFm, "utf-8");
|
|
603
|
+
results.push({ index: i, op: "frontmatter", status: "ok", detail: `Deleted keys from ${resolved.rel}` });
|
|
604
|
+
}
|
|
605
|
+
break;
|
|
606
|
+
}
|
|
607
|
+
break;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
results.push({ index: i, op: op.op, status: "error", detail: err?.message ?? String(err) });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
const okCount = results.filter((r) => r.status === "ok").length;
|
|
616
|
+
return {
|
|
617
|
+
content: [
|
|
618
|
+
{
|
|
619
|
+
type: "text",
|
|
620
|
+
text: JSON.stringify({
|
|
621
|
+
total: operations.length,
|
|
622
|
+
succeeded: okCount,
|
|
623
|
+
failed: operations.length - okCount,
|
|
624
|
+
results,
|
|
625
|
+
}, null, 2),
|
|
626
|
+
},
|
|
627
|
+
],
|
|
628
|
+
};
|
|
629
|
+
});
|
|
630
|
+
// 12. DAILY NOTE helper
|
|
631
|
+
server.tool("daily_note", "Quick access to today's daily note (or a specific date). Creates from template if missing. Perfect for rapid capture.", {
|
|
632
|
+
date: z.string().optional().describe("Date in YYYY-MM-DD format (default: today)"),
|
|
633
|
+
folder: z.string().default("01-Daily").describe("Daily notes folder"),
|
|
634
|
+
content_to_append: z.string().optional().describe("Content to append to the daily note"),
|
|
635
|
+
template: z.string().optional().describe("Template content if creating a new daily note"),
|
|
636
|
+
}, async ({ date, folder, content_to_append, template }) => {
|
|
637
|
+
await vault.waitReady();
|
|
638
|
+
const targetDate = date ?? new Date().toISOString().slice(0, 10);
|
|
639
|
+
const relPath = `${folder}/${targetDate}.md`;
|
|
640
|
+
const absPath = abs(relPath);
|
|
641
|
+
let existed = vault.has(relPath);
|
|
642
|
+
let content;
|
|
643
|
+
if (!existed) {
|
|
644
|
+
await ensureDir(absPath);
|
|
645
|
+
const initial = template ?? `# ${targetDate}\n\n`;
|
|
646
|
+
await writeFile(absPath, initial, "utf-8");
|
|
647
|
+
content = initial;
|
|
648
|
+
}
|
|
649
|
+
else {
|
|
650
|
+
content = await readFile(absPath, "utf-8");
|
|
651
|
+
}
|
|
652
|
+
if (content_to_append) {
|
|
653
|
+
const payload = `\n${content_to_append}`;
|
|
654
|
+
await appendFile(absPath, payload, "utf-8");
|
|
655
|
+
content += payload;
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
content: [
|
|
659
|
+
{
|
|
660
|
+
type: "text",
|
|
661
|
+
text: JSON.stringify({
|
|
662
|
+
path: relPath,
|
|
663
|
+
created: !existed,
|
|
664
|
+
appended: !!content_to_append,
|
|
665
|
+
content,
|
|
666
|
+
}),
|
|
667
|
+
},
|
|
668
|
+
],
|
|
669
|
+
};
|
|
670
|
+
});
|
|
671
|
+
// ── Canvas Tools ────────────────────────────────────────────────────
|
|
672
|
+
registerCanvasCreate(server, VAULT_PATH, vault);
|
|
673
|
+
registerCanvasRead(server, VAULT_PATH, vault);
|
|
674
|
+
registerCanvasPatch(server, VAULT_PATH, vault);
|
|
675
|
+
registerCanvasRelayout(server, VAULT_PATH, vault);
|
|
676
|
+
// ── File & Link Tools ──────────────────────────────────────────────
|
|
677
|
+
registerEditRegex(server, VAULT_PATH, vault);
|
|
678
|
+
registerBatchRename(server, VAULT_PATH, vault);
|
|
679
|
+
registerDeleteFolder(server, VAULT_PATH, vault);
|
|
680
|
+
registerPruneEmptyDirs(server, VAULT_PATH, vault);
|
|
681
|
+
registerUpdateLinks(server, VAULT_PATH, vault);
|
|
682
|
+
registerBacklinks(server, VAULT_PATH, vault);
|
|
683
|
+
registerFrontmatter(server, VAULT_PATH, vault);
|
|
684
|
+
// ── Search & Intelligence Tools ────────────────────────────────────
|
|
685
|
+
registerSmartSearch(server, VAULT_PATH, vault);
|
|
686
|
+
registerSearchReindex(server, VAULT_PATH, vault);
|
|
687
|
+
registerVaultThemes(server, VAULT_PATH, vault);
|
|
688
|
+
registerVaultSuggest(server, VAULT_PATH, vault);
|
|
689
|
+
// ── Boot ─────────────────────────────────────────────────────────────
|
|
690
|
+
async function main() {
|
|
691
|
+
console.error(`[vaultforge] Starting...`);
|
|
692
|
+
console.error(`[vaultforge] Vault: ${VAULT_PATH}`);
|
|
693
|
+
// Init index in background, server starts immediately
|
|
694
|
+
vault.init().catch((err) => {
|
|
695
|
+
console.error("[vaultforge] Index init failed:", err);
|
|
696
|
+
});
|
|
697
|
+
const transport = new StdioServerTransport();
|
|
698
|
+
await server.connect(transport);
|
|
699
|
+
console.error(`[vaultforge] Connected via stdio`);
|
|
700
|
+
}
|
|
701
|
+
main().catch((err) => {
|
|
702
|
+
console.error("[vaultforge] Fatal:", err);
|
|
703
|
+
process.exit(1);
|
|
704
|
+
});
|
|
705
|
+
//# sourceMappingURL=index.js.map
|