context-vault 2.17.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +644 -65
- package/node_modules/@context-vault/core/dist/capture.d.ts +21 -0
- package/node_modules/@context-vault/core/dist/capture.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/capture.js +269 -0
- package/node_modules/@context-vault/core/dist/capture.js.map +1 -0
- package/node_modules/@context-vault/core/dist/categories.d.ts +6 -0
- package/node_modules/@context-vault/core/dist/categories.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/categories.js +50 -0
- package/node_modules/@context-vault/core/dist/categories.js.map +1 -0
- package/node_modules/@context-vault/core/dist/config.d.ts +4 -0
- package/node_modules/@context-vault/core/dist/config.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/config.js +190 -0
- package/node_modules/@context-vault/core/dist/config.js.map +1 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts +33 -0
- package/node_modules/@context-vault/core/dist/constants.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/constants.js +23 -0
- package/node_modules/@context-vault/core/dist/constants.js.map +1 -0
- package/node_modules/@context-vault/core/dist/db.d.ts +13 -0
- package/node_modules/@context-vault/core/dist/db.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/db.js +191 -0
- package/node_modules/@context-vault/core/dist/db.js.map +1 -0
- package/node_modules/@context-vault/core/dist/embed.d.ts +5 -0
- package/node_modules/@context-vault/core/dist/embed.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/embed.js +78 -0
- package/node_modules/@context-vault/core/dist/embed.js.map +1 -0
- package/node_modules/@context-vault/core/dist/files.d.ts +13 -0
- package/node_modules/@context-vault/core/dist/files.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/files.js +66 -0
- package/node_modules/@context-vault/core/dist/files.js.map +1 -0
- package/node_modules/@context-vault/core/dist/formatters.d.ts +8 -0
- package/node_modules/@context-vault/core/dist/formatters.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/formatters.js +18 -0
- package/node_modules/@context-vault/core/dist/formatters.js.map +1 -0
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts +12 -0
- package/node_modules/@context-vault/core/dist/frontmatter.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/frontmatter.js +101 -0
- package/node_modules/@context-vault/core/dist/frontmatter.js.map +1 -0
- package/node_modules/@context-vault/core/dist/index.d.ts +10 -0
- package/node_modules/@context-vault/core/dist/index.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/index.js +297 -0
- package/node_modules/@context-vault/core/dist/index.js.map +1 -0
- package/node_modules/@context-vault/core/dist/ingest-url.d.ts +20 -0
- package/node_modules/@context-vault/core/dist/ingest-url.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/ingest-url.js +113 -0
- package/node_modules/@context-vault/core/dist/ingest-url.js.map +1 -0
- package/node_modules/@context-vault/core/dist/main.d.ts +14 -0
- package/node_modules/@context-vault/core/dist/main.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/main.js +25 -0
- package/node_modules/@context-vault/core/dist/main.js.map +1 -0
- package/node_modules/@context-vault/core/dist/search.d.ts +18 -0
- package/node_modules/@context-vault/core/dist/search.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/search.js +238 -0
- package/node_modules/@context-vault/core/dist/search.js.map +1 -0
- package/node_modules/@context-vault/core/dist/types.d.ts +176 -0
- package/node_modules/@context-vault/core/dist/types.d.ts.map +1 -0
- package/node_modules/@context-vault/core/dist/types.js +2 -0
- package/node_modules/@context-vault/core/dist/types.js.map +1 -0
- package/node_modules/@context-vault/core/package.json +66 -16
- package/node_modules/@context-vault/core/src/capture.ts +308 -0
- package/node_modules/@context-vault/core/src/categories.ts +54 -0
- package/node_modules/@context-vault/core/src/{core/config.js → config.ts} +34 -33
- package/node_modules/@context-vault/core/src/{constants.js → constants.ts} +6 -3
- package/node_modules/@context-vault/core/src/db.ts +229 -0
- package/node_modules/@context-vault/core/src/{index/embed.js → embed.ts} +10 -35
- package/node_modules/@context-vault/core/src/{core/files.js → files.ts} +15 -20
- package/node_modules/@context-vault/core/src/{capture/formatters.js → formatters.ts} +13 -11
- package/node_modules/@context-vault/core/src/{core/frontmatter.js → frontmatter.ts} +26 -33
- package/node_modules/@context-vault/core/src/index.ts +351 -0
- package/node_modules/@context-vault/core/src/ingest-url.ts +99 -0
- package/node_modules/@context-vault/core/src/main.ts +111 -0
- package/node_modules/@context-vault/core/src/{retrieve/index.js → search.ts} +62 -150
- package/node_modules/@context-vault/core/src/types.ts +166 -0
- package/package.json +12 -7
- package/scripts/postinstall.js +1 -1
- package/{node_modules/@context-vault/core/src/core → src}/error-log.js +1 -15
- package/{node_modules/@context-vault/core/src/server → src}/helpers.js +9 -4
- package/src/linking.js +100 -0
- package/{node_modules/@context-vault/core/src/server/tools.js → src/register-tools.js} +14 -13
- package/src/{server/index.js → server.js} +10 -38
- package/src/status.js +235 -0
- package/{node_modules/@context-vault/core/src/core → src}/telemetry.js +9 -19
- package/src/temporal.js +97 -0
- package/{node_modules/@context-vault/core/src/server → src}/tools/context-status.js +3 -4
- package/{node_modules/@context-vault/core/src/server → src}/tools/create-snapshot.js +6 -7
- package/{node_modules/@context-vault/core/src/server → src}/tools/delete-context.js +0 -2
- package/{node_modules/@context-vault/core/src/server → src}/tools/get-context.js +17 -21
- package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-project.js +5 -6
- package/{node_modules/@context-vault/core/src/server → src}/tools/ingest-url.js +3 -4
- package/{node_modules/@context-vault/core/src/server → src}/tools/list-buckets.js +4 -5
- package/{node_modules/@context-vault/core/src/server → src}/tools/list-context.js +3 -6
- package/{node_modules/@context-vault/core/src/server → src}/tools/save-context.js +17 -20
- package/{node_modules/@context-vault/core/src/server → src}/tools/session-start.js +9 -16
- package/node_modules/@context-vault/core/src/capture/file-ops.js +0 -99
- package/node_modules/@context-vault/core/src/capture/import-pipeline.js +0 -46
- package/node_modules/@context-vault/core/src/capture/importers.js +0 -387
- package/node_modules/@context-vault/core/src/capture/index.js +0 -250
- package/node_modules/@context-vault/core/src/capture/ingest-url.js +0 -252
- package/node_modules/@context-vault/core/src/consolidation/index.js +0 -112
- package/node_modules/@context-vault/core/src/core/categories.js +0 -73
- package/node_modules/@context-vault/core/src/core/linking.js +0 -161
- package/node_modules/@context-vault/core/src/core/migrate-dirs.js +0 -196
- package/node_modules/@context-vault/core/src/core/status.js +0 -350
- package/node_modules/@context-vault/core/src/core/temporal.js +0 -146
- package/node_modules/@context-vault/core/src/index/db.js +0 -586
- package/node_modules/@context-vault/core/src/index/index.js +0 -583
- package/node_modules/@context-vault/core/src/index.js +0 -71
- package/node_modules/@context-vault/core/src/sync/sync.js +0 -235
- package/src/hooks/post-tool-call.mjs +0 -62
- package/src/hooks/session-end.mjs +0 -492
- /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
|
|
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
|
|
310
|
-
${cyan("export")} Export vault
|
|
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/
|
|
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/
|
|
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
|
|
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/
|
|
1698
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
1677
1699
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1678
|
-
await import("@context-vault/core/
|
|
1679
|
-
const { embed } = await import("@context-vault/core/
|
|
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/
|
|
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/
|
|
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/
|
|
1809
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
1788
1810
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
1789
|
-
await import("@context-vault/core/
|
|
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/
|
|
1849
|
-
const { initDatabase } = await import("@context-vault/core/
|
|
1850
|
-
const { gatherVaultStatus } = await import("
|
|
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("
|
|
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/
|
|
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
|
|
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
|
|
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/
|
|
2199
|
-
const { embed } = await import("@context-vault/core/
|
|
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
|
-
|
|
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/
|
|
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/
|
|
3022
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
2450
3023
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
2451
|
-
await import("@context-vault/core/
|
|
2452
|
-
const { embed } = await import("@context-vault/core/
|
|
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/
|
|
3089
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
2517
3090
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
2518
|
-
await import("@context-vault/core/
|
|
2519
|
-
const { embed } = await import("@context-vault/core/
|
|
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/
|
|
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/
|
|
2737
|
-
const { embed } = await import("@context-vault/core/
|
|
3309
|
+
await import("@context-vault/core/db");
|
|
3310
|
+
const { embed } = await import("@context-vault/core/embed");
|
|
2738
3311
|
const { hybridSearch } = await import("@context-vault/core/retrieve/index");
|
|
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/
|
|
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/
|
|
2785
|
-
const { initDatabase } = await import("@context-vault/core/
|
|
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/
|
|
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/
|
|
2836
|
-
const { embed } = await import("@context-vault/core/
|
|
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);
|
|
@@ -2912,7 +3485,7 @@ async function runSave() {
|
|
|
2912
3485
|
|
|
2913
3486
|
let db;
|
|
2914
3487
|
try {
|
|
2915
|
-
const { resolveConfig } = await import("@context-vault/core/
|
|
3488
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
2916
3489
|
const config = resolveConfig();
|
|
2917
3490
|
if (!config.vaultDirExists) {
|
|
2918
3491
|
console.error(
|
|
@@ -2921,8 +3494,8 @@ async function runSave() {
|
|
|
2921
3494
|
process.exit(1);
|
|
2922
3495
|
}
|
|
2923
3496
|
const { initDatabase, prepareStatements, insertVec, deleteVec } =
|
|
2924
|
-
await import("@context-vault/core/
|
|
2925
|
-
const { embed } = await import("@context-vault/core/
|
|
3497
|
+
await import("@context-vault/core/db");
|
|
3498
|
+
const { embed } = await import("@context-vault/core/embed");
|
|
2926
3499
|
const { captureAndIndex } = await import("@context-vault/core/capture");
|
|
2927
3500
|
db = await initDatabase(config.dbPath);
|
|
2928
3501
|
const stmts = prepareStatements(db);
|
|
@@ -2996,7 +3569,7 @@ async function runSearch() {
|
|
|
2996
3569
|
|
|
2997
3570
|
let db;
|
|
2998
3571
|
try {
|
|
2999
|
-
const { resolveConfig } = await import("@context-vault/core/
|
|
3572
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
3000
3573
|
const config = resolveConfig();
|
|
3001
3574
|
if (!config.vaultDirExists) {
|
|
3002
3575
|
console.error(red("No vault found. Run: context-vault setup"));
|
|
@@ -3004,8 +3577,8 @@ async function runSearch() {
|
|
|
3004
3577
|
}
|
|
3005
3578
|
|
|
3006
3579
|
const { initDatabase, prepareStatements } =
|
|
3007
|
-
await import("@context-vault/core/
|
|
3008
|
-
const { embed } = await import("@context-vault/core/
|
|
3580
|
+
await import("@context-vault/core/db");
|
|
3581
|
+
const { embed } = await import("@context-vault/core/embed");
|
|
3009
3582
|
const { hybridSearch } = await import("@context-vault/core/retrieve/index");
|
|
3010
3583
|
|
|
3011
3584
|
db = await initDatabase(config.dbPath);
|
|
@@ -3763,9 +4336,9 @@ ${bold("Commands:")}
|
|
|
3763
4336
|
}
|
|
3764
4337
|
|
|
3765
4338
|
async function runDoctor() {
|
|
3766
|
-
const { resolveConfig } = await import("@context-vault/core/
|
|
4339
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
3767
4340
|
const { errorLogPath, errorLogCount } =
|
|
3768
|
-
await import("
|
|
4341
|
+
await import("../src/error-log.js");
|
|
3769
4342
|
|
|
3770
4343
|
console.log();
|
|
3771
4344
|
console.log(` ${bold("◇ context-vault doctor")} ${dim(`v${VERSION}`)}`);
|
|
@@ -3839,7 +4412,7 @@ async function runDoctor() {
|
|
|
3839
4412
|
let db;
|
|
3840
4413
|
if (existsSync(config.dbPath)) {
|
|
3841
4414
|
try {
|
|
3842
|
-
const { initDatabase } = await import("@context-vault/core/
|
|
4415
|
+
const { initDatabase } = await import("@context-vault/core/db");
|
|
3843
4416
|
db = await initDatabase(config.dbPath);
|
|
3844
4417
|
const schemaRow = db.prepare("PRAGMA user_version").get();
|
|
3845
4418
|
const schemaVersion = schemaRow?.user_version ?? "unknown";
|
|
@@ -3861,7 +4434,7 @@ async function runDoctor() {
|
|
|
3861
4434
|
|
|
3862
4435
|
// ── Embedding model ──────────────────────────────────────────────────
|
|
3863
4436
|
try {
|
|
3864
|
-
const { embed } = await import("@context-vault/core/
|
|
4437
|
+
const { embed } = await import("@context-vault/core/embed");
|
|
3865
4438
|
const vec = await embed("doctor check");
|
|
3866
4439
|
if (vec && vec.length > 0) {
|
|
3867
4440
|
console.log(
|
|
@@ -4227,9 +4800,9 @@ async function runDoctor() {
|
|
|
4227
4800
|
}
|
|
4228
4801
|
|
|
4229
4802
|
async function runHealth() {
|
|
4230
|
-
const { resolveConfig } = await import("@context-vault/core/
|
|
4803
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
4231
4804
|
const { initDatabase, testConnection } =
|
|
4232
|
-
await import("@context-vault/core/
|
|
4805
|
+
await import("@context-vault/core/db");
|
|
4233
4806
|
|
|
4234
4807
|
let config;
|
|
4235
4808
|
let healthy = true;
|
|
@@ -4418,8 +4991,8 @@ async function runConsolidate() {
|
|
|
4418
4991
|
const dryRun = flags.has("--dry-run");
|
|
4419
4992
|
const tagArg = getFlag("--tag");
|
|
4420
4993
|
|
|
4421
|
-
const { resolveConfig } = await import("@context-vault/core/
|
|
4422
|
-
const { initDatabase } = await import("@context-vault/core/
|
|
4994
|
+
const { resolveConfig } = await import("@context-vault/core/config");
|
|
4995
|
+
const { initDatabase } = await import("@context-vault/core/db");
|
|
4423
4996
|
const { findHotTags, findColdEntries } =
|
|
4424
4997
|
await import("@context-vault/core/consolidation/index");
|
|
4425
4998
|
|
|
@@ -4538,7 +5111,7 @@ async function runConsolidate() {
|
|
|
4538
5111
|
);
|
|
4539
5112
|
console.log(
|
|
4540
5113
|
dim(
|
|
4541
|
-
` To archive:
|
|
5114
|
+
` To archive: run context-vault archive (or --dry-run to preview).`,
|
|
4542
5115
|
),
|
|
4543
5116
|
);
|
|
4544
5117
|
}
|
|
@@ -4548,7 +5121,7 @@ async function runConsolidate() {
|
|
|
4548
5121
|
}
|
|
4549
5122
|
|
|
4550
5123
|
async function runServe() {
|
|
4551
|
-
await import("../src/server
|
|
5124
|
+
await import("../src/server.js");
|
|
4552
5125
|
}
|
|
4553
5126
|
|
|
4554
5127
|
async function main() {
|
|
@@ -4633,6 +5206,12 @@ async function main() {
|
|
|
4633
5206
|
case "migrate-dirs":
|
|
4634
5207
|
await runMigrateDirs();
|
|
4635
5208
|
break;
|
|
5209
|
+
case "archive":
|
|
5210
|
+
await runArchive();
|
|
5211
|
+
break;
|
|
5212
|
+
case "restore":
|
|
5213
|
+
await runRestore();
|
|
5214
|
+
break;
|
|
4636
5215
|
case "prune":
|
|
4637
5216
|
await runPrune();
|
|
4638
5217
|
break;
|