context-vault 3.13.0 → 3.16.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.
Files changed (94) hide show
  1. package/bin/cli.js +263 -414
  2. package/dist/error-log.d.ts +2 -0
  3. package/dist/error-log.d.ts.map +1 -1
  4. package/dist/error-log.js +31 -1
  5. package/dist/error-log.js.map +1 -1
  6. package/dist/register-tools.d.ts.map +1 -1
  7. package/dist/register-tools.js +4 -0
  8. package/dist/register-tools.js.map +1 -1
  9. package/dist/server.js +23 -426
  10. package/dist/server.js.map +1 -1
  11. package/dist/status.d.ts.map +1 -1
  12. package/dist/status.js +17 -0
  13. package/dist/status.js.map +1 -1
  14. package/dist/tools/context-status.d.ts.map +1 -1
  15. package/dist/tools/context-status.js +26 -1
  16. package/dist/tools/context-status.js.map +1 -1
  17. package/dist/tools/delete-context.d.ts +1 -1
  18. package/dist/tools/delete-context.d.ts.map +1 -1
  19. package/dist/tools/delete-context.js +15 -2
  20. package/dist/tools/delete-context.js.map +1 -1
  21. package/dist/tools/get-context.d.ts.map +1 -1
  22. package/dist/tools/get-context.js +3 -2
  23. package/dist/tools/get-context.js.map +1 -1
  24. package/dist/tools/list-context.d.ts +7 -15
  25. package/dist/tools/list-context.d.ts.map +1 -1
  26. package/dist/tools/list-context.js +570 -111
  27. package/dist/tools/list-context.js.map +1 -1
  28. package/dist/tools/publish-to-team.js +1 -1
  29. package/dist/tools/publish-to-team.js.map +1 -1
  30. package/dist/tools/save-context.js +2 -2
  31. package/dist/tools/save-context.js.map +1 -1
  32. package/dist/tools/session-start.d.ts +20 -7
  33. package/dist/tools/session-start.d.ts.map +1 -1
  34. package/dist/tools/session-start.js +406 -439
  35. package/dist/tools/session-start.js.map +1 -1
  36. package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -1
  37. package/node_modules/@context-vault/core/dist/capture.js +4 -0
  38. package/node_modules/@context-vault/core/dist/capture.js.map +1 -1
  39. package/node_modules/@context-vault/core/dist/categories.d.ts.map +1 -1
  40. package/node_modules/@context-vault/core/dist/categories.js +8 -0
  41. package/node_modules/@context-vault/core/dist/categories.js.map +1 -1
  42. package/node_modules/@context-vault/core/dist/compact.d.ts +38 -0
  43. package/node_modules/@context-vault/core/dist/compact.d.ts.map +1 -0
  44. package/node_modules/@context-vault/core/dist/compact.js +127 -0
  45. package/node_modules/@context-vault/core/dist/compact.js.map +1 -0
  46. package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -1
  47. package/node_modules/@context-vault/core/dist/config.js +12 -0
  48. package/node_modules/@context-vault/core/dist/config.js.map +1 -1
  49. package/node_modules/@context-vault/core/dist/db.d.ts +1 -1
  50. package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -1
  51. package/node_modules/@context-vault/core/dist/db.js +40 -4
  52. package/node_modules/@context-vault/core/dist/db.js.map +1 -1
  53. package/node_modules/@context-vault/core/dist/main.d.ts +6 -2
  54. package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -1
  55. package/node_modules/@context-vault/core/dist/main.js +5 -1
  56. package/node_modules/@context-vault/core/dist/main.js.map +1 -1
  57. package/node_modules/@context-vault/core/dist/search.d.ts +13 -1
  58. package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -1
  59. package/node_modules/@context-vault/core/dist/search.js +50 -5
  60. package/node_modules/@context-vault/core/dist/search.js.map +1 -1
  61. package/node_modules/@context-vault/core/dist/tier-analysis.d.ts +36 -0
  62. package/node_modules/@context-vault/core/dist/tier-analysis.d.ts.map +1 -0
  63. package/node_modules/@context-vault/core/dist/tier-analysis.js +227 -0
  64. package/node_modules/@context-vault/core/dist/tier-analysis.js.map +1 -0
  65. package/node_modules/@context-vault/core/dist/types.d.ts +12 -0
  66. package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -1
  67. package/node_modules/@context-vault/core/dist/watch.d.ts +21 -0
  68. package/node_modules/@context-vault/core/dist/watch.d.ts.map +1 -0
  69. package/node_modules/@context-vault/core/dist/watch.js +230 -0
  70. package/node_modules/@context-vault/core/dist/watch.js.map +1 -0
  71. package/node_modules/@context-vault/core/package.json +13 -1
  72. package/node_modules/@context-vault/core/src/capture.ts +4 -0
  73. package/node_modules/@context-vault/core/src/categories.ts +8 -0
  74. package/node_modules/@context-vault/core/src/compact.ts +183 -0
  75. package/node_modules/@context-vault/core/src/config.ts +8 -0
  76. package/node_modules/@context-vault/core/src/db.ts +40 -4
  77. package/node_modules/@context-vault/core/src/main.ts +10 -0
  78. package/node_modules/@context-vault/core/src/search.ts +55 -4
  79. package/node_modules/@context-vault/core/src/tier-analysis.ts +299 -0
  80. package/node_modules/@context-vault/core/src/types.ts +10 -0
  81. package/node_modules/@context-vault/core/src/watch.ts +269 -0
  82. package/package.json +2 -2
  83. package/scripts/postinstall.js +26 -1
  84. package/src/error-log.ts +30 -0
  85. package/src/register-tools.ts +4 -0
  86. package/src/server.ts +23 -423
  87. package/src/status.ts +17 -0
  88. package/src/tools/context-status.ts +30 -1
  89. package/src/tools/delete-context.ts +10 -5
  90. package/src/tools/get-context.ts +3 -2
  91. package/src/tools/list-context.ts +620 -119
  92. package/src/tools/publish-to-team.ts +1 -1
  93. package/src/tools/save-context.ts +2 -2
  94. package/src/tools/session-start.ts +444 -484
package/bin/cli.js CHANGED
@@ -443,7 +443,6 @@ ${bold('Commands:')}
443
443
  ${cyan('status')} Show vault diagnostics
444
444
  ${cyan('doctor')} Diagnose and repair common issues
445
445
  ${cyan('debug')} Generate AI-pasteable debug report
446
- ${cyan('daemon')} start|stop|status Run vault as a shared HTTP daemon (one process, all sessions)
447
446
  ${cyan('restart')} Stop running MCP server processes (client auto-restarts)
448
447
  ${cyan('reconnect')} Fix vault path, kill stale servers, re-register MCP, reindex
449
448
  ${cyan('search')} Search vault entries from CLI
@@ -453,6 +452,7 @@ ${bold('Commands:')}
453
452
  ${cyan('ingest')} <url> Fetch URL and save as vault entry
454
453
  ${cyan('ingest-project')} <path> Scan project directory and register as project entity
455
454
  ${cyan('reindex')} Rebuild search index from knowledge files
455
+ ${cyan('reclassify')} Move prompt-history entries from knowledge to event category
456
456
  ${cyan('sync')} [dir] Index .context/ files into vault DB (use --dry-run to preview)
457
457
  ${cyan('migrate-dirs')} [--dry-run] Rename plural vault dirs to singular (post-2.18.0)
458
458
  ${cyan('archive')} Archive old ephemeral/event entries (use --dry-run to preview)
@@ -2188,6 +2188,101 @@ async function runReindex() {
2188
2188
  }
2189
2189
  }
2190
2190
 
2191
+ async function runReclassify() {
2192
+ const dryRun = flags.has('--dry-run');
2193
+ const kindFilter = getFlag('--kind');
2194
+
2195
+ const EVENT_KINDS = [
2196
+ 'events', 'activity', 'user-prompts', 'agent',
2197
+ 'handoff', 'outcome', 'session-review', 'session-summary',
2198
+ ];
2199
+
2200
+ const kinds = kindFilter ? [kindFilter] : EVENT_KINDS;
2201
+ const placeholders = kinds.map(() => '?').join(', ');
2202
+
2203
+ const { resolveConfig } = await import('@context-vault/core/config');
2204
+ const { initDatabase, prepareStatements, deleteVec } =
2205
+ await import('@context-vault/core/db');
2206
+
2207
+ const config = resolveConfig();
2208
+ if (!config.vaultDirExists) {
2209
+ console.error(red(`Vault directory not found: ${config.vaultDir}`));
2210
+ console.error('Run ' + cyan('context-vault setup') + ' to configure.');
2211
+ process.exit(1);
2212
+ }
2213
+
2214
+ const db = await initDatabase(config.dbPath);
2215
+
2216
+ const countRow = db
2217
+ .prepare(
2218
+ `SELECT COUNT(*) as cnt FROM vault WHERE kind IN (${placeholders}) AND category = 'knowledge'`
2219
+ )
2220
+ .get(...kinds);
2221
+ const total = countRow.cnt;
2222
+
2223
+ if (total === 0) {
2224
+ console.log(green(' No entries need reclassification.'));
2225
+ db.close();
2226
+ return;
2227
+ }
2228
+
2229
+ if (dryRun) {
2230
+ console.log(`\n ${bold(String(total))} entries would be reclassified from ${yellow('knowledge')} to ${green('event')}:\n`);
2231
+ const breakdown = db
2232
+ .prepare(
2233
+ `SELECT kind, COUNT(*) as cnt FROM vault WHERE kind IN (${placeholders}) AND category = 'knowledge' GROUP BY kind ORDER BY cnt DESC`
2234
+ )
2235
+ .all(...kinds);
2236
+ for (const row of breakdown) {
2237
+ console.log(` ${row.kind}: ${bold(String(row.cnt))}`);
2238
+ }
2239
+ console.log(dim('\n Dry run, no changes made.'));
2240
+ db.close();
2241
+ return;
2242
+ }
2243
+
2244
+ const stmts = prepareStatements(db);
2245
+ const BATCH_SIZE = 1000;
2246
+ let reclassified = 0;
2247
+ let embeddingsRemoved = 0;
2248
+
2249
+ console.log(dim(` Reclassifying ${total} entries...`));
2250
+
2251
+ while (true) {
2252
+ const batch = db
2253
+ .prepare(
2254
+ `SELECT rowid, id FROM vault WHERE kind IN (${placeholders}) AND category = 'knowledge' LIMIT ${BATCH_SIZE}`
2255
+ )
2256
+ .all(...kinds);
2257
+
2258
+ if (batch.length === 0) break;
2259
+
2260
+ const rowids = batch.map((r) => r.rowid);
2261
+ const batchPlaceholders = rowids.map(() => '?').join(', ');
2262
+
2263
+ db.prepare(
2264
+ `UPDATE vault SET category = 'event', indexed = 0 WHERE rowid IN (${batchPlaceholders})`
2265
+ ).run(...rowids);
2266
+
2267
+ for (const rowid of rowids) {
2268
+ try {
2269
+ deleteVec(stmts, rowid);
2270
+ embeddingsRemoved++;
2271
+ } catch {
2272
+ // Entry may not have had an embedding
2273
+ }
2274
+ }
2275
+
2276
+ reclassified += batch.length;
2277
+ process.stdout.write(`\r Progress: ${reclassified}/${total}`);
2278
+ }
2279
+
2280
+ db.close();
2281
+ console.log('');
2282
+ console.log(green(` Reclassified ${reclassified} entries from knowledge to event`));
2283
+ console.log(` Embeddings removed: ${embeddingsRemoved}`);
2284
+ }
2285
+
2191
2286
  async function runSync() {
2192
2287
  const dryRun = flags.has('--dry-run');
2193
2288
  const positional = args.slice(1).find((a) => !a.startsWith('--'));
@@ -2697,6 +2792,7 @@ async function runRestore() {
2697
2792
  await import('@context-vault/core/db');
2698
2793
  const { embed } = await import('@context-vault/core/embed');
2699
2794
  const { restoreEntry } = await import('../dist/archive.js');
2795
+ const { restoreCompactedBody } = await import('@context-vault/core/compact');
2700
2796
 
2701
2797
  const config = resolveConfig();
2702
2798
  if (!config.vaultDirExists) {
@@ -2716,6 +2812,26 @@ async function runRestore() {
2716
2812
  deleteVec: (r) => deleteVec(stmts, r),
2717
2813
  };
2718
2814
 
2815
+ // First try restoring from compacted gz archive
2816
+ const fullBody = restoreCompactedBody(config.vaultDir, entryId);
2817
+ if (fullBody !== null) {
2818
+ // Entry was compacted, restore its body and re-enable indexing
2819
+ db.prepare('UPDATE vault SET body = ?, indexed = 1, updated_at = ? WHERE id = ?')
2820
+ .run(fullBody, new Date().toISOString(), entryId);
2821
+
2822
+ // Remove the gz archive file
2823
+ const { unlinkSync } = await import('node:fs');
2824
+ const { join } = await import('node:path');
2825
+ const gzPath = join(config.vaultDir, '_archive', `${entryId}.md.gz`);
2826
+ try { unlinkSync(gzPath); } catch {}
2827
+
2828
+ db.close();
2829
+ console.log(green(` ✓ Restored compacted entry: ${entryId}`));
2830
+ console.log(dim(' Full body recovered from gzip archive. Entry will be re-indexed on next search.'));
2831
+ return;
2832
+ }
2833
+
2834
+ // Fall back to standard archive restore
2719
2835
  const result = await restoreEntry(ctx, entryId);
2720
2836
  db.close();
2721
2837
 
@@ -2728,6 +2844,65 @@ async function runRestore() {
2728
2844
  }
2729
2845
  }
2730
2846
 
2847
+ async function runCompact() {
2848
+ const dryRun = flags.has('--dry-run');
2849
+ const tierFlag = getFlag('--tier');
2850
+
2851
+ const { resolveConfig } = await import('@context-vault/core/config');
2852
+ const { initDatabase, prepareStatements, deleteVec, deleteCtxVec } =
2853
+ await import('@context-vault/core/db');
2854
+ const { compact } = await import('@context-vault/core/compact');
2855
+
2856
+ const config = resolveConfig();
2857
+ if (!config.vaultDirExists) {
2858
+ console.error(red(`Vault directory not found: ${config.vaultDir}`));
2859
+ console.error('Run ' + cyan('context-vault setup') + ' to configure.');
2860
+ process.exit(1);
2861
+ }
2862
+
2863
+ const db = await initDatabase(config.dbPath);
2864
+ const stmts = prepareStatements(db);
2865
+ const ctx = {
2866
+ db,
2867
+ config,
2868
+ stmts,
2869
+ deleteVec: (r) => deleteVec(stmts, r),
2870
+ deleteCtxVec: (r) => deleteCtxVec(stmts, r),
2871
+ };
2872
+
2873
+ const options = { dryRun, tier: tierFlag === 'cold' ? 'cold' : undefined };
2874
+ const result = await compact(ctx, options);
2875
+ db.close();
2876
+
2877
+ if (result.candidates === 0) {
2878
+ console.log(green(' No entries eligible for compaction.'));
2879
+ console.log(dim(`\n Criteria: recall_count=0, heat_tier cold/null, age > ${tierFlag === 'cold' ? '30' : '90'} days`));
2880
+ return;
2881
+ }
2882
+
2883
+ if (dryRun) {
2884
+ console.log(
2885
+ `\n ${bold(String(result.candidates))} ${result.candidates === 1 ? 'entry' : 'entries'} eligible for compaction:\n`
2886
+ );
2887
+ let totalBytes = 0;
2888
+ for (const e of result.entries) {
2889
+ const label = e.title ? `${e.kind}: ${e.title.slice(0, 60)}` : `${e.kind} (${e.id})`;
2890
+ const sizeKb = (e.bodySize / 1024).toFixed(1);
2891
+ console.log(` ${dim('-')} ${label} ${dim(`(heat=${e.heat}, age=${e.ageDays}d, body=${sizeKb}KB)`)}`);
2892
+ totalBytes += e.bodySize;
2893
+ }
2894
+ const totalKb = (totalBytes / 1024).toFixed(1);
2895
+ console.log(dim(`\n Total body size: ${totalKb}KB`));
2896
+ console.log(dim(' Dry run, no changes made. Remove --dry-run to compact.'));
2897
+ return;
2898
+ }
2899
+
2900
+ console.log(green(` Compacted ${result.compacted} ${result.compacted === 1 ? 'entry' : 'entries'}.`));
2901
+ const reclaimedKb = (result.bytesReclaimed / 1024).toFixed(1);
2902
+ console.log(dim(` Reclaimed ~${reclaimedKb}KB. Bodies archived to _archive/<id>.md.gz`));
2903
+ console.log(dim(' Use context-vault restore <id> to recover full body.'));
2904
+ }
2905
+
2731
2906
  async function runStatus() {
2732
2907
  const { resolveConfig } = await import('@context-vault/core/config');
2733
2908
  const { initDatabase } = await import('@context-vault/core/db');
@@ -6232,116 +6407,6 @@ async function runHealth() {
6232
6407
  if (!healthy) process.exit(1);
6233
6408
  }
6234
6409
 
6235
- async function runRestart() {
6236
- const force = flags.has('--force');
6237
-
6238
- console.log();
6239
- console.log(` ${bold('◇ context-vault restart')}`);
6240
- console.log();
6241
-
6242
- const isWin = platform() === 'win32';
6243
- let psOutput;
6244
- try {
6245
- const psCmd = isWin
6246
- ? 'wmic process where "CommandLine like \'%context-vault%\'" get ProcessId,CommandLine /format:list'
6247
- : 'ps aux';
6248
- psOutput = execSync(psCmd, { encoding: 'utf-8', timeout: 5000 });
6249
- } catch (e) {
6250
- console.error(red(` Failed to list processes: ${e.message}`));
6251
- process.exit(1);
6252
- }
6253
-
6254
- const currentPid = process.pid;
6255
- const serverPids = [];
6256
-
6257
- if (isWin) {
6258
- const pidMatches = psOutput.matchAll(/ProcessId=(\d+)/g);
6259
- for (const m of pidMatches) {
6260
- const pid = parseInt(m[1], 10);
6261
- if (pid !== currentPid) serverPids.push(pid);
6262
- }
6263
- } else {
6264
- const lines = psOutput.split('\n');
6265
- for (const line of lines) {
6266
- const match = line.match(/^\S+\s+(\d+)\s/);
6267
- if (!match) continue;
6268
- const pid = parseInt(match[1], 10);
6269
- if (pid === currentPid) continue;
6270
- if (
6271
- /context-vault.*(serve|stdio|server\/index)/.test(line) ||
6272
- /server\/index\.js.*context-vault/.test(line)
6273
- ) {
6274
- serverPids.push(pid);
6275
- }
6276
- }
6277
- }
6278
-
6279
- if (serverPids.length === 0) {
6280
- console.log(dim(' No running context-vault MCP server processes found.'));
6281
- console.log(dim(' The MCP client will start the server automatically on the next tool call.'));
6282
- console.log();
6283
- return;
6284
- }
6285
-
6286
- console.log(
6287
- ` Found ${serverPids.length} server process${serverPids.length === 1 ? '' : 'es'}: ${dim(serverPids.join(', '))}`
6288
- );
6289
- console.log();
6290
-
6291
- const signal = force ? 'SIGKILL' : 'SIGTERM';
6292
- const killed = [];
6293
- const failed = [];
6294
-
6295
- for (const pid of serverPids) {
6296
- try {
6297
- process.kill(pid, signal);
6298
- killed.push(pid);
6299
- console.log(` ${green('✓')} Sent ${signal} to PID ${pid}`);
6300
- } catch (e) {
6301
- if (e.code === 'ESRCH') {
6302
- console.log(` ${dim('-')} PID ${pid} already gone`);
6303
- } else {
6304
- failed.push(pid);
6305
- console.log(` ${red('✘')} Failed to signal PID ${pid}: ${e.message}`);
6306
- }
6307
- }
6308
- }
6309
-
6310
- if (!force && killed.length > 0) {
6311
- await new Promise((resolve) => setTimeout(resolve, 2000));
6312
-
6313
- for (const pid of killed) {
6314
- try {
6315
- process.kill(pid, 0);
6316
- console.log(` ${yellow('!')} PID ${pid} still running — sending SIGKILL`);
6317
- try {
6318
- process.kill(pid, 'SIGKILL');
6319
- } catch {}
6320
- } catch {
6321
- // process is gone — expected
6322
- }
6323
- }
6324
- }
6325
-
6326
- console.log();
6327
-
6328
- if (failed.length > 0) {
6329
- console.log(
6330
- red(
6331
- ` Could not stop ${failed.length} process${failed.length === 1 ? '' : 'es'}. Try --force.`
6332
- )
6333
- );
6334
- process.exit(1);
6335
- } else {
6336
- console.log(
6337
- green(' Server stopped.') +
6338
- dim(' The MCP client will restart it automatically on the next tool call.')
6339
- );
6340
- }
6341
-
6342
- console.log();
6343
- }
6344
-
6345
6410
  async function runReconnect() {
6346
6411
  console.log();
6347
6412
  console.log(` ${bold('◇ context-vault reconnect')}`);
@@ -6681,321 +6746,94 @@ async function runDebug() {
6681
6746
  console.log(lines.join('\n'));
6682
6747
  }
6683
6748
 
6684
- async function runDaemon() {
6685
- const sub = args[1];
6686
- const pidPath = join(HOME, '.context-mcp', 'daemon.pid');
6687
- const defaultPort = 3377;
6688
-
6689
- function readPid() {
6690
- try {
6691
- return JSON.parse(readFileSync(pidPath, 'utf-8'));
6692
- } catch {
6693
- return null;
6694
- }
6695
- }
6696
-
6697
- function isAlive(pid) {
6698
- try {
6699
- process.kill(pid, 0);
6700
- return true;
6701
- } catch {
6702
- return false;
6703
- }
6704
- }
6705
-
6706
- async function pollHealth(port, timeoutMs = 5000) {
6707
- const start = Date.now();
6708
- while (Date.now() - start < timeoutMs) {
6709
- try {
6710
- const res = await fetch(`http://localhost:${port}/health`);
6711
- if (res.ok) return await res.json();
6712
- } catch {}
6713
- await new Promise((r) => setTimeout(r, 200));
6714
- }
6715
- return null;
6716
- }
6717
-
6718
- function configureClaudeDaemon(port) {
6719
- const env = { ...process.env };
6720
- delete env.CLAUDECODE;
6721
-
6722
- for (const oldName of ['context-mcp', 'context-vault']) {
6723
- try {
6724
- execFileSync('claude', ['mcp', 'remove', oldName, '-s', 'user'], {
6725
- stdio: 'pipe',
6726
- env,
6727
- });
6728
- } catch {}
6729
- }
6749
+ async function runTier() {
6750
+ const { resolveConfig } = await import('@context-vault/core/config');
6751
+ const { initDatabase, prepareStatements, insertVec, deleteVec, insertCtxVec, deleteCtxVec } = await import('@context-vault/core/db');
6752
+ const { runTierAnalysis } = await import('@context-vault/core/tier-analysis');
6730
6753
 
6731
- try {
6732
- execFileSync(
6733
- 'claude',
6734
- [
6735
- 'mcp', 'add', '-s', 'user',
6736
- '--transport', 'http',
6737
- 'context-vault',
6738
- `http://localhost:${port}/mcp`,
6739
- ],
6740
- { stdio: 'pipe', env }
6741
- );
6742
- } catch (e) {
6743
- const stderr = e.stderr?.toString().trim();
6744
- throw new Error(stderr || e.message);
6745
- }
6746
- }
6754
+ const config = resolveConfig();
6755
+ const dryRun = args.includes('--dry-run');
6756
+ const hotThreshold = parseInt(argValue('--hot-threshold') || '5', 10);
6757
+ const coldDays = parseInt(argValue('--cold-days') || '30', 10);
6758
+ const bundleThreshold = parseInt(argValue('--bundle-threshold') || '3', 10);
6747
6759
 
6748
- if (!sub || sub === '--help') {
6749
- console.log(`
6750
- ${bold('◇ context-vault daemon')} ${dim('— shared HTTP daemon')}
6751
-
6752
- ${bold('Subcommands:')}
6753
- ${cyan('start')} [--port PORT] Start the daemon (default port: ${defaultPort})
6754
- ${cyan('stop')} Stop the running daemon
6755
- ${cyan('status')} Show daemon status
6756
- ${cyan('install')} Start daemon + configure Claude Code to use it
6757
- ${cyan('uninstall')} Stop daemon + revert Claude Code to stdio mode
6758
- `);
6759
- return;
6760
+ let db;
6761
+ try {
6762
+ db = await initDatabase(config.dbPath);
6763
+ } catch (e) {
6764
+ console.error(red(` Database not accessible: ${e.message}`));
6765
+ process.exit(1);
6760
6766
  }
6761
6767
 
6762
- if (sub === 'start') {
6763
- const port = parseInt(getFlag('--port') || String(defaultPort), 10);
6764
- const existing = readPid();
6765
-
6766
- if (existing && isAlive(existing.pid)) {
6767
- console.log(` ${green('✓')} Daemon already running (PID ${existing.pid} on port ${existing.port})`);
6768
- return;
6769
- }
6770
-
6771
- if (existing) {
6772
- try { unlinkSync(pidPath); } catch {}
6773
- }
6774
-
6775
- console.log(` Starting daemon on port ${port}...`);
6776
-
6777
- const vaultDir = getFlag('--vault-dir');
6778
- const serverArgs = [SERVER_PATH, '--http', '--port', String(port)];
6779
- if (vaultDir) serverArgs.push('--vault-dir', vaultDir);
6768
+ const stmts = prepareStatements(db);
6769
+ const embedMod = await import('@context-vault/core/embed');
6770
+ const ctx = {
6771
+ db, config, stmts,
6772
+ embed: (text) => embedMod.embed(text),
6773
+ insertVec: (rowid, emb) => insertVec(stmts, rowid, emb),
6774
+ deleteVec: (rowid) => deleteVec(stmts, rowid),
6775
+ insertCtxVec: (rowid, emb) => insertCtxVec(stmts, rowid, emb),
6776
+ deleteCtxVec: (rowid) => deleteCtxVec(stmts, rowid),
6777
+ };
6780
6778
 
6781
- const child = spawn(process.execPath, serverArgs, {
6782
- detached: true,
6783
- stdio: 'ignore',
6784
- env: { ...process.env, NODE_OPTIONS: '--no-warnings=ExperimentalWarning' },
6779
+ let report;
6780
+ try {
6781
+ report = await runTierAnalysis(ctx, {
6782
+ hotThreshold,
6783
+ coldDays,
6784
+ coAccessThreshold: bundleThreshold,
6785
+ dryRun,
6785
6786
  });
6786
- child.unref();
6787
-
6788
- const health = await pollHealth(port);
6789
- if (health) {
6790
- console.log(` ${green('✓')} Daemon started on http://localhost:${port}/mcp (PID ${health.pid})`);
6791
- } else {
6792
- console.error(red(` Failed to start daemon. Check error log: ~/.context-mcp/error.log`));
6793
- process.exit(1);
6794
- }
6795
-
6796
- } else if (sub === 'stop') {
6797
- const existing = readPid();
6798
- if (!existing) {
6799
- console.log(dim(' No daemon running.'));
6800
- return;
6801
- }
6802
-
6803
- if (!isAlive(existing.pid)) {
6804
- console.log(dim(' Stale PID file (process not running). Cleaning up.'));
6805
- try { unlinkSync(pidPath); } catch {}
6806
- return;
6807
- }
6808
-
6809
- console.log(` Stopping daemon (PID ${existing.pid})...`);
6810
- process.kill(existing.pid, 'SIGTERM');
6811
-
6812
- const deadline = Date.now() + 3000;
6813
- while (Date.now() < deadline && isAlive(existing.pid)) {
6814
- await new Promise((r) => setTimeout(r, 200));
6815
- }
6816
-
6817
- if (isAlive(existing.pid)) {
6818
- console.log(` ${yellow('!')} Still alive, sending SIGKILL...`);
6819
- try { process.kill(existing.pid, 'SIGKILL'); } catch {}
6820
- }
6821
-
6822
- try { unlinkSync(pidPath); } catch {}
6823
- console.log(` ${green('✓')} Daemon stopped.`);
6824
-
6825
- } else if (sub === 'status') {
6826
- const existing = readPid();
6827
- if (!existing) {
6828
- console.log(dim(' Not running.'));
6829
- return;
6830
- }
6831
-
6832
- if (!isAlive(existing.pid)) {
6833
- console.log(` ${yellow('!')} Stale PID file (PID ${existing.pid} not found).`);
6834
- return;
6835
- }
6836
-
6837
- try {
6838
- const res = await fetch(`http://localhost:${existing.port}/health`);
6839
- const health = await res.json();
6840
- const uptimeMin = Math.floor(health.uptime / 60);
6841
- console.log(
6842
- ` ${green('●')} Running (PID ${health.pid}, port ${existing.port}, v${health.version}, ` +
6843
- `${health.sessions} session${health.sessions === 1 ? '' : 's'}, uptime ${uptimeMin}m)`
6844
- );
6845
- } catch (e) {
6846
- console.log(` ${yellow('!')} Process alive (PID ${existing.pid}) but health check failed: ${e.message}`);
6847
- }
6848
-
6849
- } else if (sub === 'install') {
6850
- const port = parseInt(getFlag('--port') || String(defaultPort), 10);
6851
-
6852
- // 1. Install LaunchAgent on macOS for auto-start on login
6853
- if (platform() === 'darwin') {
6854
- const launchAgentDir = join(HOME, 'Library', 'LaunchAgents');
6855
- const plistPath = join(launchAgentDir, 'com.context-vault.daemon.plist');
6856
- const logPath = join(HOME, '.context-mcp', 'daemon.log');
6857
- const vaultDir = getFlag('--vault-dir');
6858
- const progArgs = [process.execPath, SERVER_PATH, '--http', '--port', String(port)];
6859
- if (vaultDir) progArgs.push('--vault-dir', vaultDir);
6860
-
6861
- const plist = `<?xml version="1.0" encoding="UTF-8"?>
6862
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
6863
- <plist version="1.0">
6864
- <dict>
6865
- <key>Label</key>
6866
- <string>com.context-vault.daemon</string>
6867
- <key>ProgramArguments</key>
6868
- <array>
6869
- ${progArgs.map(a => ` <string>${a}</string>`).join('\n')}
6870
- </array>
6871
- <key>RunAtLoad</key>
6872
- <true/>
6873
- <key>KeepAlive</key>
6874
- <dict>
6875
- <key>SuccessfulExit</key>
6876
- <false/>
6877
- </dict>
6878
- <key>StandardErrorPath</key>
6879
- <string>${logPath}</string>
6880
- <key>StandardOutPath</key>
6881
- <string>/dev/null</string>
6882
- <key>EnvironmentVariables</key>
6883
- <dict>
6884
- <key>NODE_OPTIONS</key>
6885
- <string>--no-warnings=ExperimentalWarning</string>
6886
- <key>CONTEXT_VAULT_NO_DAEMON</key>
6887
- <string>1</string>
6888
- </dict>
6889
- <key>ThrottleInterval</key>
6890
- <integer>5</integer>
6891
- </dict>
6892
- </plist>`;
6893
-
6894
- mkdirSync(launchAgentDir, { recursive: true });
6895
-
6896
- // Unload existing agent if present
6897
- try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: 'pipe' }); } catch {}
6898
-
6899
- writeFileSync(plistPath, plist);
6900
- try {
6901
- execSync(`launchctl load -w "${plistPath}"`, { stdio: 'pipe' });
6902
- console.log(` ${green('✓')} LaunchAgent installed (auto-starts on login, restarts on crash)`);
6903
- } catch (e) {
6904
- console.log(` ${yellow('!')} LaunchAgent write succeeded but launchctl load failed: ${e.message}`);
6905
- }
6906
-
6907
- // Wait for launchd to start the daemon
6908
- const health = await pollHealth(port, 8000);
6909
- if (health) {
6910
- console.log(` ${green('✓')} Daemon running (PID ${health.pid})`);
6911
- } else {
6912
- console.error(red(` Daemon did not start. Check log: ${logPath}`));
6913
- process.exit(1);
6914
- }
6915
- } else {
6916
- // Non-macOS: direct spawn (no service manager integration yet)
6917
- const existing = readPid();
6918
- if (!existing || !isAlive(existing.pid)) {
6919
- if (existing) try { unlinkSync(pidPath); } catch {}
6920
-
6921
- console.log(` Starting daemon on port ${port}...`);
6922
- const vaultDir = getFlag('--vault-dir');
6923
- const serverArgs = [SERVER_PATH, '--http', '--port', String(port)];
6924
- if (vaultDir) serverArgs.push('--vault-dir', vaultDir);
6925
-
6926
- const child = spawn(process.execPath, serverArgs, {
6927
- detached: true,
6928
- stdio: 'ignore',
6929
- env: { ...process.env, NODE_OPTIONS: '--no-warnings=ExperimentalWarning' },
6930
- });
6931
- child.unref();
6787
+ } finally {
6788
+ db.close();
6789
+ }
6932
6790
 
6933
- const health = await pollHealth(port);
6934
- if (!health) {
6935
- console.error(red(` Failed to start daemon.`));
6936
- process.exit(1);
6937
- }
6938
- console.log(` ${green('✓')} Daemon started (PID ${health.pid})`);
6939
- } else {
6940
- console.log(` ${green('✓')} Daemon already running (PID ${existing.pid})`);
6941
- }
6942
- }
6791
+ console.log();
6792
+ console.log(` ${bold('◇ context-vault tier')}${dryRun ? dim(' (dry run)') : ''}`);
6793
+ console.log();
6794
+ console.log(` Hot entries (${hotThreshold}+ accesses / 7 days): ${bold(String(report.hotEntries.length))}`);
6795
+ console.log(` Cold entries (no access / ${coldDays} days): ${bold(String(report.coldEntries.length))}`);
6796
+ console.log(` Warm resets: ${bold(String(report.warmReset))}`);
6797
+ console.log(` Co-access bundles: ${bold(String(report.bundles.length))}`);
6798
+ console.log(` Access log pruned: ${bold(String(report.accessLogPruned))} rows`);
6943
6799
 
6944
- // 2. Configure Claude Code for HTTP transport
6945
- console.log(` Configuring Claude Code to use HTTP transport...`);
6946
- try {
6947
- configureClaudeDaemon(port);
6948
- console.log(` ${green('✓')} Claude Code configured for http://localhost:${port}/mcp`);
6949
- console.log();
6950
- console.log(dim(' Restart any open Claude Code sessions for the change to take effect.'));
6951
- } catch (e) {
6952
- console.error(red(` Failed to configure Claude Code: ${e.message}`));
6953
- process.exit(1);
6800
+ if (report.hotEntries.length > 0) {
6801
+ console.log();
6802
+ console.log(` ${bold('Hot entries:')}`);
6803
+ for (let i = 0; i < report.hotEntries.length; i++) {
6804
+ const e = report.hotEntries[i];
6805
+ const title = (e.title || '(untitled)').slice(0, 50);
6806
+ console.log(` ${i + 1}. "${title}" (${e.accessCount} accesses)`);
6954
6807
  }
6808
+ }
6955
6809
 
6956
- } else if (sub === 'uninstall') {
6957
- // 1. Revert Claude Code to stdio
6958
- console.log(` Reverting Claude Code to stdio mode...`);
6959
- try {
6960
- const vaultDir = getFlag('--vault-dir') || join(HOME, '.vault');
6961
- const tool = { name: 'Claude Code', configPath: null };
6962
- await configureClaude(tool, vaultDir);
6963
- console.log(` ${green('✓')} Claude Code reverted to stdio`);
6964
- } catch (e) {
6965
- console.error(red(` Failed to reconfigure Claude Code: ${e.message}`));
6810
+ if (report.coldEntries.length > 0) {
6811
+ console.log();
6812
+ console.log(` ${bold('Cold entries:')} ${dim(`(showing first 10 of ${report.coldEntries.length})`)}`);
6813
+ for (let i = 0; i < Math.min(10, report.coldEntries.length); i++) {
6814
+ const e = report.coldEntries[i];
6815
+ const title = (e.title || '(untitled)').slice(0, 50);
6816
+ const last = e.lastAccessed ? e.lastAccessed.slice(0, 10) : 'never';
6817
+ console.log(` ${i + 1}. "${title}" (last: ${last})`);
6966
6818
  }
6819
+ }
6967
6820
 
6968
- // 2. Remove LaunchAgent on macOS
6969
- if (platform() === 'darwin') {
6970
- const plistPath = join(HOME, 'Library', 'LaunchAgents', 'com.context-vault.daemon.plist');
6971
- if (existsSync(plistPath)) {
6972
- try { execSync(`launchctl unload "${plistPath}" 2>/dev/null`, { stdio: 'pipe' }); } catch {}
6973
- try { unlinkSync(plistPath); } catch {}
6974
- console.log(` ${green('✓')} LaunchAgent removed`);
6975
- }
6821
+ if (report.bundles.length > 0) {
6822
+ console.log();
6823
+ console.log(` ${bold('Co-access bundles:')}`);
6824
+ for (let i = 0; i < report.bundles.length; i++) {
6825
+ const b = report.bundles[i];
6826
+ const names = b.entries.slice(0, 4).map((e) => e.title || e.id.slice(-8)).join(', ');
6827
+ console.log(` ${i + 1}. [${b.entries.length} entries, weight: ${b.totalWeight}] ${names}`);
6976
6828
  }
6829
+ }
6977
6830
 
6978
- // 3. Stop daemon if running
6979
- const existing = readPid();
6980
- if (existing && isAlive(existing.pid)) {
6981
- console.log(` Stopping daemon (PID ${existing.pid})...`);
6982
- process.kill(existing.pid, 'SIGTERM');
6983
- const deadline = Date.now() + 3000;
6984
- while (Date.now() < deadline && isAlive(existing.pid)) {
6985
- await new Promise((r) => setTimeout(r, 200));
6986
- }
6987
- if (isAlive(existing.pid)) {
6988
- try { process.kill(existing.pid, 'SIGKILL'); } catch {}
6989
- }
6990
- try { unlinkSync(pidPath); } catch {}
6991
- console.log(` ${green('✓')} Daemon stopped.`);
6992
- }
6831
+ console.log();
6832
+ }
6993
6833
 
6994
- } else {
6995
- console.error(red(` Unknown daemon subcommand: ${sub}`));
6996
- console.error(` Run ${cyan('context-vault daemon --help')} for usage.`);
6997
- process.exit(1);
6998
- }
6834
+ function argValue(flag) {
6835
+ const idx = args.indexOf(flag);
6836
+ return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : null;
6999
6837
  }
7000
6838
 
7001
6839
  async function runStats() {
@@ -8230,7 +8068,7 @@ async function main() {
8230
8068
 
8231
8069
  if (flags.has('--help') || command === 'help') {
8232
8070
  // Commands with their own --help handling: delegate to them
8233
- const commandsWithHelp = new Set(['save', 'search', 'rules', 'hooks', 'daemon', 'team', 'remote']);
8071
+ const commandsWithHelp = new Set(['save', 'search', 'rules', 'hooks', 'team', 'remote']);
8234
8072
  if (!command || command === 'help' || !commandsWithHelp.has(command)) {
8235
8073
  showHelp(flags.has('--all'));
8236
8074
  return;
@@ -8259,7 +8097,8 @@ async function main() {
8259
8097
  await runSwitch();
8260
8098
  break;
8261
8099
  case 'daemon':
8262
- await runDaemon();
8100
+ console.log('The daemon command was removed in v3.16.1. context-vault now runs in stdio mode only.');
8101
+ process.exit(0);
8263
8102
  break;
8264
8103
  case 'serve':
8265
8104
  await runServe();
@@ -8312,6 +8151,9 @@ async function main() {
8312
8151
  case 'reindex':
8313
8152
  await runReindex();
8314
8153
  break;
8154
+ case 'reclassify':
8155
+ await runReclassify();
8156
+ break;
8315
8157
  case 'sync':
8316
8158
  await runSync();
8317
8159
  break;
@@ -8346,7 +8188,8 @@ async function main() {
8346
8188
  await runHealth();
8347
8189
  break;
8348
8190
  case 'restart':
8349
- await runRestart();
8191
+ console.log('The restart command was removed in v3.16.1. Use "context-vault reconnect" instead.');
8192
+ process.exit(0);
8350
8193
  break;
8351
8194
  case 'reconnect':
8352
8195
  await runReconnect();
@@ -8360,6 +8203,9 @@ async function main() {
8360
8203
  case 'stats':
8361
8204
  await runStats();
8362
8205
  break;
8206
+ case 'tier':
8207
+ await runTier();
8208
+ break;
8363
8209
  case 'remote':
8364
8210
  await runRemote();
8365
8211
  break;
@@ -8369,6 +8215,9 @@ async function main() {
8369
8215
  case 'public':
8370
8216
  await runPublic();
8371
8217
  break;
8218
+ case 'compact':
8219
+ await runCompact();
8220
+ break;
8372
8221
  default:
8373
8222
  console.error(red(`Unknown command: ${command}`));
8374
8223
  console.error(`Run ${cyan('context-vault --help')} for usage.`);