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 +6 -3
- package/bin/cli.js +126 -2
- package/node_modules/@context-vault/core/package.json +1 -1
- package/package.json +2 -2
- package/src/linking.js +17 -28
- package/src/telemetry.js +4 -8
- package/src/temporal.js +4 -11
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
|
-
|
|
13
|
+
npm install -g context-vault
|
|
14
|
+
context-vault setup
|
|
14
15
|
```
|
|
15
16
|
|
|
16
|
-
|
|
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": "
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "context-vault",
|
|
3
|
-
"version": "3.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.
|
|
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
|
-
|
|
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
|
|
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)
|
|
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)
|
|
41
|
+
.all(likePattern);
|
|
50
42
|
} catch {
|
|
51
43
|
return [];
|
|
52
44
|
}
|
|
53
45
|
}
|
|
54
46
|
|
|
55
|
-
export function collectLinkedEntries(
|
|
56
|
-
|
|
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
|
|
50
|
+
const forwardIds = [];
|
|
62
51
|
for (const entry of primaryEntries) {
|
|
63
|
-
const ids = parseRelatedTo(entry.related_to
|
|
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
|
|
58
|
+
(e) => !primaryIds.has(e.id),
|
|
70
59
|
);
|
|
71
60
|
|
|
72
|
-
const backwardSeen = new Set
|
|
73
|
-
const backwardEntries
|
|
61
|
+
const backwardSeen = new Set();
|
|
62
|
+
const backwardEntries = [];
|
|
74
63
|
for (const entry of primaryEntries) {
|
|
75
|
-
const backlinks = resolveBacklinks(db, entry.id
|
|
64
|
+
const backlinks = resolveBacklinks(db, entry.id);
|
|
76
65
|
for (const bl of backlinks) {
|
|
77
|
-
if (!primaryIds.has(bl.id
|
|
78
|
-
backwardSeen.add(bl.id
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|