hub-vaults.mjs
73 lines 3.0 KB
Raw
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor ⚠ breaking 1 day ago
1 /**
2 * Multi-vault (Phase 15): read/write data/hub_vaults.yaml.
3 * Format: { vaults: [ { id: string, path: string, label?: string } ] }
4 * At least one entry with id "default" when file exists.
5 */
6
7 import fs from 'fs';
8 import path from 'path';
9 import yaml from 'js-yaml';
10
11 const VAULTS_FILE = 'hub_vaults.yaml';
12
13 /**
14 * Read vault list from data_dir. Returns empty array if file missing or invalid.
15 * @param {string} dataDir - Resolved data_dir path
16 * @param {string} [cwd] - For resolving relative paths (default dataDir parent)
17 * @returns {{ id: string, path: string, label?: string }[]}
18 */
19 export function readHubVaults(dataDir, cwd) {
20 if (!dataDir || typeof dataDir !== 'string') return [];
21 const p = path.join(dataDir, VAULTS_FILE);
22 if (!fs.existsSync(p)) return [];
23 try {
24 const raw = fs.readFileSync(p, 'utf8');
25 const data = yaml.load(raw) || {};
26 const list = Array.isArray(data.vaults) ? data.vaults : [];
27 const cwdResolve = cwd || path.dirname(path.dirname(dataDir));
28 return list
29 .filter((v) => v && typeof v.id === 'string' && typeof v.path === 'string')
30 .map((v) => ({
31 id: String(v.id).trim(),
32 path: path.isAbsolute(v.path) ? v.path : path.resolve(cwdResolve, v.path),
33 label: typeof v.label === 'string' ? v.label.trim() : undefined,
34 }));
35 } catch (_) {
36 return [];
37 }
38 }
39
40 /**
41 * Write hub_vaults.yaml. Validates that each path exists and is a directory.
42 * @param {string} dataDir - Resolved data_dir path
43 * @param {{ id: string, path: string, label?: string }[]} vaults
44 * @param {string} [cwd] - For resolving relative paths when writing (paths stored relative if under cwd)
45 */
46 export function writeHubVaults(dataDir, vaults, cwd) {
47 if (!dataDir || typeof dataDir !== 'string') throw new Error('data_dir is required');
48 if (!Array.isArray(vaults)) throw new Error('vaults must be an array');
49 const cwdResolve = cwd || path.dirname(path.dirname(dataDir));
50 const hasDefault = vaults.some((v) => v && String(v.id).trim() === 'default');
51 if (!hasDefault) throw new Error('At least one vault must have id "default"');
52
53 const list = vaults.map((v) => {
54 const id = String(v.id).trim();
55 if (!id) throw new Error('Vault id cannot be empty');
56 const rawPath = String(v.path).trim();
57 if (!rawPath) throw new Error(`Vault "${id}" path cannot be empty`);
58 const resolved = path.isAbsolute(rawPath) ? rawPath : path.resolve(cwdResolve, rawPath);
59 if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
60 throw new Error(`Vault path does not exist or is not a directory: ${resolved}`);
61 }
62 const storedPath = path.relative(cwdResolve, resolved);
63 return {
64 id,
65 path: storedPath.startsWith('..') ? resolved : storedPath,
66 ...(v.label != null && v.label !== '' ? { label: String(v.label).trim() } : {}),
67 };
68 });
69
70 const out = yaml.dump({ vaults: list }, { lineWidth: 120 });
71 fs.mkdirSync(dataDir, { recursive: true });
72 fs.writeFileSync(path.join(dataDir, VAULTS_FILE), out, 'utf8');
73 }
File History 2 commits
sha256:65ccb454656ea5acdea0a10e559b78bcde1eb6ff753ecc2911bc99d1c3d7cadd feat(calendar): enforce agent context tiers in retrieval AP… Human minor 1 day ago
sha256:9103f98c89257ed2b01c237cea895dabb3e85ea337dccb1161c175e4422355b6 docs: accept Calendar Events v0 spec with Phase 0 security … Human 2 days ago