context-vault 2.1.0 → 2.3.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.
Files changed (29) hide show
  1. package/bin/cli.js +408 -61
  2. package/node_modules/@context-vault/core/package.json +36 -0
  3. package/{src → node_modules/@context-vault/core/src}/capture/index.js +66 -1
  4. package/{src → node_modules/@context-vault/core/src}/core/categories.js +1 -0
  5. package/{src → node_modules/@context-vault/core/src}/core/files.js +1 -0
  6. package/{src → node_modules/@context-vault/core/src}/index/db.js +1 -0
  7. package/{src → node_modules/@context-vault/core/src}/index/embed.js +10 -1
  8. package/node_modules/@context-vault/core/src/index.js +29 -0
  9. package/node_modules/@context-vault/core/src/server/tools.js +433 -0
  10. package/package.json +8 -8
  11. package/src/server/index.js +21 -4
  12. package/ui/serve.js +7 -6
  13. package/LICENSE +0 -21
  14. package/README.md +0 -395
  15. package/smithery.yaml +0 -10
  16. package/src/capture/README.md +0 -23
  17. package/src/core/README.md +0 -20
  18. package/src/index/README.md +0 -28
  19. package/src/retrieve/README.md +0 -19
  20. package/src/server/README.md +0 -44
  21. package/src/server/tools.js +0 -211
  22. /package/{src → node_modules/@context-vault/core/src}/capture/file-ops.js +0 -0
  23. /package/{src → node_modules/@context-vault/core/src}/capture/formatters.js +0 -0
  24. /package/{src → node_modules/@context-vault/core/src}/core/config.js +0 -0
  25. /package/{src → node_modules/@context-vault/core/src}/core/frontmatter.js +0 -0
  26. /package/{src → node_modules/@context-vault/core/src}/core/status.js +0 -0
  27. /package/{src → node_modules/@context-vault/core/src}/index/index.js +0 -0
  28. /package/{src → node_modules/@context-vault/core/src}/retrieve/index.js +0 -0
  29. /package/{src → node_modules/@context-vault/core/src}/server/helpers.js +0 -0
package/bin/cli.js CHANGED
@@ -156,7 +156,8 @@ const TOOLS = [
156
156
 
157
157
  function showHelp() {
158
158
  console.log(`
159
- ${bold("context-mcp")} v${VERSION} — Persistent memory for AI agents
159
+ ${bold("context-vault")} ${dim(`v${VERSION}`)}
160
+ ${dim("Persistent memory for AI agents")}
160
161
 
161
162
  ${bold("Usage:")}
162
163
  context-mcp <command> [options]
@@ -167,6 +168,9 @@ ${bold("Commands:")}
167
168
  ${cyan("ui")} [--port 3141] Launch web dashboard
168
169
  ${cyan("reindex")} Rebuild search index from knowledge files
169
170
  ${cyan("status")} Show vault diagnostics
171
+ ${cyan("update")} Check for and install updates
172
+ ${cyan("uninstall")} Remove MCP configs and optionally data
173
+ ${cyan("migrate")} Migrate vault between local and hosted
170
174
 
171
175
  ${bold("Options:")}
172
176
  --help Show this help
@@ -180,14 +184,108 @@ ${bold("Options:")}
180
184
  async function runSetup() {
181
185
  // Banner
182
186
  console.log();
183
- console.log(bold(" context-mcp") + dim(` v${VERSION}`));
184
- console.log(dim(" Persistent memory for AI agents — saves and searches knowledge across sessions"));
185
- console.log();
186
- console.log(dim(" Setup will: detect tools, configure MCP, download embeddings, and verify."));
187
+ console.log(` ${bold("context-vault")} ${dim(`v${VERSION}`)}`);
188
+ console.log(dim(" Persistent memory for AI agents"));
187
189
  console.log();
188
190
 
191
+ // Check for existing installation
192
+ const existingConfig = join(HOME, ".context-mcp", "config.json");
193
+ if (existsSync(existingConfig) && !isNonInteractive) {
194
+ let existingVault = "(unknown)";
195
+ try {
196
+ const cfg = JSON.parse(readFileSync(existingConfig, "utf-8"));
197
+ existingVault = cfg.vaultDir || existingVault;
198
+ } catch {}
199
+
200
+ console.log(yellow(` Existing installation detected`));
201
+ console.log(dim(` Vault: ${existingVault}`));
202
+ console.log(dim(` Config: ${existingConfig}`));
203
+ console.log();
204
+ console.log(` 1) Full reconfigure`);
205
+ console.log(` 2) Update tool configs only ${dim("(skip vault setup)")}`);
206
+ console.log(` 3) Cancel`);
207
+ console.log();
208
+ const choice = await prompt(" Select:", "1");
209
+
210
+ if (choice === "3") {
211
+ console.log(dim(" Cancelled."));
212
+ return;
213
+ }
214
+
215
+ if (choice === "2") {
216
+ // Skip vault setup, just reconfigure tools
217
+ console.log();
218
+ console.log(dim(` [1/2]`) + bold(" Detecting tools...\n"));
219
+ const detected = [];
220
+ for (const tool of TOOLS) {
221
+ const found = tool.detect();
222
+ if (found) {
223
+ detected.push(tool);
224
+ console.log(` ${green("+")} ${tool.name}`);
225
+ } else {
226
+ console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
227
+ }
228
+ }
229
+ console.log();
230
+
231
+ if (detected.length === 0) {
232
+ console.log(yellow(" No supported tools detected."));
233
+ return;
234
+ }
235
+
236
+ let selected;
237
+ console.log(bold(" Which tools should context-mcp connect to?\n"));
238
+ for (let i = 0; i < detected.length; i++) {
239
+ console.log(` ${i + 1}) ${detected[i].name}`);
240
+ }
241
+ console.log();
242
+ const answer = await prompt(
243
+ ` Select (${dim("1,2,3")} or ${dim('"all"')}):`,
244
+ "all"
245
+ );
246
+ if (answer === "all" || answer === "") {
247
+ selected = detected;
248
+ } else {
249
+ const nums = answer.split(/[,\s]+/).map((n) => parseInt(n, 10) - 1).filter((n) => n >= 0 && n < detected.length);
250
+ selected = nums.map((n) => detected[n]);
251
+ if (selected.length === 0) selected = detected;
252
+ }
253
+
254
+ // Read vault dir from existing config
255
+ let customVaultDir = null;
256
+ try {
257
+ const cfg = JSON.parse(readFileSync(existingConfig, "utf-8"));
258
+ const defaultVDir = join(HOME, "vault");
259
+ if (cfg.vaultDir && resolve(cfg.vaultDir) !== resolve(defaultVDir)) {
260
+ customVaultDir = cfg.vaultDir;
261
+ }
262
+ } catch {}
263
+
264
+ console.log(`\n ${dim("[2/2]")}${bold(" Configuring tools...\n")}`);
265
+ for (const tool of selected) {
266
+ try {
267
+ if (tool.configType === "cli") {
268
+ await configureClaude(tool, customVaultDir);
269
+ } else {
270
+ configureJsonTool(tool, customVaultDir);
271
+ }
272
+ console.log(` ${green("+")} ${tool.name} — configured`);
273
+ } catch (e) {
274
+ console.log(` ${red("x")} ${tool.name} — ${e.message}`);
275
+ }
276
+ }
277
+
278
+ console.log();
279
+ console.log(green(" ✓ Tool configs updated."));
280
+ console.log();
281
+ return;
282
+ }
283
+ // choice === "1" falls through to full setup below
284
+ console.log();
285
+ }
286
+
189
287
  // Detect tools
190
- console.log(bold(" Detecting installed tools...\n"));
288
+ console.log(dim(` [1/5]`) + bold(" Detecting tools...\n"));
191
289
  const detected = [];
192
290
  for (const tool of TOOLS) {
193
291
  const found = tool.detect();
@@ -254,6 +352,7 @@ async function runSetup() {
254
352
  }
255
353
 
256
354
  // Vault directory (content files)
355
+ console.log(dim(` [2/5]`) + bold(" Configuring vault...\n"));
257
356
  const defaultVaultDir = join(HOME, "vault");
258
357
  const vaultDir = isNonInteractive
259
358
  ? defaultVaultDir
@@ -299,10 +398,10 @@ async function runSetup() {
299
398
  console.log(`\n ${green("+")} Wrote ${configPath}`);
300
399
 
301
400
  // Pre-download embedding model
302
- console.log(`\n${bold(" Downloading embedding model...")}`);
401
+ console.log(`\n ${dim("[3/5]")}${bold(" Downloading embedding model...")}`);
303
402
  console.log(dim(" all-MiniLM-L6-v2 (~22MB, one-time download)\n"));
304
403
  try {
305
- const { embed } = await import("../src/index/embed.js");
404
+ const { embed } = await import("@context-vault/core/index/embed");
306
405
  await embed("warmup");
307
406
  console.log(` ${green("+")} Embedding model ready`);
308
407
  } catch (e) {
@@ -319,7 +418,7 @@ async function runSetup() {
319
418
  }
320
419
 
321
420
  // Configure each tool — pass vault dir as arg if non-default
322
- console.log(`\n${bold(" Configuring tools...\n")}`);
421
+ console.log(`\n ${dim("[4/5]")}${bold(" Configuring tools...\n")}`);
323
422
  const results = [];
324
423
  const defaultVDir = join(HOME, "vault");
325
424
  const customVaultDir = resolvedVaultDir !== resolve(defaultVDir) ? resolvedVaultDir : null;
@@ -340,9 +439,9 @@ async function runSetup() {
340
439
  }
341
440
 
342
441
  // Seed entry
343
- const seeded = createSeedEntry(resolvedVaultDir);
344
- if (seeded) {
345
- console.log(`\n ${green("+")} Created starter entry in vault`);
442
+ const seeded = createSeedEntries(resolvedVaultDir);
443
+ if (seeded > 0) {
444
+ console.log(`\n ${green("+")} Created ${seeded} starter ${seeded === 1 ? "entry" : "entries"} in vault`);
346
445
  }
347
446
 
348
447
  // Offer to launch UI
@@ -360,26 +459,41 @@ async function runSetup() {
360
459
  }
361
460
 
362
461
  // Health check
363
- const ok = results.filter((r) => r.ok);
462
+ console.log(`\n ${dim("[5/5]")}${bold(" Health check...")}\n`);
463
+ const okResults = results.filter((r) => r.ok);
364
464
  const checks = [
365
465
  { label: "Vault directory exists", pass: existsSync(resolvedVaultDir) },
366
466
  { label: "Config file written", pass: existsSync(configPath) },
367
- { label: "At least one tool configured", pass: ok.length > 0 },
467
+ { label: "At least one tool configured", pass: okResults.length > 0 },
368
468
  ];
369
469
  const passed = checks.filter((c) => c.pass).length;
370
- console.log(bold(`\n Health check: ${passed}/${checks.length} passed\n`));
371
470
  for (const c of checks) {
372
- console.log(` ${c.pass ? green("+") : red("x")} ${c.label}`);
471
+ console.log(` ${c.pass ? green("") : red("")} ${c.label}`);
373
472
  }
374
473
 
375
- // First-use guidance
376
- const toolName = ok.length ? ok[0].tool.name : "your AI tool";
377
- console.log(bold("\n What to do next:\n"));
378
- console.log(` 1. Open ${toolName}`);
379
- console.log(` 2. Try: ${cyan('"Search my vault for getting started"')}`);
380
- console.log(` 3. Try: ${cyan('"Save an insight: JavaScript Date objects are mutable"')}`);
381
- console.log(`\n Vault: ${resolvedVaultDir}`);
382
- console.log(` Dashboard: ${cyan("context-mcp ui")}`);
474
+ // Completion box
475
+ const toolName = okResults.length ? okResults[0].tool.name : "your AI tool";
476
+ const boxLines = [
477
+ ` Setup complete — ${passed}/${checks.length} checks passed`,
478
+ ``,
479
+ ` ${bold("AI Tools")} open ${toolName} and try:`,
480
+ ` "Search my vault for getting started"`,
481
+ ` "Save an insight about [topic]"`,
482
+ ` "Show my vault status"`,
483
+ ``,
484
+ ` ${bold("CLI Commands:")}`,
485
+ ` context-mcp status Show vault health`,
486
+ ` context-mcp ui Launch web dashboard`,
487
+ ` context-mcp update Check for updates`,
488
+ ];
489
+ const innerWidth = Math.max(...boxLines.map((l) => l.length)) + 2;
490
+ const pad = (s) => s + " ".repeat(Math.max(0, innerWidth - s.length));
491
+ console.log();
492
+ console.log(` ${dim("┌" + "─".repeat(innerWidth) + "┐")}`);
493
+ for (const line of boxLines) {
494
+ console.log(` ${dim("│")}${pad(line)}${dim("│")}`);
495
+ }
496
+ console.log(` ${dim("└" + "─".repeat(innerWidth) + "┘")}`);
383
497
  console.log();
384
498
  }
385
499
 
@@ -458,18 +572,21 @@ function configureJsonTool(tool, vaultDir) {
458
572
  writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
459
573
  }
460
574
 
461
- // ─── Seed Entry ─────────────────────────────────────────────────────────────
462
-
463
- function createSeedEntry(vaultDir) {
464
- const seedDir = join(vaultDir, "knowledge", "insights");
465
- const seedPath = join(seedDir, "getting-started.md");
466
- if (existsSync(seedPath)) return false;
467
- mkdirSync(seedDir, { recursive: true });
468
- const id = Date.now().toString(36).toUpperCase().padStart(10, "0");
469
- const now = new Date().toISOString();
470
- const content = `---
471
- id: ${id}
472
- tags: ["getting-started"]
575
+ // ─── Seed Entries ────────────────────────────────────────────────────────────
576
+
577
+ function createSeedEntries(vaultDir) {
578
+ let created = 0;
579
+
580
+ // Entry 1: Getting started (improved)
581
+ const insightDir = join(vaultDir, "knowledge", "insights");
582
+ const insightPath = join(insightDir, "getting-started.md");
583
+ if (!existsSync(insightPath)) {
584
+ mkdirSync(insightDir, { recursive: true });
585
+ const id1 = Date.now().toString(36).toUpperCase().padStart(10, "0");
586
+ const now = new Date().toISOString();
587
+ writeFileSync(insightPath, `---
588
+ id: ${id1}
589
+ tags: ["getting-started", "vault"]
473
590
  source: context-mcp-setup
474
591
  created: ${now}
475
592
  ---
@@ -478,16 +595,48 @@ Welcome to your context vault! This is a seed entry created during setup.
478
595
  Your vault stores knowledge as plain markdown files with YAML frontmatter.
479
596
  AI agents search it using hybrid full-text + semantic search.
480
597
 
481
- Try these commands in your AI tool:
482
- - "Search my vault for getting started"
483
- - "Save an insight: JavaScript Date objects are mutable"
484
- - "Show my vault status"
598
+ **Quick start:**
599
+ - "Search my vault for getting started" — find this entry
600
+ - "Save an insight about [topic]" add knowledge
601
+ - "Show my vault status" — check health
602
+ - "List my recent entries" — browse your vault
485
603
 
486
604
  You can edit or delete this file anytime — it lives at:
487
- ${seedPath}
488
- `;
489
- writeFileSync(seedPath, content);
490
- return true;
605
+ ${insightPath}
606
+ `);
607
+ created++;
608
+ }
609
+
610
+ // Entry 2: Example decision
611
+ const decisionDir = join(vaultDir, "knowledge", "decisions");
612
+ const decisionPath = join(decisionDir, "example-local-first-data.md");
613
+ if (!existsSync(decisionPath)) {
614
+ mkdirSync(decisionDir, { recursive: true });
615
+ const id2 = (Date.now() + 1).toString(36).toUpperCase().padStart(10, "0");
616
+ const now = new Date().toISOString();
617
+ writeFileSync(decisionPath, `---
618
+ id: ${id2}
619
+ tags: ["example", "architecture"]
620
+ source: context-mcp-setup
621
+ created: ${now}
622
+ ---
623
+ Example decision: Use local-first data storage (SQLite + files) over cloud databases.
624
+
625
+ **Context:** For personal knowledge management, local storage provides better privacy,
626
+ offline access, and zero ongoing cost. The vault uses plain markdown files as the
627
+ source of truth with a SQLite index for fast search.
628
+
629
+ **Trade-offs:**
630
+ - Pro: Full data ownership, git-versioned, human-editable
631
+ - Pro: No cloud dependency, works offline
632
+ - Con: No built-in sync across devices (use git or Syncthing)
633
+
634
+ This is an example entry showing the decision format. Feel free to delete it.
635
+ `);
636
+ created++;
637
+ }
638
+
639
+ return created;
491
640
  }
492
641
 
493
642
  // ─── UI Command ──────────────────────────────────────────────────────────────
@@ -509,10 +658,10 @@ function runUi() {
509
658
  async function runReindex() {
510
659
  console.log(dim("Loading vault..."));
511
660
 
512
- const { resolveConfig } = await import("../src/core/config.js");
513
- const { initDatabase, prepareStatements, insertVec, deleteVec } = await import("../src/index/db.js");
514
- const { embed } = await import("../src/index/embed.js");
515
- const { reindex } = await import("../src/index/index.js");
661
+ const { resolveConfig } = await import("@context-vault/core/core/config");
662
+ const { initDatabase, prepareStatements, insertVec, deleteVec } = await import("@context-vault/core/index/db");
663
+ const { embed } = await import("@context-vault/core/index/embed");
664
+ const { reindex } = await import("@context-vault/core/index");
516
665
 
517
666
  const config = resolveConfig();
518
667
  if (!config.vaultDirExists) {
@@ -537,19 +686,19 @@ async function runReindex() {
537
686
  const stats = await reindex(ctx, { fullSync: true });
538
687
 
539
688
  db.close();
540
- console.log(green("Reindex complete:"));
541
- console.log(` Added: ${stats.added}`);
542
- console.log(` Updated: ${stats.updated}`);
543
- console.log(` Removed: ${stats.removed}`);
544
- console.log(` Unchanged: ${stats.unchanged}`);
689
+ console.log(green("Reindex complete"));
690
+ console.log(` ${green("+")} ${stats.added} added`);
691
+ console.log(` ${yellow("~")} ${stats.updated} updated`);
692
+ console.log(` ${red("-")} ${stats.removed} removed`);
693
+ console.log(` ${dim("·")} ${stats.unchanged} unchanged`);
545
694
  }
546
695
 
547
696
  // ─── Status Command ──────────────────────────────────────────────────────────
548
697
 
549
698
  async function runStatus() {
550
- const { resolveConfig } = await import("../src/core/config.js");
551
- const { initDatabase } = await import("../src/index/db.js");
552
- const { gatherVaultStatus } = await import("../src/core/status.js");
699
+ const { resolveConfig } = await import("@context-vault/core/core/config");
700
+ const { initDatabase } = await import("@context-vault/core/index/db");
701
+ const { gatherVaultStatus } = await import("@context-vault/core/core/status");
553
702
 
554
703
  const config = resolveConfig();
555
704
  const db = initDatabase(config.dbPath);
@@ -559,26 +708,43 @@ async function runStatus() {
559
708
  db.close();
560
709
 
561
710
  console.log();
562
- console.log(bold(" Vault Status"));
711
+ console.log(` ${bold(" context-vault")} ${dim(`v${VERSION}`)}`);
563
712
  console.log();
564
- console.log(` Vault: ${config.vaultDir} (exists: ${config.vaultDirExists}, ${status.fileCount} files)`);
565
- console.log(` Database: ${config.dbPath} (${status.dbSize})`);
713
+ console.log(` Vault: ${config.vaultDir} ${dim(`(${config.vaultDirExists ? status.fileCount + " files" : "missing"})`)}`);
714
+ console.log(` Database: ${config.dbPath} ${dim(`(${status.dbSize})`)}`);
566
715
  console.log(` Dev dir: ${config.devDir}`);
567
716
  console.log(` Data dir: ${config.dataDir}`);
568
- console.log(` Config: ${config.configPath} (exists: ${existsSync(config.configPath)})`);
717
+ console.log(` Config: ${config.configPath} ${dim(`(${existsSync(config.configPath) ? "exists" : "missing"})`)}`);
569
718
  console.log(` Resolved: ${status.resolvedFrom}`);
570
719
  console.log(` Schema: v5 (categories)`);
571
720
 
572
721
  if (status.kindCounts.length) {
722
+ const BAR_WIDTH = 20;
723
+ const maxCount = Math.max(...status.kindCounts.map((k) => k.c));
573
724
  console.log();
574
725
  console.log(bold(" Indexed"));
575
726
  for (const { kind, c } of status.kindCounts) {
576
- console.log(` ${c} ${kind}s`);
727
+ const filled = maxCount > 0 ? Math.round((c / maxCount) * BAR_WIDTH) : 0;
728
+ const bar = "█".repeat(filled) + "░".repeat(BAR_WIDTH - filled);
729
+ const countStr = String(c).padStart(4);
730
+ console.log(` ${countStr} ${kind}s ${dim(bar)}`);
577
731
  }
578
732
  } else {
579
733
  console.log(`\n ${dim("(empty — no entries indexed)")}`);
580
734
  }
581
735
 
736
+ if (status.embeddingStatus) {
737
+ const { indexed, total, missing } = status.embeddingStatus;
738
+ if (missing > 0) {
739
+ const BAR_WIDTH = 20;
740
+ const filled = total > 0 ? Math.round((indexed / total) * BAR_WIDTH) : 0;
741
+ const bar = "█".repeat(filled) + "░".repeat(BAR_WIDTH - filled);
742
+ const pct = total > 0 ? Math.round((indexed / total) * 100) : 0;
743
+ console.log();
744
+ console.log(` Embeddings ${dim(bar)} ${indexed}/${total} (${pct}%)`);
745
+ }
746
+ }
747
+
582
748
  if (status.subdirs.length) {
583
749
  console.log();
584
750
  console.log(bold(" Disk Directories"));
@@ -595,6 +761,178 @@ async function runStatus() {
595
761
  console.log();
596
762
  }
597
763
 
764
+ // ─── Update Command ─────────────────────────────────────────────────────────
765
+
766
+ async function runUpdate() {
767
+ console.log();
768
+ console.log(` ${bold("◇ context-vault")} ${dim(`v${VERSION}`)}`);
769
+ console.log();
770
+
771
+ let latest;
772
+ try {
773
+ latest = execSync("npm view context-vault version", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
774
+ } catch {
775
+ console.error(red(" Could not check for updates. Verify your network connection."));
776
+ return;
777
+ }
778
+
779
+ if (latest === VERSION) {
780
+ console.log(green(" Already up to date."));
781
+ console.log();
782
+ return;
783
+ }
784
+
785
+ console.log(` Current: ${dim(VERSION)}`);
786
+ console.log(` Latest: ${green(latest)}`);
787
+ console.log();
788
+
789
+ if (!isNonInteractive) {
790
+ const answer = await prompt(` Update to v${latest}? (Y/n):`, "Y");
791
+ if (answer.toLowerCase() === "n") {
792
+ console.log(dim(" Cancelled."));
793
+ return;
794
+ }
795
+ }
796
+
797
+ console.log(dim(" Installing..."));
798
+ try {
799
+ execSync("npm install -g context-vault@latest", { stdio: "inherit" });
800
+ console.log();
801
+ console.log(green(` ✓ Updated to v${latest}`));
802
+ } catch {
803
+ console.error(red(" Update failed. Try manually: npm install -g context-vault@latest"));
804
+ }
805
+ console.log();
806
+ }
807
+
808
+ // ─── Uninstall Command ──────────────────────────────────────────────────────
809
+
810
+ async function runUninstall() {
811
+ console.log();
812
+ console.log(` ${bold("◇ context-vault")} ${dim("uninstall")}`);
813
+ console.log();
814
+
815
+ // Remove from Claude Code
816
+ try {
817
+ const env = { ...process.env };
818
+ delete env.CLAUDECODE;
819
+ execSync("claude mcp remove context-mcp -s user", { stdio: "pipe", env });
820
+ console.log(` ${green("+")} Removed from Claude Code`);
821
+ } catch {
822
+ console.log(` ${dim("-")} Claude Code — not configured or not installed`);
823
+ }
824
+
825
+ // Remove from JSON-configured tools
826
+ for (const tool of TOOLS.filter((t) => t.configType === "json")) {
827
+ if (!existsSync(tool.configPath)) continue;
828
+ try {
829
+ const config = JSON.parse(readFileSync(tool.configPath, "utf-8"));
830
+ if (config[tool.configKey]?.["context-mcp"]) {
831
+ delete config[tool.configKey]["context-mcp"];
832
+ writeFileSync(tool.configPath, JSON.stringify(config, null, 2) + "\n");
833
+ console.log(` ${green("+")} Removed from ${tool.name}`);
834
+ }
835
+ } catch {
836
+ console.log(` ${dim("-")} ${tool.name} — could not update config`);
837
+ }
838
+ }
839
+
840
+ // Optionally remove data directory
841
+ const dataDir = join(HOME, ".context-mcp");
842
+ if (existsSync(dataDir)) {
843
+ console.log();
844
+ const answer = isNonInteractive
845
+ ? "n"
846
+ : await prompt(` Remove data directory (${dataDir})? (y/N):`, "N");
847
+ if (answer.toLowerCase() === "y") {
848
+ const { rmSync } = await import("node:fs");
849
+ rmSync(dataDir, { recursive: true, force: true });
850
+ console.log(` ${green("+")} Removed ${dataDir}`);
851
+ } else {
852
+ console.log(` ${dim("Kept")} ${dataDir}`);
853
+ }
854
+ }
855
+
856
+ console.log();
857
+ console.log(dim(" Vault directory was not touched (your knowledge files are safe)."));
858
+ console.log(` To fully remove: ${cyan("npm uninstall -g context-vault")}`);
859
+ console.log();
860
+ }
861
+
862
+ // ─── Migrate Command ─────────────────────────────────────────────────────────
863
+
864
+ async function runMigrate() {
865
+ const direction = args.includes("--to-hosted") ? "to-hosted"
866
+ : args.includes("--to-local") ? "to-local"
867
+ : null;
868
+
869
+ if (!direction) {
870
+ console.log(`\n ${bold("context-mcp migrate")}\n`);
871
+ console.log(` Usage:`);
872
+ console.log(` context-mcp migrate --to-hosted Upload local vault to hosted service`);
873
+ console.log(` context-mcp migrate --to-local Download hosted vault to local files`);
874
+ console.log(`\n Options:`);
875
+ console.log(` --url <url> Hosted server URL (default: https://vault.contextvault.dev)`);
876
+ console.log(` --key <key> API key (cv_...)`);
877
+ console.log();
878
+ return;
879
+ }
880
+
881
+ const hostedUrl = getFlag("--url") || "https://vault.contextvault.dev";
882
+ const apiKey = getFlag("--key");
883
+
884
+ if (!apiKey) {
885
+ console.error(red(" Error: --key <api_key> is required for migration."));
886
+ console.error(` Get your API key at ${cyan(hostedUrl + "/dashboard")}`);
887
+ return;
888
+ }
889
+
890
+ const { resolveConfig } = await import("@context-vault/core/core/config");
891
+ const config = resolveConfig();
892
+
893
+ if (direction === "to-hosted") {
894
+ const { migrateToHosted } = await import("@context-vault/hosted/migration/migrate");
895
+ console.log(`\n ${bold("Migrating to hosted")}...`);
896
+ console.log(dim(` Vault: ${config.vaultDir}`));
897
+ console.log(dim(` Target: ${hostedUrl}\n`));
898
+
899
+ const results = await migrateToHosted({
900
+ vaultDir: config.vaultDir,
901
+ hostedUrl,
902
+ apiKey,
903
+ log: (msg) => console.log(` ${dim(msg)}`),
904
+ });
905
+
906
+ console.log(`\n ${green("+")} ${results.uploaded} entries uploaded`);
907
+ if (results.failed > 0) {
908
+ console.log(` ${red("-")} ${results.failed} failed`);
909
+ for (const err of results.errors.slice(0, 5)) {
910
+ console.log(` ${dim(err)}`);
911
+ }
912
+ }
913
+ console.log(dim("\n Your local vault was not modified (safe backup)."));
914
+ } else {
915
+ const { migrateToLocal } = await import("@context-vault/hosted/migration/migrate");
916
+ console.log(`\n ${bold("Migrating to local")}...`);
917
+ console.log(dim(` Source: ${hostedUrl}`));
918
+ console.log(dim(` Target: ${config.vaultDir}\n`));
919
+
920
+ const results = await migrateToLocal({
921
+ vaultDir: config.vaultDir,
922
+ hostedUrl,
923
+ apiKey,
924
+ log: (msg) => console.log(` ${dim(msg)}`),
925
+ });
926
+
927
+ console.log(`\n ${green("+")} ${results.downloaded} entries restored`);
928
+ if (results.failed > 0) {
929
+ console.log(` ${red("-")} ${results.failed} failed`);
930
+ }
931
+ console.log(dim("\n Run `context-mcp reindex` to rebuild the search index."));
932
+ }
933
+ console.log();
934
+ }
935
+
598
936
  // ─── Serve Command ──────────────────────────────────────────────────────────
599
937
 
600
938
  async function runServe() {
@@ -634,6 +972,15 @@ async function main() {
634
972
  case "status":
635
973
  await runStatus();
636
974
  break;
975
+ case "update":
976
+ await runUpdate();
977
+ break;
978
+ case "uninstall":
979
+ await runUninstall();
980
+ break;
981
+ case "migrate":
982
+ await runMigrate();
983
+ break;
637
984
  default:
638
985
  console.error(red(`Unknown command: ${command}`));
639
986
  console.error(`Run ${cyan("context-mcp --help")} for usage.`);
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@context-vault/core",
3
+ "version": "2.3.0",
4
+ "type": "module",
5
+ "description": "Shared core: capture, index, retrieve, tools, and utilities for context-vault",
6
+ "main": "src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js",
9
+ "./capture": "./src/capture/index.js",
10
+ "./capture/formatters": "./src/capture/formatters.js",
11
+ "./capture/file-ops": "./src/capture/file-ops.js",
12
+ "./index/db": "./src/index/db.js",
13
+ "./index/embed": "./src/index/embed.js",
14
+ "./index": "./src/index/index.js",
15
+ "./retrieve": "./src/retrieve/index.js",
16
+ "./server/tools": "./src/server/tools.js",
17
+ "./server/helpers": "./src/server/helpers.js",
18
+ "./core/categories": "./src/core/categories.js",
19
+ "./core/config": "./src/core/config.js",
20
+ "./core/files": "./src/core/files.js",
21
+ "./core/frontmatter": "./src/core/frontmatter.js",
22
+ "./core/status": "./src/core/status.js"
23
+ },
24
+ "files": ["src/"],
25
+ "license": "MIT",
26
+ "engines": { "node": ">=20" },
27
+ "author": "Felix Hellstrom",
28
+ "repository": { "type": "git", "url": "https://github.com/fellanH/context-mcp.git", "directory": "packages/core" },
29
+ "homepage": "https://github.com/fellanH/context-mcp",
30
+ "dependencies": {
31
+ "@huggingface/transformers": "^3.0.0",
32
+ "@modelcontextprotocol/sdk": "^1.26.0",
33
+ "better-sqlite3": "^12.6.2",
34
+ "sqlite-vec": "^0.1.0"
35
+ }
36
+ }