context-vault 3.4.5 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +489 -299
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/dist/status.d.ts.map +1 -1
- package/dist/status.js +29 -0
- package/dist/status.js.map +1 -1
- package/dist/tools/context-status.d.ts.map +1 -1
- package/dist/tools/context-status.js +39 -5
- package/dist/tools/context-status.js.map +1 -1
- package/dist/tools/get-context.d.ts.map +1 -1
- package/dist/tools/get-context.js +1 -0
- package/dist/tools/get-context.js.map +1 -1
- package/dist/tools/list-context.d.ts +2 -1
- package/dist/tools/list-context.d.ts.map +1 -1
- package/dist/tools/list-context.js +22 -5
- package/dist/tools/list-context.js.map +1 -1
- package/dist/tools/save-context.d.ts +2 -1
- package/dist/tools/save-context.d.ts.map +1 -1
- package/dist/tools/save-context.js +58 -4
- package/dist/tools/save-context.js.map +1 -1
- package/dist/tools/session-start.d.ts.map +1 -1
- package/dist/tools/session-start.js +192 -7
- package/dist/tools/session-start.js.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/capture.js +2 -0
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/config.js +27 -1
- package/node_modules/@context-vault/core/dist/config.js.map +1 -1
- package/node_modules/@context-vault/core/dist/constants.d.ts +13 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/constants.js +13 -0
- package/node_modules/@context-vault/core/dist/constants.js.map +1 -1
- package/node_modules/@context-vault/core/dist/db.d.ts +1 -1
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/db.js +73 -9
- package/node_modules/@context-vault/core/dist/db.js.map +1 -1
- package/node_modules/@context-vault/core/dist/index.d.ts +4 -1
- package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/index.js +58 -10
- package/node_modules/@context-vault/core/dist/index.js.map +1 -1
- package/node_modules/@context-vault/core/dist/indexing.d.ts +8 -0
- package/node_modules/@context-vault/core/dist/indexing.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/indexing.js +22 -0
- package/node_modules/@context-vault/core/dist/indexing.js.map +1 -0
- package/node_modules/@context-vault/core/dist/main.d.ts +3 -2
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/main.js +3 -1
- package/node_modules/@context-vault/core/dist/main.js.map +1 -1
- package/node_modules/@context-vault/core/dist/search.d.ts +2 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
- package/node_modules/@context-vault/core/dist/search.js +82 -6
- package/node_modules/@context-vault/core/dist/search.js.map +1 -1
- package/node_modules/@context-vault/core/dist/types.d.ts +24 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
- package/node_modules/@context-vault/core/package.json +5 -1
- package/node_modules/@context-vault/core/src/capture.ts +2 -0
- package/node_modules/@context-vault/core/src/config.ts +18 -1
- package/node_modules/@context-vault/core/src/constants.ts +15 -0
- package/node_modules/@context-vault/core/src/db.ts +73 -9
- package/node_modules/@context-vault/core/src/index.ts +65 -11
- package/node_modules/@context-vault/core/src/indexing.ts +35 -0
- package/node_modules/@context-vault/core/src/main.ts +5 -0
- package/node_modules/@context-vault/core/src/search.ts +96 -6
- package/node_modules/@context-vault/core/src/types.ts +26 -0
- package/package.json +2 -2
- package/src/server.ts +3 -0
- package/src/status.ts +35 -0
- package/src/tools/context-status.ts +40 -5
- package/src/tools/get-context.ts +1 -0
- package/src/tools/list-context.ts +20 -5
- package/src/tools/save-context.ts +67 -4
- package/src/tools/session-start.ts +222 -9
package/bin/cli.js
CHANGED
|
@@ -188,6 +188,7 @@ const args = process.argv.slice(2);
|
|
|
188
188
|
const command = args[0];
|
|
189
189
|
const flags = new Set(args.filter((a) => a.startsWith('--')));
|
|
190
190
|
const isNonInteractive = flags.has('--yes') || !process.stdin.isTTY;
|
|
191
|
+
const isDryRun = flags.has('--dry-run');
|
|
191
192
|
|
|
192
193
|
function getFlag(name) {
|
|
193
194
|
const idx = args.indexOf(name);
|
|
@@ -252,12 +253,16 @@ const TOOLS = [
|
|
|
252
253
|
name: 'Claude Code',
|
|
253
254
|
detect: () => commandExistsAsync('claude'),
|
|
254
255
|
configType: 'cli',
|
|
256
|
+
rulesPath: join(HOME, '.claude', 'rules', 'context-vault.md'),
|
|
257
|
+
rulesMethod: 'write',
|
|
255
258
|
},
|
|
256
259
|
{
|
|
257
260
|
id: 'codex',
|
|
258
261
|
name: 'Codex',
|
|
259
262
|
detect: () => commandExistsAsync('codex'),
|
|
260
263
|
configType: 'cli',
|
|
264
|
+
rulesPath: null,
|
|
265
|
+
rulesMethod: null,
|
|
261
266
|
},
|
|
262
267
|
{
|
|
263
268
|
id: 'claude-desktop',
|
|
@@ -266,6 +271,8 @@ const TOOLS = [
|
|
|
266
271
|
configType: 'json',
|
|
267
272
|
configPath: join(appDataDir(), 'Claude', 'claude_desktop_config.json'),
|
|
268
273
|
configKey: 'mcpServers',
|
|
274
|
+
rulesPath: null,
|
|
275
|
+
rulesMethod: null,
|
|
269
276
|
},
|
|
270
277
|
{
|
|
271
278
|
id: 'cursor',
|
|
@@ -274,6 +281,8 @@ const TOOLS = [
|
|
|
274
281
|
configType: 'json',
|
|
275
282
|
configPath: join(HOME, '.cursor', 'mcp.json'),
|
|
276
283
|
configKey: 'mcpServers',
|
|
284
|
+
rulesPath: join(HOME, '.cursor', 'rules', 'context-vault.mdc'),
|
|
285
|
+
rulesMethod: 'write',
|
|
277
286
|
},
|
|
278
287
|
{
|
|
279
288
|
id: 'windsurf',
|
|
@@ -286,6 +295,8 @@ const TOOLS = [
|
|
|
286
295
|
: join(HOME, '.codeium', 'windsurf', 'mcp_config.json');
|
|
287
296
|
},
|
|
288
297
|
configKey: 'mcpServers',
|
|
298
|
+
rulesPath: join(HOME, '.windsurfrules'),
|
|
299
|
+
rulesMethod: 'append',
|
|
289
300
|
},
|
|
290
301
|
{
|
|
291
302
|
id: 'antigravity',
|
|
@@ -294,6 +305,8 @@ const TOOLS = [
|
|
|
294
305
|
configType: 'json',
|
|
295
306
|
configPath: join(HOME, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
296
307
|
configKey: 'mcpServers',
|
|
308
|
+
rulesPath: null,
|
|
309
|
+
rulesMethod: null,
|
|
297
310
|
},
|
|
298
311
|
{
|
|
299
312
|
id: 'cline',
|
|
@@ -307,6 +320,8 @@ const TOOLS = [
|
|
|
307
320
|
'cline_mcp_settings.json'
|
|
308
321
|
),
|
|
309
322
|
configKey: 'mcpServers',
|
|
323
|
+
rulesPath: null,
|
|
324
|
+
rulesMethod: null,
|
|
310
325
|
},
|
|
311
326
|
{
|
|
312
327
|
id: 'roo-code',
|
|
@@ -320,6 +335,8 @@ const TOOLS = [
|
|
|
320
335
|
'cline_mcp_settings.json'
|
|
321
336
|
),
|
|
322
337
|
configKey: 'mcpServers',
|
|
338
|
+
rulesPath: null,
|
|
339
|
+
rulesMethod: null,
|
|
323
340
|
},
|
|
324
341
|
];
|
|
325
342
|
|
|
@@ -410,6 +427,7 @@ ${bold('Commands:')}
|
|
|
410
427
|
--yes Non-interactive mode (accept all defaults)
|
|
411
428
|
--force Overwrite existing config without confirmation
|
|
412
429
|
--skip-embeddings Skip embedding model download (FTS-only mode)
|
|
430
|
+
--dry-run Show what setup would do without writing anything
|
|
413
431
|
`);
|
|
414
432
|
}
|
|
415
433
|
|
|
@@ -421,11 +439,15 @@ async function runSetup() {
|
|
|
421
439
|
console.log();
|
|
422
440
|
console.log(` ${bold('◇ context-vault')} ${dim(`v${VERSION}`)}`);
|
|
423
441
|
console.log(dim(' Persistent memory for AI agents'));
|
|
442
|
+
if (isDryRun) {
|
|
443
|
+
console.log();
|
|
444
|
+
console.log(yellow(' [dry-run] No files will be written. Showing what setup would do.'));
|
|
445
|
+
}
|
|
424
446
|
console.log();
|
|
425
447
|
|
|
426
448
|
// Check for existing installation
|
|
427
449
|
const existingConfig = join(HOME, '.context-mcp', 'config.json');
|
|
428
|
-
if (existsSync(existingConfig) && !isNonInteractive) {
|
|
450
|
+
if (existsSync(existingConfig) && !isNonInteractive && !isDryRun) {
|
|
429
451
|
let existingVault = '(unknown)';
|
|
430
452
|
try {
|
|
431
453
|
const cfg = JSON.parse(readFileSync(existingConfig, 'utf-8'));
|
|
@@ -619,7 +641,7 @@ async function runSetup() {
|
|
|
619
641
|
}
|
|
620
642
|
|
|
621
643
|
// Detect tools
|
|
622
|
-
console.log(dim(` [1/
|
|
644
|
+
console.log(dim(` [1/7]`) + bold(' Detecting tools...\n'));
|
|
623
645
|
verbose(userLevel, 'Scanning for AI tools on this machine.');
|
|
624
646
|
if (userLevel === 'beginner') console.log();
|
|
625
647
|
const { detected, results: detectionResults } = await detectAllTools();
|
|
@@ -656,9 +678,9 @@ async function runSetup() {
|
|
|
656
678
|
${dim('}')}\n`);
|
|
657
679
|
}
|
|
658
680
|
|
|
659
|
-
// In non-interactive mode, continue setup without tools (vault, config, etc.)
|
|
660
|
-
if (isNonInteractive) {
|
|
661
|
-
console.log(dim(
|
|
681
|
+
// In non-interactive/dry-run mode, continue setup without tools (vault, config, etc.)
|
|
682
|
+
if (isDryRun || isNonInteractive) {
|
|
683
|
+
console.log(dim(` Continuing setup without tool configuration (${isDryRun ? '--dry-run' : '--yes'} mode).\n`));
|
|
662
684
|
} else {
|
|
663
685
|
return;
|
|
664
686
|
}
|
|
@@ -666,7 +688,7 @@ async function runSetup() {
|
|
|
666
688
|
|
|
667
689
|
// Select tools
|
|
668
690
|
let selected;
|
|
669
|
-
if (isNonInteractive || detected.length === 1) {
|
|
691
|
+
if (isDryRun || isNonInteractive || detected.length === 1) {
|
|
670
692
|
selected = detected;
|
|
671
693
|
if (detected.length === 1) {
|
|
672
694
|
console.log(` ${dim('→')} Auto-selected ${detected[0].name}\n`);
|
|
@@ -690,8 +712,23 @@ async function runSetup() {
|
|
|
690
712
|
}
|
|
691
713
|
}
|
|
692
714
|
|
|
715
|
+
// Fast path for new users: recommended defaults
|
|
716
|
+
let useRecommendedDefaults = false;
|
|
717
|
+
const existingConfigForFastPath = join(HOME, '.context-mcp', 'config.json');
|
|
718
|
+
const isNewInstall = !existsSync(existingConfigForFastPath);
|
|
719
|
+
if (isDryRun) {
|
|
720
|
+
useRecommendedDefaults = true;
|
|
721
|
+
} else if (isNewInstall && !isNonInteractive) {
|
|
722
|
+
console.log(dim(' Install with recommended settings?'));
|
|
723
|
+
console.log(dim(' Vault in default location, all hooks, skills, and rules installed.'));
|
|
724
|
+
console.log();
|
|
725
|
+
const fastAnswer = await prompt(' Install with recommended settings? (Y/n):', 'Y');
|
|
726
|
+
useRecommendedDefaults = fastAnswer.toLowerCase() !== 'n';
|
|
727
|
+
if (useRecommendedDefaults) console.log();
|
|
728
|
+
}
|
|
729
|
+
|
|
693
730
|
// Vault directory (content files)
|
|
694
|
-
console.log(dim(` [2/
|
|
731
|
+
console.log(dim(` [2/7]`) + bold(' Configuring vault...\n'));
|
|
695
732
|
verbose(userLevel, 'Your vault is a folder of plain markdown files — you own it.');
|
|
696
733
|
if (userLevel === 'beginner') console.log();
|
|
697
734
|
|
|
@@ -711,7 +748,7 @@ async function runSetup() {
|
|
|
711
748
|
}
|
|
712
749
|
}
|
|
713
750
|
|
|
714
|
-
if (!getFlag('--vault-dir') && !isNonInteractive) {
|
|
751
|
+
if (!getFlag('--vault-dir') && !isNonInteractive && !useRecommendedDefaults) {
|
|
715
752
|
const existingVaults = scanForVaults();
|
|
716
753
|
if (existingVaults.length === 1) {
|
|
717
754
|
console.log(
|
|
@@ -743,9 +780,16 @@ async function runSetup() {
|
|
|
743
780
|
}
|
|
744
781
|
console.log();
|
|
745
782
|
}
|
|
783
|
+
} else if (!getFlag('--vault-dir') && useRecommendedDefaults) {
|
|
784
|
+
// Fast path: still use detected vaults if found
|
|
785
|
+
const existingVaults = scanForVaults();
|
|
786
|
+
if (existingVaults.length >= 1) {
|
|
787
|
+
defaultVaultDir = existingVaults[0].path;
|
|
788
|
+
console.log(` ${green('+')} Using existing vault at ${defaultVaultDir}`);
|
|
789
|
+
}
|
|
746
790
|
}
|
|
747
791
|
|
|
748
|
-
const vaultDir = isNonInteractive
|
|
792
|
+
const vaultDir = (isNonInteractive || useRecommendedDefaults || isDryRun)
|
|
749
793
|
? defaultVaultDir
|
|
750
794
|
: await prompt(` Vault directory:`, defaultVaultDir);
|
|
751
795
|
let resolvedVaultDir = resolve(vaultDir);
|
|
@@ -757,7 +801,9 @@ async function runSetup() {
|
|
|
757
801
|
console.error(dim(` Remove or rename the file, then run setup again.\n`));
|
|
758
802
|
process.exit(1);
|
|
759
803
|
}
|
|
760
|
-
} else if (
|
|
804
|
+
} else if (isDryRun) {
|
|
805
|
+
console.log(`\n ${yellow('[dry-run]')} Would create directory: ${resolvedVaultDir}`);
|
|
806
|
+
} else if (isNonInteractive || useRecommendedDefaults) {
|
|
761
807
|
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
762
808
|
console.log(`\n ${green('+')} Created ${resolvedVaultDir}`);
|
|
763
809
|
} else {
|
|
@@ -772,11 +818,19 @@ async function runSetup() {
|
|
|
772
818
|
}
|
|
773
819
|
|
|
774
820
|
// Write marker file for vault auto-detection
|
|
775
|
-
|
|
821
|
+
if (isDryRun) {
|
|
822
|
+
console.log(` ${yellow('[dry-run]')} Would write marker file: ${join(resolvedVaultDir, MARKER_FILE)}`);
|
|
823
|
+
} else {
|
|
824
|
+
writeMarkerFile(resolvedVaultDir);
|
|
825
|
+
}
|
|
776
826
|
|
|
777
827
|
// Ensure data dir exists for DB storage
|
|
778
828
|
const dataDir = join(HOME, '.context-mcp');
|
|
779
|
-
|
|
829
|
+
if (isDryRun) {
|
|
830
|
+
console.log(` ${yellow('[dry-run]')} Would create directory: ${dataDir}`);
|
|
831
|
+
} else {
|
|
832
|
+
mkdirSync(dataDir, { recursive: true });
|
|
833
|
+
}
|
|
780
834
|
|
|
781
835
|
// Write config.json to data dir (persistent, survives reinstalls)
|
|
782
836
|
const configPath = join(dataDir, 'config.json');
|
|
@@ -812,18 +866,21 @@ async function runSetup() {
|
|
|
812
866
|
);
|
|
813
867
|
console.log(` Setup would change vaultDir to: ${resolvedVaultDir}`);
|
|
814
868
|
|
|
815
|
-
if (
|
|
869
|
+
if (isDryRun) {
|
|
870
|
+
console.log(` ${yellow('[dry-run]')} Would change vaultDir from ${resolve(existingVaultDir)} to ${resolvedVaultDir}`);
|
|
871
|
+
resolvedVaultDir = resolve(existingVaultDir);
|
|
872
|
+
} else if (isNonInteractive) {
|
|
816
873
|
console.log();
|
|
817
874
|
console.log(red(' Refusing to overwrite vaultDir in non-interactive mode.'));
|
|
818
875
|
console.log(dim(' Use --force to override, or --vault-dir to set explicitly.'));
|
|
819
876
|
process.exit(1);
|
|
820
|
-
}
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
877
|
+
} else {
|
|
878
|
+
console.log();
|
|
879
|
+
const overwrite = await prompt(' Overwrite? (y/N):', 'N');
|
|
880
|
+
if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
|
|
881
|
+
console.log(dim(` Keeping existing vaultDir: ${resolve(existingVaultDir)}`));
|
|
882
|
+
resolvedVaultDir = resolve(existingVaultDir);
|
|
883
|
+
}
|
|
827
884
|
}
|
|
828
885
|
}
|
|
829
886
|
|
|
@@ -833,42 +890,26 @@ async function runSetup() {
|
|
|
833
890
|
vaultConfig.devDir = join(HOME, 'dev');
|
|
834
891
|
vaultConfig.mode = 'local';
|
|
835
892
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
console.log();
|
|
844
|
-
|
|
845
|
-
let telemetryEnabled = vaultConfig.telemetry === true;
|
|
846
|
-
if (!isNonInteractive) {
|
|
847
|
-
const defaultChoice = telemetryEnabled ? 'Y' : 'n';
|
|
848
|
-
const telemetryAnswer = await prompt(
|
|
849
|
-
` Enable anonymous error reporting? (y/N):`,
|
|
850
|
-
defaultChoice
|
|
851
|
-
);
|
|
852
|
-
telemetryEnabled =
|
|
853
|
-
telemetryAnswer.toLowerCase() === 'y' || telemetryAnswer.toLowerCase() === 'yes';
|
|
893
|
+
if (isDryRun) {
|
|
894
|
+
console.log(`\n ${yellow('[dry-run]')} Would write config: ${configPath}`);
|
|
895
|
+
console.log(dim(` ${JSON.stringify(vaultConfig, null, 2)}`));
|
|
896
|
+
} else {
|
|
897
|
+
assertNotTestMode(configPath);
|
|
898
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
|
|
899
|
+
console.log(`\n ${green('+')} Wrote ${configPath}`);
|
|
854
900
|
}
|
|
855
|
-
vaultConfig.telemetry = telemetryEnabled;
|
|
856
|
-
console.log(
|
|
857
|
-
` ${telemetryEnabled ? green('+') : dim('-')} Telemetry: ${telemetryEnabled ? 'enabled' : 'disabled'}`
|
|
858
|
-
);
|
|
859
|
-
|
|
860
|
-
assertNotTestMode(configPath);
|
|
861
|
-
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
|
|
862
|
-
console.log(`\n ${green('+')} Wrote ${configPath}`);
|
|
863
901
|
|
|
864
902
|
// Pre-download embedding model with spinner (skip with --skip-embeddings)
|
|
865
|
-
const skipEmbeddings = flags.has('--skip-embeddings');
|
|
866
|
-
if (
|
|
867
|
-
console.log(`\n ${dim('[
|
|
903
|
+
const skipEmbeddings = flags.has('--skip-embeddings') || isDryRun;
|
|
904
|
+
if (isDryRun) {
|
|
905
|
+
console.log(`\n ${dim('[3/7]')}${bold(' Embedding model')} ${yellow('(dry-run, skipped)')}`);
|
|
906
|
+
console.log(` ${yellow('[dry-run]')} Would download embedding model (~22MB)`);
|
|
907
|
+
} else if (skipEmbeddings) {
|
|
908
|
+
console.log(`\n ${dim('[3/7]')}${bold(' Embedding model')} ${dim('(skipped)')}`);
|
|
868
909
|
console.log(dim(' FTS-only mode — full-text search works, semantic search disabled.'));
|
|
869
910
|
console.log(dim(' To enable later: context-vault setup (without --skip-embeddings)'));
|
|
870
911
|
} else {
|
|
871
|
-
console.log(`\n ${dim('[
|
|
912
|
+
console.log(`\n ${dim('[3/7]')}${bold(' Downloading embedding model...')}`);
|
|
872
913
|
verbose(userLevel, 'Enables meaning-based search. ~22MB download, runs fully offline.');
|
|
873
914
|
console.log(dim(' all-MiniLM-L6-v2 (~22MB, one-time download)'));
|
|
874
915
|
console.log(dim(` Slow connection? Re-run with --skip-embeddings (enables FTS-only mode)\n`));
|
|
@@ -944,236 +985,264 @@ async function runSetup() {
|
|
|
944
985
|
// Clean up legacy project-root config.json if it exists
|
|
945
986
|
const legacyConfigPath = join(ROOT, 'config.json');
|
|
946
987
|
if (existsSync(legacyConfigPath)) {
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
988
|
+
if (isDryRun) {
|
|
989
|
+
console.log(` ${yellow('[dry-run]')} Would remove legacy config: ${legacyConfigPath}`);
|
|
990
|
+
} else {
|
|
991
|
+
try {
|
|
992
|
+
unlinkSync(legacyConfigPath);
|
|
993
|
+
console.log(` ${dim('Removed legacy config at ' + legacyConfigPath)}`);
|
|
994
|
+
} catch {}
|
|
995
|
+
}
|
|
951
996
|
}
|
|
952
997
|
|
|
953
998
|
// Configure each tool — always pass vault dir explicitly to prevent config drift
|
|
954
|
-
console.log(`\n ${dim('[
|
|
999
|
+
console.log(`\n ${dim('[4/7]')}${bold(' Configuring tools...\n')}`);
|
|
955
1000
|
verbose(userLevel, 'Writing config so your AI tool can find your vault.\n');
|
|
956
1001
|
const results = [];
|
|
957
1002
|
const customVaultDir = resolvedVaultDir;
|
|
958
1003
|
|
|
959
1004
|
for (const tool of selected) {
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
await configureCodex(tool, customVaultDir);
|
|
963
|
-
} else if (tool.configType === 'cli') {
|
|
964
|
-
await configureClaude(tool, customVaultDir);
|
|
965
|
-
} else {
|
|
966
|
-
configureJsonTool(tool, customVaultDir);
|
|
967
|
-
}
|
|
1005
|
+
if (isDryRun) {
|
|
1006
|
+
console.log(` ${yellow('[dry-run]')} Would configure: ${tool.name} (${tool.configPath || tool.id})`);
|
|
968
1007
|
results.push({ tool, ok: true });
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1008
|
+
} else {
|
|
1009
|
+
try {
|
|
1010
|
+
if (tool.configType === 'cli' && tool.id === 'codex') {
|
|
1011
|
+
await configureCodex(tool, customVaultDir);
|
|
1012
|
+
} else if (tool.configType === 'cli') {
|
|
1013
|
+
await configureClaude(tool, customVaultDir);
|
|
1014
|
+
} else {
|
|
1015
|
+
configureJsonTool(tool, customVaultDir);
|
|
1016
|
+
}
|
|
1017
|
+
results.push({ tool, ok: true });
|
|
1018
|
+
console.log(` ${green('+')} ${tool.name} — configured`);
|
|
1019
|
+
} catch (e) {
|
|
1020
|
+
results.push({ tool, ok: false, error: e.message });
|
|
1021
|
+
console.log(` ${red('x')} ${tool.name} — ${e.message}`);
|
|
1022
|
+
}
|
|
973
1023
|
}
|
|
974
1024
|
}
|
|
975
1025
|
|
|
976
|
-
// Claude Code hooks (
|
|
1026
|
+
// Claude Code extras: hooks, skills, rules (bundled into one step)
|
|
1027
|
+
console.log(`\n ${dim('[5/7]')}${bold(' Extras...\n')}`);
|
|
977
1028
|
const claudeConfigured = results.some((r) => r.ok && r.tool.id === 'claude-code');
|
|
978
1029
|
const hookFlag = flags.has('--hooks');
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
let installHook = hookFlag;
|
|
982
|
-
if (!hookFlag && !isNonInteractive) {
|
|
983
|
-
console.log();
|
|
984
|
-
console.log(dim(' Claude Code detected — install memory hook?'));
|
|
985
|
-
console.log(dim(' Searches your vault on every prompt and injects relevant entries'));
|
|
986
|
-
console.log(dim(" as additional context alongside Claude's native memory."));
|
|
987
|
-
console.log();
|
|
988
|
-
const answer = await prompt(' Install Claude Code memory hook? (Y/n):', 'Y');
|
|
989
|
-
installHook = answer.toLowerCase() !== 'n';
|
|
990
|
-
}
|
|
991
|
-
if (installHook) {
|
|
992
|
-
try {
|
|
993
|
-
const installed = installClaudeHook();
|
|
994
|
-
if (installed) {
|
|
995
|
-
console.log(`\n ${green('+')} Memory hook installed`);
|
|
996
|
-
}
|
|
997
|
-
} catch (e) {
|
|
998
|
-
console.log(`\n ${red('x')} Hook install failed: ${e.message}`);
|
|
999
|
-
}
|
|
1030
|
+
const configuredTools = results.filter((r) => r.ok).map((r) => r.tool);
|
|
1031
|
+
const installedRulesPaths = [];
|
|
1000
1032
|
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1033
|
+
if (claudeConfigured) {
|
|
1034
|
+
if (isDryRun) {
|
|
1035
|
+
console.log(` ${yellow('[dry-run]')} Would install Claude Code hooks (memory recall, session capture, auto-capture)`);
|
|
1036
|
+
console.log(` ${yellow('[dry-run]')} Would install Claude Code skills (compile-context, vault-setup)`);
|
|
1037
|
+
} else {
|
|
1038
|
+
// Bundled hooks prompt: one Y/n for all three hooks
|
|
1039
|
+
let installHooks = hookFlag || useRecommendedDefaults;
|
|
1040
|
+
if (!hookFlag && !isNonInteractive && !useRecommendedDefaults) {
|
|
1041
|
+
console.log(dim(' Install Claude Code hooks? (recommended)'));
|
|
1042
|
+
console.log(dim(' Memory recall, session capture, and auto-capture.'));
|
|
1006
1043
|
console.log();
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
}
|
|
1044
|
+
const answer = await prompt(' Install Claude Code hooks? (Y/n):', 'Y');
|
|
1045
|
+
installHooks = answer.toLowerCase() !== 'n';
|
|
1046
|
+
}
|
|
1047
|
+
if (installHooks) {
|
|
1048
|
+
try {
|
|
1049
|
+
const hookInstalled = installClaudeHook();
|
|
1050
|
+
if (hookInstalled) console.log(` ${green('+')} Memory recall hook installed`);
|
|
1051
|
+
} catch (e) {
|
|
1052
|
+
console.log(` ${red('x')} Memory hook failed: ${e.message}`);
|
|
1017
1053
|
}
|
|
1018
|
-
} else if (hookFlag) {
|
|
1019
1054
|
try {
|
|
1020
|
-
installSessionCaptureHook();
|
|
1021
|
-
|
|
1055
|
+
const captureInstalled = installSessionCaptureHook();
|
|
1056
|
+
if (captureInstalled) console.log(` ${green('+')} Session capture hook installed`);
|
|
1057
|
+
} catch (e) {
|
|
1058
|
+
console.log(` ${red('x')} Session capture hook failed: ${e.message}`);
|
|
1059
|
+
}
|
|
1060
|
+
try {
|
|
1061
|
+
const acInstalled = installPostToolCallHook();
|
|
1062
|
+
if (acInstalled) console.log(` ${green('+')} Auto-capture hook installed`);
|
|
1063
|
+
} catch (e) {
|
|
1064
|
+
console.log(` ${red('x')} Auto-capture hook failed: ${e.message}`);
|
|
1065
|
+
}
|
|
1066
|
+
} else {
|
|
1067
|
+
console.log(dim(` Hooks skipped. Install later: context-vault hooks install`));
|
|
1022
1068
|
}
|
|
1023
1069
|
|
|
1024
|
-
//
|
|
1025
|
-
|
|
1070
|
+
// Skills (bundled, no separate prompt unless not using fast path)
|
|
1071
|
+
let installSkillsFlag = useRecommendedDefaults || isNonInteractive;
|
|
1072
|
+
if (!isNonInteractive && !useRecommendedDefaults) {
|
|
1026
1073
|
console.log();
|
|
1027
|
-
console.log(dim('
|
|
1028
|
-
console.log(dim('
|
|
1074
|
+
console.log(dim(' Install Claude Code skills? (recommended)'));
|
|
1075
|
+
console.log(dim(' compile-context, vault-setup'));
|
|
1029
1076
|
console.log();
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
try {
|
|
1033
|
-
const acInstalled = installPostToolCallHook();
|
|
1034
|
-
if (acInstalled) {
|
|
1035
|
-
console.log(` ${green('+')} Auto-capture hook installed`);
|
|
1036
|
-
}
|
|
1037
|
-
} catch (e) {
|
|
1038
|
-
console.log(` ${red('x')} Auto-capture hook failed: ${e.message}`);
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
} else if (hookFlag) {
|
|
1042
|
-
try {
|
|
1043
|
-
installPostToolCallHook();
|
|
1044
|
-
} catch {}
|
|
1077
|
+
const skillAnswer = await prompt(' Install Claude Code skills? (Y/n):', 'Y');
|
|
1078
|
+
installSkillsFlag = skillAnswer.toLowerCase() !== 'n';
|
|
1045
1079
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
// Claude Code skills (opt-in)
|
|
1052
|
-
if (claudeConfigured && !isNonInteractive) {
|
|
1053
|
-
console.log();
|
|
1054
|
-
console.log(dim(' Install Claude Code skills? (recommended)'));
|
|
1055
|
-
console.log(dim(' compile-context — compile vault entries into a project brief'));
|
|
1056
|
-
console.log(dim(' vault-setup — agent-assisted vault customization (/vault-setup)'));
|
|
1057
|
-
console.log();
|
|
1058
|
-
const skillAnswer = await prompt(' Install Claude Code skills? (Y/n):', 'Y');
|
|
1059
|
-
const installSkillsFlag = skillAnswer.toLowerCase() !== 'n';
|
|
1060
|
-
if (installSkillsFlag) {
|
|
1061
|
-
try {
|
|
1062
|
-
const names = installSkills();
|
|
1063
|
-
if (names.length > 0) {
|
|
1080
|
+
if (installSkillsFlag) {
|
|
1081
|
+
try {
|
|
1082
|
+
const names = installSkills();
|
|
1064
1083
|
for (const name of names) {
|
|
1065
|
-
console.log(
|
|
1084
|
+
console.log(` ${green('+')} ${name} skill installed`);
|
|
1066
1085
|
}
|
|
1086
|
+
} catch (e) {
|
|
1087
|
+
console.log(` ${red('x')} Skills install failed: ${e.message}`);
|
|
1067
1088
|
}
|
|
1068
|
-
}
|
|
1069
|
-
console.log(
|
|
1089
|
+
} else {
|
|
1090
|
+
console.log(dim(` Skills skipped. Install later: context-vault skills install`));
|
|
1070
1091
|
}
|
|
1071
|
-
} else {
|
|
1072
|
-
console.log(dim(` Skipped — install later: context-vault skills install`));
|
|
1073
1092
|
}
|
|
1074
1093
|
}
|
|
1075
1094
|
|
|
1076
|
-
// Agent rules installation
|
|
1077
|
-
const configuredTools = results.filter((r) => r.ok).map((r) => r.tool);
|
|
1078
|
-
const installedRulesPaths = [];
|
|
1095
|
+
// Agent rules installation
|
|
1079
1096
|
if (configuredTools.length > 0 && !flags.has('--no-rules')) {
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1097
|
+
if (isDryRun) {
|
|
1098
|
+
for (const tool of configuredTools) {
|
|
1099
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
1100
|
+
console.log(` ${yellow('[dry-run]')} Would install agent rules for ${tool.name}${rulesPath ? ': ' + rulesPath : ''}`);
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
let installRules = isNonInteractive || useRecommendedDefaults;
|
|
1104
|
+
if (!isNonInteractive && !useRecommendedDefaults) {
|
|
1105
|
+
console.log();
|
|
1106
|
+
console.log(dim(' Install agent rules? (recommended)'));
|
|
1107
|
+
console.log(dim(' Teaches your AI agent when and how to save knowledge to the vault.'));
|
|
1108
|
+
console.log();
|
|
1109
|
+
const rulesAnswer = await prompt(' Install agent rules? (Y/n):', 'Y');
|
|
1110
|
+
installRules = rulesAnswer.toLowerCase() !== 'n';
|
|
1111
|
+
}
|
|
1112
|
+
if (installRules) {
|
|
1113
|
+
const rulesContent = loadAgentRules();
|
|
1114
|
+
if (rulesContent) {
|
|
1115
|
+
for (const tool of configuredTools) {
|
|
1116
|
+
try {
|
|
1117
|
+
const installed = installAgentRulesForTool(tool, rulesContent);
|
|
1118
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
1119
|
+
if (installed) {
|
|
1120
|
+
console.log(` ${green('+')} ${tool.name} agent rules installed`);
|
|
1121
|
+
if (rulesPath) {
|
|
1122
|
+
console.log(` ${dim(rulesPath)}`);
|
|
1123
|
+
installedRulesPaths.push({ tool: tool.name, path: rulesPath });
|
|
1124
|
+
}
|
|
1102
1125
|
}
|
|
1126
|
+
} catch (e) {
|
|
1127
|
+
console.log(` ${red('x')} ${tool.name} rules: ${e.message}`);
|
|
1103
1128
|
}
|
|
1104
|
-
} catch (e) {
|
|
1105
|
-
console.log(` ${red('x')} ${tool.name} — ${e.message}`);
|
|
1106
1129
|
}
|
|
1130
|
+
} else {
|
|
1131
|
+
console.log(dim(' Agent rules file not found in package.'));
|
|
1107
1132
|
}
|
|
1108
1133
|
} else {
|
|
1109
|
-
console.log(dim('
|
|
1134
|
+
console.log(dim(' Rules skipped. Install later: context-vault rules install'));
|
|
1110
1135
|
}
|
|
1111
|
-
} else {
|
|
1112
|
-
console.log(dim(' Skipped — install later: context-vault rules install'));
|
|
1113
1136
|
}
|
|
1114
1137
|
} else if (flags.has('--no-rules')) {
|
|
1115
1138
|
console.log(dim(' Agent rules skipped (--no-rules)'));
|
|
1116
1139
|
}
|
|
1117
1140
|
|
|
1118
1141
|
// Seed entry
|
|
1119
|
-
|
|
1120
|
-
|
|
1142
|
+
if (isDryRun) {
|
|
1143
|
+
console.log(`\n ${yellow('[dry-run]')} Would create seed entries in ${resolvedVaultDir}`);
|
|
1144
|
+
} else {
|
|
1145
|
+
const seeded = createSeedEntries(resolvedVaultDir);
|
|
1146
|
+
if (seeded > 0) {
|
|
1147
|
+
console.log(
|
|
1148
|
+
`\n ${green('+')} Created ${seeded} starter ${seeded === 1 ? 'entry' : 'entries'} in vault`
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
// Telemetry opt-in (moved to end, after user has seen value)
|
|
1154
|
+
console.log(`\n ${dim('[6/7]')}${bold(' Anonymous error reporting\n')}`);
|
|
1155
|
+
if (isDryRun) {
|
|
1156
|
+
console.log(` ${yellow('[dry-run]')} Would prompt for telemetry preference`);
|
|
1157
|
+
console.log(` ${yellow('[dry-run]')} Would update config: ${configPath}`);
|
|
1158
|
+
} else {
|
|
1159
|
+
verbose(userLevel, 'Entirely optional. Works identically either way.\n');
|
|
1160
|
+
console.log(dim(' When enabled, unhandled errors send a minimal event (type, tool name,'));
|
|
1161
|
+
console.log(dim(' version, platform) to help diagnose issues. No vault content,'));
|
|
1162
|
+
console.log(dim(' file paths, or personal data is ever sent. Off by default.'));
|
|
1163
|
+
console.log(dim(` Full schema: ${MARKETING_URL}/telemetry`));
|
|
1164
|
+
console.log();
|
|
1165
|
+
|
|
1166
|
+
let telemetryEnabled = vaultConfig.telemetry === true;
|
|
1167
|
+
if (!isNonInteractive && !useRecommendedDefaults) {
|
|
1168
|
+
const defaultChoice = telemetryEnabled ? 'Y' : 'n';
|
|
1169
|
+
const telemetryAnswer = await prompt(
|
|
1170
|
+
` Enable anonymous error reporting? (y/N):`,
|
|
1171
|
+
defaultChoice
|
|
1172
|
+
);
|
|
1173
|
+
telemetryEnabled =
|
|
1174
|
+
telemetryAnswer.toLowerCase() === 'y' || telemetryAnswer.toLowerCase() === 'yes';
|
|
1175
|
+
}
|
|
1176
|
+
vaultConfig.telemetry = telemetryEnabled;
|
|
1121
1177
|
console.log(
|
|
1122
|
-
|
|
1178
|
+
` ${telemetryEnabled ? green('+') : dim('-')} Telemetry: ${telemetryEnabled ? 'enabled' : 'disabled'}`
|
|
1123
1179
|
);
|
|
1180
|
+
|
|
1181
|
+
// Re-write config with telemetry setting
|
|
1182
|
+
assertNotTestMode(configPath);
|
|
1183
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
|
|
1124
1184
|
}
|
|
1125
1185
|
|
|
1126
1186
|
// Health check
|
|
1127
|
-
console.log(`\n ${dim('[
|
|
1128
|
-
verbose(userLevel, 'Verifying vault, config, and database are accessible.\n');
|
|
1187
|
+
console.log(`\n ${dim('[7/7]')}${bold(' Health check...')}\n`);
|
|
1129
1188
|
const okResults = results.filter((r) => r.ok);
|
|
1189
|
+
let passed = 0;
|
|
1190
|
+
let checksTotal = 0;
|
|
1130
1191
|
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
const db = await initDatabase(vaultConfig.dbPath);
|
|
1137
|
-
db.prepare('SELECT 1').get();
|
|
1138
|
-
db.close();
|
|
1139
|
-
dbAccessible = true;
|
|
1140
|
-
} catch (e) {
|
|
1141
|
-
dbError = e;
|
|
1142
|
-
}
|
|
1192
|
+
if (isDryRun) {
|
|
1193
|
+
console.log(` ${yellow('[dry-run]')} Skipping health check (no files were written)`);
|
|
1194
|
+
console.log(` ${yellow('[dry-run]')} Skipping smoke test`);
|
|
1195
|
+
} else {
|
|
1196
|
+
verbose(userLevel, 'Verifying vault, config, and database are accessible.\n');
|
|
1143
1197
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
{
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1198
|
+
// Verify DB is accessible
|
|
1199
|
+
let dbAccessible = false;
|
|
1200
|
+
let dbError = null;
|
|
1201
|
+
try {
|
|
1202
|
+
const { initDatabase } = await import('@context-vault/core/db');
|
|
1203
|
+
const db = await initDatabase(vaultConfig.dbPath);
|
|
1204
|
+
db.prepare('SELECT 1').get();
|
|
1205
|
+
db.close();
|
|
1206
|
+
dbAccessible = true;
|
|
1207
|
+
} catch (e) {
|
|
1208
|
+
dbError = e;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
const checks = [
|
|
1212
|
+
{ label: 'Vault directory exists', pass: existsSync(resolvedVaultDir) },
|
|
1213
|
+
{ label: 'Config file written', pass: existsSync(configPath) },
|
|
1214
|
+
{ label: 'Database accessible', pass: dbAccessible, error: dbError },
|
|
1215
|
+
{ label: 'At least one tool configured', pass: okResults.length > 0 },
|
|
1216
|
+
];
|
|
1217
|
+
passed = checks.filter((c) => c.pass).length;
|
|
1218
|
+
checksTotal = checks.length;
|
|
1219
|
+
for (const c of checks) {
|
|
1220
|
+
console.log(` ${c.pass ? green('✓') : red('✗')} ${c.label}`);
|
|
1221
|
+
if (!c.pass && c.error) {
|
|
1222
|
+
console.log(` ${dim(c.error.message)}`);
|
|
1223
|
+
if (c.error.message.includes('EACCES') || c.error.message.includes('permission')) {
|
|
1224
|
+
console.log(` ${dim('Fix: check file permissions on ' + vaultConfig.dbPath)}`);
|
|
1225
|
+
}
|
|
1157
1226
|
}
|
|
1158
1227
|
}
|
|
1159
|
-
}
|
|
1160
1228
|
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1229
|
+
// Smoke test — write and read a test file to verify vault I/O
|
|
1230
|
+
{
|
|
1231
|
+
const testFile = join(resolvedVaultDir, '.smoke-test-' + Date.now() + '.md');
|
|
1232
|
+
try {
|
|
1233
|
+
writeFileSync(testFile, '# Smoke test\n');
|
|
1234
|
+
const content = readFileSync(testFile, 'utf-8');
|
|
1235
|
+
unlinkSync(testFile);
|
|
1236
|
+
if (content.includes('Smoke test')) {
|
|
1237
|
+
console.log(` ${green('✓')} Smoke test: vault read/write verified`);
|
|
1238
|
+
} else {
|
|
1239
|
+
console.log(` ${red('✗')} Smoke test: file written but content mismatch`);
|
|
1240
|
+
}
|
|
1241
|
+
} catch (e) {
|
|
1242
|
+
try { unlinkSync(testFile); } catch {}
|
|
1243
|
+
console.log(` ${red('✗')} Smoke test failed: ${e.message}`);
|
|
1244
|
+
console.log(` ${dim('Check permissions on ' + resolvedVaultDir)}`);
|
|
1172
1245
|
}
|
|
1173
|
-
} catch (e) {
|
|
1174
|
-
try { unlinkSync(testFile); } catch {}
|
|
1175
|
-
console.log(` ${red('✗')} Smoke test failed: ${e.message}`);
|
|
1176
|
-
console.log(` ${dim('Check permissions on ' + resolvedVaultDir)}`);
|
|
1177
1246
|
}
|
|
1178
1247
|
}
|
|
1179
1248
|
|
|
@@ -1183,9 +1252,26 @@ async function runSetup() {
|
|
|
1183
1252
|
const cli = isNpx() ? 'npx context-vault' : 'context-vault';
|
|
1184
1253
|
|
|
1185
1254
|
let boxLines;
|
|
1255
|
+
if (isDryRun) {
|
|
1256
|
+
boxLines = [
|
|
1257
|
+
` ${yellow('Dry run complete')} (${elapsed}s)`,
|
|
1258
|
+
``,
|
|
1259
|
+
` No files were written. Run without --dry-run to apply.`,
|
|
1260
|
+
];
|
|
1261
|
+
const innerWidth = Math.max(...boxLines.map((l) => l.length)) + 2;
|
|
1262
|
+
const pad = (s) => s + ' '.repeat(Math.max(0, innerWidth - s.length));
|
|
1263
|
+
console.log();
|
|
1264
|
+
console.log(` ${dim('┌' + '─'.repeat(innerWidth) + '┐')}`);
|
|
1265
|
+
for (const line of boxLines) {
|
|
1266
|
+
console.log(` ${dim('│')}${pad(line)}${dim('│')}`);
|
|
1267
|
+
}
|
|
1268
|
+
console.log(` ${dim('└' + '─'.repeat(innerWidth) + '┘')}`);
|
|
1269
|
+
console.log();
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1186
1272
|
if (userLevel === 'beginner') {
|
|
1187
1273
|
boxLines = [
|
|
1188
|
-
` ✓ Setup complete — ${passed}/${
|
|
1274
|
+
` ✓ Setup complete — ${passed}/${checksTotal} checks passed (${elapsed}s)`,
|
|
1189
1275
|
``,
|
|
1190
1276
|
` ${bold('What to do next:')}`,
|
|
1191
1277
|
``,
|
|
@@ -1203,7 +1289,7 @@ async function runSetup() {
|
|
|
1203
1289
|
];
|
|
1204
1290
|
} else {
|
|
1205
1291
|
boxLines = [
|
|
1206
|
-
` ✓ Setup complete — ${passed}/${
|
|
1292
|
+
` ✓ Setup complete — ${passed}/${checksTotal} checks passed (${elapsed}s)`,
|
|
1207
1293
|
``,
|
|
1208
1294
|
` ${bold('Next:')} restart ${toolName} to activate the vault`,
|
|
1209
1295
|
``,
|
|
@@ -1894,7 +1980,11 @@ async function runSwitch() {
|
|
|
1894
1980
|
}
|
|
1895
1981
|
|
|
1896
1982
|
async function runReindex() {
|
|
1897
|
-
|
|
1983
|
+
const dryRun = flags.has('--dry-run');
|
|
1984
|
+
const kindIdx = args.indexOf('--kind');
|
|
1985
|
+
const kindFilter = kindIdx !== -1 && args[kindIdx + 1] ? args[kindIdx + 1] : null;
|
|
1986
|
+
|
|
1987
|
+
console.log(dim(dryRun ? 'Analyzing vault (dry run)...' : 'Loading vault...'));
|
|
1898
1988
|
|
|
1899
1989
|
const { resolveConfig } = await import('@context-vault/core/config');
|
|
1900
1990
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
@@ -1920,14 +2010,34 @@ async function runReindex() {
|
|
|
1920
2010
|
deleteVec: (r) => deleteVec(stmts, r),
|
|
1921
2011
|
};
|
|
1922
2012
|
|
|
1923
|
-
const
|
|
2013
|
+
const reindexOpts = {
|
|
2014
|
+
fullSync: true,
|
|
2015
|
+
indexingConfig: config.indexing,
|
|
2016
|
+
dryRun,
|
|
2017
|
+
kindFilter,
|
|
2018
|
+
};
|
|
2019
|
+
|
|
2020
|
+
const stats = await reindex(ctx, reindexOpts);
|
|
1924
2021
|
|
|
1925
2022
|
db.close();
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
2023
|
+
|
|
2024
|
+
if (dryRun) {
|
|
2025
|
+
console.log(yellow('Dry run results (no changes made):'));
|
|
2026
|
+
console.log(` Would index: ${stats.added}`);
|
|
2027
|
+
console.log(` Would skip: ${stats.skippedIndexing ?? 0}`);
|
|
2028
|
+
} else {
|
|
2029
|
+
console.log(green('✓ Reindex complete'));
|
|
2030
|
+
console.log(` ${green('+')} ${stats.added} added`);
|
|
2031
|
+
console.log(` ${yellow('~')} ${stats.updated} updated`);
|
|
2032
|
+
console.log(` ${red('-')} ${stats.removed} removed`);
|
|
2033
|
+
console.log(` ${dim('·')} ${stats.unchanged} unchanged`);
|
|
2034
|
+
if (stats.skippedIndexing) {
|
|
2035
|
+
console.log(` ${dim('○')} ${stats.skippedIndexing} skipped indexing`);
|
|
2036
|
+
}
|
|
2037
|
+
if (stats.embeddingsCleared) {
|
|
2038
|
+
console.log(` ${dim('⊘')} ${stats.embeddingsCleared} embeddings cleared`);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
1931
2041
|
}
|
|
1932
2042
|
|
|
1933
2043
|
async function runMigrateDirs() {
|
|
@@ -2278,7 +2388,8 @@ async function runStatus() {
|
|
|
2278
2388
|
const filled = maxCount > 0 ? Math.round((c / maxCount) * BAR_WIDTH) : 0;
|
|
2279
2389
|
const bar = '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
|
|
2280
2390
|
const countStr = String(c).padStart(4);
|
|
2281
|
-
const
|
|
2391
|
+
const IRREGULAR_PLURALS = { activity: 'activities', inbox: 'inboxes', index: 'indexes', match: 'matches' };
|
|
2392
|
+
const plural = IRREGULAR_PLURALS[kind] || (kind.endsWith('s') ? kind : kind + 's');
|
|
2282
2393
|
console.log(` ${dim(bar)} ${countStr} ${plural}`);
|
|
2283
2394
|
}
|
|
2284
2395
|
} else {
|
|
@@ -3796,6 +3907,78 @@ async function runSessionEnd() {
|
|
|
3796
3907
|
meta: { session_id: session_id ?? null, cwd, message_count },
|
|
3797
3908
|
});
|
|
3798
3909
|
console.log(`context-vault session captured — id: ${entry.id}`);
|
|
3910
|
+
|
|
3911
|
+
// ── Auto-insight extraction ──────────────────────────────────────────────
|
|
3912
|
+
const aiConfig = config.autoInsights ?? { enabled: true, patterns: ['★ Insight'], minChars: 50, maxPerSession: 5, tier: 'working' };
|
|
3913
|
+
if (aiConfig.enabled !== false) {
|
|
3914
|
+
try {
|
|
3915
|
+
const patterns = aiConfig.patterns ?? ['★ Insight'];
|
|
3916
|
+
const minChars = aiConfig.minChars ?? 50;
|
|
3917
|
+
const maxInsights = aiConfig.maxPerSession ?? 5;
|
|
3918
|
+
const defaultTier = aiConfig.tier ?? 'working';
|
|
3919
|
+
|
|
3920
|
+
// Build regex for all configured patterns
|
|
3921
|
+
const escapedPatterns = patterns.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
3922
|
+
const patternRe = new RegExp(
|
|
3923
|
+
`(?:${escapedPatterns.join('|')})[─\\s]*\`?\\n([\\s\\S]*?)\\n\`?─{10,}`,
|
|
3924
|
+
'g'
|
|
3925
|
+
);
|
|
3926
|
+
|
|
3927
|
+
const insightBlocks = [];
|
|
3928
|
+
for (const turn of turns) {
|
|
3929
|
+
if (turn.role !== 'assistant') continue;
|
|
3930
|
+
const text = extractText(turn);
|
|
3931
|
+
if (!text) continue;
|
|
3932
|
+
for (const m of text.matchAll(patternRe)) {
|
|
3933
|
+
const insightBody = m[1].trim();
|
|
3934
|
+
if (insightBody.length >= minChars && insightBlocks.length < maxInsights) {
|
|
3935
|
+
insightBlocks.push(insightBody);
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
if (insightBlocks.length > 0) {
|
|
3941
|
+
// Check existing auto-insight entries for dedup (by title, lightweight)
|
|
3942
|
+
const existingTitles = new Set();
|
|
3943
|
+
try {
|
|
3944
|
+
const rows = db.prepare(
|
|
3945
|
+
`SELECT title FROM entries WHERE tags LIKE '%auto-insight%' ORDER BY created_at DESC LIMIT 100`
|
|
3946
|
+
).all();
|
|
3947
|
+
for (const r of rows) {
|
|
3948
|
+
if (r.title) existingTitles.add(r.title.toLowerCase());
|
|
3949
|
+
}
|
|
3950
|
+
} catch {}
|
|
3951
|
+
|
|
3952
|
+
let savedCount = 0;
|
|
3953
|
+
for (const insightBody of insightBlocks) {
|
|
3954
|
+
const boldMatch = insightBody.match(/\*\*(.+?)\*\*/);
|
|
3955
|
+
const firstLine = insightBody.split('\n')[0].replace(/\*\*/g, '').trim();
|
|
3956
|
+
const insightTitle = boldMatch ? boldMatch[1].slice(0, 80) : firstLine.slice(0, 80);
|
|
3957
|
+
|
|
3958
|
+
// Skip near-duplicates by title
|
|
3959
|
+
if (existingTitles.has(insightTitle.toLowerCase())) continue;
|
|
3960
|
+
|
|
3961
|
+
const insightTags = ['auto-insight', 'session-insight', `bucket:${project}`];
|
|
3962
|
+
await captureAndIndex(ctx, {
|
|
3963
|
+
kind: 'insight',
|
|
3964
|
+
title: insightTitle,
|
|
3965
|
+
body: insightBody,
|
|
3966
|
+
tags: insightTags,
|
|
3967
|
+
source: `claude-code session ${new Date().toISOString().slice(0, 10)}`,
|
|
3968
|
+
tier: defaultTier,
|
|
3969
|
+
meta: { auto_extracted: true, session_id: session_id ?? null },
|
|
3970
|
+
});
|
|
3971
|
+
existingTitles.add(insightTitle.toLowerCase());
|
|
3972
|
+
savedCount++;
|
|
3973
|
+
}
|
|
3974
|
+
if (savedCount > 0) {
|
|
3975
|
+
console.log(`context-vault auto-insights — ${savedCount} insight${savedCount === 1 ? '' : 's'} saved`);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
} catch {
|
|
3979
|
+
// Auto-insight extraction is best-effort
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3799
3982
|
} catch {
|
|
3800
3983
|
// fail silently — never block session end
|
|
3801
3984
|
} finally {
|
|
@@ -4102,48 +4285,45 @@ function loadAgentRules() {
|
|
|
4102
4285
|
* Returns null for tools with no rules install path.
|
|
4103
4286
|
*/
|
|
4104
4287
|
function getRulesPathForTool(tool) {
|
|
4105
|
-
|
|
4106
|
-
if (tool.id === 'cursor') return join(HOME, '.cursor', 'rules', 'context-vault.mdc');
|
|
4107
|
-
if (tool.id === 'windsurf') return join(HOME, '.windsurfrules');
|
|
4108
|
-
return null;
|
|
4288
|
+
return tool.rulesPath || null;
|
|
4109
4289
|
}
|
|
4110
4290
|
|
|
4111
4291
|
/**
|
|
4112
4292
|
* Install agent rules for a specific tool.
|
|
4113
|
-
*
|
|
4114
|
-
* -
|
|
4115
|
-
* -
|
|
4116
|
-
*
|
|
4117
|
-
* Returns true if installed, false if skipped or already present.
|
|
4293
|
+
* Uses tool.rulesPath and tool.rulesMethod from the TOOLS array.
|
|
4294
|
+
* - 'write' method: writes the file directly (Claude Code, Cursor)
|
|
4295
|
+
* - 'append' method: appends with delimiter markers (Windsurf)
|
|
4296
|
+
* Returns true if installed/updated, false if already up to date or skipped.
|
|
4118
4297
|
*/
|
|
4119
4298
|
function installAgentRulesForTool(tool, rulesContent) {
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4299
|
+
const rulesPath = tool.rulesPath;
|
|
4300
|
+
if (!rulesPath) return false;
|
|
4301
|
+
|
|
4302
|
+
if (tool.rulesMethod === 'write') {
|
|
4123
4303
|
if (existsSync(rulesPath)) {
|
|
4124
4304
|
const existing = readFileSync(rulesPath, 'utf-8');
|
|
4125
4305
|
if (existing.trim() === rulesContent.trim()) return false;
|
|
4126
4306
|
}
|
|
4127
|
-
mkdirSync(
|
|
4128
|
-
writeFileSync(rulesPath, rulesContent);
|
|
4129
|
-
return true;
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
if (tool.id === 'cursor') {
|
|
4133
|
-
const rulesPath = join(HOME, '.cursor', 'rules', 'context-vault.mdc');
|
|
4134
|
-
// Cursor supports project rules in .cursor/rules/ directory
|
|
4135
|
-
if (existsSync(rulesPath)) return false;
|
|
4136
|
-
mkdirSync(join(HOME, '.cursor', 'rules'), { recursive: true });
|
|
4307
|
+
mkdirSync(dirname(rulesPath), { recursive: true });
|
|
4137
4308
|
writeFileSync(rulesPath, rulesContent);
|
|
4138
4309
|
return true;
|
|
4139
4310
|
}
|
|
4140
4311
|
|
|
4141
|
-
if (tool.
|
|
4142
|
-
const rulesPath = join(HOME, '.windsurfrules');
|
|
4312
|
+
if (tool.rulesMethod === 'append') {
|
|
4143
4313
|
const delimited = `\n${RULES_DELIMITER_START}\n${rulesContent}\n${RULES_DELIMITER_END}\n`;
|
|
4144
4314
|
if (existsSync(rulesPath)) {
|
|
4145
4315
|
const existing = readFileSync(rulesPath, 'utf-8');
|
|
4146
|
-
if (existing.includes(RULES_DELIMITER_START))
|
|
4316
|
+
if (existing.includes(RULES_DELIMITER_START)) {
|
|
4317
|
+
const delimiterRegex = new RegExp(
|
|
4318
|
+
`\n?${RULES_DELIMITER_START}[\\s\\S]*?${RULES_DELIMITER_END}\n?`,
|
|
4319
|
+
'g'
|
|
4320
|
+
);
|
|
4321
|
+
const existingSection = existing.match(delimiterRegex)?.[0] || '';
|
|
4322
|
+
if (existingSection.includes(rulesContent.trim())) return false;
|
|
4323
|
+
const cleaned = existing.replace(delimiterRegex, '');
|
|
4324
|
+
writeFileSync(rulesPath, cleaned + delimited);
|
|
4325
|
+
return true;
|
|
4326
|
+
}
|
|
4147
4327
|
writeFileSync(rulesPath, existing + delimited);
|
|
4148
4328
|
} else {
|
|
4149
4329
|
writeFileSync(rulesPath, delimited.trimStart());
|
|
@@ -4151,7 +4331,6 @@ function installAgentRulesForTool(tool, rulesContent) {
|
|
|
4151
4331
|
return true;
|
|
4152
4332
|
}
|
|
4153
4333
|
|
|
4154
|
-
// Other tools: no rules installation path yet
|
|
4155
4334
|
return false;
|
|
4156
4335
|
}
|
|
4157
4336
|
|
|
@@ -4567,19 +4746,28 @@ async function runRules() {
|
|
|
4567
4746
|
console.log();
|
|
4568
4747
|
} else if (sub === 'show') {
|
|
4569
4748
|
const { detected } = await detectAllTools();
|
|
4570
|
-
const
|
|
4571
|
-
if (
|
|
4749
|
+
const toolsWithRules = detected.filter((t) => getRulesPathForTool(t));
|
|
4750
|
+
if (toolsWithRules.length === 0) {
|
|
4572
4751
|
console.log(`\n ${yellow('!')} No supported tool detected.\n`);
|
|
4573
4752
|
process.exit(1);
|
|
4574
4753
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4754
|
+
let anyShown = false;
|
|
4755
|
+
for (const tool of toolsWithRules) {
|
|
4756
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
4757
|
+
if (!rulesPath || !existsSync(rulesPath)) {
|
|
4758
|
+
console.log(`\n ${yellow('!')} No rules file installed for ${tool.name}.`);
|
|
4759
|
+
console.log(dim(` Run: context-vault rules install`));
|
|
4760
|
+
continue;
|
|
4761
|
+
}
|
|
4762
|
+
if (anyShown) console.log(dim(' ' + '─'.repeat(40)));
|
|
4763
|
+
console.log(`\n ${dim(`${tool.name}: ${rulesPath}`)}\n`);
|
|
4764
|
+
console.log(readFileSync(rulesPath, 'utf-8'));
|
|
4765
|
+
anyShown = true;
|
|
4766
|
+
}
|
|
4767
|
+
if (!anyShown) {
|
|
4768
|
+
console.log(dim(`\n Run: context-vault rules install\n`));
|
|
4579
4769
|
process.exit(1);
|
|
4580
4770
|
}
|
|
4581
|
-
console.log(`\n ${dim(`${tool.name}: ${rulesPath}`)}\n`);
|
|
4582
|
-
console.log(readFileSync(rulesPath, 'utf-8'));
|
|
4583
4771
|
} else if (sub === 'path') {
|
|
4584
4772
|
const { detected } = await detectAllTools();
|
|
4585
4773
|
const supportedTools = detected.filter((t) => getRulesPathForTool(t));
|
|
@@ -4601,42 +4789,44 @@ async function runRules() {
|
|
|
4601
4789
|
process.exit(1);
|
|
4602
4790
|
}
|
|
4603
4791
|
const { detected } = await detectAllTools();
|
|
4604
|
-
const
|
|
4605
|
-
if (
|
|
4792
|
+
const toolsWithRules = detected.filter((t) => getRulesPathForTool(t));
|
|
4793
|
+
if (toolsWithRules.length === 0) {
|
|
4606
4794
|
console.log(`\n ${yellow('!')} No supported tool detected.\n`);
|
|
4607
4795
|
process.exit(1);
|
|
4608
4796
|
}
|
|
4609
|
-
const
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4797
|
+
for (const tool of toolsWithRules) {
|
|
4798
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
4799
|
+
if (!rulesPath || !existsSync(rulesPath)) {
|
|
4800
|
+
console.log(`\n ${yellow('!')} No rules file installed for ${tool.name}.`);
|
|
4801
|
+
console.log(dim(` Run: context-vault rules install`));
|
|
4802
|
+
continue;
|
|
4803
|
+
}
|
|
4804
|
+
const installed = readFileSync(rulesPath, 'utf-8');
|
|
4805
|
+
if (installed.trim() === bundled.trim()) {
|
|
4806
|
+
console.log(`\n ${green('✓')} ${tool.name}: rules are up to date (${rulesPath})`);
|
|
4807
|
+
} else {
|
|
4808
|
+
console.log(`\n ${yellow('!')} ${tool.name}: installed rules differ from bundled version.`);
|
|
4809
|
+
console.log(` ${dim(rulesPath)}\n`);
|
|
4810
|
+
const installedLines = installed.split('\n');
|
|
4811
|
+
const bundledLines = bundled.split('\n');
|
|
4812
|
+
const maxLines = Math.max(installedLines.length, bundledLines.length);
|
|
4813
|
+
for (let i = 0; i < maxLines; i++) {
|
|
4814
|
+
const a = installedLines[i];
|
|
4815
|
+
const b = bundledLines[i];
|
|
4816
|
+
if (a === undefined) {
|
|
4817
|
+
console.log(` ${green('+')} ${b}`);
|
|
4818
|
+
} else if (b === undefined) {
|
|
4819
|
+
console.log(` ${red('-')} ${a}`);
|
|
4820
|
+
} else if (a !== b) {
|
|
4821
|
+
console.log(` ${red('-')} ${a}`);
|
|
4822
|
+
console.log(` ${green('+')} ${b}`);
|
|
4823
|
+
}
|
|
4634
4824
|
}
|
|
4825
|
+
console.log();
|
|
4826
|
+
console.log(dim(' To upgrade: context-vault rules install'));
|
|
4635
4827
|
}
|
|
4636
|
-
console.log();
|
|
4637
|
-
console.log(dim(' To upgrade: context-vault rules install'));
|
|
4638
|
-
console.log();
|
|
4639
4828
|
}
|
|
4829
|
+
console.log();
|
|
4640
4830
|
} else {
|
|
4641
4831
|
console.log(`
|
|
4642
4832
|
${bold('context-vault rules')} <command>
|