context-vault 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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/cli.js +588 -0
- package/package.json +30 -0
- package/smithery.yaml +10 -0
- package/src/capture/README.md +23 -0
- package/src/capture/file-ops.js +75 -0
- package/src/capture/formatters.js +29 -0
- package/src/capture/index.js +91 -0
- package/src/core/README.md +20 -0
- package/src/core/categories.js +50 -0
- package/src/core/config.js +76 -0
- package/src/core/files.js +114 -0
- package/src/core/frontmatter.js +108 -0
- package/src/core/status.js +105 -0
- package/src/index/README.md +28 -0
- package/src/index/db.js +138 -0
- package/src/index/embed.js +56 -0
- package/src/index/index.js +258 -0
- package/src/retrieve/README.md +19 -0
- package/src/retrieve/index.js +173 -0
- package/src/server/README.md +44 -0
- package/src/server/helpers.js +29 -0
- package/src/server/index.js +82 -0
- package/src/server/tools.js +211 -0
- package/ui/Context.applescript +36 -0
- package/ui/index.html +1377 -0
- package/ui/serve.js +473 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* context-mcp CLI — Unified entry point
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* context-mcp setup Interactive MCP installer
|
|
8
|
+
* context-mcp ui [--port 3141] Launch web dashboard
|
|
9
|
+
* context-mcp reindex Rebuild search index
|
|
10
|
+
* context-mcp status Show vault diagnostics
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createInterface } from "node:readline";
|
|
14
|
+
import {
|
|
15
|
+
existsSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
mkdirSync,
|
|
19
|
+
copyFileSync,
|
|
20
|
+
unlinkSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { join, resolve, dirname } from "node:path";
|
|
23
|
+
import { homedir } from "node:os";
|
|
24
|
+
import { execSync, fork } from "node:child_process";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
26
|
+
|
|
27
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
28
|
+
const __dirname = dirname(__filename);
|
|
29
|
+
const ROOT = resolve(__dirname, "..");
|
|
30
|
+
const HOME = homedir();
|
|
31
|
+
|
|
32
|
+
const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
|
|
33
|
+
const VERSION = pkg.version;
|
|
34
|
+
const SERVER_PATH = resolve(ROOT, "src", "server", "index.js");
|
|
35
|
+
|
|
36
|
+
/** Detect if running as an npm-installed package (global or local) vs local dev clone */
|
|
37
|
+
function isInstalledPackage() {
|
|
38
|
+
return ROOT.includes("/node_modules/") || ROOT.includes("\\node_modules\\");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ─── ANSI Helpers ────────────────────────────────────────────────────────────
|
|
42
|
+
|
|
43
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
44
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
45
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
46
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
47
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
48
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
49
|
+
|
|
50
|
+
// ─── Arg Parsing ─────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
const args = process.argv.slice(2);
|
|
53
|
+
const command = args[0];
|
|
54
|
+
const flags = new Set(args.filter((a) => a.startsWith("--")));
|
|
55
|
+
const isNonInteractive = flags.has("--yes") || !process.stdin.isTTY;
|
|
56
|
+
|
|
57
|
+
function getFlag(name) {
|
|
58
|
+
const idx = args.indexOf(name);
|
|
59
|
+
return idx !== -1 && args[idx + 1] ? args[idx + 1] : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ─── Readline Prompt ─────────────────────────────────────────────────────────
|
|
63
|
+
|
|
64
|
+
function prompt(question, defaultVal) {
|
|
65
|
+
if (isNonInteractive) return Promise.resolve(defaultVal || "");
|
|
66
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
67
|
+
const suffix = defaultVal ? ` ${dim(`(${defaultVal})`)}` : "";
|
|
68
|
+
return new Promise((res) => {
|
|
69
|
+
rl.question(`${question}${suffix} `, (answer) => {
|
|
70
|
+
rl.close();
|
|
71
|
+
res(answer.trim() || defaultVal || "");
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ─── Tool Detection ──────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
const TOOLS = [
|
|
79
|
+
{
|
|
80
|
+
id: "claude-code",
|
|
81
|
+
name: "Claude Code",
|
|
82
|
+
detect: () => {
|
|
83
|
+
try {
|
|
84
|
+
execSync("which claude", { stdio: "pipe" });
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
configType: "cli",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "claude-desktop",
|
|
94
|
+
name: "Claude Desktop",
|
|
95
|
+
detect: () =>
|
|
96
|
+
existsSync(join(HOME, "Library", "Application Support", "Claude")),
|
|
97
|
+
configType: "json",
|
|
98
|
+
configPath: join(
|
|
99
|
+
HOME,
|
|
100
|
+
"Library",
|
|
101
|
+
"Application Support",
|
|
102
|
+
"Claude",
|
|
103
|
+
"claude_desktop_config.json"
|
|
104
|
+
),
|
|
105
|
+
configKey: "mcpServers",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
id: "cursor",
|
|
109
|
+
name: "Cursor",
|
|
110
|
+
detect: () => existsSync(join(HOME, ".cursor")),
|
|
111
|
+
configType: "json",
|
|
112
|
+
configPath: join(HOME, ".cursor", "mcp.json"),
|
|
113
|
+
configKey: "mcpServers",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: "windsurf",
|
|
117
|
+
name: "Windsurf",
|
|
118
|
+
detect: () => existsSync(join(HOME, ".codeium", "windsurf")),
|
|
119
|
+
configType: "json",
|
|
120
|
+
configPath: join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
121
|
+
configKey: "mcpServers",
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "cline",
|
|
125
|
+
name: "Cline (VS Code)",
|
|
126
|
+
detect: () =>
|
|
127
|
+
existsSync(
|
|
128
|
+
join(
|
|
129
|
+
HOME,
|
|
130
|
+
"Library",
|
|
131
|
+
"Application Support",
|
|
132
|
+
"Code",
|
|
133
|
+
"User",
|
|
134
|
+
"globalStorage",
|
|
135
|
+
"saoudrizwan.claude-dev",
|
|
136
|
+
"settings"
|
|
137
|
+
)
|
|
138
|
+
),
|
|
139
|
+
configType: "json",
|
|
140
|
+
configPath: join(
|
|
141
|
+
HOME,
|
|
142
|
+
"Library",
|
|
143
|
+
"Application Support",
|
|
144
|
+
"Code",
|
|
145
|
+
"User",
|
|
146
|
+
"globalStorage",
|
|
147
|
+
"saoudrizwan.claude-dev",
|
|
148
|
+
"settings",
|
|
149
|
+
"cline_mcp_settings.json"
|
|
150
|
+
),
|
|
151
|
+
configKey: "mcpServers",
|
|
152
|
+
},
|
|
153
|
+
];
|
|
154
|
+
|
|
155
|
+
// ─── Help ────────────────────────────────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
function showHelp() {
|
|
158
|
+
console.log(`
|
|
159
|
+
${bold("context-mcp")} v${VERSION} — Personal knowledge vault for AI agents
|
|
160
|
+
|
|
161
|
+
${bold("Usage:")}
|
|
162
|
+
context-mcp <command> [options]
|
|
163
|
+
|
|
164
|
+
${bold("Commands:")}
|
|
165
|
+
${cyan("setup")} Interactive MCP server installer
|
|
166
|
+
${cyan("serve")} Start the MCP server (used by AI clients)
|
|
167
|
+
${cyan("ui")} [--port 3141] Launch web dashboard
|
|
168
|
+
${cyan("reindex")} Rebuild search index from knowledge files
|
|
169
|
+
${cyan("status")} Show vault diagnostics
|
|
170
|
+
|
|
171
|
+
${bold("Options:")}
|
|
172
|
+
--help Show this help
|
|
173
|
+
--version Show version
|
|
174
|
+
--yes Non-interactive mode (accept all defaults)
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ─── Setup Command ───────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
async function runSetup() {
|
|
181
|
+
// Banner
|
|
182
|
+
console.log();
|
|
183
|
+
console.log(bold(" context-mcp") + dim(` v${VERSION}`));
|
|
184
|
+
console.log(dim(" Personal knowledge vault for AI agents"));
|
|
185
|
+
console.log();
|
|
186
|
+
|
|
187
|
+
// Detect tools
|
|
188
|
+
console.log(bold(" Detecting installed tools...\n"));
|
|
189
|
+
const detected = [];
|
|
190
|
+
for (const tool of TOOLS) {
|
|
191
|
+
const found = tool.detect();
|
|
192
|
+
if (found) {
|
|
193
|
+
detected.push(tool);
|
|
194
|
+
console.log(` ${green("+")} ${tool.name}`);
|
|
195
|
+
} else {
|
|
196
|
+
console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
console.log();
|
|
200
|
+
|
|
201
|
+
if (detected.length === 0) {
|
|
202
|
+
console.log(
|
|
203
|
+
yellow(" No supported tools detected.\n")
|
|
204
|
+
);
|
|
205
|
+
console.log(" To manually configure, add to your tool's MCP config:\n");
|
|
206
|
+
if (isInstalledPackage()) {
|
|
207
|
+
console.log(` ${dim("{")}
|
|
208
|
+
${dim('"mcpServers": {')}
|
|
209
|
+
${dim('"context-mcp": {')}
|
|
210
|
+
${dim('"command": "context-mcp",')}
|
|
211
|
+
${dim(`"args": ["serve", "--vault-dir", "/path/to/vault"]`)}
|
|
212
|
+
${dim("}")}
|
|
213
|
+
${dim("}")}
|
|
214
|
+
${dim("}")}\n`);
|
|
215
|
+
} else {
|
|
216
|
+
console.log(` ${dim("{")}
|
|
217
|
+
${dim('"mcpServers": {')}
|
|
218
|
+
${dim('"context-mcp": {')}
|
|
219
|
+
${dim('"command": "node",')}
|
|
220
|
+
${dim(`"args": ["${SERVER_PATH}", "--vault-dir", "/path/to/vault"]`)}
|
|
221
|
+
${dim("}")}
|
|
222
|
+
${dim("}")}
|
|
223
|
+
${dim("}")}\n`);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Select tools
|
|
229
|
+
let selected;
|
|
230
|
+
if (isNonInteractive) {
|
|
231
|
+
selected = detected;
|
|
232
|
+
} else {
|
|
233
|
+
const listing = detected
|
|
234
|
+
.map((t, i) => `${i + 1}) ${t.name}`)
|
|
235
|
+
.join(" ");
|
|
236
|
+
const answer = await prompt(
|
|
237
|
+
` Configure: ${dim("all")} or enter numbers (${listing}):`,
|
|
238
|
+
"all"
|
|
239
|
+
);
|
|
240
|
+
if (answer === "all" || answer === "") {
|
|
241
|
+
selected = detected;
|
|
242
|
+
} else {
|
|
243
|
+
const nums = answer
|
|
244
|
+
.split(/[,\s]+/)
|
|
245
|
+
.map((n) => parseInt(n, 10) - 1)
|
|
246
|
+
.filter((n) => n >= 0 && n < detected.length);
|
|
247
|
+
selected = nums.map((n) => detected[n]);
|
|
248
|
+
if (selected.length === 0) selected = detected;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Vault directory (content files)
|
|
253
|
+
const defaultVaultDir = join(HOME, "vault");
|
|
254
|
+
const vaultDir = isNonInteractive
|
|
255
|
+
? defaultVaultDir
|
|
256
|
+
: await prompt(`\n Vault directory:`, defaultVaultDir);
|
|
257
|
+
const resolvedVaultDir = resolve(vaultDir);
|
|
258
|
+
|
|
259
|
+
// Create vault dir if needed
|
|
260
|
+
if (!existsSync(resolvedVaultDir)) {
|
|
261
|
+
if (isNonInteractive) {
|
|
262
|
+
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
263
|
+
console.log(
|
|
264
|
+
`\n ${green("+")} Created ${resolvedVaultDir}`
|
|
265
|
+
);
|
|
266
|
+
} else {
|
|
267
|
+
const create = await prompt(
|
|
268
|
+
`\n ${resolvedVaultDir} doesn't exist. Create it? (Y/n):`,
|
|
269
|
+
"Y"
|
|
270
|
+
);
|
|
271
|
+
if (create.toLowerCase() !== "n") {
|
|
272
|
+
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
273
|
+
console.log(` ${green("+")} Created ${resolvedVaultDir}`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Ensure data dir exists for DB storage
|
|
279
|
+
const dataDir = join(HOME, ".context-mcp");
|
|
280
|
+
mkdirSync(dataDir, { recursive: true });
|
|
281
|
+
|
|
282
|
+
// Write config.json to data dir (persistent, survives reinstalls)
|
|
283
|
+
const configPath = join(dataDir, "config.json");
|
|
284
|
+
const vaultConfig = {};
|
|
285
|
+
if (existsSync(configPath)) {
|
|
286
|
+
try {
|
|
287
|
+
Object.assign(vaultConfig, JSON.parse(readFileSync(configPath, "utf-8")));
|
|
288
|
+
} catch {}
|
|
289
|
+
}
|
|
290
|
+
vaultConfig.vaultDir = resolvedVaultDir;
|
|
291
|
+
vaultConfig.dataDir = dataDir;
|
|
292
|
+
vaultConfig.dbPath = join(dataDir, "vault.db");
|
|
293
|
+
vaultConfig.devDir = join(HOME, "dev");
|
|
294
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + "\n");
|
|
295
|
+
console.log(`\n ${green("+")} Wrote ${configPath}`);
|
|
296
|
+
|
|
297
|
+
// Clean up legacy project-root config.json if it exists
|
|
298
|
+
const legacyConfigPath = join(ROOT, "config.json");
|
|
299
|
+
if (existsSync(legacyConfigPath)) {
|
|
300
|
+
try {
|
|
301
|
+
unlinkSync(legacyConfigPath);
|
|
302
|
+
console.log(` ${dim("Removed legacy config at " + legacyConfigPath)}`);
|
|
303
|
+
} catch {}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Configure each tool — pass vault dir as arg if non-default
|
|
307
|
+
console.log(`\n${bold(" Configuring tools...\n")}`);
|
|
308
|
+
const results = [];
|
|
309
|
+
const defaultVDir = join(HOME, "vault");
|
|
310
|
+
const customVaultDir = resolvedVaultDir !== resolve(defaultVDir) ? resolvedVaultDir : null;
|
|
311
|
+
|
|
312
|
+
for (const tool of selected) {
|
|
313
|
+
try {
|
|
314
|
+
if (tool.configType === "cli") {
|
|
315
|
+
await configureClaude(tool, customVaultDir);
|
|
316
|
+
} else {
|
|
317
|
+
configureJsonTool(tool, customVaultDir);
|
|
318
|
+
}
|
|
319
|
+
results.push({ tool, ok: true });
|
|
320
|
+
console.log(` ${green("+")} ${tool.name} — configured`);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
results.push({ tool, ok: false, error: e.message });
|
|
323
|
+
console.log(` ${red("x")} ${tool.name} — ${e.message}`);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Offer to launch UI
|
|
328
|
+
console.log();
|
|
329
|
+
if (!isNonInteractive) {
|
|
330
|
+
const launchUi = await prompt(
|
|
331
|
+
` Launch web dashboard? (y/N):`,
|
|
332
|
+
"N"
|
|
333
|
+
);
|
|
334
|
+
if (launchUi.toLowerCase() === "y") {
|
|
335
|
+
console.log();
|
|
336
|
+
runUi();
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Summary
|
|
342
|
+
console.log(bold("\n Setup complete!\n"));
|
|
343
|
+
const ok = results.filter((r) => r.ok);
|
|
344
|
+
if (ok.length) {
|
|
345
|
+
console.log(
|
|
346
|
+
` ${green(ok.length)} tool${ok.length > 1 ? "s" : ""} configured:`
|
|
347
|
+
);
|
|
348
|
+
for (const r of ok) {
|
|
349
|
+
console.log(` ${r.tool.name}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
console.log(`\n Vault dir: ${resolvedVaultDir}`);
|
|
353
|
+
console.log(` Config: ${configPath}`);
|
|
354
|
+
console.log(` MCP server: ${isInstalledPackage() ? "context-mcp serve" : SERVER_PATH}`);
|
|
355
|
+
console.log(`\n Run ${cyan("context-mcp ui")} to open the dashboard.`);
|
|
356
|
+
console.log();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function configureClaude(tool, vaultDir) {
|
|
360
|
+
const env = { ...process.env };
|
|
361
|
+
delete env.CLAUDECODE;
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
execSync("claude mcp remove context-mcp -s user", { stdio: "pipe", env });
|
|
365
|
+
} catch {}
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
execSync("claude mcp remove context-vault -s user", { stdio: "pipe", env });
|
|
369
|
+
} catch {}
|
|
370
|
+
|
|
371
|
+
if (isInstalledPackage()) {
|
|
372
|
+
const cmdArgs = ["serve"];
|
|
373
|
+
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
374
|
+
execSync(
|
|
375
|
+
`claude mcp add -s user context-mcp -- context-mcp ${cmdArgs.join(" ")}`,
|
|
376
|
+
{ stdio: "pipe", env }
|
|
377
|
+
);
|
|
378
|
+
} else {
|
|
379
|
+
const cmdArgs = [`"${SERVER_PATH}"`];
|
|
380
|
+
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
381
|
+
execSync(
|
|
382
|
+
`claude mcp add -s user context-mcp -- node ${cmdArgs.join(" ")}`,
|
|
383
|
+
{ stdio: "pipe", env }
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function configureJsonTool(tool, vaultDir) {
|
|
389
|
+
const configPath = tool.configPath;
|
|
390
|
+
const configDir = dirname(configPath);
|
|
391
|
+
|
|
392
|
+
if (!existsSync(configDir)) {
|
|
393
|
+
mkdirSync(configDir, { recursive: true });
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
let config = {};
|
|
397
|
+
if (existsSync(configPath)) {
|
|
398
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
399
|
+
try {
|
|
400
|
+
config = JSON.parse(raw);
|
|
401
|
+
} catch {
|
|
402
|
+
const bakPath = configPath + ".bak";
|
|
403
|
+
copyFileSync(configPath, bakPath);
|
|
404
|
+
console.log(` ${yellow("!")} Backed up corrupted config to ${bakPath}`);
|
|
405
|
+
config = {};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
if (!config[tool.configKey]) {
|
|
410
|
+
config[tool.configKey] = {};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
delete config[tool.configKey]["context-vault"];
|
|
414
|
+
|
|
415
|
+
if (isInstalledPackage()) {
|
|
416
|
+
const serverArgs = ["serve"];
|
|
417
|
+
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
418
|
+
config[tool.configKey]["context-mcp"] = {
|
|
419
|
+
command: "context-mcp",
|
|
420
|
+
args: serverArgs,
|
|
421
|
+
};
|
|
422
|
+
} else {
|
|
423
|
+
const serverArgs = [SERVER_PATH];
|
|
424
|
+
if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
|
|
425
|
+
config[tool.configKey]["context-mcp"] = {
|
|
426
|
+
command: "node",
|
|
427
|
+
args: serverArgs,
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// ─── UI Command ──────────────────────────────────────────────────────────────
|
|
435
|
+
|
|
436
|
+
function runUi() {
|
|
437
|
+
const serveScript = resolve(ROOT, "ui", "serve.js");
|
|
438
|
+
if (!existsSync(serveScript)) {
|
|
439
|
+
console.error(red("Error: ui/serve.js not found."));
|
|
440
|
+
process.exit(1);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const uiArgs = args.slice(1);
|
|
444
|
+
const child = fork(serveScript, uiArgs, { stdio: "inherit" });
|
|
445
|
+
child.on("exit", (code) => process.exit(code ?? 0));
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─── Reindex Command ─────────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
async function runReindex() {
|
|
451
|
+
console.log(dim("Loading vault..."));
|
|
452
|
+
|
|
453
|
+
const { resolveConfig } = await import("../src/core/config.js");
|
|
454
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } = await import("../src/index/db.js");
|
|
455
|
+
const { embed } = await import("../src/index/embed.js");
|
|
456
|
+
const { reindex } = await import("../src/index/index.js");
|
|
457
|
+
|
|
458
|
+
const config = resolveConfig();
|
|
459
|
+
if (!config.vaultDirExists) {
|
|
460
|
+
console.error(
|
|
461
|
+
red(`Vault directory not found: ${config.vaultDir}`)
|
|
462
|
+
);
|
|
463
|
+
console.error("Run " + cyan("context-mcp setup") + " to configure.");
|
|
464
|
+
process.exit(1);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const db = initDatabase(config.dbPath);
|
|
468
|
+
const stmts = prepareStatements(db);
|
|
469
|
+
const ctx = {
|
|
470
|
+
db,
|
|
471
|
+
config,
|
|
472
|
+
stmts,
|
|
473
|
+
embed,
|
|
474
|
+
insertVec: (r, e) => insertVec(stmts, r, e),
|
|
475
|
+
deleteVec: (r) => deleteVec(stmts, r),
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
const stats = await reindex(ctx, { fullSync: true });
|
|
479
|
+
|
|
480
|
+
db.close();
|
|
481
|
+
console.log(green("Reindex complete:"));
|
|
482
|
+
console.log(` Added: ${stats.added}`);
|
|
483
|
+
console.log(` Updated: ${stats.updated}`);
|
|
484
|
+
console.log(` Removed: ${stats.removed}`);
|
|
485
|
+
console.log(` Unchanged: ${stats.unchanged}`);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// ─── Status Command ──────────────────────────────────────────────────────────
|
|
489
|
+
|
|
490
|
+
async function runStatus() {
|
|
491
|
+
const { resolveConfig } = await import("../src/core/config.js");
|
|
492
|
+
const { initDatabase } = await import("../src/index/db.js");
|
|
493
|
+
const { gatherVaultStatus } = await import("../src/core/status.js");
|
|
494
|
+
|
|
495
|
+
const config = resolveConfig();
|
|
496
|
+
const db = initDatabase(config.dbPath);
|
|
497
|
+
|
|
498
|
+
const status = gatherVaultStatus({ db, config });
|
|
499
|
+
|
|
500
|
+
db.close();
|
|
501
|
+
|
|
502
|
+
console.log();
|
|
503
|
+
console.log(bold(" Vault Status"));
|
|
504
|
+
console.log();
|
|
505
|
+
console.log(` Vault: ${config.vaultDir} (exists: ${config.vaultDirExists}, ${status.fileCount} files)`);
|
|
506
|
+
console.log(` Database: ${config.dbPath} (${status.dbSize})`);
|
|
507
|
+
console.log(` Dev dir: ${config.devDir}`);
|
|
508
|
+
console.log(` Data dir: ${config.dataDir}`);
|
|
509
|
+
console.log(` Config: ${config.configPath} (exists: ${existsSync(config.configPath)})`);
|
|
510
|
+
console.log(` Resolved: ${status.resolvedFrom}`);
|
|
511
|
+
console.log(` Schema: v5 (categories)`);
|
|
512
|
+
|
|
513
|
+
if (status.kindCounts.length) {
|
|
514
|
+
console.log();
|
|
515
|
+
console.log(bold(" Indexed"));
|
|
516
|
+
for (const { kind, c } of status.kindCounts) {
|
|
517
|
+
console.log(` ${c} ${kind}s`);
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
console.log(`\n ${dim("(empty — no entries indexed)")}`);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (status.subdirs.length) {
|
|
524
|
+
console.log();
|
|
525
|
+
console.log(bold(" Disk Directories"));
|
|
526
|
+
for (const { name, count } of status.subdirs) {
|
|
527
|
+
console.log(` ${name}/: ${count} files`);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (status.stalePaths) {
|
|
532
|
+
console.log();
|
|
533
|
+
console.log(yellow(" Stale paths detected in DB."));
|
|
534
|
+
console.log(` Run ${cyan("context-mcp reindex")} to update.`);
|
|
535
|
+
}
|
|
536
|
+
console.log();
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// ─── Serve Command ──────────────────────────────────────────────────────────
|
|
540
|
+
|
|
541
|
+
async function runServe() {
|
|
542
|
+
await import("../src/server/index.js");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ─── Main Router ─────────────────────────────────────────────────────────────
|
|
546
|
+
|
|
547
|
+
async function main() {
|
|
548
|
+
if (flags.has("--version") || command === "version") {
|
|
549
|
+
console.log(VERSION);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (flags.has("--help") || command === "help" || !command) {
|
|
554
|
+
showHelp();
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
switch (command) {
|
|
559
|
+
case "setup":
|
|
560
|
+
await runSetup();
|
|
561
|
+
break;
|
|
562
|
+
case "serve":
|
|
563
|
+
await runServe();
|
|
564
|
+
break;
|
|
565
|
+
case "ui":
|
|
566
|
+
runUi();
|
|
567
|
+
break;
|
|
568
|
+
case "import":
|
|
569
|
+
case "export":
|
|
570
|
+
console.log(`Import/export removed. Add .md files to vault/ and run \`context-mcp reindex\`.`);
|
|
571
|
+
break;
|
|
572
|
+
case "reindex":
|
|
573
|
+
await runReindex();
|
|
574
|
+
break;
|
|
575
|
+
case "status":
|
|
576
|
+
await runStatus();
|
|
577
|
+
break;
|
|
578
|
+
default:
|
|
579
|
+
console.error(red(`Unknown command: ${command}`));
|
|
580
|
+
console.error(`Run ${cyan("context-mcp --help")} for usage.`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
main().catch((e) => {
|
|
586
|
+
console.error(red(e.message));
|
|
587
|
+
process.exit(1);
|
|
588
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "context-vault",
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Personal context vault — connects any AI agent to your accumulated knowledge",
|
|
6
|
+
"bin": {
|
|
7
|
+
"context-mcp": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/server/index.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"bin/",
|
|
12
|
+
"src/",
|
|
13
|
+
"ui/",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE",
|
|
16
|
+
"smithery.yaml"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"engines": { "node": ">=20" },
|
|
20
|
+
"author": "Felix Hellstrom",
|
|
21
|
+
"repository": { "type": "git", "url": "https://github.com/fellanH/context-mcp.git" },
|
|
22
|
+
"homepage": "https://github.com/fellanH/context-mcp",
|
|
23
|
+
"keywords": ["mcp", "model-context-protocol", "ai", "knowledge-base", "knowledge-management", "vault", "rag", "sqlite", "embeddings", "claude", "cursor", "cline", "windsurf"],
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@huggingface/transformers": "^3.0.0",
|
|
26
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
27
|
+
"better-sqlite3": "^12.6.2",
|
|
28
|
+
"sqlite-vec": "^0.1.0"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/smithery.yaml
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
startCommand:
|
|
2
|
+
type: stdio
|
|
3
|
+
configSchema:
|
|
4
|
+
type: object
|
|
5
|
+
properties:
|
|
6
|
+
vaultDir:
|
|
7
|
+
type: string
|
|
8
|
+
description: "Path to the vault directory containing your knowledge files. Defaults to ~/vault/"
|
|
9
|
+
commandFunction: |-
|
|
10
|
+
(config) => ({ command: 'npx', args: ['-y', '@fellanh/context-mcp', 'serve', ...(config.vaultDir ? ['--vault-dir', config.vaultDir] : [])] })
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Capture Layer
|
|
2
|
+
|
|
3
|
+
The write path. Creates `.md` files in the vault directory with YAML frontmatter. That is its entire job — it does not index, embed, or query.
|
|
4
|
+
|
|
5
|
+
## Public API (`index.js`)
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
writeEntry(ctx, { kind, title, body, meta, tags, source, folder })
|
|
9
|
+
→ { id, filePath, kind, title, body, meta, tags, source, createdAt }
|
|
10
|
+
|
|
11
|
+
captureAndIndex(ctx, data, indexFn)
|
|
12
|
+
→ Promise<entry> // Writes file, indexes, rolls back file on index failure
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`captureAndIndex` is the primary entry point used by `save_context`. It writes the file via `writeEntry`, then calls the provided `indexFn`. If indexing fails, the file is deleted to maintain consistency.
|
|
16
|
+
|
|
17
|
+
## Dependency Rule
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
capture/ → core/ (only)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Never import from `index/`, `retrieve/`, or `server/`.
|