context-vault 3.1.1 → 3.1.3

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/README.md CHANGED
@@ -10,13 +10,16 @@ Persistent memory for AI agents — saves and searches knowledge across sessions
10
10
  ## Quick Start
11
11
 
12
12
  ```bash
13
- npx context-vault
13
+ npm install -g context-vault
14
+ context-vault setup
14
15
  ```
15
16
 
16
- One command — no global install required. Setup detects your AI tools (Claude Code, Codex, Claude Desktop, Cursor, Windsurf, Cline, and more), downloads the embedding model (~22MB), seeds your vault, and configures MCP.
17
+ Setup detects your AI tools (Claude Code, Codex, Claude Desktop, Cursor, Windsurf, Cline, and more), downloads the embedding model (~22MB), seeds your vault, and configures MCP.
17
18
 
18
19
  Then open your AI tool and try: **"Search my vault for getting started"**
19
20
 
21
+ > **Quick try without installing?** `npx context-vault` works too, but a global install is recommended for reliable MCP server startup.
22
+
20
23
  ## What It Does
21
24
 
22
25
  - **Save** — insights, decisions, patterns, contacts. Your AI agent writes them as you work.
@@ -73,7 +76,7 @@ Claude Code exposes shell hooks that fire on session events. context-vault integ
73
76
  "hooks": [
74
77
  {
75
78
  "type": "command",
76
- "command": "npx context-vault flush",
79
+ "command": "context-vault flush",
77
80
  "timeout": 10
78
81
  }
79
82
  ]
package/bin/cli.js CHANGED
@@ -379,6 +379,45 @@ async function runSetup() {
379
379
  green(` ✓ context-vault v${VERSION} is up to date`) +
380
380
  dim(` (vault: ${existingVault})`),
381
381
  );
382
+
383
+ // Check for stale tool configs using hardcoded node paths
384
+ const staleConfigs = findStaleToolConfigs();
385
+ if (staleConfigs.length > 0) {
386
+ console.log();
387
+ console.log(
388
+ yellow(` ! ${staleConfigs.length} tool config(s) using legacy hardcoded paths`),
389
+ );
390
+ for (const s of staleConfigs) {
391
+ console.log(dim(` ${s.name}: ${s.command}`));
392
+ }
393
+ console.log();
394
+ const fix = await prompt(
395
+ ` Auto-fix to use context-vault binary? (Y/n):`,
396
+ "Y",
397
+ );
398
+ if (fix.toLowerCase() !== "n") {
399
+ let customVaultDir = null;
400
+ try {
401
+ const cfg = JSON.parse(readFileSync(existingConfig, "utf-8"));
402
+ const defaultVDir = join(HOME, "vault");
403
+ if (cfg.vaultDir && resolve(cfg.vaultDir) !== resolve(defaultVDir)) {
404
+ customVaultDir = cfg.vaultDir;
405
+ }
406
+ } catch {}
407
+ for (const s of staleConfigs) {
408
+ try {
409
+ repairToolConfig(s, customVaultDir);
410
+ console.log(` ${green("+")} ${s.name} — fixed`);
411
+ } catch (e) {
412
+ console.log(` ${red("x")} ${s.name} — ${e.message}`);
413
+ }
414
+ }
415
+ console.log();
416
+ console.log(green(" ✓ Tool configs updated."));
417
+ console.log(dim(" Restart your AI tools to apply the changes."));
418
+ }
419
+ }
420
+
382
421
  console.log();
383
422
  return;
384
423
  }
@@ -1195,6 +1234,77 @@ async function configureCodex(tool, vaultDir) {
1195
1234
  }
1196
1235
  }
1197
1236
 
1237
+ function findStaleToolConfigs() {
1238
+ const stale = [];
1239
+
1240
+ // Check Claude Code (~/.claude.json)
1241
+ const claudePath = join(HOME, ".claude.json");
1242
+ if (existsSync(claudePath)) {
1243
+ try {
1244
+ const cfg = JSON.parse(readFileSync(claudePath, "utf-8"));
1245
+ const srv = cfg?.mcpServers?.["context-vault"];
1246
+ if (srv && srv.command !== "context-vault" && srv.command !== "npx") {
1247
+ stale.push({
1248
+ name: "Claude Code",
1249
+ id: "claude-code",
1250
+ configType: "cli",
1251
+ configPath: claudePath,
1252
+ command: [srv.command, ...(srv.args || [])].join(" "),
1253
+ });
1254
+ }
1255
+ } catch {}
1256
+ }
1257
+
1258
+ // Check JSON-configured tools
1259
+ for (const tool of TOOLS.filter((t) => t.configType === "json")) {
1260
+ const cfgPath = tool.configPath;
1261
+ if (!cfgPath || !existsSync(cfgPath)) continue;
1262
+ try {
1263
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
1264
+ const srv = cfg?.[tool.configKey]?.["context-vault"];
1265
+ if (srv && srv.command !== "context-vault" && srv.command !== "npx") {
1266
+ stale.push({
1267
+ name: tool.name,
1268
+ id: tool.id,
1269
+ configType: "json",
1270
+ configPath: cfgPath,
1271
+ configKey: tool.configKey,
1272
+ command: [srv.command, ...(srv.args || [])].join(" "),
1273
+ });
1274
+ }
1275
+ } catch {}
1276
+ }
1277
+
1278
+ return stale;
1279
+ }
1280
+
1281
+ function repairToolConfig(staleEntry, vaultDir) {
1282
+ const serverArgs = ["serve"];
1283
+ if (vaultDir) serverArgs.push("--vault-dir", vaultDir);
1284
+ const newConfig = { command: "context-vault", args: serverArgs };
1285
+
1286
+ if (staleEntry.id === "claude-code") {
1287
+ // Use `claude mcp remove` + `claude mcp add`
1288
+ try {
1289
+ execFileSync("claude", ["mcp", "remove", "context-vault"], {
1290
+ stdio: "pipe",
1291
+ });
1292
+ } catch {}
1293
+ execFileSync(
1294
+ "claude",
1295
+ ["mcp", "add", "context-vault", "--", "context-vault", ...serverArgs],
1296
+ { stdio: "pipe" },
1297
+ );
1298
+ return;
1299
+ }
1300
+
1301
+ // JSON config tools
1302
+ const cfgPath = staleEntry.configPath;
1303
+ const cfg = JSON.parse(readFileSync(cfgPath, "utf-8"));
1304
+ cfg[staleEntry.configKey]["context-vault"] = newConfig;
1305
+ writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + "\n");
1306
+ }
1307
+
1198
1308
  function configureJsonTool(tool, vaultDir) {
1199
1309
  const configPath = tool.configPath;
1200
1310
  const configDir = dirname(configPath);
@@ -4729,6 +4839,8 @@ async function runDoctor() {
4729
4839
  console.log(bold(" Tool Configurations"));
4730
4840
  let anyToolConfigured = false;
4731
4841
 
4842
+ const isStaleCmd = (cmd) => cmd !== "context-vault" && cmd !== "npx";
4843
+
4732
4844
  // Check Claude Code
4733
4845
  const claudeConfigPath = join(HOME, ".claude.json");
4734
4846
  if (existsSync(claudeConfigPath)) {
@@ -4738,7 +4850,13 @@ async function runDoctor() {
4738
4850
  if (servers["context-vault"]) {
4739
4851
  const srv = servers["context-vault"];
4740
4852
  const cmd = [srv.command, ...(srv.args || [])].join(" ");
4741
- console.log(` ${green("+")} Claude Code: ${dim(cmd)}`);
4853
+ if (isStaleCmd(srv.command)) {
4854
+ console.log(` ${yellow("!")} Claude Code: ${dim(cmd)}`);
4855
+ console.log(` ${dim("Fix: run context-vault setup to update")}`);
4856
+ allOk = false;
4857
+ } else {
4858
+ console.log(` ${green("+")} Claude Code: ${dim(cmd)}`);
4859
+ }
4742
4860
  anyToolConfigured = true;
4743
4861
  } else {
4744
4862
  console.log(` ${dim("-")} Claude Code: not configured`);
@@ -4764,7 +4882,13 @@ async function runDoctor() {
4764
4882
  if (servers["context-vault"]) {
4765
4883
  const srv = servers["context-vault"];
4766
4884
  const cmd = [srv.command, ...(srv.args || [])].join(" ");
4767
- console.log(` ${green("+")} ${tool.name}: ${dim(cmd)}`);
4885
+ if (isStaleCmd(srv.command)) {
4886
+ console.log(` ${yellow("!")} ${tool.name}: ${dim(cmd)}`);
4887
+ console.log(` ${dim("Fix: run context-vault setup to update")}`);
4888
+ allOk = false;
4889
+ } else {
4890
+ console.log(` ${green("+")} ${tool.name}: ${dim(cmd)}`);
4891
+ }
4768
4892
  anyToolConfigured = true;
4769
4893
  } else if (servers["context-mcp"]) {
4770
4894
  console.log(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-vault/core",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "type": "module",
5
5
  "description": "Pure local engine: capture, index, search, and utilities for context-vault",
6
6
  "main": "dist/main.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-vault",
3
- "version": "3.1.1",
3
+ "version": "3.1.3",
4
4
  "type": "module",
5
5
  "description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
6
6
  "bin": {
@@ -57,7 +57,7 @@
57
57
  "@context-vault/core"
58
58
  ],
59
59
  "dependencies": {
60
- "@context-vault/core": "^3.1.1",
60
+ "@context-vault/core": "^3.1.3",
61
61
  "@modelcontextprotocol/sdk": "^1.26.0",
62
62
  "adm-zip": "^0.5.16",
63
63
  "sqlite-vec": "^0.1.0"
package/src/linking.js CHANGED
@@ -1,20 +1,15 @@
1
- import type { DatabaseSync } from "node:sqlite";
2
-
3
- export function parseRelatedTo(raw: string | null | undefined): string[] {
1
+ export function parseRelatedTo(raw) {
4
2
  if (!raw) return [];
5
3
  try {
6
4
  const parsed = JSON.parse(raw);
7
5
  if (!Array.isArray(parsed)) return [];
8
- return parsed.filter((id: unknown) => typeof id === "string" && (id as string).trim());
6
+ return parsed.filter((id) => typeof id === "string" && id.trim());
9
7
  } catch {
10
8
  return [];
11
9
  }
12
10
  }
13
11
 
14
- export function resolveLinks(
15
- db: DatabaseSync,
16
- ids: string[],
17
- ): Record<string, unknown>[] {
12
+ export function resolveLinks(db, ids) {
18
13
  if (!ids.length) return [];
19
14
  const unique = [...new Set(ids)];
20
15
  const placeholders = unique.map(() => "?").join(",");
@@ -26,16 +21,13 @@ export function resolveLinks(
26
21
  AND (expires_at IS NULL OR expires_at > datetime('now'))
27
22
  AND superseded_by IS NULL`,
28
23
  )
29
- .all(...unique) as unknown as Record<string, unknown>[];
24
+ .all(...unique);
30
25
  } catch {
31
26
  return [];
32
27
  }
33
28
  }
34
29
 
35
- export function resolveBacklinks(
36
- db: DatabaseSync,
37
- entryId: string,
38
- ): Record<string, unknown>[] {
30
+ export function resolveBacklinks(db, entryId) {
39
31
  if (!entryId) return [];
40
32
  const likePattern = `%"${entryId}"%`;
41
33
  try {
@@ -46,36 +38,33 @@ export function resolveBacklinks(
46
38
  AND (expires_at IS NULL OR expires_at > datetime('now'))
47
39
  AND superseded_by IS NULL`,
48
40
  )
49
- .all(likePattern) as unknown as Record<string, unknown>[];
41
+ .all(likePattern);
50
42
  } catch {
51
43
  return [];
52
44
  }
53
45
  }
54
46
 
55
- export function collectLinkedEntries(
56
- db: DatabaseSync,
57
- primaryEntries: Record<string, unknown>[],
58
- ): { forward: Record<string, unknown>[]; backward: Record<string, unknown>[] } {
59
- const primaryIds = new Set(primaryEntries.map((e) => e.id as string));
47
+ export function collectLinkedEntries(db, primaryEntries) {
48
+ const primaryIds = new Set(primaryEntries.map((e) => e.id));
60
49
 
61
- const forwardIds: string[] = [];
50
+ const forwardIds = [];
62
51
  for (const entry of primaryEntries) {
63
- const ids = parseRelatedTo(entry.related_to as string);
52
+ const ids = parseRelatedTo(entry.related_to);
64
53
  for (const id of ids) {
65
54
  if (!primaryIds.has(id)) forwardIds.push(id);
66
55
  }
67
56
  }
68
57
  const forwardEntries = resolveLinks(db, forwardIds).filter(
69
- (e) => !primaryIds.has(e.id as string),
58
+ (e) => !primaryIds.has(e.id),
70
59
  );
71
60
 
72
- const backwardSeen = new Set<string>();
73
- const backwardEntries: Record<string, unknown>[] = [];
61
+ const backwardSeen = new Set();
62
+ const backwardEntries = [];
74
63
  for (const entry of primaryEntries) {
75
- const backlinks = resolveBacklinks(db, entry.id as string);
64
+ const backlinks = resolveBacklinks(db, entry.id);
76
65
  for (const bl of backlinks) {
77
- if (!primaryIds.has(bl.id as string) && !backwardSeen.has(bl.id as string)) {
78
- backwardSeen.add(bl.id as string);
66
+ if (!primaryIds.has(bl.id) && !backwardSeen.has(bl.id)) {
67
+ backwardSeen.add(bl.id);
79
68
  backwardEntries.push(bl);
80
69
  }
81
70
  }
@@ -84,7 +73,7 @@ export function collectLinkedEntries(
84
73
  return { forward: forwardEntries, backward: backwardEntries };
85
74
  }
86
75
 
87
- export function validateRelatedTo(relatedTo: unknown): string | null {
76
+ export function validateRelatedTo(relatedTo) {
88
77
  if (relatedTo === undefined || relatedTo === null) return null;
89
78
  if (!Array.isArray(relatedTo))
90
79
  return "related_to must be an array of entry IDs";
package/src/telemetry.js CHANGED
@@ -1,22 +1,18 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { API_URL, MARKETING_URL, GITHUB_ISSUES_URL } from "@context-vault/core/constants";
4
- import type { VaultConfig } from "@context-vault/core/types";
5
4
 
6
5
  const TELEMETRY_ENDPOINT = `${API_URL}/telemetry`;
7
6
  const NOTICE_MARKER = ".telemetry-notice-shown";
8
7
  const FEEDBACK_PROMPT_MARKER = ".feedback-prompt-shown";
9
8
 
10
- export function isTelemetryEnabled(config: VaultConfig | undefined): boolean {
9
+ export function isTelemetryEnabled(config) {
11
10
  const envVal = process.env.CONTEXT_VAULT_TELEMETRY;
12
11
  if (envVal !== undefined) return envVal === "1" || envVal === "true";
13
12
  return config?.telemetry === true;
14
13
  }
15
14
 
16
- export function sendTelemetryEvent(
17
- config: VaultConfig | undefined,
18
- payload: { event: string; code?: string | null; tool?: string | null; cv_version: string },
19
- ): void {
15
+ export function sendTelemetryEvent(config, payload) {
20
16
  if (!isTelemetryEnabled(config)) return;
21
17
 
22
18
  const event = {
@@ -38,7 +34,7 @@ export function sendTelemetryEvent(
38
34
  }).catch(() => {});
39
35
  }
40
36
 
41
- export function maybeShowTelemetryNotice(dataDir: string): void {
37
+ export function maybeShowTelemetryNotice(dataDir) {
42
38
  try {
43
39
  const markerPath = join(dataDir, NOTICE_MARKER);
44
40
  if (existsSync(markerPath)) return;
@@ -60,7 +56,7 @@ export function maybeShowTelemetryNotice(dataDir: string): void {
60
56
  }
61
57
  }
62
58
 
63
- export function maybeShowFeedbackPrompt(dataDir: string): void {
59
+ export function maybeShowFeedbackPrompt(dataDir) {
64
60
  try {
65
61
  const markerPath = join(dataDir, FEEDBACK_PROMPT_MARKER);
66
62
  if (existsSync(markerPath)) return;
package/src/temporal.js CHANGED
@@ -1,16 +1,12 @@
1
1
  const SHORTCUT_RE = /^last[_ ](\d+)[_ ](day|days|week|weeks|month|months)$/i;
2
2
 
3
- function startOfToday(now: Date): Date {
3
+ function startOfToday(now) {
4
4
  const d = new Date(now);
5
5
  d.setUTCHours(0, 0, 0, 0);
6
6
  return d;
7
7
  }
8
8
 
9
- export function resolveTemporalShortcut(
10
- role: "since" | "until",
11
- value: string,
12
- now: Date = new Date(),
13
- ): string {
9
+ export function resolveTemporalShortcut(role, value, now = new Date()) {
14
10
  if (!value || typeof value !== "string") return value;
15
11
  const trimmed = value.trim().toLowerCase().replace(/\s+/g, "_");
16
12
 
@@ -57,7 +53,7 @@ export function resolveTemporalShortcut(
57
53
  if (m) {
58
54
  const n = parseInt(m[1], 10);
59
55
  const unit = m[2].replace(/s$/, "");
60
- let ms: number;
56
+ let ms;
61
57
  if (unit === "day") ms = n * 86400000;
62
58
  else if (unit === "week") ms = n * 7 * 86400000;
63
59
  else ms = n * 30 * 86400000;
@@ -69,10 +65,7 @@ export function resolveTemporalShortcut(
69
65
  return value;
70
66
  }
71
67
 
72
- export function resolveTemporalParams(
73
- params: { since?: string; until?: string },
74
- now: Date = new Date(),
75
- ): { since: string | undefined; until: string | undefined } {
68
+ export function resolveTemporalParams(params, now = new Date()) {
76
69
  let { since, until } = params;
77
70
 
78
71
  if (