context-vault 3.4.5 → 3.5.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 +299 -194
- 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
|
@@ -252,12 +252,16 @@ const TOOLS = [
|
|
|
252
252
|
name: 'Claude Code',
|
|
253
253
|
detect: () => commandExistsAsync('claude'),
|
|
254
254
|
configType: 'cli',
|
|
255
|
+
rulesPath: join(HOME, '.claude', 'rules', 'context-vault.md'),
|
|
256
|
+
rulesMethod: 'write',
|
|
255
257
|
},
|
|
256
258
|
{
|
|
257
259
|
id: 'codex',
|
|
258
260
|
name: 'Codex',
|
|
259
261
|
detect: () => commandExistsAsync('codex'),
|
|
260
262
|
configType: 'cli',
|
|
263
|
+
rulesPath: null,
|
|
264
|
+
rulesMethod: null,
|
|
261
265
|
},
|
|
262
266
|
{
|
|
263
267
|
id: 'claude-desktop',
|
|
@@ -266,6 +270,8 @@ const TOOLS = [
|
|
|
266
270
|
configType: 'json',
|
|
267
271
|
configPath: join(appDataDir(), 'Claude', 'claude_desktop_config.json'),
|
|
268
272
|
configKey: 'mcpServers',
|
|
273
|
+
rulesPath: null,
|
|
274
|
+
rulesMethod: null,
|
|
269
275
|
},
|
|
270
276
|
{
|
|
271
277
|
id: 'cursor',
|
|
@@ -274,6 +280,8 @@ const TOOLS = [
|
|
|
274
280
|
configType: 'json',
|
|
275
281
|
configPath: join(HOME, '.cursor', 'mcp.json'),
|
|
276
282
|
configKey: 'mcpServers',
|
|
283
|
+
rulesPath: join(HOME, '.cursor', 'rules', 'context-vault.mdc'),
|
|
284
|
+
rulesMethod: 'write',
|
|
277
285
|
},
|
|
278
286
|
{
|
|
279
287
|
id: 'windsurf',
|
|
@@ -286,6 +294,8 @@ const TOOLS = [
|
|
|
286
294
|
: join(HOME, '.codeium', 'windsurf', 'mcp_config.json');
|
|
287
295
|
},
|
|
288
296
|
configKey: 'mcpServers',
|
|
297
|
+
rulesPath: join(HOME, '.windsurfrules'),
|
|
298
|
+
rulesMethod: 'append',
|
|
289
299
|
},
|
|
290
300
|
{
|
|
291
301
|
id: 'antigravity',
|
|
@@ -294,6 +304,8 @@ const TOOLS = [
|
|
|
294
304
|
configType: 'json',
|
|
295
305
|
configPath: join(HOME, '.gemini', 'antigravity', 'mcp_config.json'),
|
|
296
306
|
configKey: 'mcpServers',
|
|
307
|
+
rulesPath: null,
|
|
308
|
+
rulesMethod: null,
|
|
297
309
|
},
|
|
298
310
|
{
|
|
299
311
|
id: 'cline',
|
|
@@ -307,6 +319,8 @@ const TOOLS = [
|
|
|
307
319
|
'cline_mcp_settings.json'
|
|
308
320
|
),
|
|
309
321
|
configKey: 'mcpServers',
|
|
322
|
+
rulesPath: null,
|
|
323
|
+
rulesMethod: null,
|
|
310
324
|
},
|
|
311
325
|
{
|
|
312
326
|
id: 'roo-code',
|
|
@@ -320,6 +334,8 @@ const TOOLS = [
|
|
|
320
334
|
'cline_mcp_settings.json'
|
|
321
335
|
),
|
|
322
336
|
configKey: 'mcpServers',
|
|
337
|
+
rulesPath: null,
|
|
338
|
+
rulesMethod: null,
|
|
323
339
|
},
|
|
324
340
|
];
|
|
325
341
|
|
|
@@ -619,7 +635,7 @@ async function runSetup() {
|
|
|
619
635
|
}
|
|
620
636
|
|
|
621
637
|
// Detect tools
|
|
622
|
-
console.log(dim(` [1/
|
|
638
|
+
console.log(dim(` [1/7]`) + bold(' Detecting tools...\n'));
|
|
623
639
|
verbose(userLevel, 'Scanning for AI tools on this machine.');
|
|
624
640
|
if (userLevel === 'beginner') console.log();
|
|
625
641
|
const { detected, results: detectionResults } = await detectAllTools();
|
|
@@ -690,8 +706,21 @@ async function runSetup() {
|
|
|
690
706
|
}
|
|
691
707
|
}
|
|
692
708
|
|
|
709
|
+
// Fast path for new users: recommended defaults
|
|
710
|
+
let useRecommendedDefaults = false;
|
|
711
|
+
const existingConfigForFastPath = join(HOME, '.context-mcp', 'config.json');
|
|
712
|
+
const isNewInstall = !existsSync(existingConfigForFastPath);
|
|
713
|
+
if (isNewInstall && !isNonInteractive) {
|
|
714
|
+
console.log(dim(' Install with recommended settings?'));
|
|
715
|
+
console.log(dim(' Vault in default location, all hooks, skills, and rules installed.'));
|
|
716
|
+
console.log();
|
|
717
|
+
const fastAnswer = await prompt(' Install with recommended settings? (Y/n):', 'Y');
|
|
718
|
+
useRecommendedDefaults = fastAnswer.toLowerCase() !== 'n';
|
|
719
|
+
if (useRecommendedDefaults) console.log();
|
|
720
|
+
}
|
|
721
|
+
|
|
693
722
|
// Vault directory (content files)
|
|
694
|
-
console.log(dim(` [2/
|
|
723
|
+
console.log(dim(` [2/7]`) + bold(' Configuring vault...\n'));
|
|
695
724
|
verbose(userLevel, 'Your vault is a folder of plain markdown files — you own it.');
|
|
696
725
|
if (userLevel === 'beginner') console.log();
|
|
697
726
|
|
|
@@ -711,7 +740,7 @@ async function runSetup() {
|
|
|
711
740
|
}
|
|
712
741
|
}
|
|
713
742
|
|
|
714
|
-
if (!getFlag('--vault-dir') && !isNonInteractive) {
|
|
743
|
+
if (!getFlag('--vault-dir') && !isNonInteractive && !useRecommendedDefaults) {
|
|
715
744
|
const existingVaults = scanForVaults();
|
|
716
745
|
if (existingVaults.length === 1) {
|
|
717
746
|
console.log(
|
|
@@ -743,9 +772,16 @@ async function runSetup() {
|
|
|
743
772
|
}
|
|
744
773
|
console.log();
|
|
745
774
|
}
|
|
775
|
+
} else if (!getFlag('--vault-dir') && useRecommendedDefaults) {
|
|
776
|
+
// Fast path: still use detected vaults if found
|
|
777
|
+
const existingVaults = scanForVaults();
|
|
778
|
+
if (existingVaults.length >= 1) {
|
|
779
|
+
defaultVaultDir = existingVaults[0].path;
|
|
780
|
+
console.log(` ${green('+')} Using existing vault at ${defaultVaultDir}`);
|
|
781
|
+
}
|
|
746
782
|
}
|
|
747
783
|
|
|
748
|
-
const vaultDir = isNonInteractive
|
|
784
|
+
const vaultDir = (isNonInteractive || useRecommendedDefaults)
|
|
749
785
|
? defaultVaultDir
|
|
750
786
|
: await prompt(` Vault directory:`, defaultVaultDir);
|
|
751
787
|
let resolvedVaultDir = resolve(vaultDir);
|
|
@@ -757,7 +793,7 @@ async function runSetup() {
|
|
|
757
793
|
console.error(dim(` Remove or rename the file, then run setup again.\n`));
|
|
758
794
|
process.exit(1);
|
|
759
795
|
}
|
|
760
|
-
} else if (isNonInteractive) {
|
|
796
|
+
} else if (isNonInteractive || useRecommendedDefaults) {
|
|
761
797
|
mkdirSync(resolvedVaultDir, { recursive: true });
|
|
762
798
|
console.log(`\n ${green('+')} Created ${resolvedVaultDir}`);
|
|
763
799
|
} else {
|
|
@@ -833,30 +869,6 @@ async function runSetup() {
|
|
|
833
869
|
vaultConfig.devDir = join(HOME, 'dev');
|
|
834
870
|
vaultConfig.mode = 'local';
|
|
835
871
|
|
|
836
|
-
// Telemetry opt-in
|
|
837
|
-
console.log(`\n ${dim('[3/6]')}${bold(' Anonymous error reporting\n')}`);
|
|
838
|
-
verbose(userLevel, 'Entirely optional — works identically either way.\n');
|
|
839
|
-
console.log(dim(' When enabled, unhandled errors send a minimal event (type, tool name,'));
|
|
840
|
-
console.log(dim(' version, platform) to help diagnose issues. No vault content,'));
|
|
841
|
-
console.log(dim(' file paths, or personal data is ever sent. Off by default.'));
|
|
842
|
-
console.log(dim(` Full schema: ${MARKETING_URL}/telemetry`));
|
|
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';
|
|
854
|
-
}
|
|
855
|
-
vaultConfig.telemetry = telemetryEnabled;
|
|
856
|
-
console.log(
|
|
857
|
-
` ${telemetryEnabled ? green('+') : dim('-')} Telemetry: ${telemetryEnabled ? 'enabled' : 'disabled'}`
|
|
858
|
-
);
|
|
859
|
-
|
|
860
872
|
assertNotTestMode(configPath);
|
|
861
873
|
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
|
|
862
874
|
console.log(`\n ${green('+')} Wrote ${configPath}`);
|
|
@@ -864,11 +876,11 @@ async function runSetup() {
|
|
|
864
876
|
// Pre-download embedding model with spinner (skip with --skip-embeddings)
|
|
865
877
|
const skipEmbeddings = flags.has('--skip-embeddings');
|
|
866
878
|
if (skipEmbeddings) {
|
|
867
|
-
console.log(`\n ${dim('[
|
|
879
|
+
console.log(`\n ${dim('[3/7]')}${bold(' Embedding model')} ${dim('(skipped)')}`);
|
|
868
880
|
console.log(dim(' FTS-only mode — full-text search works, semantic search disabled.'));
|
|
869
881
|
console.log(dim(' To enable later: context-vault setup (without --skip-embeddings)'));
|
|
870
882
|
} else {
|
|
871
|
-
console.log(`\n ${dim('[
|
|
883
|
+
console.log(`\n ${dim('[3/7]')}${bold(' Downloading embedding model...')}`);
|
|
872
884
|
verbose(userLevel, 'Enables meaning-based search. ~22MB download, runs fully offline.');
|
|
873
885
|
console.log(dim(' all-MiniLM-L6-v2 (~22MB, one-time download)'));
|
|
874
886
|
console.log(dim(` Slow connection? Re-run with --skip-embeddings (enables FTS-only mode)\n`));
|
|
@@ -951,7 +963,7 @@ async function runSetup() {
|
|
|
951
963
|
}
|
|
952
964
|
|
|
953
965
|
// Configure each tool — always pass vault dir explicitly to prevent config drift
|
|
954
|
-
console.log(`\n ${dim('[
|
|
966
|
+
console.log(`\n ${dim('[4/7]')}${bold(' Configuring tools...\n')}`);
|
|
955
967
|
verbose(userLevel, 'Writing config so your AI tool can find your vault.\n');
|
|
956
968
|
const results = [];
|
|
957
969
|
const customVaultDir = resolvedVaultDir;
|
|
@@ -973,116 +985,77 @@ async function runSetup() {
|
|
|
973
985
|
}
|
|
974
986
|
}
|
|
975
987
|
|
|
976
|
-
// Claude Code hooks (
|
|
988
|
+
// Claude Code extras: hooks, skills, rules (bundled into one step)
|
|
989
|
+
console.log(`\n ${dim('[5/7]')}${bold(' Extras...\n')}`);
|
|
977
990
|
const claudeConfigured = results.some((r) => r.ok && r.tool.id === 'claude-code');
|
|
978
991
|
const hookFlag = flags.has('--hooks');
|
|
992
|
+
const configuredTools = results.filter((r) => r.ok).map((r) => r.tool);
|
|
993
|
+
const installedRulesPaths = [];
|
|
994
|
+
|
|
979
995
|
if (claudeConfigured) {
|
|
980
|
-
//
|
|
981
|
-
let
|
|
982
|
-
if (!hookFlag && !isNonInteractive) {
|
|
983
|
-
console.log();
|
|
984
|
-
console.log(dim('
|
|
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."));
|
|
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.'));
|
|
987
1001
|
console.log();
|
|
988
|
-
const answer = await prompt(' Install Claude Code
|
|
989
|
-
|
|
1002
|
+
const answer = await prompt(' Install Claude Code hooks? (Y/n):', 'Y');
|
|
1003
|
+
installHooks = answer.toLowerCase() !== 'n';
|
|
990
1004
|
}
|
|
991
|
-
if (
|
|
1005
|
+
if (installHooks) {
|
|
992
1006
|
try {
|
|
993
|
-
const
|
|
994
|
-
if (
|
|
995
|
-
console.log(`\n ${green('+')} Memory hook installed`);
|
|
996
|
-
}
|
|
1007
|
+
const hookInstalled = installClaudeHook();
|
|
1008
|
+
if (hookInstalled) console.log(` ${green('+')} Memory recall hook installed`);
|
|
997
1009
|
} catch (e) {
|
|
998
|
-
console.log(
|
|
1010
|
+
console.log(` ${red('x')} Memory hook failed: ${e.message}`);
|
|
999
1011
|
}
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
console.log(
|
|
1005
|
-
console.log(dim(' Captures files touched, tools used, and decisions made per session.'));
|
|
1006
|
-
console.log();
|
|
1007
|
-
const captureAnswer = await prompt(' Install session capture hook? (Y/n):', 'Y');
|
|
1008
|
-
if (captureAnswer.toLowerCase() !== 'n') {
|
|
1009
|
-
try {
|
|
1010
|
-
const captureInstalled = installSessionCaptureHook();
|
|
1011
|
-
if (captureInstalled) {
|
|
1012
|
-
console.log(` ${green('+')} Session capture hook installed`);
|
|
1013
|
-
}
|
|
1014
|
-
} catch (e) {
|
|
1015
|
-
console.log(` ${red('x')} Session capture hook failed: ${e.message}`);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
} else if (hookFlag) {
|
|
1019
|
-
try {
|
|
1020
|
-
installSessionCaptureHook();
|
|
1021
|
-
} catch {}
|
|
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}`);
|
|
1022
1017
|
}
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
console.log(
|
|
1028
|
-
console.log(dim(' Records tool names and file paths after each tool call (lightweight).'));
|
|
1029
|
-
console.log();
|
|
1030
|
-
const autoCaptureAnswer = await prompt(' Install auto-capture hook? (Y/n):', 'Y');
|
|
1031
|
-
if (autoCaptureAnswer.toLowerCase() !== 'n') {
|
|
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 {}
|
|
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}`);
|
|
1045
1023
|
}
|
|
1046
|
-
} else
|
|
1047
|
-
console.log(dim(`
|
|
1024
|
+
} else {
|
|
1025
|
+
console.log(dim(` Hooks skipped. Install later: context-vault hooks install`));
|
|
1048
1026
|
}
|
|
1049
|
-
}
|
|
1050
1027
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
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
|
+
}
|
|
1060
1038
|
if (installSkillsFlag) {
|
|
1061
1039
|
try {
|
|
1062
1040
|
const names = installSkills();
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
console.log(`\n ${green('+')} ${name} skill installed`);
|
|
1066
|
-
}
|
|
1041
|
+
for (const name of names) {
|
|
1042
|
+
console.log(` ${green('+')} ${name} skill installed`);
|
|
1067
1043
|
}
|
|
1068
1044
|
} catch (e) {
|
|
1069
|
-
console.log(
|
|
1045
|
+
console.log(` ${red('x')} Skills install failed: ${e.message}`);
|
|
1070
1046
|
}
|
|
1071
1047
|
} else {
|
|
1072
|
-
console.log(dim(`
|
|
1048
|
+
console.log(dim(` Skills skipped. Install later: context-vault skills install`));
|
|
1073
1049
|
}
|
|
1074
1050
|
}
|
|
1075
1051
|
|
|
1076
|
-
// Agent rules installation
|
|
1077
|
-
const configuredTools = results.filter((r) => r.ok).map((r) => r.tool);
|
|
1078
|
-
const installedRulesPaths = [];
|
|
1052
|
+
// Agent rules installation
|
|
1079
1053
|
if (configuredTools.length > 0 && !flags.has('--no-rules')) {
|
|
1080
|
-
let installRules = isNonInteractive;
|
|
1081
|
-
if (!isNonInteractive) {
|
|
1054
|
+
let installRules = isNonInteractive || useRecommendedDefaults;
|
|
1055
|
+
if (!isNonInteractive && !useRecommendedDefaults) {
|
|
1082
1056
|
console.log();
|
|
1083
1057
|
console.log(dim(' Install agent rules? (recommended)'));
|
|
1084
|
-
console.log(dim(' Teaches your AI agent when and how to save knowledge to the vault'));
|
|
1085
|
-
console.log(dim(' automatically — the key to building useful memory over time.'));
|
|
1058
|
+
console.log(dim(' Teaches your AI agent when and how to save knowledge to the vault.'));
|
|
1086
1059
|
console.log();
|
|
1087
1060
|
const rulesAnswer = await prompt(' Install agent rules? (Y/n):', 'Y');
|
|
1088
1061
|
installRules = rulesAnswer.toLowerCase() !== 'n';
|
|
@@ -1095,21 +1068,21 @@ async function runSetup() {
|
|
|
1095
1068
|
const installed = installAgentRulesForTool(tool, rulesContent);
|
|
1096
1069
|
const rulesPath = getRulesPathForTool(tool);
|
|
1097
1070
|
if (installed) {
|
|
1098
|
-
console.log(` ${green('+')} ${tool.name}
|
|
1071
|
+
console.log(` ${green('+')} ${tool.name} agent rules installed`);
|
|
1099
1072
|
if (rulesPath) {
|
|
1100
1073
|
console.log(` ${dim(rulesPath)}`);
|
|
1101
1074
|
installedRulesPaths.push({ tool: tool.name, path: rulesPath });
|
|
1102
1075
|
}
|
|
1103
1076
|
}
|
|
1104
1077
|
} catch (e) {
|
|
1105
|
-
console.log(` ${red('x')} ${tool.name}
|
|
1078
|
+
console.log(` ${red('x')} ${tool.name} rules: ${e.message}`);
|
|
1106
1079
|
}
|
|
1107
1080
|
}
|
|
1108
1081
|
} else {
|
|
1109
|
-
console.log(dim(' Agent rules file not found in package
|
|
1082
|
+
console.log(dim(' Agent rules file not found in package.'));
|
|
1110
1083
|
}
|
|
1111
1084
|
} else {
|
|
1112
|
-
console.log(dim('
|
|
1085
|
+
console.log(dim(' Rules skipped. Install later: context-vault rules install'));
|
|
1113
1086
|
}
|
|
1114
1087
|
} else if (flags.has('--no-rules')) {
|
|
1115
1088
|
console.log(dim(' Agent rules skipped (--no-rules)'));
|
|
@@ -1123,8 +1096,36 @@ async function runSetup() {
|
|
|
1123
1096
|
);
|
|
1124
1097
|
}
|
|
1125
1098
|
|
|
1099
|
+
// Telemetry opt-in (moved to end, after user has seen value)
|
|
1100
|
+
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();
|
|
1107
|
+
|
|
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
|
|
1114
|
+
);
|
|
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
|
+
|
|
1123
|
+
// Re-write config with telemetry setting
|
|
1124
|
+
assertNotTestMode(configPath);
|
|
1125
|
+
writeFileSync(configPath, JSON.stringify(vaultConfig, null, 2) + '\n');
|
|
1126
|
+
|
|
1126
1127
|
// Health check
|
|
1127
|
-
console.log(`\n ${dim('[
|
|
1128
|
+
console.log(`\n ${dim('[7/7]')}${bold(' Health check...')}\n`);
|
|
1128
1129
|
verbose(userLevel, 'Verifying vault, config, and database are accessible.\n');
|
|
1129
1130
|
const okResults = results.filter((r) => r.ok);
|
|
1130
1131
|
|
|
@@ -1894,7 +1895,11 @@ async function runSwitch() {
|
|
|
1894
1895
|
}
|
|
1895
1896
|
|
|
1896
1897
|
async function runReindex() {
|
|
1897
|
-
|
|
1898
|
+
const dryRun = flags.has('--dry-run');
|
|
1899
|
+
const kindIdx = args.indexOf('--kind');
|
|
1900
|
+
const kindFilter = kindIdx !== -1 && args[kindIdx + 1] ? args[kindIdx + 1] : null;
|
|
1901
|
+
|
|
1902
|
+
console.log(dim(dryRun ? 'Analyzing vault (dry run)...' : 'Loading vault...'));
|
|
1898
1903
|
|
|
1899
1904
|
const { resolveConfig } = await import('@context-vault/core/config');
|
|
1900
1905
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
@@ -1920,14 +1925,34 @@ async function runReindex() {
|
|
|
1920
1925
|
deleteVec: (r) => deleteVec(stmts, r),
|
|
1921
1926
|
};
|
|
1922
1927
|
|
|
1923
|
-
const
|
|
1928
|
+
const reindexOpts = {
|
|
1929
|
+
fullSync: true,
|
|
1930
|
+
indexingConfig: config.indexing,
|
|
1931
|
+
dryRun,
|
|
1932
|
+
kindFilter,
|
|
1933
|
+
};
|
|
1934
|
+
|
|
1935
|
+
const stats = await reindex(ctx, reindexOpts);
|
|
1924
1936
|
|
|
1925
1937
|
db.close();
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1938
|
+
|
|
1939
|
+
if (dryRun) {
|
|
1940
|
+
console.log(yellow('Dry run results (no changes made):'));
|
|
1941
|
+
console.log(` Would index: ${stats.added}`);
|
|
1942
|
+
console.log(` Would skip: ${stats.skippedIndexing ?? 0}`);
|
|
1943
|
+
} else {
|
|
1944
|
+
console.log(green('✓ Reindex complete'));
|
|
1945
|
+
console.log(` ${green('+')} ${stats.added} added`);
|
|
1946
|
+
console.log(` ${yellow('~')} ${stats.updated} updated`);
|
|
1947
|
+
console.log(` ${red('-')} ${stats.removed} removed`);
|
|
1948
|
+
console.log(` ${dim('·')} ${stats.unchanged} unchanged`);
|
|
1949
|
+
if (stats.skippedIndexing) {
|
|
1950
|
+
console.log(` ${dim('○')} ${stats.skippedIndexing} skipped indexing`);
|
|
1951
|
+
}
|
|
1952
|
+
if (stats.embeddingsCleared) {
|
|
1953
|
+
console.log(` ${dim('⊘')} ${stats.embeddingsCleared} embeddings cleared`);
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1931
1956
|
}
|
|
1932
1957
|
|
|
1933
1958
|
async function runMigrateDirs() {
|
|
@@ -2278,7 +2303,8 @@ async function runStatus() {
|
|
|
2278
2303
|
const filled = maxCount > 0 ? Math.round((c / maxCount) * BAR_WIDTH) : 0;
|
|
2279
2304
|
const bar = '█'.repeat(filled) + '░'.repeat(BAR_WIDTH - filled);
|
|
2280
2305
|
const countStr = String(c).padStart(4);
|
|
2281
|
-
const
|
|
2306
|
+
const IRREGULAR_PLURALS = { activity: 'activities', inbox: 'inboxes', index: 'indexes', match: 'matches' };
|
|
2307
|
+
const plural = IRREGULAR_PLURALS[kind] || (kind.endsWith('s') ? kind : kind + 's');
|
|
2282
2308
|
console.log(` ${dim(bar)} ${countStr} ${plural}`);
|
|
2283
2309
|
}
|
|
2284
2310
|
} else {
|
|
@@ -3796,6 +3822,78 @@ async function runSessionEnd() {
|
|
|
3796
3822
|
meta: { session_id: session_id ?? null, cwd, message_count },
|
|
3797
3823
|
});
|
|
3798
3824
|
console.log(`context-vault session captured — id: ${entry.id}`);
|
|
3825
|
+
|
|
3826
|
+
// ── Auto-insight extraction ──────────────────────────────────────────────
|
|
3827
|
+
const aiConfig = config.autoInsights ?? { enabled: true, patterns: ['★ Insight'], minChars: 50, maxPerSession: 5, tier: 'working' };
|
|
3828
|
+
if (aiConfig.enabled !== false) {
|
|
3829
|
+
try {
|
|
3830
|
+
const patterns = aiConfig.patterns ?? ['★ Insight'];
|
|
3831
|
+
const minChars = aiConfig.minChars ?? 50;
|
|
3832
|
+
const maxInsights = aiConfig.maxPerSession ?? 5;
|
|
3833
|
+
const defaultTier = aiConfig.tier ?? 'working';
|
|
3834
|
+
|
|
3835
|
+
// Build regex for all configured patterns
|
|
3836
|
+
const escapedPatterns = patterns.map((p) => p.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
3837
|
+
const patternRe = new RegExp(
|
|
3838
|
+
`(?:${escapedPatterns.join('|')})[─\\s]*\`?\\n([\\s\\S]*?)\\n\`?─{10,}`,
|
|
3839
|
+
'g'
|
|
3840
|
+
);
|
|
3841
|
+
|
|
3842
|
+
const insightBlocks = [];
|
|
3843
|
+
for (const turn of turns) {
|
|
3844
|
+
if (turn.role !== 'assistant') continue;
|
|
3845
|
+
const text = extractText(turn);
|
|
3846
|
+
if (!text) continue;
|
|
3847
|
+
for (const m of text.matchAll(patternRe)) {
|
|
3848
|
+
const insightBody = m[1].trim();
|
|
3849
|
+
if (insightBody.length >= minChars && insightBlocks.length < maxInsights) {
|
|
3850
|
+
insightBlocks.push(insightBody);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
}
|
|
3854
|
+
|
|
3855
|
+
if (insightBlocks.length > 0) {
|
|
3856
|
+
// Check existing auto-insight entries for dedup (by title, lightweight)
|
|
3857
|
+
const existingTitles = new Set();
|
|
3858
|
+
try {
|
|
3859
|
+
const rows = db.prepare(
|
|
3860
|
+
`SELECT title FROM entries WHERE tags LIKE '%auto-insight%' ORDER BY created_at DESC LIMIT 100`
|
|
3861
|
+
).all();
|
|
3862
|
+
for (const r of rows) {
|
|
3863
|
+
if (r.title) existingTitles.add(r.title.toLowerCase());
|
|
3864
|
+
}
|
|
3865
|
+
} catch {}
|
|
3866
|
+
|
|
3867
|
+
let savedCount = 0;
|
|
3868
|
+
for (const insightBody of insightBlocks) {
|
|
3869
|
+
const boldMatch = insightBody.match(/\*\*(.+?)\*\*/);
|
|
3870
|
+
const firstLine = insightBody.split('\n')[0].replace(/\*\*/g, '').trim();
|
|
3871
|
+
const insightTitle = boldMatch ? boldMatch[1].slice(0, 80) : firstLine.slice(0, 80);
|
|
3872
|
+
|
|
3873
|
+
// Skip near-duplicates by title
|
|
3874
|
+
if (existingTitles.has(insightTitle.toLowerCase())) continue;
|
|
3875
|
+
|
|
3876
|
+
const insightTags = ['auto-insight', 'session-insight', `bucket:${project}`];
|
|
3877
|
+
await captureAndIndex(ctx, {
|
|
3878
|
+
kind: 'insight',
|
|
3879
|
+
title: insightTitle,
|
|
3880
|
+
body: insightBody,
|
|
3881
|
+
tags: insightTags,
|
|
3882
|
+
source: `claude-code session ${new Date().toISOString().slice(0, 10)}`,
|
|
3883
|
+
tier: defaultTier,
|
|
3884
|
+
meta: { auto_extracted: true, session_id: session_id ?? null },
|
|
3885
|
+
});
|
|
3886
|
+
existingTitles.add(insightTitle.toLowerCase());
|
|
3887
|
+
savedCount++;
|
|
3888
|
+
}
|
|
3889
|
+
if (savedCount > 0) {
|
|
3890
|
+
console.log(`context-vault auto-insights — ${savedCount} insight${savedCount === 1 ? '' : 's'} saved`);
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
} catch {
|
|
3894
|
+
// Auto-insight extraction is best-effort
|
|
3895
|
+
}
|
|
3896
|
+
}
|
|
3799
3897
|
} catch {
|
|
3800
3898
|
// fail silently — never block session end
|
|
3801
3899
|
} finally {
|
|
@@ -4102,48 +4200,45 @@ function loadAgentRules() {
|
|
|
4102
4200
|
* Returns null for tools with no rules install path.
|
|
4103
4201
|
*/
|
|
4104
4202
|
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;
|
|
4203
|
+
return tool.rulesPath || null;
|
|
4109
4204
|
}
|
|
4110
4205
|
|
|
4111
4206
|
/**
|
|
4112
4207
|
* Install agent rules for a specific tool.
|
|
4113
|
-
*
|
|
4114
|
-
* -
|
|
4115
|
-
* -
|
|
4116
|
-
*
|
|
4117
|
-
* Returns true if installed, false if skipped or already present.
|
|
4208
|
+
* Uses tool.rulesPath and tool.rulesMethod from the TOOLS array.
|
|
4209
|
+
* - 'write' method: writes the file directly (Claude Code, Cursor)
|
|
4210
|
+
* - 'append' method: appends with delimiter markers (Windsurf)
|
|
4211
|
+
* Returns true if installed/updated, false if already up to date or skipped.
|
|
4118
4212
|
*/
|
|
4119
4213
|
function installAgentRulesForTool(tool, rulesContent) {
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4214
|
+
const rulesPath = tool.rulesPath;
|
|
4215
|
+
if (!rulesPath) return false;
|
|
4216
|
+
|
|
4217
|
+
if (tool.rulesMethod === 'write') {
|
|
4123
4218
|
if (existsSync(rulesPath)) {
|
|
4124
4219
|
const existing = readFileSync(rulesPath, 'utf-8');
|
|
4125
4220
|
if (existing.trim() === rulesContent.trim()) return false;
|
|
4126
4221
|
}
|
|
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 });
|
|
4222
|
+
mkdirSync(dirname(rulesPath), { recursive: true });
|
|
4137
4223
|
writeFileSync(rulesPath, rulesContent);
|
|
4138
4224
|
return true;
|
|
4139
4225
|
}
|
|
4140
4226
|
|
|
4141
|
-
if (tool.
|
|
4142
|
-
const rulesPath = join(HOME, '.windsurfrules');
|
|
4227
|
+
if (tool.rulesMethod === 'append') {
|
|
4143
4228
|
const delimited = `\n${RULES_DELIMITER_START}\n${rulesContent}\n${RULES_DELIMITER_END}\n`;
|
|
4144
4229
|
if (existsSync(rulesPath)) {
|
|
4145
4230
|
const existing = readFileSync(rulesPath, 'utf-8');
|
|
4146
|
-
if (existing.includes(RULES_DELIMITER_START))
|
|
4231
|
+
if (existing.includes(RULES_DELIMITER_START)) {
|
|
4232
|
+
const delimiterRegex = new RegExp(
|
|
4233
|
+
`\n?${RULES_DELIMITER_START}[\\s\\S]*?${RULES_DELIMITER_END}\n?`,
|
|
4234
|
+
'g'
|
|
4235
|
+
);
|
|
4236
|
+
const existingSection = existing.match(delimiterRegex)?.[0] || '';
|
|
4237
|
+
if (existingSection.includes(rulesContent.trim())) return false;
|
|
4238
|
+
const cleaned = existing.replace(delimiterRegex, '');
|
|
4239
|
+
writeFileSync(rulesPath, cleaned + delimited);
|
|
4240
|
+
return true;
|
|
4241
|
+
}
|
|
4147
4242
|
writeFileSync(rulesPath, existing + delimited);
|
|
4148
4243
|
} else {
|
|
4149
4244
|
writeFileSync(rulesPath, delimited.trimStart());
|
|
@@ -4151,7 +4246,6 @@ function installAgentRulesForTool(tool, rulesContent) {
|
|
|
4151
4246
|
return true;
|
|
4152
4247
|
}
|
|
4153
4248
|
|
|
4154
|
-
// Other tools: no rules installation path yet
|
|
4155
4249
|
return false;
|
|
4156
4250
|
}
|
|
4157
4251
|
|
|
@@ -4567,19 +4661,28 @@ async function runRules() {
|
|
|
4567
4661
|
console.log();
|
|
4568
4662
|
} else if (sub === 'show') {
|
|
4569
4663
|
const { detected } = await detectAllTools();
|
|
4570
|
-
const
|
|
4571
|
-
if (
|
|
4664
|
+
const toolsWithRules = detected.filter((t) => getRulesPathForTool(t));
|
|
4665
|
+
if (toolsWithRules.length === 0) {
|
|
4572
4666
|
console.log(`\n ${yellow('!')} No supported tool detected.\n`);
|
|
4573
4667
|
process.exit(1);
|
|
4574
4668
|
}
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4669
|
+
let anyShown = false;
|
|
4670
|
+
for (const tool of toolsWithRules) {
|
|
4671
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
4672
|
+
if (!rulesPath || !existsSync(rulesPath)) {
|
|
4673
|
+
console.log(`\n ${yellow('!')} No rules file installed for ${tool.name}.`);
|
|
4674
|
+
console.log(dim(` Run: context-vault rules install`));
|
|
4675
|
+
continue;
|
|
4676
|
+
}
|
|
4677
|
+
if (anyShown) console.log(dim(' ' + '─'.repeat(40)));
|
|
4678
|
+
console.log(`\n ${dim(`${tool.name}: ${rulesPath}`)}\n`);
|
|
4679
|
+
console.log(readFileSync(rulesPath, 'utf-8'));
|
|
4680
|
+
anyShown = true;
|
|
4681
|
+
}
|
|
4682
|
+
if (!anyShown) {
|
|
4683
|
+
console.log(dim(`\n Run: context-vault rules install\n`));
|
|
4579
4684
|
process.exit(1);
|
|
4580
4685
|
}
|
|
4581
|
-
console.log(`\n ${dim(`${tool.name}: ${rulesPath}`)}\n`);
|
|
4582
|
-
console.log(readFileSync(rulesPath, 'utf-8'));
|
|
4583
4686
|
} else if (sub === 'path') {
|
|
4584
4687
|
const { detected } = await detectAllTools();
|
|
4585
4688
|
const supportedTools = detected.filter((t) => getRulesPathForTool(t));
|
|
@@ -4601,42 +4704,44 @@ async function runRules() {
|
|
|
4601
4704
|
process.exit(1);
|
|
4602
4705
|
}
|
|
4603
4706
|
const { detected } = await detectAllTools();
|
|
4604
|
-
const
|
|
4605
|
-
if (
|
|
4707
|
+
const toolsWithRules = detected.filter((t) => getRulesPathForTool(t));
|
|
4708
|
+
if (toolsWithRules.length === 0) {
|
|
4606
4709
|
console.log(`\n ${yellow('!')} No supported tool detected.\n`);
|
|
4607
4710
|
process.exit(1);
|
|
4608
4711
|
}
|
|
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
|
-
|
|
4712
|
+
for (const tool of toolsWithRules) {
|
|
4713
|
+
const rulesPath = getRulesPathForTool(tool);
|
|
4714
|
+
if (!rulesPath || !existsSync(rulesPath)) {
|
|
4715
|
+
console.log(`\n ${yellow('!')} No rules file installed for ${tool.name}.`);
|
|
4716
|
+
console.log(dim(` Run: context-vault rules install`));
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
const installed = readFileSync(rulesPath, 'utf-8');
|
|
4720
|
+
if (installed.trim() === bundled.trim()) {
|
|
4721
|
+
console.log(`\n ${green('✓')} ${tool.name}: rules are up to date (${rulesPath})`);
|
|
4722
|
+
} else {
|
|
4723
|
+
console.log(`\n ${yellow('!')} ${tool.name}: installed rules differ from bundled version.`);
|
|
4724
|
+
console.log(` ${dim(rulesPath)}\n`);
|
|
4725
|
+
const installedLines = installed.split('\n');
|
|
4726
|
+
const bundledLines = bundled.split('\n');
|
|
4727
|
+
const maxLines = Math.max(installedLines.length, bundledLines.length);
|
|
4728
|
+
for (let i = 0; i < maxLines; i++) {
|
|
4729
|
+
const a = installedLines[i];
|
|
4730
|
+
const b = bundledLines[i];
|
|
4731
|
+
if (a === undefined) {
|
|
4732
|
+
console.log(` ${green('+')} ${b}`);
|
|
4733
|
+
} else if (b === undefined) {
|
|
4734
|
+
console.log(` ${red('-')} ${a}`);
|
|
4735
|
+
} else if (a !== b) {
|
|
4736
|
+
console.log(` ${red('-')} ${a}`);
|
|
4737
|
+
console.log(` ${green('+')} ${b}`);
|
|
4738
|
+
}
|
|
4634
4739
|
}
|
|
4740
|
+
console.log();
|
|
4741
|
+
console.log(dim(' To upgrade: context-vault rules install'));
|
|
4635
4742
|
}
|
|
4636
|
-
console.log();
|
|
4637
|
-
console.log(dim(' To upgrade: context-vault rules install'));
|
|
4638
|
-
console.log();
|
|
4639
4743
|
}
|
|
4744
|
+
console.log();
|
|
4640
4745
|
} else {
|
|
4641
4746
|
console.log(`
|
|
4642
4747
|
${bold('context-vault rules')} <command>
|