context-vault 2.17.1 → 3.0.2

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 (110) hide show
  1. package/bin/cli.js +795 -71
  2. package/node_modules/@context-vault/core/dist/capture.d.ts +21 -0
  3. package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -0
  4. package/node_modules/@context-vault/core/dist/capture.js +269 -0
  5. package/node_modules/@context-vault/core/dist/capture.js.map +1 -0
  6. package/node_modules/@context-vault/core/dist/categories.d.ts +6 -0
  7. package/node_modules/@context-vault/core/dist/categories.d.ts.map +1 -0
  8. package/node_modules/@context-vault/core/dist/categories.js +50 -0
  9. package/node_modules/@context-vault/core/dist/categories.js.map +1 -0
  10. package/node_modules/@context-vault/core/dist/config.d.ts +4 -0
  11. package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -0
  12. package/node_modules/@context-vault/core/dist/config.js +190 -0
  13. package/node_modules/@context-vault/core/dist/config.js.map +1 -0
  14. package/node_modules/@context-vault/core/dist/constants.d.ts +33 -0
  15. package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -0
  16. package/node_modules/@context-vault/core/dist/constants.js +23 -0
  17. package/node_modules/@context-vault/core/dist/constants.js.map +1 -0
  18. package/node_modules/@context-vault/core/dist/db.d.ts +13 -0
  19. package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -0
  20. package/node_modules/@context-vault/core/dist/db.js +191 -0
  21. package/node_modules/@context-vault/core/dist/db.js.map +1 -0
  22. package/node_modules/@context-vault/core/dist/embed.d.ts +5 -0
  23. package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -0
  24. package/node_modules/@context-vault/core/dist/embed.js +78 -0
  25. package/node_modules/@context-vault/core/dist/embed.js.map +1 -0
  26. package/node_modules/@context-vault/core/dist/files.d.ts +13 -0
  27. package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -0
  28. package/node_modules/@context-vault/core/dist/files.js +66 -0
  29. package/node_modules/@context-vault/core/dist/files.js.map +1 -0
  30. package/node_modules/@context-vault/core/dist/formatters.d.ts +8 -0
  31. package/node_modules/@context-vault/core/dist/formatters.d.ts.map +1 -0
  32. package/node_modules/@context-vault/core/dist/formatters.js +18 -0
  33. package/node_modules/@context-vault/core/dist/formatters.js.map +1 -0
  34. package/node_modules/@context-vault/core/dist/frontmatter.d.ts +12 -0
  35. package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -0
  36. package/node_modules/@context-vault/core/dist/frontmatter.js +101 -0
  37. package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -0
  38. package/node_modules/@context-vault/core/dist/index.d.ts +10 -0
  39. package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -0
  40. package/node_modules/@context-vault/core/dist/index.js +297 -0
  41. package/node_modules/@context-vault/core/dist/index.js.map +1 -0
  42. package/node_modules/@context-vault/core/dist/ingest-url.d.ts +20 -0
  43. package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -0
  44. package/node_modules/@context-vault/core/dist/ingest-url.js +113 -0
  45. package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -0
  46. package/node_modules/@context-vault/core/dist/main.d.ts +14 -0
  47. package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -0
  48. package/node_modules/@context-vault/core/dist/main.js +25 -0
  49. package/node_modules/@context-vault/core/dist/main.js.map +1 -0
  50. package/node_modules/@context-vault/core/dist/search.d.ts +18 -0
  51. package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -0
  52. package/node_modules/@context-vault/core/dist/search.js +238 -0
  53. package/node_modules/@context-vault/core/dist/search.js.map +1 -0
  54. package/node_modules/@context-vault/core/dist/types.d.ts +176 -0
  55. package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -0
  56. package/node_modules/@context-vault/core/dist/types.js +2 -0
  57. package/node_modules/@context-vault/core/dist/types.js.map +1 -0
  58. package/node_modules/@context-vault/core/package.json +66 -16
  59. package/node_modules/@context-vault/core/src/capture.ts +308 -0
  60. package/node_modules/@context-vault/core/src/categories.ts +54 -0
  61. package/node_modules/@context-vault/core/src/{core/config.js → config.ts} +34 -33
  62. package/node_modules/@context-vault/core/src/{constants.js → constants.ts} +6 -3
  63. package/node_modules/@context-vault/core/src/db.ts +229 -0
  64. package/node_modules/@context-vault/core/src/{index/embed.js → embed.ts} +10 -35
  65. package/node_modules/@context-vault/core/src/{core/files.js → files.ts} +15 -20
  66. package/node_modules/@context-vault/core/src/{capture/formatters.js → formatters.ts} +13 -11
  67. package/node_modules/@context-vault/core/src/{core/frontmatter.js → frontmatter.ts} +26 -33
  68. package/node_modules/@context-vault/core/src/index.ts +351 -0
  69. package/node_modules/@context-vault/core/src/ingest-url.ts +99 -0
  70. package/node_modules/@context-vault/core/src/main.ts +111 -0
  71. package/node_modules/@context-vault/core/src/{retrieve/index.js → search.ts} +62 -150
  72. package/node_modules/@context-vault/core/src/types.ts +166 -0
  73. package/package.json +12 -7
  74. package/scripts/postinstall.js +1 -1
  75. package/{node_modules/@context-vault/core/src/core → src}/error-log.js +1 -15
  76. package/{node_modules/@context-vault/core/src/server → src}/helpers.js +9 -4
  77. package/src/linking.js +100 -0
  78. package/{node_modules/@context-vault/core/src/server/tools.js → src/register-tools.js} +14 -13
  79. package/src/{server/index.js → server.js} +10 -38
  80. package/src/status.js +235 -0
  81. package/{node_modules/@context-vault/core/src/core → src}/telemetry.js +9 -19
  82. package/src/temporal.js +97 -0
  83. package/{node_modules/@context-vault/core/src/server → src}/tools/context-status.js +3 -4
  84. package/{node_modules/@context-vault/core/src/server → src}/tools/create-snapshot.js +6 -7
  85. package/{node_modules/@context-vault/core/src/server → src}/tools/delete-context.js +0 -2
  86. package/{node_modules/@context-vault/core/src/server → src}/tools/get-context.js +17 -21
  87. package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-project.js +5 -6
  88. package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-url.js +3 -4
  89. package/{node_modules/@context-vault/core/src/server → src}/tools/list-buckets.js +4 -5
  90. package/{node_modules/@context-vault/core/src/server → src}/tools/list-context.js +3 -6
  91. package/{node_modules/@context-vault/core/src/server → src}/tools/save-context.js +17 -20
  92. package/{node_modules/@context-vault/core/src/server → src}/tools/session-start.js +9 -16
  93. package/node_modules/@context-vault/core/src/capture/file-ops.js +0 -99
  94. package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -46
  95. package/node_modules/@context-vault/core/src/capture/importers.js +0 -387
  96. package/node_modules/@context-vault/core/src/capture/index.js +0 -250
  97. package/node_modules/@context-vault/core/src/capture/ingest-url.js +0 -252
  98. package/node_modules/@context-vault/core/src/consolidation/index.js +0 -112
  99. package/node_modules/@context-vault/core/src/core/categories.js +0 -73
  100. package/node_modules/@context-vault/core/src/core/linking.js +0 -161
  101. package/node_modules/@context-vault/core/src/core/migrate-dirs.js +0 -196
  102. package/node_modules/@context-vault/core/src/core/status.js +0 -350
  103. package/node_modules/@context-vault/core/src/core/temporal.js +0 -146
  104. package/node_modules/@context-vault/core/src/index/db.js +0 -586
  105. package/node_modules/@context-vault/core/src/index/index.js +0 -583
  106. package/node_modules/@context-vault/core/src/index.js +0 -71
  107. package/node_modules/@context-vault/core/src/sync/sync.js +0 -235
  108. package/src/hooks/post-tool-call.mjs +0 -62
  109. package/src/hooks/session-end.mjs +0 -492
  110. /package/{node_modules/@context-vault/core/src/server → src}/tools/clear-context.js +0 -0
package/bin/cli.js CHANGED
@@ -34,7 +34,7 @@ const HOME = homedir();
34
34
 
35
35
  const pkg = JSON.parse(readFileSync(join(ROOT, "package.json"), "utf-8"));
36
36
  const VERSION = pkg.version;
37
- const SERVER_PATH = resolve(ROOT, "src", "server", "index.js");
37
+ const SERVER_PATH = resolve(ROOT, "src", "server.js");
38
38
 
39
39
  /** Detect if running as an npm-installed package (global or local) vs local dev clone */
40
40
  function isInstalledPackage() {
@@ -306,12 +306,14 @@ ${bold("Commands:")}
306
306
  ${cyan("restart")} Stop running MCP server processes (client auto-restarts)
307
307
  ${cyan("search")} Search vault entries from CLI
308
308
  ${cyan("save")} Save an entry to the vault from CLI
309
- ${cyan("import")} <path> Import entries from file or directory
310
- ${cyan("export")} Export vault to JSON or CSV
309
+ ${cyan("import")} <path> Import entries from file, directory, or .zip archive
310
+ ${cyan("export")} Export vault entries (JSON, CSV, or portable ZIP)
311
311
  ${cyan("ingest")} <url> Fetch URL and save as vault entry
312
312
  ${cyan("ingest-project")} <path> Scan project directory and register as project entity
313
313
  ${cyan("reindex")} Rebuild search index from knowledge files
314
314
  ${cyan("migrate-dirs")} [--dry-run] Rename plural vault dirs to singular (post-2.18.0)
315
+ ${cyan("archive")} Archive old ephemeral/event entries (use --dry-run to preview)
316
+ ${cyan("restore")} <id> Restore an archived entry back into the vault
315
317
  ${cyan("prune")} Remove expired entries (use --dry-run to preview)
316
318
  ${cyan("update")} Check for and install updates
317
319
  ${cyan("uninstall")} Remove MCP configs and optionally data
@@ -372,6 +374,26 @@ async function runSetup() {
372
374
  } catch {}
373
375
 
374
376
  if (latestVersion === VERSION) {
377
+ // Even when "up to date", ensure the launcher points to a valid server
378
+ const dataDir = join(HOME, ".context-mcp");
379
+ const launcherPath = join(dataDir, "server.mjs");
380
+ let launcherOk = false;
381
+ if (existsSync(launcherPath)) {
382
+ const content = readFileSync(launcherPath, "utf-8");
383
+ const m = content.match(/import "(.+?)"/);
384
+ if (m && existsSync(m[1])) launcherOk = true;
385
+ }
386
+ if (!launcherOk && !isNpx()) {
387
+ mkdirSync(dataDir, { recursive: true });
388
+ writeFileSync(launcherPath, `import "${SERVER_PATH}";\n`);
389
+ console.log(
390
+ green(` ✓ context-vault v${VERSION} is up to date`) +
391
+ dim(` (vault: ${existingVault})`),
392
+ );
393
+ console.log(dim(` ↳ Repaired server launcher → ${SERVER_PATH}`));
394
+ console.log();
395
+ return;
396
+ }
375
397
  console.log(
376
398
  green(` ✓ context-vault v${VERSION} is up to date`) +
377
399
  dim(` (vault: ${existingVault})`),
@@ -799,7 +821,7 @@ async function runSetup() {
799
821
  }, 100);
800
822
 
801
823
  try {
802
- const { embed } = await import("@context-vault/core/index/embed");
824
+ const { embed } = await import("@context-vault/core/embed");
803
825
  let timeoutHandle;
804
826
  const timeout = new Promise((_, reject) => {
805
827
  timeoutHandle = setTimeout(
@@ -1034,7 +1056,7 @@ async function runSetup() {
1034
1056
  // Verify DB is accessible
1035
1057
  let dbAccessible = false;
1036
1058
  try {
1037
- const { initDatabase } = await import("@context-vault/core/index/db");
1059
+ const { initDatabase } = await import("@context-vault/core/db");
1038
1060
  const db = await initDatabase(vaultConfig.dbPath);
1039
1061
  db.prepare("SELECT 1").get();
1040
1062
  db.close();
@@ -1577,7 +1599,7 @@ async function runSwitch() {
1577
1599
  if (target === "local") {
1578
1600
  const launcherPath = join(dataDir, "server.mjs");
1579
1601
  if (!existsSync(launcherPath)) {
1580
- const serverAbs = resolve(ROOT, "src", "server", "index.js");
1602
+ const serverAbs = resolve(ROOT, "src", "server.js");
1581
1603
  mkdirSync(dataDir, { recursive: true });
1582
1604
  writeFileSync(launcherPath, `import "${serverAbs}";\n`);
1583
1605
  }
@@ -1673,10 +1695,10 @@ async function runSwitch() {
1673
1695
  async function runReindex() {
1674
1696
  console.log(dim("Loading vault..."));
1675
1697
 
1676
- const { resolveConfig } = await import("@context-vault/core/core/config");
1698
+ const { resolveConfig } = await import("@context-vault/core/config");
1677
1699
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
1678
- await import("@context-vault/core/index/db");
1679
- const { embed } = await import("@context-vault/core/index/embed");
1700
+ await import("@context-vault/core/db");
1701
+ const { embed } = await import("@context-vault/core/embed");
1680
1702
  const { reindex } = await import("@context-vault/core/index");
1681
1703
 
1682
1704
  const config = resolveConfig();
@@ -1715,7 +1737,7 @@ async function runMigrateDirs() {
1715
1737
  let vaultDir = positional;
1716
1738
 
1717
1739
  if (!vaultDir) {
1718
- const { resolveConfig } = await import("@context-vault/core/core/config");
1740
+ const { resolveConfig } = await import("@context-vault/core/config");
1719
1741
  const config = resolveConfig();
1720
1742
  if (!config.vaultDirExists) {
1721
1743
  console.error(red(`Vault directory not found: ${config.vaultDir}`));
@@ -1731,7 +1753,7 @@ async function runMigrateDirs() {
1731
1753
  }
1732
1754
 
1733
1755
  const { planMigration, executeMigration } =
1734
- await import("@context-vault/core/core/migrate-dirs");
1756
+ await import("@context-vault/core/migrate-dirs");
1735
1757
 
1736
1758
  const ops = planMigration(vaultDir);
1737
1759
 
@@ -1784,9 +1806,9 @@ async function runMigrateDirs() {
1784
1806
  async function runPrune() {
1785
1807
  const dryRun = flags.has("--dry-run");
1786
1808
 
1787
- const { resolveConfig } = await import("@context-vault/core/core/config");
1809
+ const { resolveConfig } = await import("@context-vault/core/config");
1788
1810
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
1789
- await import("@context-vault/core/index/db");
1811
+ await import("@context-vault/core/db");
1790
1812
  const { pruneExpired } = await import("@context-vault/core/index");
1791
1813
 
1792
1814
  const config = resolveConfig();
@@ -1844,12 +1866,169 @@ async function runPrune() {
1844
1866
  }
1845
1867
  }
1846
1868
 
1869
+ async function runArchive() {
1870
+ const dryRun = flags.has("--dry-run");
1871
+
1872
+ const { resolveConfig } = await import("@context-vault/core/config");
1873
+ const { initDatabase, prepareStatements, insertVec, deleteVec } =
1874
+ await import("@context-vault/core/db");
1875
+ const { findArchiveCandidates, archiveEntries } =
1876
+ await import("@context-vault/core/archive");
1877
+
1878
+ const config = resolveConfig();
1879
+ if (!config.vaultDirExists) {
1880
+ console.error(red(`Vault directory not found: ${config.vaultDir}`));
1881
+ console.error("Run " + cyan("context-vault setup") + " to configure.");
1882
+ process.exit(1);
1883
+ }
1884
+
1885
+ const db = await initDatabase(config.dbPath);
1886
+
1887
+ if (dryRun) {
1888
+ const ctx = {
1889
+ db,
1890
+ config,
1891
+ stmts: prepareStatements(db),
1892
+ embed: async () => null,
1893
+ insertVec: () => {},
1894
+ deleteVec: () => {},
1895
+ };
1896
+ const candidates = findArchiveCandidates(ctx);
1897
+ db.close();
1898
+
1899
+ if (candidates.length === 0) {
1900
+ console.log(green(" No entries eligible for archiving."));
1901
+ const lifecycle = config.lifecycle || {};
1902
+ console.log(dim("\n Retention windows:"));
1903
+ for (const [tier, rules] of Object.entries(lifecycle)) {
1904
+ if (rules?.archiveAfterDays) {
1905
+ console.log(dim(` ${tier}: archive after ${rules.archiveAfterDays} days`));
1906
+ }
1907
+ }
1908
+ return;
1909
+ }
1910
+
1911
+ console.log(
1912
+ `\n ${bold(String(candidates.length))} ${candidates.length === 1 ? "entry" : "entries"} eligible for archiving:\n`,
1913
+ );
1914
+ for (const e of candidates) {
1915
+ const label = e.title ? `${e.kind}: ${e.title}` : `${e.kind} (${e.id})`;
1916
+ const age = e.updated_at || e.created_at;
1917
+ console.log(
1918
+ ` ${dim("-")} ${label} ${dim(`(tier=${e.tier}, last updated ${age})`)}`,
1919
+ );
1920
+ }
1921
+ console.log(dim("\n Dry run — no entries were archived."));
1922
+ console.log(dim(" Remove --dry-run to archive."));
1923
+ return;
1924
+ }
1925
+
1926
+ const stmts = prepareStatements(db);
1927
+ const ctx = {
1928
+ db,
1929
+ config,
1930
+ stmts,
1931
+ embed: async () => null,
1932
+ insertVec: (r, e) => insertVec(stmts, r, e),
1933
+ deleteVec: (r) => deleteVec(stmts, r),
1934
+ };
1935
+
1936
+ const result = await archiveEntries(ctx);
1937
+ db.close();
1938
+
1939
+ if (result.count === 0) {
1940
+ console.log(green(" No entries eligible for archiving."));
1941
+ } else {
1942
+ console.log(
1943
+ green(
1944
+ ` ✓ Archived ${result.count} ${result.count === 1 ? "entry" : "entries"} to _archive/`,
1945
+ ),
1946
+ );
1947
+ console.log(
1948
+ dim(` Files moved to: ${join(config.vaultDir, "_archive")}`),
1949
+ );
1950
+ console.log(
1951
+ dim(" Restore with: context-vault restore <id>"),
1952
+ );
1953
+ }
1954
+ }
1955
+
1956
+ async function runRestore() {
1957
+ const entryId = args[1];
1958
+
1959
+ if (!entryId || entryId.startsWith("--")) {
1960
+ const { resolveConfig } = await import("@context-vault/core/config");
1961
+ const { listArchivedEntries } =
1962
+ await import("@context-vault/core/archive");
1963
+
1964
+ const config = resolveConfig();
1965
+
1966
+ console.log(`\n ${bold("context-vault restore")} <id>\n`);
1967
+ console.log(` Restore an archived entry back into the active vault.\n`);
1968
+
1969
+ if (config.vaultDirExists) {
1970
+ const entries = listArchivedEntries(config.vaultDir);
1971
+ if (entries.length > 0) {
1972
+ console.log(` ${bold("Archived entries")} (${entries.length}):\n`);
1973
+ for (const e of entries.slice(0, 20)) {
1974
+ const label = e.title
1975
+ ? `${e.kind}: ${e.title.slice(0, 60)}`
1976
+ : `${e.kind} (${e.id})`;
1977
+ console.log(` ${dim(e.id || "?")} ${label}`);
1978
+ }
1979
+ if (entries.length > 20) {
1980
+ console.log(dim(`\n ... and ${entries.length - 20} more`));
1981
+ }
1982
+ } else {
1983
+ console.log(dim(" No archived entries found."));
1984
+ }
1985
+ }
1986
+ console.log();
1987
+ return;
1988
+ }
1989
+
1990
+ const { resolveConfig } = await import("@context-vault/core/config");
1991
+ const { initDatabase, prepareStatements, insertVec, deleteVec } =
1992
+ await import("@context-vault/core/db");
1993
+ const { embed } = await import("@context-vault/core/embed");
1994
+ const { restoreEntry } = await import("@context-vault/core/archive");
1995
+
1996
+ const config = resolveConfig();
1997
+ if (!config.vaultDirExists) {
1998
+ console.error(red(`Vault directory not found: ${config.vaultDir}`));
1999
+ console.error("Run " + cyan("context-vault setup") + " to configure.");
2000
+ process.exit(1);
2001
+ }
2002
+
2003
+ const db = await initDatabase(config.dbPath);
2004
+ const stmts = prepareStatements(db);
2005
+ const ctx = {
2006
+ db,
2007
+ config,
2008
+ stmts,
2009
+ embed,
2010
+ insertVec: (r, e) => insertVec(stmts, r, e),
2011
+ deleteVec: (r) => deleteVec(stmts, r),
2012
+ };
2013
+
2014
+ const result = await restoreEntry(ctx, entryId);
2015
+ db.close();
2016
+
2017
+ if (result.restored) {
2018
+ console.log(green(` ✓ Restored ${result.kind} entry: ${result.id}`));
2019
+ console.log(dim(` File: ${result.filePath}`));
2020
+ } else {
2021
+ console.error(red(` ✗ ${result.reason}`));
2022
+ process.exit(1);
2023
+ }
2024
+ }
2025
+
1847
2026
  async function runStatus() {
1848
- const { resolveConfig } = await import("@context-vault/core/core/config");
1849
- const { initDatabase } = await import("@context-vault/core/index/db");
1850
- const { gatherVaultStatus } = await import("@context-vault/core/core/status");
2027
+ const { resolveConfig } = await import("@context-vault/core/config");
2028
+ const { initDatabase } = await import("@context-vault/core/db");
2029
+ const { gatherVaultStatus } = await import("../src/status.js");
1851
2030
  const { errorLogPath, errorLogCount } =
1852
- await import("@context-vault/core/core/error-log");
2031
+ await import("../src/error-log.js");
1853
2032
 
1854
2033
  const config = resolveConfig();
1855
2034
 
@@ -1938,6 +2117,15 @@ async function runStatus() {
1938
2117
  }
1939
2118
  }
1940
2119
 
2120
+ if (status.archivedCount > 0) {
2121
+ console.log();
2122
+ console.log(
2123
+ dim(
2124
+ ` ${status.archivedCount} archived ${status.archivedCount === 1 ? "entry" : "entries"} in _archive/ (excluded from search)`,
2125
+ ),
2126
+ );
2127
+ }
2128
+
1941
2129
  if (status.stalePaths) {
1942
2130
  console.log();
1943
2131
  console.log(yellow(" Stale paths detected in DB."));
@@ -2129,7 +2317,7 @@ async function runMigrate() {
2129
2317
  return;
2130
2318
  }
2131
2319
 
2132
- const { resolveConfig } = await import("@context-vault/core/core/config");
2320
+ const { resolveConfig } = await import("@context-vault/core/config");
2133
2321
  const config = resolveConfig();
2134
2322
 
2135
2323
  if (direction === "to-hosted") {
@@ -2183,20 +2371,33 @@ async function runImport() {
2183
2371
  const target = args[1];
2184
2372
  if (!target) {
2185
2373
  console.log(`\n ${bold("context-vault import")} <path>\n`);
2186
- console.log(` Import entries from a file or directory.\n`);
2187
- console.log(` Supported formats: .md, .csv, .tsv, .json, .txt\n`);
2374
+ console.log(` Import entries from a file, directory, or portable archive.\n`);
2375
+ console.log(` Supported formats: .md, .csv, .tsv, .json, .txt, .zip\n`);
2188
2376
  console.log(` Options:`);
2189
2377
  console.log(` --kind <kind> Default kind (default: insight)`);
2190
2378
  console.log(` --source <src> Default source (default: cli-import)`);
2191
2379
  console.log(` --dry-run Show parsed entries without importing`);
2380
+ console.log(` --vault <path> Target vault directory (default: configured vault)`);
2192
2381
  console.log();
2193
2382
  return;
2194
2383
  }
2195
2384
 
2196
- const { resolveConfig } = await import("@context-vault/core/core/config");
2385
+ const dryRun = flags.has("--dry-run");
2386
+ const targetPath = resolve(target);
2387
+
2388
+ if (!existsSync(targetPath)) {
2389
+ console.error(red(` Path not found: ${targetPath}`));
2390
+ process.exit(1);
2391
+ }
2392
+
2393
+ if (targetPath.endsWith(".zip")) {
2394
+ return runImportZip(targetPath, dryRun);
2395
+ }
2396
+
2397
+ const { resolveConfig } = await import("@context-vault/core/config");
2197
2398
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
2198
- await import("@context-vault/core/index/db");
2199
- const { embed } = await import("@context-vault/core/index/embed");
2399
+ await import("@context-vault/core/db");
2400
+ const { embed } = await import("@context-vault/core/embed");
2200
2401
  const { parseFile, parseDirectory } =
2201
2402
  await import("@context-vault/core/capture/importers");
2202
2403
  const { importEntries } =
@@ -2205,13 +2406,6 @@ async function runImport() {
2205
2406
 
2206
2407
  const kind = getFlag("--kind") || undefined;
2207
2408
  const source = getFlag("--source") || "cli-import";
2208
- const dryRun = flags.has("--dry-run");
2209
-
2210
- const targetPath = resolve(target);
2211
- if (!existsSync(targetPath)) {
2212
- console.error(red(` Path not found: ${targetPath}`));
2213
- process.exit(1);
2214
- }
2215
2409
 
2216
2410
  const stat = statSync(targetPath);
2217
2411
  let entries;
@@ -2282,17 +2476,215 @@ async function runImport() {
2282
2476
  console.log();
2283
2477
  }
2284
2478
 
2479
+ async function runImportZip(zipPath, dryRun) {
2480
+ const AdmZip = (await import("adm-zip")).default;
2481
+ const { resolveConfig } = await import("@context-vault/core/config");
2482
+ const { initDatabase, prepareStatements, insertVec, deleteVec } =
2483
+ await import("@context-vault/core/db");
2484
+ const { embed } = await import("@context-vault/core/embed");
2485
+ const { indexEntry } = await import("@context-vault/core/index");
2486
+ const { parseFrontmatter } = await import("@context-vault/core/frontmatter");
2487
+ const { categoryDirFor } = await import("@context-vault/core/categories");
2488
+ const { mkdirSync, writeFileSync, existsSync: existsFn } = await import("node:fs");
2489
+ const { join: joinPath, basename: baseName } = await import("node:path");
2490
+
2491
+ let zip;
2492
+ try {
2493
+ zip = new AdmZip(zipPath);
2494
+ } catch (e) {
2495
+ console.error(red(`\n Failed to open archive: ${e.message}\n`));
2496
+ process.exit(1);
2497
+ }
2498
+
2499
+ const manifestEntry = zip.getEntry("manifest.json");
2500
+ if (!manifestEntry) {
2501
+ console.error(red("\n Invalid archive: missing manifest.json\n"));
2502
+ process.exit(1);
2503
+ }
2504
+
2505
+ let manifest;
2506
+ try {
2507
+ manifest = JSON.parse(zip.readAsText("manifest.json"));
2508
+ } catch {
2509
+ console.error(red("\n Invalid archive: corrupt manifest.json\n"));
2510
+ process.exit(1);
2511
+ }
2512
+
2513
+ const indexEntry_ = zip.getEntry("index.json");
2514
+ if (!indexEntry_) {
2515
+ console.error(red("\n Invalid archive: missing index.json\n"));
2516
+ process.exit(1);
2517
+ }
2518
+
2519
+ let index;
2520
+ try {
2521
+ index = JSON.parse(zip.readAsText("index.json"));
2522
+ } catch {
2523
+ console.error(red("\n Invalid archive: corrupt index.json\n"));
2524
+ process.exit(1);
2525
+ }
2526
+
2527
+ const entries = index.entries || [];
2528
+ if (entries.length === 0) {
2529
+ console.log(yellow("\n Archive contains no entries.\n"));
2530
+ return;
2531
+ }
2532
+
2533
+ console.log(`\n ${bold("◇ context-vault import")} ${dim(baseName(zipPath))}`);
2534
+ console.log(dim(` Archive: v${manifest.version} · ${manifest.entry_count} entries · ${manifest.context_vault_version || "?"}`));
2535
+
2536
+ const kindCounts = {};
2537
+ for (const e of entries) {
2538
+ kindCounts[e.kind] = (kindCounts[e.kind] || 0) + 1;
2539
+ }
2540
+ console.log();
2541
+ for (const [k, count] of Object.entries(kindCounts).sort((a, b) => b[1] - a[1])) {
2542
+ console.log(` ${k}: ${count}`);
2543
+ }
2544
+
2545
+ const vaultDirOverride = getFlag("--vault");
2546
+ const config = (await import("@context-vault/core/config")).resolveConfig();
2547
+ const targetVaultDir = vaultDirOverride ? resolve(vaultDirOverride) : config.vaultDir;
2548
+
2549
+ if (!existsFn(targetVaultDir)) {
2550
+ console.error(red(`\n Vault directory not found: ${targetVaultDir}`));
2551
+ console.error(` Run ${cyan("context-vault setup")} to configure.`);
2552
+ process.exit(1);
2553
+ }
2554
+
2555
+ const db = await initDatabase(config.dbPath);
2556
+ const stmts = prepareStatements(db);
2557
+ const ctx = {
2558
+ db,
2559
+ config: { ...config, vaultDir: targetVaultDir },
2560
+ stmts,
2561
+ embed,
2562
+ insertVec: (r, e) => insertVec(stmts, r, e),
2563
+ deleteVec: (r) => deleteVec(stmts, r),
2564
+ };
2565
+
2566
+ const existingIds = new Set();
2567
+ const allIds = db.prepare("SELECT id FROM vault").all();
2568
+ for (const row of allIds) existingIds.add(row.id);
2569
+
2570
+ let imported = 0;
2571
+ let skippedDuplicate = 0;
2572
+ let skippedMissing = 0;
2573
+ let failed = 0;
2574
+ const errors = [];
2575
+
2576
+ if (dryRun) {
2577
+ for (let i = 0; i < Math.min(entries.length, 25); i++) {
2578
+ const e = entries[i];
2579
+ const isDuplicate = existingIds.has(e.id);
2580
+ const tagStr = e.tags?.length ? ` ${dim(`[${e.tags.join(", ")}]`)}` : "";
2581
+ const statusIcon = isDuplicate ? yellow("~") : green("+");
2582
+ const statusText = isDuplicate ? dim(" (duplicate, would skip)") : "";
2583
+ console.log(`\n ${statusIcon} ${dim(`[${i + 1}]`)} ${e.kind} — ${e.title || e.id}${tagStr}${statusText}`);
2584
+ }
2585
+ if (entries.length > 25) {
2586
+ console.log(dim(`\n ... and ${entries.length - 25} more`));
2587
+ }
2588
+ const wouldSkip = entries.filter((e) => existingIds.has(e.id)).length;
2589
+ console.log(`\n ${dim(`Would import ${entries.length - wouldSkip}, skip ${wouldSkip} duplicates.`)}`);
2590
+ console.log(dim(" Dry run — no entries were imported.\n"));
2591
+ db.close();
2592
+ return;
2593
+ }
2594
+
2595
+ for (let i = 0; i < entries.length; i++) {
2596
+ const entryMeta = entries[i];
2597
+ process.stdout.write(`\r Importing... ${i + 1}/${entries.length}`);
2598
+
2599
+ if (existingIds.has(entryMeta.id)) {
2600
+ skippedDuplicate++;
2601
+ continue;
2602
+ }
2603
+
2604
+ const zipEntry = zip.getEntry(entryMeta.file);
2605
+ if (!zipEntry) {
2606
+ skippedMissing++;
2607
+ continue;
2608
+ }
2609
+
2610
+ const mdContent = zip.readAsText(entryMeta.file);
2611
+ const { meta: fmMeta, body: rawBody } = parseFrontmatter(mdContent);
2612
+
2613
+ const kind = entryMeta.kind || fmMeta.kind || "insight";
2614
+ const categoryDir = categoryDirFor(kind);
2615
+ const targetDir = joinPath(targetVaultDir, categoryDir, kind);
2616
+
2617
+ try {
2618
+ mkdirSync(targetDir, { recursive: true });
2619
+
2620
+ const fileName = baseName(entryMeta.file);
2621
+ const filePath = joinPath(targetDir, fileName);
2622
+ writeFileSync(filePath, mdContent);
2623
+
2624
+ const id = fmMeta.id || entryMeta.id;
2625
+ const tags = Array.isArray(fmMeta.tags) ? fmMeta.tags : entryMeta.tags || [];
2626
+ const title = fmMeta.title || entryMeta.title || null;
2627
+ const source = fmMeta.source || entryMeta.source || "archive-import";
2628
+ const identity_key = fmMeta.identity_key || entryMeta.identity_key || null;
2629
+ const expires_at = fmMeta.expires_at || entryMeta.expires_at || null;
2630
+ const createdAt = fmMeta.created || entryMeta.created_at || new Date().toISOString();
2631
+
2632
+ await indexEntry(ctx, {
2633
+ id,
2634
+ kind,
2635
+ category: entryMeta.category || undefined,
2636
+ title,
2637
+ body: rawBody,
2638
+ meta: null,
2639
+ tags,
2640
+ source,
2641
+ filePath,
2642
+ createdAt,
2643
+ identity_key,
2644
+ expires_at,
2645
+ });
2646
+
2647
+ imported++;
2648
+ } catch (e) {
2649
+ failed++;
2650
+ errors.push({ id: entryMeta.id, error: e.message });
2651
+ }
2652
+ }
2653
+
2654
+ db.close();
2655
+
2656
+ console.log(`\r ${green("✓")} Import complete `);
2657
+ console.log(` ${green("+")} ${imported} imported`);
2658
+ if (skippedDuplicate > 0) {
2659
+ console.log(` ${dim("~")} ${skippedDuplicate} skipped (already exist)`);
2660
+ }
2661
+ if (skippedMissing > 0) {
2662
+ console.log(` ${yellow("!")} ${skippedMissing} skipped (file missing in archive)`);
2663
+ }
2664
+ if (failed > 0) {
2665
+ console.log(` ${red("x")} ${failed} failed`);
2666
+ for (const e of errors.slice(0, 5)) {
2667
+ console.log(` ${dim(e.error)}`);
2668
+ }
2669
+ }
2670
+ console.log();
2671
+ }
2672
+
2285
2673
  async function runExport() {
2286
2674
  const format = getFlag("--format") || "json";
2287
- const output = getFlag("--output");
2675
+ const output = getFlag("--output") || getFlag("-o");
2288
2676
  const rawPageSize = getFlag("--page-size");
2289
2677
  const pageSize = rawPageSize
2290
2678
  ? Math.max(1, parseInt(rawPageSize, 10) || 100)
2291
2679
  : null;
2292
2680
 
2293
- const { resolveConfig } = await import("@context-vault/core/core/config");
2681
+ if (format === "zip") {
2682
+ return runExportZip();
2683
+ }
2684
+
2685
+ const { resolveConfig } = await import("@context-vault/core/config");
2294
2686
  const { initDatabase, prepareStatements } =
2295
- await import("@context-vault/core/index/db");
2687
+ await import("@context-vault/core/db");
2296
2688
  const { writeFileSync } = await import("node:fs");
2297
2689
 
2298
2690
  const config = resolveConfig();
@@ -2308,7 +2700,6 @@ async function runExport() {
2308
2700
 
2309
2701
  let entries;
2310
2702
  if (pageSize) {
2311
- // Paginated: fetch in chunks to avoid loading everything into memory
2312
2703
  entries = [];
2313
2704
  let offset = 0;
2314
2705
  const stmt = db.prepare(
@@ -2378,6 +2769,188 @@ async function runExport() {
2378
2769
  }
2379
2770
  }
2380
2771
 
2772
+ async function runExportZip() {
2773
+ const output = getFlag("--output") || getFlag("-o");
2774
+ const dryRun = flags.has("--dry-run");
2775
+ const tagsRaw = getFlag("--tags");
2776
+ const kindRaw = getFlag("--kind");
2777
+ const since = getFlag("--since");
2778
+ const until = getFlag("--until");
2779
+ const exportAll = flags.has("--all");
2780
+
2781
+ const tagsFilter = tagsRaw
2782
+ ? tagsRaw.split(",").map((t) => t.trim()).filter(Boolean)
2783
+ : null;
2784
+ const kindFilter = kindRaw
2785
+ ? kindRaw.split(",").map((k) => k.trim()).filter(Boolean)
2786
+ : null;
2787
+
2788
+ if (!exportAll && !tagsFilter && !kindFilter && !since && !until) {
2789
+ console.log(`\n ${bold("context-vault export --format zip")} [options]\n`);
2790
+ console.log(` Export vault entries as a portable ZIP archive.\n`);
2791
+ console.log(` ${bold("Filters (at least one required, or use --all):")}`);
2792
+ console.log(` --tags <t1,t2> Filter by tags (comma-separated)`);
2793
+ console.log(` --kind <k1,k2> Filter by kind (comma-separated)`);
2794
+ console.log(` --since <YYYY-MM-DD> Entries created on or after date`);
2795
+ console.log(` --until <YYYY-MM-DD> Entries created on or before date`);
2796
+ console.log(` --all Export all entries\n`);
2797
+ console.log(` ${bold("Options:")}`);
2798
+ console.log(` --output, -o <path> Output file path`);
2799
+ console.log(` --dry-run Show what would be exported\n`);
2800
+ console.log(` ${bold("Examples:")}`);
2801
+ console.log(` context-vault export --tags stormfors --format zip -o stormfors.zip`);
2802
+ console.log(` context-vault export --kind decision,pattern --format zip`);
2803
+ console.log(` context-vault export --since 2026-01-01 --until 2026-02-28 --format zip`);
2804
+ console.log(` context-vault export --all --format zip --dry-run\n`);
2805
+ return;
2806
+ }
2807
+
2808
+ const { resolveConfig } = await import("@context-vault/core/config");
2809
+ const { initDatabase } = await import("@context-vault/core/db");
2810
+ const { readFileSync: readFs, existsSync: existsFn } = await import("node:fs");
2811
+ const { basename } = await import("node:path");
2812
+
2813
+ const config = resolveConfig();
2814
+ if (!config.vaultDirExists) {
2815
+ console.error(red(` Vault directory not found: ${config.vaultDir}`));
2816
+ process.exit(1);
2817
+ }
2818
+
2819
+ const db = await initDatabase(config.dbPath);
2820
+
2821
+ const conditions = ["(expires_at IS NULL OR expires_at > datetime('now'))"];
2822
+ const params = [];
2823
+
2824
+ if (tagsFilter) {
2825
+ const tagClauses = tagsFilter.map(() =>
2826
+ "EXISTS (SELECT 1 FROM json_each(vault.tags) WHERE json_each.value = ?)"
2827
+ );
2828
+ conditions.push(`(${tagClauses.join(" OR ")})`);
2829
+ params.push(...tagsFilter);
2830
+ }
2831
+
2832
+ if (kindFilter) {
2833
+ const placeholders = kindFilter.map(() => "?").join(", ");
2834
+ conditions.push(`kind IN (${placeholders})`);
2835
+ params.push(...kindFilter);
2836
+ }
2837
+
2838
+ if (since) {
2839
+ conditions.push("created_at >= ?");
2840
+ params.push(since.includes("T") ? since : `${since}T00:00:00.000Z`);
2841
+ }
2842
+
2843
+ if (until) {
2844
+ conditions.push("created_at <= ?");
2845
+ params.push(until.includes("T") ? until : `${until}T23:59:59.999Z`);
2846
+ }
2847
+
2848
+ const sql = `SELECT * FROM vault WHERE ${conditions.join(" AND ")} ORDER BY created_at DESC`;
2849
+ const rows = db.prepare(sql).all(...params);
2850
+ db.close();
2851
+
2852
+ if (rows.length === 0) {
2853
+ console.log(yellow("\n No entries match the given filters.\n"));
2854
+ return;
2855
+ }
2856
+
2857
+ const kindCounts = {};
2858
+ for (const row of rows) {
2859
+ kindCounts[row.kind] = (kindCounts[row.kind] || 0) + 1;
2860
+ }
2861
+
2862
+ console.log(`\n ${bold(String(rows.length))} entries match filters:\n`);
2863
+ for (const [k, count] of Object.entries(kindCounts).sort((a, b) => b[1] - a[1])) {
2864
+ console.log(` ${k}: ${count}`);
2865
+ }
2866
+
2867
+ const earliest = rows[rows.length - 1]?.created_at;
2868
+ const latest = rows[0]?.created_at;
2869
+ console.log(dim(`\n Date range: ${earliest?.slice(0, 10) || "?"} → ${latest?.slice(0, 10) || "?"}`));
2870
+
2871
+ if (dryRun) {
2872
+ console.log();
2873
+ for (let i = 0; i < Math.min(rows.length, 25); i++) {
2874
+ const r = rows[i];
2875
+ const tags = safeJsonParse(r.tags, []);
2876
+ const tagStr = tags.length ? ` ${dim(`[${tags.join(", ")}]`)}` : "";
2877
+ console.log(` ${dim(`[${i + 1}]`)} ${r.kind} — ${r.title || (r.body || "").slice(0, 60)}${tagStr}`);
2878
+ }
2879
+ if (rows.length > 25) {
2880
+ console.log(dim(` ... and ${rows.length - 25} more`));
2881
+ }
2882
+ console.log(dim("\n Dry run — no archive created.\n"));
2883
+ return;
2884
+ }
2885
+
2886
+ const AdmZip = (await import("adm-zip")).default;
2887
+ const zip = new AdmZip();
2888
+
2889
+ const indexEntries = [];
2890
+ let filesSkipped = 0;
2891
+
2892
+ for (const row of rows) {
2893
+ const entryPath = `entries/${row.kind}/${basename(row.file_path || `${row.id}.md`)}`;
2894
+
2895
+ let fileContent = null;
2896
+ if (row.file_path && existsFn(row.file_path)) {
2897
+ fileContent = readFs(row.file_path);
2898
+ }
2899
+
2900
+ if (!fileContent) {
2901
+ filesSkipped++;
2902
+ continue;
2903
+ }
2904
+
2905
+ zip.addFile(entryPath, fileContent);
2906
+
2907
+ indexEntries.push({
2908
+ id: row.id,
2909
+ kind: row.kind,
2910
+ category: row.category,
2911
+ title: row.title || null,
2912
+ tags: safeJsonParse(row.tags, []),
2913
+ source: row.source || null,
2914
+ identity_key: row.identity_key || null,
2915
+ expires_at: row.expires_at || null,
2916
+ created_at: row.created_at,
2917
+ file: entryPath,
2918
+ });
2919
+ }
2920
+
2921
+ const manifest = {
2922
+ version: 1,
2923
+ created_at: new Date().toISOString(),
2924
+ context_vault_version: VERSION,
2925
+ entry_count: indexEntries.length,
2926
+ date_range: { earliest, latest },
2927
+ filters: {
2928
+ tags: tagsFilter || null,
2929
+ kind: kindFilter || null,
2930
+ since: since || null,
2931
+ until: until || null,
2932
+ all: exportAll || false,
2933
+ },
2934
+ };
2935
+
2936
+ zip.addFile("manifest.json", Buffer.from(JSON.stringify(manifest, null, 2)));
2937
+ zip.addFile("index.json", Buffer.from(JSON.stringify({ entries: indexEntries }, null, 2)));
2938
+
2939
+ const today = new Date().toISOString().slice(0, 10);
2940
+ const defaultName = tagsFilter
2941
+ ? `vault-${tagsFilter[0]}-${today}.zip`
2942
+ : `vault-export-${today}.zip`;
2943
+ const outputPath = resolve(output || defaultName);
2944
+
2945
+ zip.writeZip(outputPath);
2946
+
2947
+ console.log(`\n ${green("✓")} Exported ${indexEntries.length} entries to ${outputPath}`);
2948
+ if (filesSkipped > 0) {
2949
+ console.log(yellow(` ⚠ ${filesSkipped} entries skipped (file not found on disk)`));
2950
+ }
2951
+ console.log();
2952
+ }
2953
+
2381
2954
  function safeJsonParse(str, fallback) {
2382
2955
  if (!str) return fallback;
2383
2956
  try {
@@ -2446,10 +3019,10 @@ async function runIngest() {
2446
3019
  return;
2447
3020
  }
2448
3021
 
2449
- const { resolveConfig } = await import("@context-vault/core/core/config");
3022
+ const { resolveConfig } = await import("@context-vault/core/config");
2450
3023
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
2451
- await import("@context-vault/core/index/db");
2452
- const { embed } = await import("@context-vault/core/index/embed");
3024
+ await import("@context-vault/core/db");
3025
+ const { embed } = await import("@context-vault/core/embed");
2453
3026
  const { captureAndIndex } = await import("@context-vault/core/capture");
2454
3027
 
2455
3028
  const config = resolveConfig();
@@ -2513,10 +3086,10 @@ async function runIngestProject() {
2513
3086
 
2514
3087
  console.log(dim(` Scanning ${projectPath}...`));
2515
3088
 
2516
- const { resolveConfig } = await import("@context-vault/core/core/config");
3089
+ const { resolveConfig } = await import("@context-vault/core/config");
2517
3090
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
2518
- await import("@context-vault/core/index/db");
2519
- const { embed } = await import("@context-vault/core/index/embed");
3091
+ await import("@context-vault/core/db");
3092
+ const { embed } = await import("@context-vault/core/embed");
2520
3093
  const { captureAndIndex } = await import("@context-vault/core/capture");
2521
3094
  const { existsSync: fsExists, readFileSync: fsRead } =
2522
3095
  await import("node:fs");
@@ -2727,21 +3300,21 @@ async function runRecall() {
2727
3300
 
2728
3301
  let db;
2729
3302
  try {
2730
- const { resolveConfig } = await import("@context-vault/core/core/config");
3303
+ const { resolveConfig } = await import("@context-vault/core/config");
2731
3304
  const config = resolveConfig();
2732
3305
 
2733
3306
  if (!config.vaultDirExists) return;
2734
3307
 
2735
3308
  const { initDatabase, prepareStatements } =
2736
- await import("@context-vault/core/index/db");
2737
- const { embed } = await import("@context-vault/core/index/embed");
2738
- const { hybridSearch } = await import("@context-vault/core/retrieve/index");
3309
+ await import("@context-vault/core/db");
3310
+ const { embed } = await import("@context-vault/core/embed");
3311
+ const { hybridSearch } = await import("@context-vault/core/search");
2739
3312
 
2740
3313
  db = await initDatabase(config.dbPath);
2741
3314
  const stmts = prepareStatements(db);
2742
3315
  const ctx = { db, config, stmts, embed };
2743
3316
 
2744
- const { categoryFor } = await import("@context-vault/core/core/categories");
3317
+ const { categoryFor } = await import("@context-vault/core/categories");
2745
3318
  const recall = config.recall;
2746
3319
 
2747
3320
  const results = await hybridSearch(ctx, query, {
@@ -2781,8 +3354,8 @@ async function runRecall() {
2781
3354
  }
2782
3355
 
2783
3356
  async function runFlush() {
2784
- const { resolveConfig } = await import("@context-vault/core/core/config");
2785
- const { initDatabase } = await import("@context-vault/core/index/db");
3357
+ const { resolveConfig } = await import("@context-vault/core/config");
3358
+ const { initDatabase } = await import("@context-vault/core/db");
2786
3359
 
2787
3360
  let db;
2788
3361
  try {
@@ -2828,12 +3401,12 @@ async function runSessionCapture() {
2828
3401
  }
2829
3402
  const { kind, title, body, tags, source } = payload;
2830
3403
  if (!kind || !body) return;
2831
- const { resolveConfig } = await import("@context-vault/core/core/config");
3404
+ const { resolveConfig } = await import("@context-vault/core/config");
2832
3405
  const config = resolveConfig();
2833
3406
  if (!config.vaultDirExists) return;
2834
3407
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
2835
- await import("@context-vault/core/index/db");
2836
- const { embed } = await import("@context-vault/core/index/embed");
3408
+ await import("@context-vault/core/db");
3409
+ const { embed } = await import("@context-vault/core/embed");
2837
3410
  const { captureAndIndex } = await import("@context-vault/core/capture");
2838
3411
  db = await initDatabase(config.dbPath);
2839
3412
  const stmts = prepareStatements(db);
@@ -2863,13 +3436,158 @@ async function runSessionCapture() {
2863
3436
  }
2864
3437
 
2865
3438
  async function runSessionEnd() {
2866
- const { main } = await import("../src/hooks/session-end.mjs");
2867
- await main();
3439
+ let db;
3440
+ try {
3441
+ const raw = await new Promise((resolve) => {
3442
+ let data = "";
3443
+ process.stdin.on("data", (chunk) => (data += chunk));
3444
+ process.stdin.on("end", () => resolve(data));
3445
+ });
3446
+ if (!raw.trim()) return;
3447
+ let input;
3448
+ try {
3449
+ input = JSON.parse(raw);
3450
+ } catch {
3451
+ return;
3452
+ }
3453
+ const { session_id, transcript_path, cwd } = input ?? {};
3454
+ if (!transcript_path || !cwd) return;
3455
+
3456
+ // Read transcript (JSONL)
3457
+ let turns = [];
3458
+ try {
3459
+ const transcriptRaw = readFileSync(transcript_path, "utf-8");
3460
+ for (const line of transcriptRaw.split("\n")) {
3461
+ const trimmed = line.trim();
3462
+ if (!trimmed) continue;
3463
+ try { turns.push(JSON.parse(trimmed)); } catch {}
3464
+ }
3465
+ } catch { return; }
3466
+
3467
+ const extractText = (turn) => {
3468
+ if (typeof turn.content === "string") return turn.content;
3469
+ if (Array.isArray(turn.content))
3470
+ return turn.content.filter((b) => b.type === "text").map((b) => b.text).join(" ");
3471
+ return "";
3472
+ };
3473
+
3474
+ const userTurns = turns.filter((t) => t.role === "user");
3475
+ if (userTurns.length === 0) return;
3476
+
3477
+ // Tool use blocks
3478
+ const allToolUse = [];
3479
+ for (const turn of turns) {
3480
+ if (!Array.isArray(turn.content)) continue;
3481
+ for (const block of turn.content) {
3482
+ if (block.type === "tool_use") allToolUse.push(block);
3483
+ }
3484
+ }
3485
+
3486
+ // Files modified
3487
+ const seenFiles = new Set();
3488
+ const filesModified = [];
3489
+ for (const block of allToolUse) {
3490
+ if (block.name === "Write" || block.name === "Edit") {
3491
+ const path = block.input?.file_path ?? block.input?.path ?? null;
3492
+ if (path && !seenFiles.has(path)) { seenFiles.add(path); filesModified.push(path); }
3493
+ }
3494
+ }
3495
+
3496
+ // Commands run
3497
+ const commandsRun = [];
3498
+ for (const block of allToolUse) {
3499
+ if (block.name === "Bash") {
3500
+ const cmd = block.input?.command ?? block.input?.cmd ?? null;
3501
+ if (cmd) commandsRun.push(cmd.slice(0, 100));
3502
+ }
3503
+ }
3504
+
3505
+ // Tool counts
3506
+ const toolCounts = {};
3507
+ for (const block of allToolUse) {
3508
+ const name = block.name ?? "unknown";
3509
+ toolCounts[name] = (toolCounts[name] ?? 0) + 1;
3510
+ }
3511
+ const toolSummary = Object.entries(toolCounts)
3512
+ .sort((a, b) => b[1] - a[1])
3513
+ .map(([name, count]) => `${name}: ${count}`)
3514
+ .join(", ");
3515
+
3516
+ // Duration
3517
+ let durationStr = null;
3518
+ const timestampedTurns = turns.filter((t) => t.timestamp != null);
3519
+ if (timestampedTurns.length >= 2) {
3520
+ const diffMs = new Date(timestampedTurns[timestampedTurns.length - 1].timestamp) - new Date(timestampedTurns[0].timestamp);
3521
+ if (!isNaN(diffMs) && diffMs >= 0) {
3522
+ const totalSec = Math.round(diffMs / 1000);
3523
+ const hours = Math.floor(totalSec / 3600);
3524
+ const minutes = Math.floor((totalSec % 3600) / 60);
3525
+ const seconds = totalSec % 60;
3526
+ durationStr = hours > 0 ? `${hours}h ${minutes}m` : minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
3527
+ }
3528
+ }
3529
+
3530
+ const message_count = userTurns.length;
3531
+ const project = cwd.split("/").pop() || "unknown";
3532
+ const first_prompt = extractText(userTurns[0]).slice(0, 200);
3533
+ const last_prompt = message_count > 1 ? extractText(userTurns[message_count - 1]).slice(0, 200) : first_prompt;
3534
+
3535
+ // Build body
3536
+ const durationPart = durationStr ? `, ~${durationStr}` : "";
3537
+ const bodyLines = [
3538
+ `Session in ${project} (${message_count} exchange${message_count !== 1 ? "s" : ""}${durationPart}).`,
3539
+ "", "## What was done",
3540
+ `Opened with: ${first_prompt}`, "",
3541
+ `Closed with: ${last_prompt}`,
3542
+ ];
3543
+ const limitedFiles = filesModified.slice(0, 20);
3544
+ if (limitedFiles.length > 0) {
3545
+ bodyLines.push("", "## Files modified");
3546
+ for (const f of limitedFiles) bodyLines.push(`- ${f}`);
3547
+ if (filesModified.length > 20) bodyLines.push(`- ... and ${filesModified.length - 20} more`);
3548
+ }
3549
+ const limitedCmds = commandsRun.slice(0, 10);
3550
+ if (limitedCmds.length > 0) {
3551
+ bodyLines.push("", "## Key commands");
3552
+ for (const c of limitedCmds) bodyLines.push(`- ${c}`);
3553
+ if (commandsRun.length > 10) bodyLines.push(`- ... and ${commandsRun.length - 10} more`);
3554
+ }
3555
+ if (toolSummary) bodyLines.push("", "## Tools used", toolSummary);
3556
+ const body = bodyLines.join("\n");
3557
+
3558
+ // Save via core APIs
3559
+ const { resolveConfig } = await import("@context-vault/core/config");
3560
+ const config = resolveConfig();
3561
+ if (!config.vaultDirExists) return;
3562
+ const { initDatabase, prepareStatements, insertVec, deleteVec } =
3563
+ await import("@context-vault/core/db");
3564
+ const { captureAndIndex } = await import("@context-vault/core/capture");
3565
+ db = await initDatabase(config.dbPath);
3566
+ const stmts = prepareStatements(db);
3567
+ const ctx = {
3568
+ db, config, stmts,
3569
+ embed: async () => null,
3570
+ insertVec: (rowid, embedding) => insertVec(stmts, rowid, embedding),
3571
+ deleteVec: (rowid) => deleteVec(stmts, rowid),
3572
+ };
3573
+ const entry = await captureAndIndex(ctx, {
3574
+ kind: "session",
3575
+ title: `Session — ${project} ${new Date().toLocaleString("en-US", { month: "short", day: "numeric", year: "numeric", hour: "2-digit", minute: "2-digit" })}`,
3576
+ body,
3577
+ tags: ["session-end", "session-summary", project],
3578
+ source: "claude-code",
3579
+ meta: { session_id: session_id ?? null, cwd, message_count },
3580
+ });
3581
+ console.log(`context-vault session captured — id: ${entry.id}`);
3582
+ } catch {
3583
+ // fail silently — never block session end
3584
+ } finally {
3585
+ try { db?.close(); } catch {}
3586
+ }
2868
3587
  }
2869
3588
 
2870
3589
  async function runPostToolCall() {
2871
- const { main } = await import("../src/hooks/post-tool-call.mjs");
2872
- await main();
3590
+ // Removed in v3 post-tool-call hooks are no longer supported
2873
3591
  }
2874
3592
 
2875
3593
  async function runSave() {
@@ -2912,7 +3630,7 @@ async function runSave() {
2912
3630
 
2913
3631
  let db;
2914
3632
  try {
2915
- const { resolveConfig } = await import("@context-vault/core/core/config");
3633
+ const { resolveConfig } = await import("@context-vault/core/config");
2916
3634
  const config = resolveConfig();
2917
3635
  if (!config.vaultDirExists) {
2918
3636
  console.error(
@@ -2921,8 +3639,8 @@ async function runSave() {
2921
3639
  process.exit(1);
2922
3640
  }
2923
3641
  const { initDatabase, prepareStatements, insertVec, deleteVec } =
2924
- await import("@context-vault/core/index/db");
2925
- const { embed } = await import("@context-vault/core/index/embed");
3642
+ await import("@context-vault/core/db");
3643
+ const { embed } = await import("@context-vault/core/embed");
2926
3644
  const { captureAndIndex } = await import("@context-vault/core/capture");
2927
3645
  db = await initDatabase(config.dbPath);
2928
3646
  const stmts = prepareStatements(db);
@@ -2996,7 +3714,7 @@ async function runSearch() {
2996
3714
 
2997
3715
  let db;
2998
3716
  try {
2999
- const { resolveConfig } = await import("@context-vault/core/core/config");
3717
+ const { resolveConfig } = await import("@context-vault/core/config");
3000
3718
  const config = resolveConfig();
3001
3719
  if (!config.vaultDirExists) {
3002
3720
  console.error(red("No vault found. Run: context-vault setup"));
@@ -3004,9 +3722,9 @@ async function runSearch() {
3004
3722
  }
3005
3723
 
3006
3724
  const { initDatabase, prepareStatements } =
3007
- await import("@context-vault/core/index/db");
3008
- const { embed } = await import("@context-vault/core/index/embed");
3009
- const { hybridSearch } = await import("@context-vault/core/retrieve/index");
3725
+ await import("@context-vault/core/db");
3726
+ const { embed } = await import("@context-vault/core/embed");
3727
+ const { hybridSearch } = await import("@context-vault/core/search");
3010
3728
 
3011
3729
  db = await initDatabase(config.dbPath);
3012
3730
  const stmts = prepareStatements(db);
@@ -3763,9 +4481,9 @@ ${bold("Commands:")}
3763
4481
  }
3764
4482
 
3765
4483
  async function runDoctor() {
3766
- const { resolveConfig } = await import("@context-vault/core/core/config");
4484
+ const { resolveConfig } = await import("@context-vault/core/config");
3767
4485
  const { errorLogPath, errorLogCount } =
3768
- await import("@context-vault/core/core/error-log");
4486
+ await import("../src/error-log.js");
3769
4487
 
3770
4488
  console.log();
3771
4489
  console.log(` ${bold("◇ context-vault doctor")} ${dim(`v${VERSION}`)}`);
@@ -3839,7 +4557,7 @@ async function runDoctor() {
3839
4557
  let db;
3840
4558
  if (existsSync(config.dbPath)) {
3841
4559
  try {
3842
- const { initDatabase } = await import("@context-vault/core/index/db");
4560
+ const { initDatabase } = await import("@context-vault/core/db");
3843
4561
  db = await initDatabase(config.dbPath);
3844
4562
  const schemaRow = db.prepare("PRAGMA user_version").get();
3845
4563
  const schemaVersion = schemaRow?.user_version ?? "unknown";
@@ -3861,7 +4579,7 @@ async function runDoctor() {
3861
4579
 
3862
4580
  // ── Embedding model ──────────────────────────────────────────────────
3863
4581
  try {
3864
- const { embed } = await import("@context-vault/core/index/embed");
4582
+ const { embed } = await import("@context-vault/core/embed");
3865
4583
  const vec = await embed("doctor check");
3866
4584
  if (vec && vec.length > 0) {
3867
4585
  console.log(
@@ -4227,9 +4945,9 @@ async function runDoctor() {
4227
4945
  }
4228
4946
 
4229
4947
  async function runHealth() {
4230
- const { resolveConfig } = await import("@context-vault/core/core/config");
4948
+ const { resolveConfig } = await import("@context-vault/core/config");
4231
4949
  const { initDatabase, testConnection } =
4232
- await import("@context-vault/core/index/db");
4950
+ await import("@context-vault/core/db");
4233
4951
 
4234
4952
  let config;
4235
4953
  let healthy = true;
@@ -4418,8 +5136,8 @@ async function runConsolidate() {
4418
5136
  const dryRun = flags.has("--dry-run");
4419
5137
  const tagArg = getFlag("--tag");
4420
5138
 
4421
- const { resolveConfig } = await import("@context-vault/core/core/config");
4422
- const { initDatabase } = await import("@context-vault/core/index/db");
5139
+ const { resolveConfig } = await import("@context-vault/core/config");
5140
+ const { initDatabase } = await import("@context-vault/core/db");
4423
5141
  const { findHotTags, findColdEntries } =
4424
5142
  await import("@context-vault/core/consolidation/index");
4425
5143
 
@@ -4538,7 +5256,7 @@ async function runConsolidate() {
4538
5256
  );
4539
5257
  console.log(
4540
5258
  dim(
4541
- ` To archive: use context-vault search --kind <kind> and review manually.`,
5259
+ ` To archive: run context-vault archive (or --dry-run to preview).`,
4542
5260
  ),
4543
5261
  );
4544
5262
  }
@@ -4548,7 +5266,7 @@ async function runConsolidate() {
4548
5266
  }
4549
5267
 
4550
5268
  async function runServe() {
4551
- await import("../src/server/index.js");
5269
+ await import("../src/server.js");
4552
5270
  }
4553
5271
 
4554
5272
  async function main() {
@@ -4633,6 +5351,12 @@ async function main() {
4633
5351
  case "migrate-dirs":
4634
5352
  await runMigrateDirs();
4635
5353
  break;
5354
+ case "archive":
5355
+ await runArchive();
5356
+ break;
5357
+ case "restore":
5358
+ await runRestore();
5359
+ break;
4636
5360
  case "prune":
4637
5361
  await runPrune();
4638
5362
  break;