context-vault 2.7.1 → 2.8.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/bin/cli.js +352 -168
- package/node_modules/@context-vault/core/package.json +1 -1
- package/node_modules/@context-vault/core/src/capture/import-pipeline.js +11 -16
- package/node_modules/@context-vault/core/src/capture/index.js +57 -15
- package/node_modules/@context-vault/core/src/index/index.js +206 -52
- package/node_modules/@context-vault/core/src/server/tools/ingest-url.js +29 -11
- package/node_modules/@context-vault/core/src/server/tools/save-context.js +154 -30
- package/node_modules/@context-vault/core/src/server/tools/submit-feedback.js +24 -18
- package/node_modules/@context-vault/core/src/sync/sync.js +24 -19
- package/package.json +2 -2
- package/scripts/local-server.js +305 -93
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
// Node.js version guard — must run before any ESM imports
|
|
4
|
+
const nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
|
|
5
|
+
if (nodeVersion < 20) {
|
|
6
|
+
process.stderr.write(
|
|
7
|
+
`\ncontext-vault requires Node.js >= 20 (you have ${process.versions.node}).\n` +
|
|
8
|
+
`Install a newer version: https://nodejs.org/\n\n`,
|
|
9
|
+
);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
3
13
|
/**
|
|
4
14
|
* context-vault CLI — Unified entry point
|
|
5
15
|
*
|
|
@@ -21,7 +31,7 @@ import {
|
|
|
21
31
|
} from "node:fs";
|
|
22
32
|
import { join, resolve, dirname } from "node:path";
|
|
23
33
|
import { homedir, platform } from "node:os";
|
|
24
|
-
import { execSync, fork } from "node:child_process";
|
|
34
|
+
import { execSync, execFile, fork } from "node:child_process";
|
|
25
35
|
import { fileURLToPath } from "node:url";
|
|
26
36
|
import { createServer as createNetServer } from "node:net";
|
|
27
37
|
|
|
@@ -106,14 +116,11 @@ function vscodeDataDir() {
|
|
|
106
116
|
|
|
107
117
|
// ─── Tool Detection ──────────────────────────────────────────────────────────
|
|
108
118
|
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
} catch {
|
|
115
|
-
return false;
|
|
116
|
-
}
|
|
119
|
+
function commandExistsAsync(bin) {
|
|
120
|
+
const cmd = PLATFORM === "win32" ? "where" : "which";
|
|
121
|
+
return new Promise((resolve) => {
|
|
122
|
+
execFile(cmd, [bin], { timeout: 5000 }, (err) => resolve(!err));
|
|
123
|
+
});
|
|
117
124
|
}
|
|
118
125
|
|
|
119
126
|
/** Check if a directory exists at any of the given paths */
|
|
@@ -125,13 +132,13 @@ const TOOLS = [
|
|
|
125
132
|
{
|
|
126
133
|
id: "claude-code",
|
|
127
134
|
name: "Claude Code",
|
|
128
|
-
detect: () =>
|
|
135
|
+
detect: () => commandExistsAsync("claude"),
|
|
129
136
|
configType: "cli",
|
|
130
137
|
},
|
|
131
138
|
{
|
|
132
139
|
id: "codex",
|
|
133
140
|
name: "Codex",
|
|
134
|
-
detect: () =>
|
|
141
|
+
detect: () => commandExistsAsync("codex"),
|
|
135
142
|
configType: "cli",
|
|
136
143
|
},
|
|
137
144
|
{
|
|
@@ -145,7 +152,8 @@ const TOOLS = [
|
|
|
145
152
|
{
|
|
146
153
|
id: "cursor",
|
|
147
154
|
name: "Cursor",
|
|
148
|
-
detect: () =>
|
|
155
|
+
detect: () =>
|
|
156
|
+
anyDirExists(join(HOME, ".cursor"), join(appDataDir(), "Cursor")),
|
|
149
157
|
configType: "json",
|
|
150
158
|
configPath: join(HOME, ".cursor", "mcp.json"),
|
|
151
159
|
configKey: "mcpServers",
|
|
@@ -153,10 +161,8 @@ const TOOLS = [
|
|
|
153
161
|
{
|
|
154
162
|
id: "windsurf",
|
|
155
163
|
name: "Windsurf",
|
|
156
|
-
detect: () =>
|
|
157
|
-
join(HOME, ".codeium", "windsurf"),
|
|
158
|
-
join(HOME, ".windsurf"),
|
|
159
|
-
),
|
|
164
|
+
detect: () =>
|
|
165
|
+
anyDirExists(join(HOME, ".codeium", "windsurf"), join(HOME, ".windsurf")),
|
|
160
166
|
configType: "json",
|
|
161
167
|
configPath: join(HOME, ".codeium", "windsurf", "mcp_config.json"),
|
|
162
168
|
configKey: "mcpServers",
|
|
@@ -164,10 +170,8 @@ const TOOLS = [
|
|
|
164
170
|
{
|
|
165
171
|
id: "antigravity",
|
|
166
172
|
name: "Antigravity (Gemini CLI)",
|
|
167
|
-
detect: () =>
|
|
168
|
-
join(HOME, ".gemini", "antigravity"),
|
|
169
|
-
join(HOME, ".gemini"),
|
|
170
|
-
),
|
|
173
|
+
detect: () =>
|
|
174
|
+
anyDirExists(join(HOME, ".gemini", "antigravity"), join(HOME, ".gemini")),
|
|
171
175
|
configType: "json",
|
|
172
176
|
configPath: join(HOME, ".gemini", "antigravity", "mcp_config.json"),
|
|
173
177
|
configKey: "mcpServers",
|
|
@@ -182,7 +186,7 @@ const TOOLS = [
|
|
|
182
186
|
vscodeDataDir(),
|
|
183
187
|
"saoudrizwan.claude-dev",
|
|
184
188
|
"settings",
|
|
185
|
-
"cline_mcp_settings.json"
|
|
189
|
+
"cline_mcp_settings.json",
|
|
186
190
|
),
|
|
187
191
|
configKey: "mcpServers",
|
|
188
192
|
},
|
|
@@ -190,18 +194,43 @@ const TOOLS = [
|
|
|
190
194
|
id: "roo-code",
|
|
191
195
|
name: "Roo Code (VS Code)",
|
|
192
196
|
detect: () =>
|
|
193
|
-
existsSync(
|
|
197
|
+
existsSync(
|
|
198
|
+
join(vscodeDataDir(), "rooveterinaryinc.roo-cline", "settings"),
|
|
199
|
+
),
|
|
194
200
|
configType: "json",
|
|
195
201
|
configPath: join(
|
|
196
202
|
vscodeDataDir(),
|
|
197
203
|
"rooveterinaryinc.roo-cline",
|
|
198
204
|
"settings",
|
|
199
|
-
"cline_mcp_settings.json"
|
|
205
|
+
"cline_mcp_settings.json",
|
|
200
206
|
),
|
|
201
207
|
configKey: "mcpServers",
|
|
202
208
|
},
|
|
203
209
|
];
|
|
204
210
|
|
|
211
|
+
/** Detect all tools in parallel. Returns { detected: Tool[], results: { tool, found }[] } */
|
|
212
|
+
async function detectAllTools() {
|
|
213
|
+
const results = await Promise.all(
|
|
214
|
+
TOOLS.map(async (tool) => {
|
|
215
|
+
const found = await tool.detect();
|
|
216
|
+
return { tool, found };
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
const detected = results.filter((r) => r.found).map((r) => r.tool);
|
|
220
|
+
return { detected, results };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Print tool detection results in deterministic TOOLS order */
|
|
224
|
+
function printDetectionResults(results) {
|
|
225
|
+
for (const { tool, found } of results) {
|
|
226
|
+
if (found) {
|
|
227
|
+
console.log(` ${green("+")} ${tool.name}`);
|
|
228
|
+
} else {
|
|
229
|
+
console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
205
234
|
// ─── Help ────────────────────────────────────────────────────────────────────
|
|
206
235
|
|
|
207
236
|
function showHelp() {
|
|
@@ -239,6 +268,8 @@ ${bold("Options:")}
|
|
|
239
268
|
// ─── Setup Command ───────────────────────────────────────────────────────────
|
|
240
269
|
|
|
241
270
|
async function runSetup() {
|
|
271
|
+
const setupStart = Date.now();
|
|
272
|
+
|
|
242
273
|
// Banner
|
|
243
274
|
console.log();
|
|
244
275
|
console.log(` ${bold("◇ context-vault")} ${dim(`v${VERSION}`)}`);
|
|
@@ -273,16 +304,8 @@ async function runSetup() {
|
|
|
273
304
|
// Skip vault setup, just reconfigure tools
|
|
274
305
|
console.log();
|
|
275
306
|
console.log(dim(` [1/2]`) + bold(" Detecting tools...\n"));
|
|
276
|
-
const detected =
|
|
277
|
-
|
|
278
|
-
const found = tool.detect();
|
|
279
|
-
if (found) {
|
|
280
|
-
detected.push(tool);
|
|
281
|
-
console.log(` ${green("+")} ${tool.name}`);
|
|
282
|
-
} else {
|
|
283
|
-
console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
307
|
+
const { detected, results: detectionResults } = await detectAllTools();
|
|
308
|
+
printDetectionResults(detectionResults);
|
|
286
309
|
console.log();
|
|
287
310
|
|
|
288
311
|
if (detected.length === 0) {
|
|
@@ -298,12 +321,15 @@ async function runSetup() {
|
|
|
298
321
|
console.log();
|
|
299
322
|
const answer = await prompt(
|
|
300
323
|
` Select (${dim("1,2,3")} or ${dim('"all"')}):`,
|
|
301
|
-
"all"
|
|
324
|
+
"all",
|
|
302
325
|
);
|
|
303
326
|
if (answer === "all" || answer === "") {
|
|
304
327
|
selected = detected;
|
|
305
328
|
} else {
|
|
306
|
-
const nums = answer
|
|
329
|
+
const nums = answer
|
|
330
|
+
.split(/[,\s]+/)
|
|
331
|
+
.map((n) => parseInt(n, 10) - 1)
|
|
332
|
+
.filter((n) => n >= 0 && n < detected.length);
|
|
307
333
|
selected = nums.map((n) => detected[n]);
|
|
308
334
|
if (selected.length === 0) selected = detected;
|
|
309
335
|
}
|
|
@@ -345,22 +371,12 @@ async function runSetup() {
|
|
|
345
371
|
|
|
346
372
|
// Detect tools
|
|
347
373
|
console.log(dim(` [1/5]`) + bold(" Detecting tools...\n"));
|
|
348
|
-
const detected =
|
|
349
|
-
|
|
350
|
-
const found = tool.detect();
|
|
351
|
-
if (found) {
|
|
352
|
-
detected.push(tool);
|
|
353
|
-
console.log(` ${green("+")} ${tool.name}`);
|
|
354
|
-
} else {
|
|
355
|
-
console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
374
|
+
const { detected, results: detectionResults } = await detectAllTools();
|
|
375
|
+
printDetectionResults(detectionResults);
|
|
358
376
|
console.log();
|
|
359
377
|
|
|
360
378
|
if (detected.length === 0) {
|
|
361
|
-
console.log(
|
|
362
|
-
yellow(" No supported tools detected.\n")
|
|
363
|
-
);
|
|
379
|
+
console.log(yellow(" No supported tools detected.\n"));
|
|
364
380
|
console.log(" To manually configure, add to your tool's MCP config:\n");
|
|
365
381
|
if (isInstalledPackage()) {
|
|
366
382
|
console.log(` ${dim("{")}
|
|
@@ -381,7 +397,15 @@ async function runSetup() {
|
|
|
381
397
|
${dim("}")}
|
|
382
398
|
${dim("}")}\n`);
|
|
383
399
|
}
|
|
384
|
-
|
|
400
|
+
|
|
401
|
+
// In non-interactive mode, continue setup without tools (vault, config, etc.)
|
|
402
|
+
if (isNonInteractive) {
|
|
403
|
+
console.log(
|
|
404
|
+
dim(" Continuing setup without tool configuration (--yes mode).\n"),
|
|
405
|
+
);
|
|
406
|
+
} else {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
385
409
|
}
|
|
386
410
|
|
|
387
411
|
// Select tools
|
|
@@ -396,7 +420,7 @@ async function runSetup() {
|
|
|
396
420
|
console.log();
|
|
397
421
|
const answer = await prompt(
|
|
398
422
|
` Select (${dim("1,2,3")} or ${dim('"all"')}):`,
|
|
399
|
-
"all"
|
|
423
|
+
"all",
|
|
400
424
|
);
|
|
401
425
|
if (answer === "all" || answer === "") {
|
|
402
426
|
selected = detected;
|
|
@@ -422,13 +446,11 @@ async function runSetup() {
|
|
|
422
446
|
if (!existsSync(resolvedVaultDir)) {
|
|
423
447
|
if (isNonInteractive) {
|
|
424
448
|
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
425
|
-
console.log(
|
|
426
|
-
`\n ${green("+")} Created ${resolvedVaultDir}`
|
|
427
|
-
);
|
|
449
|
+
console.log(`\n ${green("+")} Created ${resolvedVaultDir}`);
|
|
428
450
|
} else {
|
|
429
451
|
const create = await prompt(
|
|
430
452
|
`\n ${resolvedVaultDir} doesn't exist. Create it? (Y/n):`,
|
|
431
|
-
"Y"
|
|
453
|
+
"Y",
|
|
432
454
|
);
|
|
433
455
|
if (create.toLowerCase() !== "n") {
|
|
434
456
|
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
@@ -459,11 +481,21 @@ async function runSetup() {
|
|
|
459
481
|
// Pre-download embedding model with spinner (skip with --skip-embeddings)
|
|
460
482
|
const skipEmbeddings = flags.has("--skip-embeddings");
|
|
461
483
|
if (skipEmbeddings) {
|
|
462
|
-
console.log(
|
|
463
|
-
|
|
464
|
-
|
|
484
|
+
console.log(
|
|
485
|
+
`\n ${dim("[3/5]")}${bold(" Embedding model")} ${dim("(skipped)")}`,
|
|
486
|
+
);
|
|
487
|
+
console.log(
|
|
488
|
+
dim(
|
|
489
|
+
" FTS-only mode — full-text search works, semantic search disabled.",
|
|
490
|
+
),
|
|
491
|
+
);
|
|
492
|
+
console.log(
|
|
493
|
+
dim(" To enable later: context-vault setup (without --skip-embeddings)"),
|
|
494
|
+
);
|
|
465
495
|
} else {
|
|
466
|
-
console.log(
|
|
496
|
+
console.log(
|
|
497
|
+
`\n ${dim("[3/5]")}${bold(" Downloading embedding model...")}`,
|
|
498
|
+
);
|
|
467
499
|
console.log(dim(" all-MiniLM-L6-v2 (~22MB, one-time download)\n"));
|
|
468
500
|
{
|
|
469
501
|
const spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
|
|
@@ -471,7 +503,9 @@ async function runSetup() {
|
|
|
471
503
|
const start = Date.now();
|
|
472
504
|
const spinner = setInterval(() => {
|
|
473
505
|
const elapsed = ((Date.now() - start) / 1000).toFixed(0);
|
|
474
|
-
process.stdout.write(
|
|
506
|
+
process.stdout.write(
|
|
507
|
+
`\r ${spinnerFrames[frame++ % spinnerFrames.length]} Downloading... ${dim(`${elapsed}s`)}`,
|
|
508
|
+
);
|
|
475
509
|
}, 100);
|
|
476
510
|
|
|
477
511
|
try {
|
|
@@ -479,12 +513,29 @@ async function runSetup() {
|
|
|
479
513
|
await embed("warmup");
|
|
480
514
|
|
|
481
515
|
clearInterval(spinner);
|
|
482
|
-
process.stdout.write(
|
|
516
|
+
process.stdout.write(
|
|
517
|
+
`\r ${green("+")} Embedding model ready \n`,
|
|
518
|
+
);
|
|
483
519
|
} catch (e) {
|
|
484
520
|
clearInterval(spinner);
|
|
485
|
-
|
|
521
|
+
const code = e.code || e.cause?.code || "";
|
|
522
|
+
const isNetwork = [
|
|
523
|
+
"ENOTFOUND",
|
|
524
|
+
"ETIMEDOUT",
|
|
525
|
+
"ECONNREFUSED",
|
|
526
|
+
"ECONNRESET",
|
|
527
|
+
"ERR_SOCKET_TIMEOUT",
|
|
528
|
+
].includes(code);
|
|
529
|
+
process.stdout.write(
|
|
530
|
+
`\r ${yellow("!")} Model download failed: ${e.message} \n`,
|
|
531
|
+
);
|
|
532
|
+
if (isNetwork) {
|
|
533
|
+
console.log(dim(` Check your internet connection and try again.`));
|
|
534
|
+
}
|
|
486
535
|
console.log(dim(` Retry: context-vault setup`));
|
|
487
|
-
console.log(
|
|
536
|
+
console.log(
|
|
537
|
+
dim(` Semantic search disabled — full-text search still works.`),
|
|
538
|
+
);
|
|
488
539
|
}
|
|
489
540
|
}
|
|
490
541
|
}
|
|
@@ -502,7 +553,8 @@ async function runSetup() {
|
|
|
502
553
|
console.log(`\n ${dim("[4/5]")}${bold(" Configuring tools...\n")}`);
|
|
503
554
|
const results = [];
|
|
504
555
|
const defaultVDir = join(HOME, "vault");
|
|
505
|
-
const customVaultDir =
|
|
556
|
+
const customVaultDir =
|
|
557
|
+
resolvedVaultDir !== resolve(defaultVDir) ? resolvedVaultDir : null;
|
|
506
558
|
|
|
507
559
|
for (const tool of selected) {
|
|
508
560
|
try {
|
|
@@ -524,16 +576,15 @@ async function runSetup() {
|
|
|
524
576
|
// Seed entry
|
|
525
577
|
const seeded = createSeedEntries(resolvedVaultDir);
|
|
526
578
|
if (seeded > 0) {
|
|
527
|
-
console.log(
|
|
579
|
+
console.log(
|
|
580
|
+
`\n ${green("+")} Created ${seeded} starter ${seeded === 1 ? "entry" : "entries"} in vault`,
|
|
581
|
+
);
|
|
528
582
|
}
|
|
529
583
|
|
|
530
584
|
// Offer to launch UI
|
|
531
585
|
console.log();
|
|
532
586
|
if (!isNonInteractive) {
|
|
533
|
-
const launchUi = await prompt(
|
|
534
|
-
` Launch web dashboard? (y/N):`,
|
|
535
|
-
"N"
|
|
536
|
-
);
|
|
587
|
+
const launchUi = await prompt(` Launch web dashboard? (y/N):`, "N");
|
|
537
588
|
if (launchUi.toLowerCase() === "y") {
|
|
538
589
|
console.log();
|
|
539
590
|
runUi();
|
|
@@ -544,9 +595,21 @@ async function runSetup() {
|
|
|
544
595
|
// Health check
|
|
545
596
|
console.log(`\n ${dim("[5/5]")}${bold(" Health check...")}\n`);
|
|
546
597
|
const okResults = results.filter((r) => r.ok);
|
|
598
|
+
|
|
599
|
+
// Verify DB is accessible
|
|
600
|
+
let dbAccessible = false;
|
|
601
|
+
try {
|
|
602
|
+
const { initDatabase } = await import("@context-vault/core/index/db");
|
|
603
|
+
const db = await initDatabase(vaultConfig.dbPath);
|
|
604
|
+
db.prepare("SELECT 1").get();
|
|
605
|
+
db.close();
|
|
606
|
+
dbAccessible = true;
|
|
607
|
+
} catch {}
|
|
608
|
+
|
|
547
609
|
const checks = [
|
|
548
610
|
{ label: "Vault directory exists", pass: existsSync(resolvedVaultDir) },
|
|
549
611
|
{ label: "Config file written", pass: existsSync(configPath) },
|
|
612
|
+
{ label: "Database accessible", pass: dbAccessible },
|
|
550
613
|
{ label: "At least one tool configured", pass: okResults.length > 0 },
|
|
551
614
|
];
|
|
552
615
|
const passed = checks.filter((c) => c.pass).length;
|
|
@@ -555,9 +618,10 @@ async function runSetup() {
|
|
|
555
618
|
}
|
|
556
619
|
|
|
557
620
|
// Completion box
|
|
621
|
+
const elapsed = ((Date.now() - setupStart) / 1000).toFixed(1);
|
|
558
622
|
const toolName = okResults.length ? okResults[0].tool.name : "your AI tool";
|
|
559
623
|
const boxLines = [
|
|
560
|
-
` ✓ Setup complete — ${passed}/${checks.length} checks passed`,
|
|
624
|
+
` ✓ Setup complete — ${passed}/${checks.length} checks passed (${elapsed}s)`,
|
|
561
625
|
``,
|
|
562
626
|
` ${bold("AI Tools")} — open ${toolName} and try:`,
|
|
563
627
|
` "Search my vault for getting started"`,
|
|
@@ -599,14 +663,14 @@ async function configureClaude(tool, vaultDir) {
|
|
|
599
663
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
600
664
|
execSync(
|
|
601
665
|
`claude mcp add -s user context-vault -- context-vault ${cmdArgs.join(" ")}`,
|
|
602
|
-
{ stdio: "pipe", env }
|
|
666
|
+
{ stdio: "pipe", env },
|
|
603
667
|
);
|
|
604
668
|
} else {
|
|
605
669
|
const cmdArgs = [`"${SERVER_PATH}"`];
|
|
606
670
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
607
671
|
execSync(
|
|
608
672
|
`claude mcp add -s user context-vault -- node ${cmdArgs.join(" ")}`,
|
|
609
|
-
{ stdio: "pipe", env }
|
|
673
|
+
{ stdio: "pipe", env },
|
|
610
674
|
);
|
|
611
675
|
}
|
|
612
676
|
} catch (e) {
|
|
@@ -631,15 +695,14 @@ async function configureCodex(tool, vaultDir) {
|
|
|
631
695
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
632
696
|
execSync(
|
|
633
697
|
`codex mcp add context-vault -- context-vault ${cmdArgs.join(" ")}`,
|
|
634
|
-
{ stdio: "pipe" }
|
|
698
|
+
{ stdio: "pipe" },
|
|
635
699
|
);
|
|
636
700
|
} else {
|
|
637
701
|
const cmdArgs = [`"${SERVER_PATH}"`];
|
|
638
702
|
if (vaultDir) cmdArgs.push("--vault-dir", `"${vaultDir}"`);
|
|
639
|
-
execSync(
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
);
|
|
703
|
+
execSync(`codex mcp add context-vault -- node ${cmdArgs.join(" ")}`, {
|
|
704
|
+
stdio: "pipe",
|
|
705
|
+
});
|
|
643
706
|
}
|
|
644
707
|
} catch (e) {
|
|
645
708
|
const stderr = e.stderr?.toString().trim();
|
|
@@ -706,7 +769,9 @@ function createSeedEntries(vaultDir) {
|
|
|
706
769
|
mkdirSync(insightDir, { recursive: true });
|
|
707
770
|
const id1 = Date.now().toString(36).toUpperCase().padStart(10, "0");
|
|
708
771
|
const now = new Date().toISOString();
|
|
709
|
-
writeFileSync(
|
|
772
|
+
writeFileSync(
|
|
773
|
+
insightPath,
|
|
774
|
+
`---
|
|
710
775
|
id: ${id1}
|
|
711
776
|
tags: ["getting-started", "vault"]
|
|
712
777
|
source: context-vault-setup
|
|
@@ -725,7 +790,8 @@ AI agents search it using hybrid full-text + semantic search.
|
|
|
725
790
|
|
|
726
791
|
You can edit or delete this file anytime — it lives at:
|
|
727
792
|
${insightPath}
|
|
728
|
-
|
|
793
|
+
`,
|
|
794
|
+
);
|
|
729
795
|
created++;
|
|
730
796
|
}
|
|
731
797
|
|
|
@@ -736,7 +802,9 @@ ${insightPath}
|
|
|
736
802
|
mkdirSync(decisionDir, { recursive: true });
|
|
737
803
|
const id2 = (Date.now() + 1).toString(36).toUpperCase().padStart(10, "0");
|
|
738
804
|
const now = new Date().toISOString();
|
|
739
|
-
writeFileSync(
|
|
805
|
+
writeFileSync(
|
|
806
|
+
decisionPath,
|
|
807
|
+
`---
|
|
740
808
|
id: ${id2}
|
|
741
809
|
tags: ["example", "architecture"]
|
|
742
810
|
source: context-vault-setup
|
|
@@ -754,7 +822,8 @@ source of truth with a SQLite index for fast search.
|
|
|
754
822
|
- Con: No built-in sync across devices (use git or Syncthing)
|
|
755
823
|
|
|
756
824
|
This is an example entry showing the decision format. Feel free to delete it.
|
|
757
|
-
|
|
825
|
+
`,
|
|
826
|
+
);
|
|
758
827
|
created++;
|
|
759
828
|
}
|
|
760
829
|
|
|
@@ -774,7 +843,9 @@ async function runConnect() {
|
|
|
774
843
|
console.log(` context-vault connect --key cv_...\n`);
|
|
775
844
|
console.log(` Options:`);
|
|
776
845
|
console.log(` --key <key> API key (required)`);
|
|
777
|
-
console.log(
|
|
846
|
+
console.log(
|
|
847
|
+
` --url <url> Hosted server URL (default: https://api.context-vault.com)`,
|
|
848
|
+
);
|
|
778
849
|
console.log();
|
|
779
850
|
return;
|
|
780
851
|
}
|
|
@@ -810,10 +881,17 @@ async function runConnect() {
|
|
|
810
881
|
user = await response.json();
|
|
811
882
|
console.log(` ${green("+")} Verified — ${user.email} (${user.tier})\n`);
|
|
812
883
|
} catch (e) {
|
|
813
|
-
if (
|
|
884
|
+
if (
|
|
885
|
+
e.code === "ECONNREFUSED" ||
|
|
886
|
+
e.code === "ENOTFOUND" ||
|
|
887
|
+
e.cause?.code === "ECONNREFUSED" ||
|
|
888
|
+
e.cause?.code === "ENOTFOUND"
|
|
889
|
+
) {
|
|
814
890
|
console.error(`\n ${red("Cannot reach server.")}`);
|
|
815
891
|
console.error(dim(` URL: ${hostedUrl}`));
|
|
816
|
-
console.error(
|
|
892
|
+
console.error(
|
|
893
|
+
dim(` Check your internet connection or try --url <url>\n`),
|
|
894
|
+
);
|
|
817
895
|
} else if (e.message?.includes("Invalid or expired")) {
|
|
818
896
|
// Already handled above
|
|
819
897
|
} else {
|
|
@@ -826,29 +904,31 @@ async function runConnect() {
|
|
|
826
904
|
|
|
827
905
|
// Detect tools
|
|
828
906
|
console.log(dim(` [1/2]`) + bold(" Detecting tools...\n"));
|
|
829
|
-
const detected =
|
|
830
|
-
|
|
831
|
-
const found = tool.detect();
|
|
832
|
-
if (found) {
|
|
833
|
-
detected.push(tool);
|
|
834
|
-
console.log(` ${green("+")} ${tool.name}`);
|
|
835
|
-
} else {
|
|
836
|
-
console.log(` ${dim("-")} ${dim(tool.name)} ${dim("(not found)")}`);
|
|
837
|
-
}
|
|
838
|
-
}
|
|
907
|
+
const { detected, results: connectDetectionResults } = await detectAllTools();
|
|
908
|
+
printDetectionResults(connectDetectionResults);
|
|
839
909
|
console.log();
|
|
840
910
|
|
|
841
911
|
if (detected.length === 0) {
|
|
842
912
|
console.log(yellow(" No supported tools detected."));
|
|
843
913
|
console.log(`\n Add this to your tool's MCP config manually:\n`);
|
|
844
|
-
console.log(
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
914
|
+
console.log(
|
|
915
|
+
dim(
|
|
916
|
+
` ${JSON.stringify(
|
|
917
|
+
{
|
|
918
|
+
mcpServers: {
|
|
919
|
+
"context-vault": {
|
|
920
|
+
url: `${hostedUrl}/mcp`,
|
|
921
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
},
|
|
925
|
+
null,
|
|
926
|
+
2,
|
|
927
|
+
)
|
|
928
|
+
.split("\n")
|
|
929
|
+
.join("\n ")}`,
|
|
930
|
+
),
|
|
931
|
+
);
|
|
852
932
|
console.log();
|
|
853
933
|
return;
|
|
854
934
|
}
|
|
@@ -865,12 +945,15 @@ async function runConnect() {
|
|
|
865
945
|
console.log();
|
|
866
946
|
const answer = await prompt(
|
|
867
947
|
` Select (${dim("1,2,3")} or ${dim('"all"')}):`,
|
|
868
|
-
"all"
|
|
948
|
+
"all",
|
|
869
949
|
);
|
|
870
950
|
if (answer === "all" || answer === "") {
|
|
871
951
|
selected = detected;
|
|
872
952
|
} else {
|
|
873
|
-
const nums = answer
|
|
953
|
+
const nums = answer
|
|
954
|
+
.split(/[,\s]+/)
|
|
955
|
+
.map((n) => parseInt(n, 10) - 1)
|
|
956
|
+
.filter((n) => n >= 0 && n < detected.length);
|
|
874
957
|
selected = nums.map((n) => detected[n]);
|
|
875
958
|
if (selected.length === 0) selected = detected;
|
|
876
959
|
}
|
|
@@ -894,7 +977,9 @@ async function runConnect() {
|
|
|
894
977
|
}
|
|
895
978
|
|
|
896
979
|
console.log();
|
|
897
|
-
console.log(
|
|
980
|
+
console.log(
|
|
981
|
+
green(" ✓ Connected! Your AI tools can now access your hosted vault."),
|
|
982
|
+
);
|
|
898
983
|
console.log(dim(` Endpoint: ${hostedUrl}/mcp`));
|
|
899
984
|
console.log();
|
|
900
985
|
}
|
|
@@ -903,13 +988,17 @@ function configureClaudeHosted(apiKey, hostedUrl) {
|
|
|
903
988
|
const env = { ...process.env };
|
|
904
989
|
delete env.CLAUDECODE;
|
|
905
990
|
|
|
906
|
-
try {
|
|
907
|
-
|
|
991
|
+
try {
|
|
992
|
+
execSync("claude mcp remove context-mcp -s user", { stdio: "pipe", env });
|
|
993
|
+
} catch {}
|
|
994
|
+
try {
|
|
995
|
+
execSync("claude mcp remove context-vault -s user", { stdio: "pipe", env });
|
|
996
|
+
} catch {}
|
|
908
997
|
|
|
909
998
|
try {
|
|
910
999
|
execSync(
|
|
911
1000
|
`claude mcp add -s user --transport http context-vault ${hostedUrl}/mcp`,
|
|
912
|
-
{ stdio: "pipe", env }
|
|
1001
|
+
{ stdio: "pipe", env },
|
|
913
1002
|
);
|
|
914
1003
|
} catch (e) {
|
|
915
1004
|
const stderr = e.stderr?.toString().trim();
|
|
@@ -918,14 +1007,17 @@ function configureClaudeHosted(apiKey, hostedUrl) {
|
|
|
918
1007
|
}
|
|
919
1008
|
|
|
920
1009
|
function configureCodexHosted(apiKey, hostedUrl) {
|
|
921
|
-
try {
|
|
922
|
-
|
|
1010
|
+
try {
|
|
1011
|
+
execSync("codex mcp remove context-mcp", { stdio: "pipe" });
|
|
1012
|
+
} catch {}
|
|
1013
|
+
try {
|
|
1014
|
+
execSync("codex mcp remove context-vault", { stdio: "pipe" });
|
|
1015
|
+
} catch {}
|
|
923
1016
|
|
|
924
1017
|
try {
|
|
925
|
-
execSync(
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
);
|
|
1018
|
+
execSync(`codex mcp add --transport http context-vault ${hostedUrl}/mcp`, {
|
|
1019
|
+
stdio: "pipe",
|
|
1020
|
+
});
|
|
929
1021
|
} catch (e) {
|
|
930
1022
|
const stderr = e.stderr?.toString().trim();
|
|
931
1023
|
throw new Error(stderr || e.message);
|
|
@@ -975,14 +1067,18 @@ function runUi() {
|
|
|
975
1067
|
// Try bundled path first (npm install), then workspace path (local dev)
|
|
976
1068
|
const bundledDist = resolve(ROOT, "app-dist");
|
|
977
1069
|
const workspaceDist = resolve(ROOT, "..", "app", "dist");
|
|
978
|
-
const appDist = existsSync(join(bundledDist, "index.html"))
|
|
979
|
-
|
|
980
|
-
:
|
|
1070
|
+
const appDist = existsSync(join(bundledDist, "index.html"))
|
|
1071
|
+
? bundledDist
|
|
1072
|
+
: existsSync(join(workspaceDist, "index.html"))
|
|
1073
|
+
? workspaceDist
|
|
1074
|
+
: null;
|
|
981
1075
|
|
|
982
1076
|
if (!appDist) {
|
|
983
1077
|
console.error(red("Web dashboard not found."));
|
|
984
1078
|
if (isInstalledPackage()) {
|
|
985
|
-
console.error(
|
|
1079
|
+
console.error(
|
|
1080
|
+
dim(" Try reinstalling: npm install -g context-vault@latest"),
|
|
1081
|
+
);
|
|
986
1082
|
} else {
|
|
987
1083
|
console.error(dim(" From repo: npm run build --workspace=packages/app"));
|
|
988
1084
|
console.error(dim(" Then run: context-vault ui"));
|
|
@@ -1024,7 +1120,12 @@ function launchServer(port, localServer) {
|
|
|
1024
1120
|
setTimeout(() => {
|
|
1025
1121
|
try {
|
|
1026
1122
|
const url = `http://localhost:${port}`;
|
|
1027
|
-
const open =
|
|
1123
|
+
const open =
|
|
1124
|
+
PLATFORM === "darwin"
|
|
1125
|
+
? "open"
|
|
1126
|
+
: PLATFORM === "win32"
|
|
1127
|
+
? "start"
|
|
1128
|
+
: "xdg-open";
|
|
1028
1129
|
execSync(`${open} ${url}`, { stdio: "ignore" });
|
|
1029
1130
|
} catch {}
|
|
1030
1131
|
}, 1500);
|
|
@@ -1036,15 +1137,14 @@ async function runReindex() {
|
|
|
1036
1137
|
console.log(dim("Loading vault..."));
|
|
1037
1138
|
|
|
1038
1139
|
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1039
|
-
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1140
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1141
|
+
await import("@context-vault/core/index/db");
|
|
1040
1142
|
const { embed } = await import("@context-vault/core/index/embed");
|
|
1041
1143
|
const { reindex } = await import("@context-vault/core/index");
|
|
1042
1144
|
|
|
1043
1145
|
const config = resolveConfig();
|
|
1044
1146
|
if (!config.vaultDirExists) {
|
|
1045
|
-
console.error(
|
|
1046
|
-
red(`Vault directory not found: ${config.vaultDir}`)
|
|
1047
|
-
);
|
|
1147
|
+
console.error(red(`Vault directory not found: ${config.vaultDir}`));
|
|
1048
1148
|
console.error("Run " + cyan("context-vault setup") + " to configure.");
|
|
1049
1149
|
process.exit(1);
|
|
1050
1150
|
}
|
|
@@ -1087,11 +1187,15 @@ async function runStatus() {
|
|
|
1087
1187
|
console.log();
|
|
1088
1188
|
console.log(` ${bold("◇ context-vault")} ${dim(`v${VERSION}`)}`);
|
|
1089
1189
|
console.log();
|
|
1090
|
-
console.log(
|
|
1190
|
+
console.log(
|
|
1191
|
+
` Vault: ${config.vaultDir} ${dim(`(${config.vaultDirExists ? status.fileCount + " files" : "missing"})`)}`,
|
|
1192
|
+
);
|
|
1091
1193
|
console.log(` Database: ${config.dbPath} ${dim(`(${status.dbSize})`)}`);
|
|
1092
1194
|
console.log(` Dev dir: ${config.devDir}`);
|
|
1093
1195
|
console.log(` Data dir: ${config.dataDir}`);
|
|
1094
|
-
console.log(
|
|
1196
|
+
console.log(
|
|
1197
|
+
` Config: ${config.configPath} ${dim(`(${existsSync(config.configPath) ? "exists" : "missing"})`)}`,
|
|
1198
|
+
);
|
|
1095
1199
|
console.log(` Resolved: ${status.resolvedFrom}`);
|
|
1096
1200
|
console.log(` Schema: v7 (teams)`);
|
|
1097
1201
|
|
|
@@ -1147,9 +1251,14 @@ async function runUpdate() {
|
|
|
1147
1251
|
|
|
1148
1252
|
let latest;
|
|
1149
1253
|
try {
|
|
1150
|
-
latest = execSync("npm view context-vault version", {
|
|
1254
|
+
latest = execSync("npm view context-vault version", {
|
|
1255
|
+
encoding: "utf-8",
|
|
1256
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
1257
|
+
}).trim();
|
|
1151
1258
|
} catch {
|
|
1152
|
-
console.error(
|
|
1259
|
+
console.error(
|
|
1260
|
+
red(" Could not check for updates. Verify your network connection."),
|
|
1261
|
+
);
|
|
1153
1262
|
return;
|
|
1154
1263
|
}
|
|
1155
1264
|
|
|
@@ -1177,7 +1286,9 @@ async function runUpdate() {
|
|
|
1177
1286
|
console.log();
|
|
1178
1287
|
console.log(green(` ✓ Updated to v${latest}`));
|
|
1179
1288
|
} catch {
|
|
1180
|
-
console.error(
|
|
1289
|
+
console.error(
|
|
1290
|
+
red(" Update failed. Try manually: npm install -g context-vault@latest"),
|
|
1291
|
+
);
|
|
1181
1292
|
}
|
|
1182
1293
|
console.log();
|
|
1183
1294
|
}
|
|
@@ -1193,7 +1304,9 @@ async function runUninstall() {
|
|
|
1193
1304
|
try {
|
|
1194
1305
|
const env = { ...process.env };
|
|
1195
1306
|
delete env.CLAUDECODE;
|
|
1196
|
-
try {
|
|
1307
|
+
try {
|
|
1308
|
+
execSync("claude mcp remove context-mcp -s user", { stdio: "pipe", env });
|
|
1309
|
+
} catch {}
|
|
1197
1310
|
execSync("claude mcp remove context-vault -s user", { stdio: "pipe", env });
|
|
1198
1311
|
console.log(` ${green("+")} Removed from Claude Code`);
|
|
1199
1312
|
} catch {
|
|
@@ -1202,7 +1315,9 @@ async function runUninstall() {
|
|
|
1202
1315
|
|
|
1203
1316
|
// Remove from Codex (both old and new names)
|
|
1204
1317
|
try {
|
|
1205
|
-
try {
|
|
1318
|
+
try {
|
|
1319
|
+
execSync("codex mcp remove context-mcp", { stdio: "pipe" });
|
|
1320
|
+
} catch {}
|
|
1206
1321
|
execSync("codex mcp remove context-vault", { stdio: "pipe" });
|
|
1207
1322
|
console.log(` ${green("+")} Removed from Codex`);
|
|
1208
1323
|
} catch {
|
|
@@ -1244,7 +1359,9 @@ async function runUninstall() {
|
|
|
1244
1359
|
}
|
|
1245
1360
|
|
|
1246
1361
|
console.log();
|
|
1247
|
-
console.log(
|
|
1362
|
+
console.log(
|
|
1363
|
+
dim(" Vault directory was not touched (your knowledge files are safe)."),
|
|
1364
|
+
);
|
|
1248
1365
|
console.log(` To fully remove: ${cyan("npm uninstall -g context-vault")}`);
|
|
1249
1366
|
console.log();
|
|
1250
1367
|
}
|
|
@@ -1252,17 +1369,25 @@ async function runUninstall() {
|
|
|
1252
1369
|
// ─── Migrate Command ─────────────────────────────────────────────────────────
|
|
1253
1370
|
|
|
1254
1371
|
async function runMigrate() {
|
|
1255
|
-
const direction = args.includes("--to-hosted")
|
|
1256
|
-
|
|
1257
|
-
:
|
|
1372
|
+
const direction = args.includes("--to-hosted")
|
|
1373
|
+
? "to-hosted"
|
|
1374
|
+
: args.includes("--to-local")
|
|
1375
|
+
? "to-local"
|
|
1376
|
+
: null;
|
|
1258
1377
|
|
|
1259
1378
|
if (!direction) {
|
|
1260
1379
|
console.log(`\n ${bold("context-vault migrate")}\n`);
|
|
1261
1380
|
console.log(` Usage:`);
|
|
1262
|
-
console.log(
|
|
1263
|
-
|
|
1381
|
+
console.log(
|
|
1382
|
+
` context-vault migrate --to-hosted Upload local vault to hosted service`,
|
|
1383
|
+
);
|
|
1384
|
+
console.log(
|
|
1385
|
+
` context-vault migrate --to-local Download hosted vault to local files`,
|
|
1386
|
+
);
|
|
1264
1387
|
console.log(`\n Options:`);
|
|
1265
|
-
console.log(
|
|
1388
|
+
console.log(
|
|
1389
|
+
` --url <url> Hosted server URL (default: https://api.context-vault.com)`,
|
|
1390
|
+
);
|
|
1266
1391
|
console.log(` --key <key> API key (cv_...)`);
|
|
1267
1392
|
console.log();
|
|
1268
1393
|
return;
|
|
@@ -1281,7 +1406,8 @@ async function runMigrate() {
|
|
|
1281
1406
|
const config = resolveConfig();
|
|
1282
1407
|
|
|
1283
1408
|
if (direction === "to-hosted") {
|
|
1284
|
-
const { migrateToHosted } =
|
|
1409
|
+
const { migrateToHosted } =
|
|
1410
|
+
await import("@context-vault/hosted/migration/migrate");
|
|
1285
1411
|
console.log(`\n ${bold("Migrating to hosted")}...`);
|
|
1286
1412
|
console.log(dim(` Vault: ${config.vaultDir}`));
|
|
1287
1413
|
console.log(dim(` Target: ${hostedUrl}\n`));
|
|
@@ -1302,7 +1428,8 @@ async function runMigrate() {
|
|
|
1302
1428
|
}
|
|
1303
1429
|
console.log(dim("\n Your local vault was not modified (safe backup)."));
|
|
1304
1430
|
} else {
|
|
1305
|
-
const { migrateToLocal } =
|
|
1431
|
+
const { migrateToLocal } =
|
|
1432
|
+
await import("@context-vault/hosted/migration/migrate");
|
|
1306
1433
|
console.log(`\n ${bold("Migrating to local")}...`);
|
|
1307
1434
|
console.log(dim(` Source: ${hostedUrl}`));
|
|
1308
1435
|
console.log(dim(` Target: ${config.vaultDir}\n`));
|
|
@@ -1318,7 +1445,9 @@ async function runMigrate() {
|
|
|
1318
1445
|
if (results.failed > 0) {
|
|
1319
1446
|
console.log(` ${red("-")} ${results.failed} failed`);
|
|
1320
1447
|
}
|
|
1321
|
-
console.log(
|
|
1448
|
+
console.log(
|
|
1449
|
+
dim("\n Run `context-vault reindex` to rebuild the search index."),
|
|
1450
|
+
);
|
|
1322
1451
|
}
|
|
1323
1452
|
console.log();
|
|
1324
1453
|
}
|
|
@@ -1340,10 +1469,13 @@ async function runImport() {
|
|
|
1340
1469
|
}
|
|
1341
1470
|
|
|
1342
1471
|
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1343
|
-
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1472
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1473
|
+
await import("@context-vault/core/index/db");
|
|
1344
1474
|
const { embed } = await import("@context-vault/core/index/embed");
|
|
1345
|
-
const { parseFile, parseDirectory } =
|
|
1346
|
-
|
|
1475
|
+
const { parseFile, parseDirectory } =
|
|
1476
|
+
await import("@context-vault/core/capture/importers");
|
|
1477
|
+
const { importEntries } =
|
|
1478
|
+
await import("@context-vault/core/capture/import-pipeline");
|
|
1347
1479
|
const { readFileSync, statSync } = await import("node:fs");
|
|
1348
1480
|
|
|
1349
1481
|
const kind = getFlag("--kind") || undefined;
|
|
@@ -1376,7 +1508,9 @@ async function runImport() {
|
|
|
1376
1508
|
if (dryRun) {
|
|
1377
1509
|
for (let i = 0; i < Math.min(entries.length, 20); i++) {
|
|
1378
1510
|
const e = entries[i];
|
|
1379
|
-
console.log(
|
|
1511
|
+
console.log(
|
|
1512
|
+
` ${dim(`[${i + 1}]`)} ${e.kind} — ${e.title || e.body.slice(0, 60)}${e.tags?.length ? ` ${dim(`[${e.tags.join(", ")}]`)}` : ""}`,
|
|
1513
|
+
);
|
|
1380
1514
|
}
|
|
1381
1515
|
if (entries.length > 20) {
|
|
1382
1516
|
console.log(dim(` ... and ${entries.length - 20} more`));
|
|
@@ -1395,7 +1529,10 @@ async function runImport() {
|
|
|
1395
1529
|
const db = await initDatabase(config.dbPath);
|
|
1396
1530
|
const stmts = prepareStatements(db);
|
|
1397
1531
|
const ctx = {
|
|
1398
|
-
db,
|
|
1532
|
+
db,
|
|
1533
|
+
config,
|
|
1534
|
+
stmts,
|
|
1535
|
+
embed,
|
|
1399
1536
|
insertVec: (r, e) => insertVec(stmts, r, e),
|
|
1400
1537
|
deleteVec: (r) => deleteVec(stmts, r),
|
|
1401
1538
|
};
|
|
@@ -1426,10 +1563,13 @@ async function runExport() {
|
|
|
1426
1563
|
const format = getFlag("--format") || "json";
|
|
1427
1564
|
const output = getFlag("--output");
|
|
1428
1565
|
const rawPageSize = getFlag("--page-size");
|
|
1429
|
-
const pageSize = rawPageSize
|
|
1566
|
+
const pageSize = rawPageSize
|
|
1567
|
+
? Math.max(1, parseInt(rawPageSize, 10) || 100)
|
|
1568
|
+
: null;
|
|
1430
1569
|
|
|
1431
1570
|
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1432
|
-
const { initDatabase, prepareStatements } =
|
|
1571
|
+
const { initDatabase, prepareStatements } =
|
|
1572
|
+
await import("@context-vault/core/index/db");
|
|
1433
1573
|
const { writeFileSync } = await import("node:fs");
|
|
1434
1574
|
|
|
1435
1575
|
const config = resolveConfig();
|
|
@@ -1440,7 +1580,8 @@ async function runExport() {
|
|
|
1440
1580
|
|
|
1441
1581
|
const db = await initDatabase(config.dbPath);
|
|
1442
1582
|
|
|
1443
|
-
const whereClause =
|
|
1583
|
+
const whereClause =
|
|
1584
|
+
"WHERE (expires_at IS NULL OR expires_at > datetime('now'))";
|
|
1444
1585
|
|
|
1445
1586
|
let entries;
|
|
1446
1587
|
if (pageSize) {
|
|
@@ -1448,7 +1589,7 @@ async function runExport() {
|
|
|
1448
1589
|
entries = [];
|
|
1449
1590
|
let offset = 0;
|
|
1450
1591
|
const stmt = db.prepare(
|
|
1451
|
-
`SELECT * FROM vault ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET
|
|
1592
|
+
`SELECT * FROM vault ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`,
|
|
1452
1593
|
);
|
|
1453
1594
|
while (true) {
|
|
1454
1595
|
const rows = stmt.all(pageSize, offset);
|
|
@@ -1460,9 +1601,9 @@ async function runExport() {
|
|
|
1460
1601
|
if (rows.length < pageSize) break;
|
|
1461
1602
|
}
|
|
1462
1603
|
} else {
|
|
1463
|
-
const rows = db
|
|
1464
|
-
`SELECT * FROM vault ${whereClause} ORDER BY created_at DESC`
|
|
1465
|
-
|
|
1604
|
+
const rows = db
|
|
1605
|
+
.prepare(`SELECT * FROM vault ${whereClause} ORDER BY created_at DESC`)
|
|
1606
|
+
.all();
|
|
1466
1607
|
entries = rows.map(mapExportRow);
|
|
1467
1608
|
}
|
|
1468
1609
|
|
|
@@ -1471,7 +1612,18 @@ async function runExport() {
|
|
|
1471
1612
|
let content;
|
|
1472
1613
|
|
|
1473
1614
|
if (format === "csv") {
|
|
1474
|
-
const headers = [
|
|
1615
|
+
const headers = [
|
|
1616
|
+
"id",
|
|
1617
|
+
"kind",
|
|
1618
|
+
"category",
|
|
1619
|
+
"title",
|
|
1620
|
+
"body",
|
|
1621
|
+
"tags",
|
|
1622
|
+
"source",
|
|
1623
|
+
"identity_key",
|
|
1624
|
+
"expires_at",
|
|
1625
|
+
"created_at",
|
|
1626
|
+
];
|
|
1475
1627
|
const csvLines = [headers.join(",")];
|
|
1476
1628
|
for (const e of entries) {
|
|
1477
1629
|
const row = headers.map((h) => {
|
|
@@ -1488,7 +1640,11 @@ async function runExport() {
|
|
|
1488
1640
|
}
|
|
1489
1641
|
content = csvLines.join("\n");
|
|
1490
1642
|
} else {
|
|
1491
|
-
content = JSON.stringify(
|
|
1643
|
+
content = JSON.stringify(
|
|
1644
|
+
{ entries, total: entries.length, exported_at: new Date().toISOString() },
|
|
1645
|
+
null,
|
|
1646
|
+
2,
|
|
1647
|
+
);
|
|
1492
1648
|
}
|
|
1493
1649
|
|
|
1494
1650
|
if (output) {
|
|
@@ -1547,8 +1703,11 @@ async function runIngest() {
|
|
|
1547
1703
|
}
|
|
1548
1704
|
|
|
1549
1705
|
console.log(`\n ${bold(entry.title)}`);
|
|
1550
|
-
console.log(
|
|
1551
|
-
|
|
1706
|
+
console.log(
|
|
1707
|
+
` ${dim(`kind: ${entry.kind} | source: ${entry.source} | ${entry.body.length} chars`)}`,
|
|
1708
|
+
);
|
|
1709
|
+
if (entry.tags?.length)
|
|
1710
|
+
console.log(` ${dim(`tags: ${entry.tags.join(", ")}`)}`);
|
|
1552
1711
|
|
|
1553
1712
|
if (dryRun) {
|
|
1554
1713
|
console.log(`\n${dim(" Preview (first 500 chars):")}`);
|
|
@@ -1558,10 +1717,10 @@ async function runIngest() {
|
|
|
1558
1717
|
}
|
|
1559
1718
|
|
|
1560
1719
|
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1561
|
-
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1720
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1721
|
+
await import("@context-vault/core/index/db");
|
|
1562
1722
|
const { embed } = await import("@context-vault/core/index/embed");
|
|
1563
1723
|
const { captureAndIndex } = await import("@context-vault/core/capture");
|
|
1564
|
-
const { indexEntry } = await import("@context-vault/core/index");
|
|
1565
1724
|
|
|
1566
1725
|
const config = resolveConfig();
|
|
1567
1726
|
if (!config.vaultDirExists) {
|
|
@@ -1572,12 +1731,15 @@ async function runIngest() {
|
|
|
1572
1731
|
const db = await initDatabase(config.dbPath);
|
|
1573
1732
|
const stmts = prepareStatements(db);
|
|
1574
1733
|
const ctx = {
|
|
1575
|
-
db,
|
|
1734
|
+
db,
|
|
1735
|
+
config,
|
|
1736
|
+
stmts,
|
|
1737
|
+
embed,
|
|
1576
1738
|
insertVec: (r, e) => insertVec(stmts, r, e),
|
|
1577
1739
|
deleteVec: (r) => deleteVec(stmts, r),
|
|
1578
1740
|
};
|
|
1579
1741
|
|
|
1580
|
-
const result = await captureAndIndex(ctx, entry
|
|
1742
|
+
const result = await captureAndIndex(ctx, entry);
|
|
1581
1743
|
db.close();
|
|
1582
1744
|
|
|
1583
1745
|
const relPath = result.filePath.replace(config.vaultDir + "/", "");
|
|
@@ -1597,7 +1759,9 @@ async function runLink() {
|
|
|
1597
1759
|
console.log(` Link your local vault to a hosted Context Vault account.\n`);
|
|
1598
1760
|
console.log(` Options:`);
|
|
1599
1761
|
console.log(` --key <key> API key (required)`);
|
|
1600
|
-
console.log(
|
|
1762
|
+
console.log(
|
|
1763
|
+
` --url <url> Hosted server URL (default: https://api.context-vault.com)`,
|
|
1764
|
+
);
|
|
1601
1765
|
console.log();
|
|
1602
1766
|
return;
|
|
1603
1767
|
}
|
|
@@ -1622,7 +1786,9 @@ async function runLink() {
|
|
|
1622
1786
|
const configPath = join(dataDir, "config.json");
|
|
1623
1787
|
let config = {};
|
|
1624
1788
|
if (existsSync(configPath)) {
|
|
1625
|
-
try {
|
|
1789
|
+
try {
|
|
1790
|
+
config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1791
|
+
} catch {}
|
|
1626
1792
|
}
|
|
1627
1793
|
|
|
1628
1794
|
config.hostedUrl = hostedUrl;
|
|
@@ -1654,21 +1820,34 @@ async function runSync() {
|
|
|
1654
1820
|
const configPath = join(dataDir, "config.json");
|
|
1655
1821
|
let storedConfig = {};
|
|
1656
1822
|
if (existsSync(configPath)) {
|
|
1657
|
-
try {
|
|
1823
|
+
try {
|
|
1824
|
+
storedConfig = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
1825
|
+
} catch {}
|
|
1658
1826
|
}
|
|
1659
1827
|
|
|
1660
1828
|
const apiKey = getFlag("--key") || storedConfig.apiKey;
|
|
1661
|
-
const hostedUrl =
|
|
1829
|
+
const hostedUrl =
|
|
1830
|
+
getFlag("--url") ||
|
|
1831
|
+
storedConfig.hostedUrl ||
|
|
1832
|
+
"https://api.context-vault.com";
|
|
1662
1833
|
|
|
1663
1834
|
if (!apiKey) {
|
|
1664
|
-
console.error(
|
|
1835
|
+
console.error(
|
|
1836
|
+
red(" Not linked. Run `context-vault link --key cv_...` first."),
|
|
1837
|
+
);
|
|
1665
1838
|
process.exit(1);
|
|
1666
1839
|
}
|
|
1667
1840
|
|
|
1668
1841
|
const { resolveConfig } = await import("@context-vault/core/core/config");
|
|
1669
|
-
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1842
|
+
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1843
|
+
await import("@context-vault/core/index/db");
|
|
1670
1844
|
const { embed } = await import("@context-vault/core/index/embed");
|
|
1671
|
-
const {
|
|
1845
|
+
const {
|
|
1846
|
+
buildLocalManifest,
|
|
1847
|
+
fetchRemoteManifest,
|
|
1848
|
+
computeSyncPlan,
|
|
1849
|
+
executeSync,
|
|
1850
|
+
} = await import("@context-vault/core/sync");
|
|
1672
1851
|
|
|
1673
1852
|
const config = resolveConfig();
|
|
1674
1853
|
if (!config.vaultDirExists) {
|
|
@@ -1679,7 +1858,10 @@ async function runSync() {
|
|
|
1679
1858
|
const db = await initDatabase(config.dbPath);
|
|
1680
1859
|
const stmts = prepareStatements(db);
|
|
1681
1860
|
const ctx = {
|
|
1682
|
-
db,
|
|
1861
|
+
db,
|
|
1862
|
+
config,
|
|
1863
|
+
stmts,
|
|
1864
|
+
embed,
|
|
1683
1865
|
insertVec: (r, e) => insertVec(stmts, r, e),
|
|
1684
1866
|
deleteVec: (r) => deleteVec(stmts, r),
|
|
1685
1867
|
};
|
|
@@ -1729,7 +1911,9 @@ async function runSync() {
|
|
|
1729
1911
|
apiKey,
|
|
1730
1912
|
plan,
|
|
1731
1913
|
onProgress: (phase, current, total) => {
|
|
1732
|
-
process.stdout.write(
|
|
1914
|
+
process.stdout.write(
|
|
1915
|
+
`\r ${phase === "push" ? "Pushing" : "Pulling"}... ${current}/${total}`,
|
|
1916
|
+
);
|
|
1733
1917
|
},
|
|
1734
1918
|
});
|
|
1735
1919
|
|