@polderlabs/bizar 5.5.1 → 5.5.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.
- package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js → EnvVarsSection-DoIzqrlb.js} +3 -3
- package/bizar-dash/dist/assets/{EnvVarsSection-BUQnyVQD.js.map → EnvVarsSection-DoIzqrlb.js.map} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js → MobileChat-CoUCUsbr.js} +1 -1
- package/bizar-dash/dist/assets/{MobileChat-8FupFgTv.js.map → MobileChat-CoUCUsbr.js.map} +1 -1
- package/bizar-dash/dist/assets/MobileSettings-BeO04g52.js +1 -0
- package/bizar-dash/dist/assets/{MobileSettings-BkXtglA6.js.map → MobileSettings-BeO04g52.js.map} +1 -1
- package/bizar-dash/dist/assets/{Toast-CUwkLRms.js → Toast-D9nv5N6m.js} +1 -1
- package/bizar-dash/dist/assets/{Toast-CUwkLRms.js.map → Toast-D9nv5N6m.js.map} +1 -1
- package/bizar-dash/dist/assets/{icons-COpQr35l.js → icons-Btipv2pp.js} +65 -70
- package/bizar-dash/dist/assets/icons-Btipv2pp.js.map +1 -0
- package/bizar-dash/dist/assets/main-DHXPuddY.js +18 -0
- package/bizar-dash/dist/assets/main-DHXPuddY.js.map +1 -0
- package/bizar-dash/dist/assets/{main-Bz_zWFCw.css → main-UG4jKK-F.css} +1 -1
- package/bizar-dash/dist/assets/mobile-H5KH9cTL.js +1 -0
- package/bizar-dash/dist/assets/{mobile-B7dAkmYd.js.map → mobile-H5KH9cTL.js.map} +1 -1
- package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js → mobile-layout-BqHxVb6_.js} +2 -2
- package/bizar-dash/dist/assets/{mobile-layout-ClC6Qa42.js.map → mobile-layout-BqHxVb6_.js.map} +1 -1
- package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js → useSlashCommands-C24-O1e7.js} +2 -2
- package/bizar-dash/dist/assets/{useSlashCommands-B3MZ1JXa.js.map → useSlashCommands-C24-O1e7.js.map} +1 -1
- package/bizar-dash/dist/index.html +7 -7
- package/bizar-dash/dist/mobile.html +3 -3
- package/bizar-dash/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
- package/bizar-dash/src/server/memory-store.mjs +74 -28
- package/bizar-dash/src/server/routes/memory.mjs +91 -38
- package/bizar-dash/src/web/App.tsx +36 -43
- package/bizar-dash/src/web/components/SettingsNav.tsx +92 -0
- package/bizar-dash/src/web/components/Sidebar.tsx +26 -7
- package/bizar-dash/src/web/components/Topbar.tsx +11 -48
- package/bizar-dash/src/web/styles/main.css +18 -3
- package/bizar-dash/src/web/styles/settings.css +115 -3
- package/bizar-dash/src/web/views/Settings.tsx +184 -205
- package/bizar-dash/src/web/views/memory/ConfigPanel.tsx +4 -4
- package/bizar-dash/tests/memory-namespace.test.mjs +2 -2
- package/bizar-dash/tests/memory-status-root.test.mjs +144 -0
- package/bizar-dash/tests/memory-store.test.mjs +5 -4
- package/bizar-dash/tests/memory-vault-config-endpoint.test.mjs +173 -0
- package/bizar-dash/tests/settings-layout.test.tsx +129 -0
- package/bizar-dash/tests/settings-mode-wiring.test.tsx +151 -0
- package/bizar-dash/tests/settings-nav.test.tsx +127 -0
- package/bizar-dash/tests/views/Memory.test.tsx +40 -1
- package/bizar-dash/tests/views/topbar.test.tsx +35 -50
- package/cli/memory.mjs +1 -1
- package/package.json +1 -1
- package/bizar-dash/dist/assets/MobileSettings-BkXtglA6.js +0 -1
- package/bizar-dash/dist/assets/icons-COpQr35l.js.map +0 -1
- package/bizar-dash/dist/assets/main-gqVwliHz.js +0 -18
- package/bizar-dash/dist/assets/main-gqVwliHz.js.map +0 -1
- package/bizar-dash/dist/assets/mobile-B7dAkmYd.js +0 -1
- package/bizar-dash/tests/views/Settings.test.tsx +0 -160
|
@@ -207,8 +207,14 @@ export function saveConfig(projectRoot, config) {
|
|
|
207
207
|
/**
|
|
208
208
|
* Resolve the effective vault root, namespaces, and related paths based on mode.
|
|
209
209
|
*
|
|
210
|
+
* v6.x — `vaultRoot` is now always the GENERAL vault root (e.g. `~/.bizar_memory`).
|
|
211
|
+
* A new `projectVaultRoot` field carries the project-specific subdirectory for
|
|
212
|
+
* note storage in managed/linked mode (`~/.bizar_memory/projects/<projectId>`).
|
|
213
|
+
* This distinction lets the UI display the general root while the store still
|
|
214
|
+
* writes notes to the correct project subdirectory.
|
|
215
|
+
*
|
|
210
216
|
* @param {string} projectRoot
|
|
211
|
-
* @returns {{ mode: string, projectId: string, vaultRoot: string, repoPath: string | null, configDir: string, gitRemote: string | null, branch: string, lightragDir: string, namespaces: object }}
|
|
217
|
+
* @returns {{ mode: string, projectId: string, vaultRoot: string, projectVaultRoot: string, repoPath: string | null, configDir: string, gitRemote: string | null, branch: string, lightragDir: string, namespaces: object }}
|
|
212
218
|
*/
|
|
213
219
|
export function resolveVault(projectRoot) {
|
|
214
220
|
const { config } = loadConfig(projectRoot);
|
|
@@ -219,16 +225,19 @@ export function resolveVault(projectRoot) {
|
|
|
219
225
|
const gitRemote = mr.remote || config.gitRemote || null;
|
|
220
226
|
|
|
221
227
|
let vaultRoot;
|
|
228
|
+
let projectVaultRoot;
|
|
222
229
|
let repoPath;
|
|
223
230
|
if (mode === 'local-only') {
|
|
224
231
|
vaultRoot = join(projectRoot, '.obsidian');
|
|
232
|
+
projectVaultRoot = vaultRoot;
|
|
225
233
|
repoPath = null;
|
|
226
234
|
} else {
|
|
227
235
|
// managed or linked — path may be absolute, ~-relative, or a sibling of projectRoot
|
|
228
236
|
const rawPath = mr.path || config.repoName || join(projectRoot, '.bizar', 'memory');
|
|
229
237
|
const expanded = rawPath.startsWith('~') ? join(HOME, rawPath.slice(1)) : rawPath;
|
|
230
238
|
repoPath = expanded;
|
|
231
|
-
vaultRoot =
|
|
239
|
+
vaultRoot = expanded; // v6.x — general root, e.g. ~/.bizar_memory
|
|
240
|
+
projectVaultRoot = join(expanded, 'projects', projectId); // project-specific subdirectory
|
|
232
241
|
}
|
|
233
242
|
|
|
234
243
|
const configDir = join(projectRoot, '.bizar');
|
|
@@ -239,29 +248,28 @@ export function resolveVault(projectRoot) {
|
|
|
239
248
|
user: `users/${process.env.USER || process.env.USERNAME || 'local'}`,
|
|
240
249
|
};
|
|
241
250
|
|
|
242
|
-
return { mode, projectId, vaultRoot, repoPath, configDir, gitRemote, branch, lightragDir, namespaces };
|
|
251
|
+
return { mode, projectId, vaultRoot, projectVaultRoot, repoPath, configDir, gitRemote, branch, lightragDir, namespaces };
|
|
243
252
|
}
|
|
244
253
|
|
|
245
254
|
/**
|
|
246
255
|
* Resolve the absolute path of a namespace root.
|
|
247
256
|
*
|
|
248
|
-
* For `project`, returns
|
|
249
|
-
*
|
|
250
|
-
* `
|
|
251
|
-
*
|
|
252
|
-
*
|
|
257
|
+
* For `project`, returns projectVaultRoot (the directory where project notes
|
|
258
|
+
* are stored: `<vaultRoot>/projects/<projectId>` in managed mode, or
|
|
259
|
+
* `<projectRoot>/.obsidian` in local-only mode). For `global` and `user`,
|
|
260
|
+
* returns `<vaultRoot>/<namespaces.X>` — these namespaces live at the vault
|
|
261
|
+
* root level, not inside the project subdirectory.
|
|
253
262
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* vaultRoot. This implementation matches the on-disk reality.)
|
|
263
|
+
* Routing: writeNote/readNote/deleteNote use this to determine where files
|
|
264
|
+
* live. Project relPaths go to projectVaultRoot; global/ and users/ relPaths
|
|
265
|
+
* go to vaultRoot-based paths.
|
|
258
266
|
*
|
|
259
|
-
* @param {{ mode: string, vaultRoot: string, namespaces: object }} vaultInfo
|
|
267
|
+
* @param {{ mode: string, vaultRoot: string, projectVaultRoot: string, namespaces: object }} vaultInfo
|
|
260
268
|
* @param {'project'|'global'|'user'} namespace
|
|
261
269
|
* @returns {string|null} absolute path, or null if the namespace is unknown
|
|
262
270
|
*/
|
|
263
271
|
export function resolveNamespaceRoot(vaultInfo, namespace) {
|
|
264
|
-
if (namespace === 'project') return vaultInfo.
|
|
272
|
+
if (namespace === 'project') return vaultInfo.projectVaultRoot;
|
|
265
273
|
const ns = vaultInfo.namespaces?.[namespace];
|
|
266
274
|
if (!ns) return null;
|
|
267
275
|
return join(vaultInfo.vaultRoot, ns);
|
|
@@ -381,8 +389,8 @@ export function initVault(projectRoot) {
|
|
|
381
389
|
* @returns {Array<{ relPath: string, mtime: number, size: number, frontmatter: Record<string, unknown>, body: string, schemaValid: boolean }>}
|
|
382
390
|
*/
|
|
383
391
|
export function listNotes(projectRoot, opts = {}) {
|
|
384
|
-
const {
|
|
385
|
-
const root = opts.root ||
|
|
392
|
+
const { projectVaultRoot } = resolveVault(projectRoot);
|
|
393
|
+
const root = opts.root || projectVaultRoot;
|
|
386
394
|
if (!existsSync(root)) return [];
|
|
387
395
|
|
|
388
396
|
const namespace = opts.namespace || '';
|
|
@@ -425,8 +433,16 @@ export function listNotes(projectRoot, opts = {}) {
|
|
|
425
433
|
* @returns {{ relPath: string, frontmatter: Record<string, unknown>, body: string, raw: string, mtime: number, size: number, schemaValid: boolean } | null}
|
|
426
434
|
*/
|
|
427
435
|
export function readNote(projectRoot, relPath, opts = {}) {
|
|
428
|
-
const
|
|
429
|
-
|
|
436
|
+
const { vaultRoot, projectVaultRoot } = resolveVault(projectRoot);
|
|
437
|
+
// When opts.root is explicitly provided, use it. Otherwise route based on
|
|
438
|
+
// namespace so reads find notes where writeNote placed them.
|
|
439
|
+
let root;
|
|
440
|
+
if (opts.root) {
|
|
441
|
+
root = opts.root;
|
|
442
|
+
} else {
|
|
443
|
+
const isProjectNamespace = !relPath.startsWith('global/') && !relPath.startsWith('users/');
|
|
444
|
+
root = isProjectNamespace ? projectVaultRoot : vaultRoot;
|
|
445
|
+
}
|
|
430
446
|
const filePath = resolveSafe(root, relPath);
|
|
431
447
|
if (!filePath || !existsSync(filePath)) return null;
|
|
432
448
|
try {
|
|
@@ -450,7 +466,7 @@ export function readNote(projectRoot, relPath, opts = {}) {
|
|
|
450
466
|
* @throws {Error} if schema validation fails or HIGH-severity secret found
|
|
451
467
|
*/
|
|
452
468
|
export function writeNote(projectRoot, relPath, { frontmatter, body }) {
|
|
453
|
-
const { vaultRoot } = resolveVault(projectRoot);
|
|
469
|
+
const { vaultRoot, projectVaultRoot } = resolveVault(projectRoot);
|
|
454
470
|
|
|
455
471
|
// Validate frontmatter
|
|
456
472
|
const { valid, errors, warnings } = validateNote(frontmatter, body);
|
|
@@ -473,13 +489,20 @@ export function writeNote(projectRoot, relPath, { frontmatter, body }) {
|
|
|
473
489
|
throw err;
|
|
474
490
|
}
|
|
475
491
|
|
|
492
|
+
// Determine the effective vault root based on namespace.
|
|
493
|
+
// Project notes (relPaths that don't start with global/ or users/) go under
|
|
494
|
+
// projectVaultRoot. Global and user notes go under vaultRoot.
|
|
495
|
+
// This matches the layout resolveNamespaceRoot returns for each namespace.
|
|
496
|
+
const isProjectNamespace = !relPath.startsWith('global/') && !relPath.startsWith('users/');
|
|
497
|
+
const noteVaultRoot = isProjectNamespace ? projectVaultRoot : vaultRoot;
|
|
498
|
+
|
|
476
499
|
// Build file content
|
|
477
|
-
const filePath = resolveSafe(
|
|
500
|
+
const filePath = resolveSafe(noteVaultRoot, relPath);
|
|
478
501
|
if (!filePath) {
|
|
479
502
|
throw new Error(`Invalid note path: ${relPath}`);
|
|
480
503
|
}
|
|
481
504
|
|
|
482
|
-
// Lazily create the
|
|
505
|
+
// Lazily create the namespace directory (not created at initVault)
|
|
483
506
|
const nsDir = dirname(filePath);
|
|
484
507
|
if (!existsSync(nsDir)) {
|
|
485
508
|
mkdirSync(nsDir, { recursive: true });
|
|
@@ -538,8 +561,16 @@ export function writeNote(projectRoot, relPath, { frontmatter, body }) {
|
|
|
538
561
|
* @returns {boolean}
|
|
539
562
|
*/
|
|
540
563
|
export function deleteNote(projectRoot, relPath, opts = {}) {
|
|
541
|
-
const
|
|
542
|
-
|
|
564
|
+
const { vaultRoot, projectVaultRoot } = resolveVault(projectRoot);
|
|
565
|
+
// When opts.root is explicitly provided, use it. Otherwise route based on
|
|
566
|
+
// namespace so deletes find notes where writeNote placed them.
|
|
567
|
+
let root;
|
|
568
|
+
if (opts.root) {
|
|
569
|
+
root = opts.root;
|
|
570
|
+
} else {
|
|
571
|
+
const isProjectNamespace = !relPath.startsWith('global/') && !relPath.startsWith('users/');
|
|
572
|
+
root = isProjectNamespace ? projectVaultRoot : vaultRoot;
|
|
573
|
+
}
|
|
543
574
|
const filePath = resolveSafe(root, relPath);
|
|
544
575
|
if (!filePath || !existsSync(filePath)) return false;
|
|
545
576
|
unlinkSync(filePath);
|
|
@@ -560,8 +591,23 @@ export function searchVault(projectRoot, query, { limit = 25 } = {}) {
|
|
|
560
591
|
const tokens = q.split(/\s+/).filter((t) => t.length >= 2);
|
|
561
592
|
if (tokens.length === 0) return [];
|
|
562
593
|
|
|
594
|
+
const { vaultRoot, projectVaultRoot } = resolveVault(projectRoot);
|
|
595
|
+
|
|
596
|
+
// Collect notes from all namespaces by walking vaultRoot (which contains
|
|
597
|
+
// projects/<id>/, global/, and users/ as siblings). This finds project,
|
|
598
|
+
// global, and user notes in a single scan. relPaths returned are vaultRoot-
|
|
599
|
+
// relative (e.g. "projects/<id>/decisions/foo.md" or "global/bizar/foo.md").
|
|
600
|
+
// For project notes, strip the "projects/<id>/" prefix so the relPath matches
|
|
601
|
+
// what writeNote uses (projectVaultRoot-relative).
|
|
602
|
+
const projectPrefix = join('projects', projectVaultRoot.split('/').pop());
|
|
603
|
+
const allNotes = listNotes(projectRoot, { root: vaultRoot });
|
|
604
|
+
|
|
563
605
|
const results = [];
|
|
564
|
-
for (const note of
|
|
606
|
+
for (const note of allNotes) {
|
|
607
|
+
let relPath = note.relPath;
|
|
608
|
+
if (relPath.startsWith(projectPrefix)) {
|
|
609
|
+
relPath = relPath.slice(projectPrefix.length + 1);
|
|
610
|
+
}
|
|
565
611
|
const lower = (note.body + ' ' + JSON.stringify(note.frontmatter)).toLowerCase();
|
|
566
612
|
let score = 0;
|
|
567
613
|
for (const t of tokens) {
|
|
@@ -572,7 +618,7 @@ export function searchVault(projectRoot, query, { limit = 25 } = {}) {
|
|
|
572
618
|
const start = Math.max(0, idx - 60);
|
|
573
619
|
const end = Math.min(lower.length, idx + 160);
|
|
574
620
|
const snippet = (start > 0 ? '…' : '') + lower.slice(start, end).replace(/\s+/g, ' ').trim();
|
|
575
|
-
results.push({ relPath
|
|
621
|
+
results.push({ relPath, snippet, score, mtime: note.mtime });
|
|
576
622
|
}
|
|
577
623
|
|
|
578
624
|
return results.sort((a, b) => b.score - a.score).slice(0, limit);
|
|
@@ -707,8 +753,8 @@ export function vaultStats(projectRoot) {
|
|
|
707
753
|
};
|
|
708
754
|
}
|
|
709
755
|
|
|
710
|
-
const { vaultRoot, mode, branch } = resolveVault(projectRoot);
|
|
711
|
-
if (!
|
|
756
|
+
const { vaultRoot, projectVaultRoot, mode, branch } = resolveVault(projectRoot);
|
|
757
|
+
if (!projectVaultRoot || !existsSync(projectVaultRoot)) {
|
|
712
758
|
return {
|
|
713
759
|
exists: true,
|
|
714
760
|
vaultRoot: vaultRoot || '',
|
|
@@ -737,7 +783,7 @@ export function vaultStats(projectRoot) {
|
|
|
737
783
|
try {
|
|
738
784
|
const { isGitInstalled, status: gitStatus } = memoryGit;
|
|
739
785
|
if (isGitInstalled()) {
|
|
740
|
-
const gs = gitStatus(
|
|
786
|
+
const gs = gitStatus(projectVaultRoot);
|
|
741
787
|
gitClean = gs.clean;
|
|
742
788
|
gitBranch = gs.branch;
|
|
743
789
|
}
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { Router } from 'express';
|
|
13
|
-
import { join, dirname } from 'node:path';
|
|
13
|
+
import { join, dirname, resolve as pathResolve } from 'node:path';
|
|
14
14
|
import { fileURLToPath } from 'node:url';
|
|
15
|
+
import { homedir } from 'node:os';
|
|
15
16
|
import { existsSync, mkdirSync, readFileSync, statSync, readdirSync } from 'node:fs';
|
|
16
17
|
import { execFileSync } from 'node:child_process';
|
|
17
18
|
import { warn as logWarn } from '../logger.mjs';
|
|
@@ -60,17 +61,19 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
60
61
|
|
|
61
62
|
const { config, exists } = loadConfig(projectRoot);
|
|
62
63
|
if (!exists) {
|
|
63
|
-
res.json({ initialized: false, mode: null, projectId: null, vaultRoot: null });
|
|
64
|
+
res.json({ initialized: false, mode: null, projectId: null, vaultRoot: null, projectVaultRoot: null });
|
|
64
65
|
return;
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
// v6.x — vaultRoot is now the GENERAL vault root (e.g. ~/.bizar_memory).
|
|
69
|
+
// projectVaultRoot is the project-specific subdirectory for note storage.
|
|
70
|
+
const { vaultRoot, projectVaultRoot, mode, projectId, branch } = resolveVault(projectRoot);
|
|
71
|
+
const notes = existsSync(projectVaultRoot) ? listNotes(projectRoot) : [];
|
|
69
72
|
|
|
70
73
|
let gitClean = null;
|
|
71
74
|
let gitBranch = null;
|
|
72
|
-
if ((mode === 'managed' || mode === 'linked') && existsSync(
|
|
73
|
-
const gs = gitStatus(
|
|
75
|
+
if ((mode === 'managed' || mode === 'linked') && existsSync(projectVaultRoot) && isGitInstalled()) {
|
|
76
|
+
const gs = gitStatus(projectVaultRoot);
|
|
74
77
|
gitClean = gs.clean;
|
|
75
78
|
gitBranch = gs.branch;
|
|
76
79
|
}
|
|
@@ -91,7 +94,8 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
91
94
|
initialized: true,
|
|
92
95
|
mode,
|
|
93
96
|
projectId,
|
|
94
|
-
vaultRoot,
|
|
97
|
+
vaultRoot, // v6.x — general vault root (e.g. ~/.bizar_memory)
|
|
98
|
+
projectVaultRoot, // v6.x — project-specific subdirectory for managed/linked mode
|
|
95
99
|
branch: gitBranch || branch,
|
|
96
100
|
gitClean,
|
|
97
101
|
noteCount: notes.length,
|
|
@@ -277,7 +281,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
277
281
|
router.get('/memory/git/status', wrap(async (_req, res) => {
|
|
278
282
|
const { resolveVault } = memoryStore;
|
|
279
283
|
const { isGitInstalled, status: gitStatus } = memoryGit;
|
|
280
|
-
const {
|
|
284
|
+
const { projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
281
285
|
|
|
282
286
|
if (mode === 'local-only') {
|
|
283
287
|
res.json({ ok: true, mode: 'local-only' });
|
|
@@ -289,7 +293,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
289
293
|
return;
|
|
290
294
|
}
|
|
291
295
|
|
|
292
|
-
const gs = gitStatus(
|
|
296
|
+
const gs = gitStatus(projectVaultRoot);
|
|
293
297
|
res.json({ ok: true, mode, ...gs });
|
|
294
298
|
}));
|
|
295
299
|
|
|
@@ -297,7 +301,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
297
301
|
router.post('/memory/git/pull', wrap(async (_req, res) => {
|
|
298
302
|
const { resolveVault } = memoryStore;
|
|
299
303
|
const { pull, isGitInstalled } = memoryGit;
|
|
300
|
-
const {
|
|
304
|
+
const { projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
301
305
|
|
|
302
306
|
if (mode === 'local-only') {
|
|
303
307
|
res.status(400).json({ error: 'local_only_mode' });
|
|
@@ -308,7 +312,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
308
312
|
return;
|
|
309
313
|
}
|
|
310
314
|
|
|
311
|
-
const result = pull(
|
|
315
|
+
const result = pull(projectVaultRoot);
|
|
312
316
|
if (!result.ok) {
|
|
313
317
|
res.status(500).json({ error: 'pull_failed', message: result.error });
|
|
314
318
|
return;
|
|
@@ -320,7 +324,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
320
324
|
router.post('/memory/git/commit', wrap(async (req, res) => {
|
|
321
325
|
const { resolveVault } = memoryStore;
|
|
322
326
|
const { commit: gitCommit, addAll, isGitInstalled } = memoryGit;
|
|
323
|
-
const {
|
|
327
|
+
const { projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
324
328
|
|
|
325
329
|
if (mode === 'local-only') {
|
|
326
330
|
res.status(400).json({ error: 'local_only_mode' });
|
|
@@ -331,14 +335,14 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
331
335
|
return;
|
|
332
336
|
}
|
|
333
337
|
|
|
334
|
-
const addResult = addAll(
|
|
338
|
+
const addResult = addAll(projectVaultRoot);
|
|
335
339
|
if (!addResult.ok) {
|
|
336
340
|
res.status(500).json({ error: 'git_add_failed', message: addResult.error });
|
|
337
341
|
return;
|
|
338
342
|
}
|
|
339
343
|
|
|
340
344
|
const message = req.body?.message || `[memory-sync] ${new Date().toISOString().replace(/T.*/, '')} vault sync`;
|
|
341
|
-
const result = gitCommit(
|
|
345
|
+
const result = gitCommit(projectVaultRoot, message);
|
|
342
346
|
if (!result.ok) {
|
|
343
347
|
res.status(500).json({ error: 'commit_failed', message: result.error });
|
|
344
348
|
return;
|
|
@@ -350,7 +354,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
350
354
|
router.post('/memory/git/push', wrap(async (_req, res) => {
|
|
351
355
|
const { resolveVault, loadConfig } = memoryStore;
|
|
352
356
|
const { push: gitPush, isGitInstalled } = memoryGit;
|
|
353
|
-
const {
|
|
357
|
+
const { projectVaultRoot, mode, branch } = resolveVault(projectRoot);
|
|
354
358
|
const { config } = loadConfig(projectRoot);
|
|
355
359
|
|
|
356
360
|
if (mode === 'local-only') {
|
|
@@ -362,7 +366,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
362
366
|
return;
|
|
363
367
|
}
|
|
364
368
|
|
|
365
|
-
const result = gitPush(
|
|
369
|
+
const result = gitPush(projectVaultRoot, { remote: config.gitRemote || 'origin', branch });
|
|
366
370
|
if (!result.ok) {
|
|
367
371
|
res.status(500).json({ error: 'push_failed', message: result.error });
|
|
368
372
|
return;
|
|
@@ -374,7 +378,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
374
378
|
router.post('/memory/git/sync', wrap(async (req, res) => {
|
|
375
379
|
const { resolveVault, listNotes, validateAll, scanForSecrets } = memoryStore;
|
|
376
380
|
const { pull, addAll, commit: gitCommit, push: gitPush, status: gitStatus, acquireLock, isGitInstalled } = memoryGit;
|
|
377
|
-
const {
|
|
381
|
+
const { projectVaultRoot, mode, branch } = resolveVault(projectRoot);
|
|
378
382
|
const { config } = memoryStore.loadConfig(projectRoot);
|
|
379
383
|
|
|
380
384
|
if (mode === 'local-only') {
|
|
@@ -386,7 +390,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
386
390
|
return;
|
|
387
391
|
}
|
|
388
392
|
|
|
389
|
-
const lock = acquireLock(
|
|
393
|
+
const lock = acquireLock(projectVaultRoot);
|
|
390
394
|
if (lock.error) {
|
|
391
395
|
res.status(423).json({ error: 'locked', message: 'vault is locked by another process' });
|
|
392
396
|
return;
|
|
@@ -396,13 +400,13 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
396
400
|
|
|
397
401
|
try {
|
|
398
402
|
// Pull
|
|
399
|
-
const pullResult = pull(
|
|
403
|
+
const pullResult = pull(projectVaultRoot);
|
|
400
404
|
if (!pullResult.ok) {
|
|
401
405
|
// Non-fatal — may be up to date or have no remote
|
|
402
406
|
}
|
|
403
407
|
|
|
404
408
|
// Check status
|
|
405
|
-
const gs = gitStatus(
|
|
409
|
+
const gs = gitStatus(projectVaultRoot);
|
|
406
410
|
if (gs.clean) {
|
|
407
411
|
res.json({ ok: true, message: 'nothing to sync', clean: true });
|
|
408
412
|
return;
|
|
@@ -437,7 +441,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
437
441
|
}
|
|
438
442
|
|
|
439
443
|
// Add all
|
|
440
|
-
const addResult = addAll(
|
|
444
|
+
const addResult = addAll(projectVaultRoot);
|
|
441
445
|
if (!addResult.ok) {
|
|
442
446
|
res.status(500).json({ error: 'git_add_failed' });
|
|
443
447
|
return;
|
|
@@ -447,7 +451,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
447
451
|
const date = new Date().toISOString().replace(/T.*/, '');
|
|
448
452
|
const summary = allNotes[0]?.relPath?.slice(0, 60) || 'vault sync';
|
|
449
453
|
const message = `[memory-sync] ${date} ${summary}`;
|
|
450
|
-
const commitResult = gitCommit(
|
|
454
|
+
const commitResult = gitCommit(projectVaultRoot, message);
|
|
451
455
|
if (!commitResult.ok) {
|
|
452
456
|
res.status(500).json({ error: 'commit_failed', message: commitResult.error });
|
|
453
457
|
return;
|
|
@@ -455,7 +459,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
455
459
|
|
|
456
460
|
// Push if requested
|
|
457
461
|
if (pushRequested && config.gitRemote) {
|
|
458
|
-
const pushResult = gitPush(
|
|
462
|
+
const pushResult = gitPush(projectVaultRoot, { remote: config.gitRemote, branch });
|
|
459
463
|
if (!pushResult.ok) {
|
|
460
464
|
res.status(500).json({ error: 'push_failed', message: pushResult.error });
|
|
461
465
|
return;
|
|
@@ -476,7 +480,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
476
480
|
// GET /memory/conflicts
|
|
477
481
|
router.get('/memory/conflicts', wrap(async (_req, res) => {
|
|
478
482
|
const { listNotes, readNote } = memoryStore;
|
|
479
|
-
const {
|
|
483
|
+
const { projectVaultRoot } = memoryStore.resolveVault(projectRoot);
|
|
480
484
|
const notes = listNotes(projectRoot);
|
|
481
485
|
const conflicts = [];
|
|
482
486
|
|
|
@@ -490,7 +494,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
490
494
|
try {
|
|
491
495
|
const { readFileSync: rf } = await import('node:fs');
|
|
492
496
|
for (const note of notes) {
|
|
493
|
-
const filePath = join(
|
|
497
|
+
const filePath = join(projectVaultRoot, note.relPath);
|
|
494
498
|
const content = rf(filePath, 'utf8');
|
|
495
499
|
if (/^<{7}\s|^={7}\s|>{7}\s/.test(content)) {
|
|
496
500
|
conflicts.push({ relPath: note.relPath, reason: 'git conflict markers' });
|
|
@@ -849,6 +853,49 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
849
853
|
res.json({ ok: true, exists: true, path: GLOBAL_MEMORY_CONFIG_PATH, config: next });
|
|
850
854
|
}));
|
|
851
855
|
|
|
856
|
+
// POST /memory/config/vault — v6.x — update the vault root path.
|
|
857
|
+
// Accepts { vaultRoot: string }
|
|
858
|
+
// Persists to ~/.config/bizar/memory-config.json so it survives restarts,
|
|
859
|
+
// and sets process.env.BIZAR_MEMORY_VAULT for the current process.
|
|
860
|
+
router.post('/memory/config/vault', wrap(async (req, res) => {
|
|
861
|
+
const { vaultRoot: newVaultRoot } = req.body || {};
|
|
862
|
+
const trimmed = typeof newVaultRoot === 'string' ? newVaultRoot.trim() : '';
|
|
863
|
+
if (!trimmed || typeof newVaultRoot !== 'string') {
|
|
864
|
+
res.status(400).json({ error: 'vaultRoot required' });
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Expand ~ to home directory
|
|
869
|
+
const expanded = trimmed.startsWith('~')
|
|
870
|
+
? join(homedir(), trimmed.slice(1))
|
|
871
|
+
: trimmed;
|
|
872
|
+
const resolved = pathResolve(expanded);
|
|
873
|
+
|
|
874
|
+
// Validate: try to create the directory if it doesn't exist
|
|
875
|
+
try {
|
|
876
|
+
mkdirSync(resolved, { recursive: true, mode: 0o700 });
|
|
877
|
+
} catch (err) {
|
|
878
|
+
res.status(400).json({ error: `Cannot create vault at ${resolved}: ${err.message}` });
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// Persist to the global memory config so it survives restarts.
|
|
883
|
+
// Also update process.env so the change takes effect immediately.
|
|
884
|
+
process.env.BIZAR_MEMORY_VAULT = resolved;
|
|
885
|
+
const { config: existing } = loadGlobalMemoryConfig();
|
|
886
|
+
const next = JSON.parse(JSON.stringify(existing));
|
|
887
|
+
next.git = next.git || {};
|
|
888
|
+
next.git.repoPath = resolved; // v6.x — also set git.repoPath to the vault root
|
|
889
|
+
try {
|
|
890
|
+
saveGlobalMemoryConfig(next);
|
|
891
|
+
} catch (err) {
|
|
892
|
+
res.status(500).json({ error: 'write_failed', message: err.message });
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
res.json({ ok: true, vaultRoot: resolved });
|
|
897
|
+
}));
|
|
898
|
+
|
|
852
899
|
// POST /memory/test-git — test the configured git repo. Returns:
|
|
853
900
|
// { ok: bool, checks: [...], message?: string }
|
|
854
901
|
// Steps performed:
|
|
@@ -857,15 +904,20 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
857
904
|
// 3. Is a valid git repo (has .git/)?
|
|
858
905
|
// 4. (Optional) Has a configured remote?
|
|
859
906
|
// 5. (Optional) Can we push (skipped unless ?push=1)?
|
|
907
|
+
// v6.x — falls back to the general vault root (BIZAR_MEMORY_VAULT or ~/.bizar_memory)
|
|
908
|
+
// when git.repoPath is not explicitly configured.
|
|
860
909
|
router.post('/memory/test-git', wrap(async (req, res) => {
|
|
861
910
|
const { config } = loadGlobalMemoryConfig();
|
|
862
|
-
|
|
911
|
+
// v6.x: fall back to the general vault root when not explicitly set
|
|
912
|
+
const configuredPath = config?.git?.repoPath || '';
|
|
913
|
+
const repoPath = configuredPath || memoryStore.currentVault();
|
|
863
914
|
const remoteUrl = config?.git?.remoteUrl || '';
|
|
864
915
|
const wantPush = req.query.push === '1' || req.body?.push === true;
|
|
865
916
|
|
|
866
917
|
const checks = [];
|
|
867
|
-
if
|
|
868
|
-
|
|
918
|
+
// Only fail if the fallback path also doesn't exist
|
|
919
|
+
if (!configuredPath && !existsSync(repoPath)) {
|
|
920
|
+
checks.push({ name: 'repo_path_set', pass: false, detail: `git.repoPath not set — vault at ${repoPath} does not exist yet` });
|
|
869
921
|
res.json({ ok: false, checks, message: 'repo path not configured' });
|
|
870
922
|
return;
|
|
871
923
|
}
|
|
@@ -948,8 +1000,8 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
948
1000
|
return;
|
|
949
1001
|
}
|
|
950
1002
|
|
|
951
|
-
const { vaultRoot, mode } = resolveVault(projectRoot);
|
|
952
|
-
const vaultExists = existsSync(
|
|
1003
|
+
const { vaultRoot, projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
1004
|
+
const vaultExists = existsSync(projectVaultRoot);
|
|
953
1005
|
checks.push({
|
|
954
1006
|
name: 'vault_exists',
|
|
955
1007
|
pass: vaultExists,
|
|
@@ -961,7 +1013,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
961
1013
|
let writable = false;
|
|
962
1014
|
if (vaultExists) {
|
|
963
1015
|
try {
|
|
964
|
-
const probe = join(
|
|
1016
|
+
const probe = join(projectVaultRoot, '.health-probe.tmp');
|
|
965
1017
|
writeFileSync(probe, 'ok');
|
|
966
1018
|
try {
|
|
967
1019
|
const { unlinkSync } = await import('node:fs');
|
|
@@ -978,7 +1030,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
978
1030
|
// Git clean?
|
|
979
1031
|
let gitClean = null;
|
|
980
1032
|
if ((mode === 'managed' || mode === 'linked') && vaultExists && isGitInstalled()) {
|
|
981
|
-
const gs = gitStatus(
|
|
1033
|
+
const gs = gitStatus(projectVaultRoot);
|
|
982
1034
|
gitClean = gs.clean;
|
|
983
1035
|
checks.push({
|
|
984
1036
|
name: 'git_clean',
|
|
@@ -1058,10 +1110,11 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
1058
1110
|
// Walks the vault + .bizar/memory-cache + lightrag working dir.
|
|
1059
1111
|
router.get('/memory/storage', wrap(async (_req, res) => {
|
|
1060
1112
|
const { resolveVault } = memoryStore;
|
|
1061
|
-
const { vaultRoot, mode } = resolveVault(projectRoot);
|
|
1113
|
+
const { vaultRoot, projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
1062
1114
|
|
|
1063
1115
|
const targets = [];
|
|
1064
|
-
|
|
1116
|
+
// Use projectVaultRoot for the actual note storage path
|
|
1117
|
+
if (existsSync(projectVaultRoot)) targets.push({ name: 'vault', path: projectVaultRoot });
|
|
1065
1118
|
const cacheDir = join(projectRoot, '.bizar', 'memory-cache');
|
|
1066
1119
|
if (existsSync(cacheDir)) targets.push({ name: 'memory-cache', path: cacheDir });
|
|
1067
1120
|
const lightragDir = join(projectRoot, '.bizar', 'lightrag');
|
|
@@ -1078,7 +1131,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
1078
1131
|
total,
|
|
1079
1132
|
breakdown,
|
|
1080
1133
|
mode,
|
|
1081
|
-
vaultRoot,
|
|
1134
|
+
vaultRoot, // general vault root for display
|
|
1082
1135
|
message: total === 0 ? 'no memory data on disk yet' : `${formatBytes(total)} on disk`,
|
|
1083
1136
|
});
|
|
1084
1137
|
}));
|
|
@@ -1088,7 +1141,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
1088
1141
|
router.get('/memory/git/diff', wrap(async (_req, res) => {
|
|
1089
1142
|
const { resolveVault } = memoryStore;
|
|
1090
1143
|
const { isGitInstalled } = memoryGit;
|
|
1091
|
-
const {
|
|
1144
|
+
const { projectVaultRoot, mode } = resolveVault(projectRoot);
|
|
1092
1145
|
|
|
1093
1146
|
if (mode === 'local-only') {
|
|
1094
1147
|
res.json({ hasDiff: false, lines: [], files: [], mode: 'local-only' });
|
|
@@ -1102,7 +1155,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
1102
1155
|
let raw = '';
|
|
1103
1156
|
try {
|
|
1104
1157
|
raw = execFileSync('git', ['diff', '--no-color', '--no-ext-diff'], {
|
|
1105
|
-
cwd:
|
|
1158
|
+
cwd: projectVaultRoot,
|
|
1106
1159
|
encoding: 'utf8',
|
|
1107
1160
|
maxBuffer: 8 * 1024 * 1024,
|
|
1108
1161
|
});
|
|
@@ -1115,7 +1168,7 @@ export function createMemoryRouter({ projectRoot }) {
|
|
|
1115
1168
|
let untracked = [];
|
|
1116
1169
|
try {
|
|
1117
1170
|
const statusRaw = execFileSync('git', ['status', '--porcelain'], {
|
|
1118
|
-
cwd:
|
|
1171
|
+
cwd: projectVaultRoot,
|
|
1119
1172
|
encoding: 'utf8',
|
|
1120
1173
|
});
|
|
1121
1174
|
for (const line of statusRaw.split('\n')) {
|