context-vault 3.5.0 → 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 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);
@@ -426,6 +427,7 @@ ${bold('Commands:')}
426
427
  --yes Non-interactive mode (accept all defaults)
427
428
  --force Overwrite existing config without confirmation
428
429
  --skip-embeddings Skip embedding model download (FTS-only mode)
430
+ --dry-run Show what setup would do without writing anything
429
431
  `);
430
432
  }
431
433
 
@@ -437,11 +439,15 @@ async function runSetup() {
437
439
  console.log();
438
440
  console.log(` ${bold('◇ context-vault')} ${dim(`v${VERSION}`)}`);
439
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
+ }
440
446
  console.log();
441
447
 
442
448
  // Check for existing installation
443
449
  const existingConfig = join(HOME, '.context-mcp', 'config.json');
444
- if (existsSync(existingConfig) && !isNonInteractive) {
450
+ if (existsSync(existingConfig) && !isNonInteractive && !isDryRun) {
445
451
  let existingVault = '(unknown)';
446
452
  try {
447
453
  const cfg = JSON.parse(readFileSync(existingConfig, 'utf-8'));
@@ -672,9 +678,9 @@ async function runSetup() {
672
678
  ${dim('}')}\n`);
673
679
  }
674
680
 
675
- // In non-interactive mode, continue setup without tools (vault, config, etc.)
676
- if (isNonInteractive) {
677
- console.log(dim(' Continuing setup without tool configuration (--yes mode).\n'));
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`));
678
684
  } else {
679
685
  return;
680
686
  }
@@ -682,7 +688,7 @@ async function runSetup() {
682
688
 
683
689
  // Select tools
684
690
  let selected;
685
- if (isNonInteractive || detected.length === 1) {
691
+ if (isDryRun || isNonInteractive || detected.length === 1) {
686
692
  selected = detected;
687
693
  if (detected.length === 1) {
688
694
  console.log(` ${dim('→')} Auto-selected ${detected[0].name}\n`);
@@ -710,7 +716,9 @@ async function runSetup() {
710
716
  let useRecommendedDefaults = false;
711
717
  const existingConfigForFastPath = join(HOME, '.context-mcp', 'config.json');
712
718
  const isNewInstall = !existsSync(existingConfigForFastPath);
713
- if (isNewInstall && !isNonInteractive) {
719
+ if (isDryRun) {
720
+ useRecommendedDefaults = true;
721
+ } else if (isNewInstall && !isNonInteractive) {
714
722
  console.log(dim(' Install with recommended settings?'));
715
723
  console.log(dim(' Vault in default location, all hooks, skills, and rules installed.'));
716
724
  console.log();
@@ -781,7 +789,7 @@ async function runSetup() {
781
789
  }
782
790
  }
783
791
 
784
- const vaultDir = (isNonInteractive || useRecommendedDefaults)
792
+ const vaultDir = (isNonInteractive || useRecommendedDefaults || isDryRun)
785
793
  ? defaultVaultDir
786
794
  : await prompt(` Vault directory:`, defaultVaultDir);
787
795
  let resolvedVaultDir = resolve(vaultDir);
@@ -793,6 +801,8 @@ async function runSetup() {
793
801
  console.error(dim(` Remove or rename the file, then run setup again.\n`));
794
802
  process.exit(1);
795
803
  }
804
+ } else if (isDryRun) {
805
+ console.log(`\n ${yellow('[dry-run]')} Would create directory: ${resolvedVaultDir}`);
796
806
  } else if (isNonInteractive || useRecommendedDefaults) {
797
807
  mkdirSync(resolvedVaultDir, { recursive: true });
798
808
  console.log(`\n ${green('+')} Created ${resolvedVaultDir}`);
@@ -808,11 +818,19 @@ async function runSetup() {
808
818
  }
809
819
 
810
820
  // Write marker file for vault auto-detection
811
- writeMarkerFile(resolvedVaultDir);
821
+ if (isDryRun) {
822
+ console.log(` ${yellow('[dry-run]')} Would write marker file: ${join(resolvedVaultDir, MARKER_FILE)}`);
823
+ } else {
824
+ writeMarkerFile(resolvedVaultDir);
825
+ }
812
826
 
813
827
  // Ensure data dir exists for DB storage
814
828
  const dataDir = join(HOME, '.context-mcp');
815
- mkdirSync(dataDir, { recursive: true });
829
+ if (isDryRun) {
830
+ console.log(` ${yellow('[dry-run]')} Would create directory: ${dataDir}`);
831
+ } else {
832
+ mkdirSync(dataDir, { recursive: true });
833
+ }
816
834
 
817
835
  // Write config.json to data dir (persistent, survives reinstalls)
818
836
  const configPath = join(dataDir, 'config.json');
@@ -848,18 +866,21 @@ async function runSetup() {
848
866
  );
849
867
  console.log(` Setup would change vaultDir to: ${resolvedVaultDir}`);
850
868
 
851
- if (isNonInteractive) {
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) {
852
873
  console.log();
853
874
  console.log(red(' Refusing to overwrite vaultDir in non-interactive mode.'));
854
875
  console.log(dim(' Use --force to override, or --vault-dir to set explicitly.'));
855
876
  process.exit(1);
856
- }
857
-
858
- console.log();
859
- const overwrite = await prompt(' Overwrite? (y/N):', 'N');
860
- if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {
861
- console.log(dim(` Keeping existing vaultDir: ${resolve(existingVaultDir)}`));
862
- resolvedVaultDir = resolve(existingVaultDir);
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
+ }
863
884
  }
864
885
  }
865
886
 
@@ -869,13 +890,21 @@ async function runSetup() {
869
890
  vaultConfig.devDir = join(HOME, 'dev');
870
891
  vaultConfig.mode = 'local';
871
892
 
872
- assertNotTestMode(configPath);
873
- writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
874
- console.log(`\n ${green('+')} Wrote ${configPath}`);
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}`);
900
+ }
875
901
 
876
902
  // Pre-download embedding model with spinner (skip with --skip-embeddings)
877
- const skipEmbeddings = flags.has('--skip-embeddings');
878
- if (skipEmbeddings) {
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) {
879
908
  console.log(`\n ${dim('[3/7]')}${bold(' Embedding model')} ${dim('(skipped)')}`);
880
909
  console.log(dim(' FTS-only mode — full-text search works, semantic search disabled.'));
881
910
  console.log(dim(' To enable later: context-vault setup (without --skip-embeddings)'));
@@ -956,10 +985,14 @@ async function runSetup() {
956
985
  // Clean up legacy project-root config.json if it exists
957
986
  const legacyConfigPath = join(ROOT, 'config.json');
958
987
  if (existsSync(legacyConfigPath)) {
959
- try {
960
- unlinkSync(legacyConfigPath);
961
- console.log(` ${dim('Removed legacy config at ' + legacyConfigPath)}`);
962
- } catch {}
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
+ }
963
996
  }
964
997
 
965
998
  // Configure each tool — always pass vault dir explicitly to prevent config drift
@@ -969,19 +1002,24 @@ async function runSetup() {
969
1002
  const customVaultDir = resolvedVaultDir;
970
1003
 
971
1004
  for (const tool of selected) {
972
- try {
973
- if (tool.configType === 'cli' && tool.id === 'codex') {
974
- await configureCodex(tool, customVaultDir);
975
- } else if (tool.configType === 'cli') {
976
- await configureClaude(tool, customVaultDir);
977
- } else {
978
- configureJsonTool(tool, customVaultDir);
979
- }
1005
+ if (isDryRun) {
1006
+ console.log(` ${yellow('[dry-run]')} Would configure: ${tool.name} (${tool.configPath || tool.id})`);
980
1007
  results.push({ tool, ok: true });
981
- console.log(` ${green('+')} ${tool.name} — configured`);
982
- } catch (e) {
983
- results.push({ tool, ok: false, error: e.message });
984
- console.log(` ${red('x')} ${tool.name} — ${e.message}`);
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
+ }
985
1023
  }
986
1024
  }
987
1025
 
@@ -993,188 +1031,218 @@ async function runSetup() {
993
1031
  const installedRulesPaths = [];
994
1032
 
995
1033
  if (claudeConfigured) {
996
- // Bundled hooks prompt: one Y/n for all three hooks
997
- let installHooks = hookFlag || useRecommendedDefaults;
998
- if (!hookFlag && !isNonInteractive && !useRecommendedDefaults) {
999
- console.log(dim(' Install Claude Code hooks? (recommended)'));
1000
- console.log(dim(' Memory recall, session capture, and auto-capture.'));
1001
- console.log();
1002
- const answer = await prompt(' Install Claude Code hooks? (Y/n):', 'Y');
1003
- installHooks = answer.toLowerCase() !== 'n';
1004
- }
1005
- if (installHooks) {
1006
- try {
1007
- const hookInstalled = installClaudeHook();
1008
- if (hookInstalled) console.log(` ${green('+')} Memory recall hook installed`);
1009
- } catch (e) {
1010
- console.log(` ${red('x')} Memory hook failed: ${e.message}`);
1011
- }
1012
- try {
1013
- const captureInstalled = installSessionCaptureHook();
1014
- if (captureInstalled) console.log(` ${green('+')} Session capture hook installed`);
1015
- } catch (e) {
1016
- console.log(` ${red('x')} Session capture hook failed: ${e.message}`);
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.'));
1043
+ console.log();
1044
+ const answer = await prompt(' Install Claude Code hooks? (Y/n):', 'Y');
1045
+ installHooks = answer.toLowerCase() !== 'n';
1017
1046
  }
1018
- try {
1019
- const acInstalled = installPostToolCallHook();
1020
- if (acInstalled) console.log(` ${green('+')} Auto-capture hook installed`);
1021
- } catch (e) {
1022
- console.log(` ${red('x')} Auto-capture hook failed: ${e.message}`);
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}`);
1053
+ }
1054
+ try {
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`));
1023
1068
  }
1024
- } else {
1025
- console.log(dim(` Hooks skipped. Install later: context-vault hooks install`));
1026
- }
1027
1069
 
1028
- // Skills (bundled, no separate prompt unless not using fast path)
1029
- let installSkillsFlag = useRecommendedDefaults || isNonInteractive;
1030
- if (!isNonInteractive && !useRecommendedDefaults) {
1031
- console.log();
1032
- console.log(dim(' Install Claude Code skills? (recommended)'));
1033
- console.log(dim(' compile-context, vault-setup'));
1034
- console.log();
1035
- const skillAnswer = await prompt(' Install Claude Code skills? (Y/n):', 'Y');
1036
- installSkillsFlag = skillAnswer.toLowerCase() !== 'n';
1037
- }
1038
- if (installSkillsFlag) {
1039
- try {
1040
- const names = installSkills();
1041
- for (const name of names) {
1042
- console.log(` ${green('+')} ${name} skill installed`);
1070
+ // Skills (bundled, no separate prompt unless not using fast path)
1071
+ let installSkillsFlag = useRecommendedDefaults || isNonInteractive;
1072
+ if (!isNonInteractive && !useRecommendedDefaults) {
1073
+ console.log();
1074
+ console.log(dim(' Install Claude Code skills? (recommended)'));
1075
+ console.log(dim(' compile-context, vault-setup'));
1076
+ console.log();
1077
+ const skillAnswer = await prompt(' Install Claude Code skills? (Y/n):', 'Y');
1078
+ installSkillsFlag = skillAnswer.toLowerCase() !== 'n';
1079
+ }
1080
+ if (installSkillsFlag) {
1081
+ try {
1082
+ const names = installSkills();
1083
+ for (const name of names) {
1084
+ console.log(` ${green('+')} ${name} skill installed`);
1085
+ }
1086
+ } catch (e) {
1087
+ console.log(` ${red('x')} Skills install failed: ${e.message}`);
1043
1088
  }
1044
- } catch (e) {
1045
- console.log(` ${red('x')} Skills install failed: ${e.message}`);
1089
+ } else {
1090
+ console.log(dim(` Skills skipped. Install later: context-vault skills install`));
1046
1091
  }
1047
- } else {
1048
- console.log(dim(` Skills skipped. Install later: context-vault skills install`));
1049
1092
  }
1050
1093
  }
1051
1094
 
1052
1095
  // Agent rules installation
1053
1096
  if (configuredTools.length > 0 && !flags.has('--no-rules')) {
1054
- let installRules = isNonInteractive || useRecommendedDefaults;
1055
- if (!isNonInteractive && !useRecommendedDefaults) {
1056
- console.log();
1057
- console.log(dim(' Install agent rules? (recommended)'));
1058
- console.log(dim(' Teaches your AI agent when and how to save knowledge to the vault.'));
1059
- console.log();
1060
- const rulesAnswer = await prompt(' Install agent rules? (Y/n):', 'Y');
1061
- installRules = rulesAnswer.toLowerCase() !== 'n';
1062
- }
1063
- if (installRules) {
1064
- const rulesContent = loadAgentRules();
1065
- if (rulesContent) {
1066
- for (const tool of configuredTools) {
1067
- try {
1068
- const installed = installAgentRulesForTool(tool, rulesContent);
1069
- const rulesPath = getRulesPathForTool(tool);
1070
- if (installed) {
1071
- console.log(` ${green('+')} ${tool.name} agent rules installed`);
1072
- if (rulesPath) {
1073
- console.log(` ${dim(rulesPath)}`);
1074
- installedRulesPaths.push({ tool: tool.name, path: rulesPath });
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
+ }
1075
1125
  }
1126
+ } catch (e) {
1127
+ console.log(` ${red('x')} ${tool.name} rules: ${e.message}`);
1076
1128
  }
1077
- } catch (e) {
1078
- console.log(` ${red('x')} ${tool.name} rules: ${e.message}`);
1079
1129
  }
1130
+ } else {
1131
+ console.log(dim(' Agent rules file not found in package.'));
1080
1132
  }
1081
1133
  } else {
1082
- console.log(dim(' Agent rules file not found in package.'));
1134
+ console.log(dim(' Rules skipped. Install later: context-vault rules install'));
1083
1135
  }
1084
- } else {
1085
- console.log(dim(' Rules skipped. Install later: context-vault rules install'));
1086
1136
  }
1087
1137
  } else if (flags.has('--no-rules')) {
1088
1138
  console.log(dim(' Agent rules skipped (--no-rules)'));
1089
1139
  }
1090
1140
 
1091
1141
  // Seed entry
1092
- const seeded = createSeedEntries(resolvedVaultDir);
1093
- if (seeded > 0) {
1094
- console.log(
1095
- `\n ${green('+')} Created ${seeded} starter ${seeded === 1 ? 'entry' : 'entries'} in vault`
1096
- );
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
+ }
1097
1151
  }
1098
1152
 
1099
1153
  // Telemetry opt-in (moved to end, after user has seen value)
1100
1154
  console.log(`\n ${dim('[6/7]')}${bold(' Anonymous error reporting\n')}`);
1101
- verbose(userLevel, 'Entirely optional. Works identically either way.\n');
1102
- console.log(dim(' When enabled, unhandled errors send a minimal event (type, tool name,'));
1103
- console.log(dim(' version, platform) to help diagnose issues. No vault content,'));
1104
- console.log(dim(' file paths, or personal data is ever sent. Off by default.'));
1105
- console.log(dim(` Full schema: ${MARKETING_URL}/telemetry`));
1106
- console.log();
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();
1107
1165
 
1108
- let telemetryEnabled = vaultConfig.telemetry === true;
1109
- if (!isNonInteractive && !useRecommendedDefaults) {
1110
- const defaultChoice = telemetryEnabled ? 'Y' : 'n';
1111
- const telemetryAnswer = await prompt(
1112
- ` Enable anonymous error reporting? (y/N):`,
1113
- defaultChoice
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;
1177
+ console.log(
1178
+ ` ${telemetryEnabled ? green('+') : dim('-')} Telemetry: ${telemetryEnabled ? 'enabled' : 'disabled'}`
1114
1179
  );
1115
- telemetryEnabled =
1116
- telemetryAnswer.toLowerCase() === 'y' || telemetryAnswer.toLowerCase() === 'yes';
1117
- }
1118
- vaultConfig.telemetry = telemetryEnabled;
1119
- console.log(
1120
- ` ${telemetryEnabled ? green('+') : dim('-')} Telemetry: ${telemetryEnabled ? 'enabled' : 'disabled'}`
1121
- );
1122
1180
 
1123
- // Re-write config with telemetry setting
1124
- assertNotTestMode(configPath);
1125
- writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
1181
+ // Re-write config with telemetry setting
1182
+ assertNotTestMode(configPath);
1183
+ writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
1184
+ }
1126
1185
 
1127
1186
  // Health check
1128
1187
  console.log(`\n ${dim('[7/7]')}${bold(' Health check...')}\n`);
1129
- verbose(userLevel, 'Verifying vault, config, and database are accessible.\n');
1130
1188
  const okResults = results.filter((r) => r.ok);
1189
+ let passed = 0;
1190
+ let checksTotal = 0;
1131
1191
 
1132
- // Verify DB is accessible
1133
- let dbAccessible = false;
1134
- let dbError = null;
1135
- try {
1136
- const { initDatabase } = await import('@context-vault/core/db');
1137
- const db = await initDatabase(vaultConfig.dbPath);
1138
- db.prepare('SELECT 1').get();
1139
- db.close();
1140
- dbAccessible = true;
1141
- } catch (e) {
1142
- dbError = e;
1143
- }
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');
1144
1197
 
1145
- const checks = [
1146
- { label: 'Vault directory exists', pass: existsSync(resolvedVaultDir) },
1147
- { label: 'Config file written', pass: existsSync(configPath) },
1148
- { label: 'Database accessible', pass: dbAccessible, error: dbError },
1149
- { label: 'At least one tool configured', pass: okResults.length > 0 },
1150
- ];
1151
- const passed = checks.filter((c) => c.pass).length;
1152
- for (const c of checks) {
1153
- console.log(` ${c.pass ? green('✓') : red('✗')} ${c.label}`);
1154
- if (!c.pass && c.error) {
1155
- console.log(` ${dim(c.error.message)}`);
1156
- if (c.error.message.includes('EACCES') || c.error.message.includes('permission')) {
1157
- console.log(` ${dim('Fix: check file permissions on ' + vaultConfig.dbPath)}`);
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
+ }
1158
1226
  }
1159
1227
  }
1160
- }
1161
1228
 
1162
- // Smoke test — write and read a test file to verify vault I/O
1163
- {
1164
- const testFile = join(resolvedVaultDir, '.smoke-test-' + Date.now() + '.md');
1165
- try {
1166
- writeFileSync(testFile, '# Smoke test\n');
1167
- const content = readFileSync(testFile, 'utf-8');
1168
- unlinkSync(testFile);
1169
- if (content.includes('Smoke test')) {
1170
- console.log(` ${green('✓')} Smoke test: vault read/write verified`);
1171
- } else {
1172
- console.log(` ${red('✗')} Smoke test: file written but content mismatch`);
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)}`);
1173
1245
  }
1174
- } catch (e) {
1175
- try { unlinkSync(testFile); } catch {}
1176
- console.log(` ${red('✗')} Smoke test failed: ${e.message}`);
1177
- console.log(` ${dim('Check permissions on ' + resolvedVaultDir)}`);
1178
1246
  }
1179
1247
  }
1180
1248
 
@@ -1184,9 +1252,26 @@ async function runSetup() {
1184
1252
  const cli = isNpx() ? 'npx context-vault' : 'context-vault';
1185
1253
 
1186
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
+ }
1187
1272
  if (userLevel === 'beginner') {
1188
1273
  boxLines = [
1189
- ` ✓ Setup complete — ${passed}/${checks.length} checks passed (${elapsed}s)`,
1274
+ ` ✓ Setup complete — ${passed}/${checksTotal} checks passed (${elapsed}s)`,
1190
1275
  ``,
1191
1276
  ` ${bold('What to do next:')}`,
1192
1277
  ``,
@@ -1204,7 +1289,7 @@ async function runSetup() {
1204
1289
  ];
1205
1290
  } else {
1206
1291
  boxLines = [
1207
- ` ✓ Setup complete — ${passed}/${checks.length} checks passed (${elapsed}s)`,
1292
+ ` ✓ Setup complete — ${passed}/${checksTotal} checks passed (${elapsed}s)`,
1208
1293
  ``,
1209
1294
  ` ${bold('Next:')} restart ${toolName} to activate the vault`,
1210
1295
  ``,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@context-vault/core",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "type": "module",
5
5
  "description": "Pure local engine: capture, index, search, and utilities for context-vault",
6
6
  "main": "dist/main.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "context-vault",
3
- "version": "3.5.0",
3
+ "version": "3.5.1",
4
4
  "type": "module",
5
5
  "description": "Persistent memory for AI agents — saves and searches knowledge across sessions",
6
6
  "bin": {
@@ -63,7 +63,7 @@
63
63
  "@context-vault/core"
64
64
  ],
65
65
  "dependencies": {
66
- "@context-vault/core": "^3.5.0",
66
+ "@context-vault/core": "^3.5.1",
67
67
  "@modelcontextprotocol/sdk": "^1.26.0",
68
68
  "adm-zip": "^0.5.16",
69
69
  "sqlite-vec": "^0.1.0"