@polderlabs/bizar 4.0.0 → 4.2.0
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/README.md +11 -14
- package/bizar-dash/CHANGELOG.md +1 -1
- package/bizar-dash/src/server/api.mjs +2 -2
- package/bizar-dash/src/server/artifacts-store.mjs +4 -4
- package/bizar-dash/src/server/memory-git.mjs +80 -0
- package/bizar-dash/src/server/memory-lightrag.mjs +767 -0
- package/bizar-dash/src/server/memory-store.mjs +47 -0
- package/bizar-dash/src/server/mod-security.mjs +2 -2
- package/bizar-dash/src/server/routes/memory.mjs +174 -15
- package/bizar-dash/src/server/server.mjs +1 -1
- package/bizar-dash/src/server/state.mjs +2 -2
- package/bizar-dash/src/web/views/Config.tsx +461 -1
- package/bizar-dash/tests/memory-cli.test.mjs +542 -0
- package/bizar-dash/tests/memory-config.test.mjs +422 -0
- package/bizar-dash/tests/memory-git.test.mjs +109 -1
- package/bizar-dash/tests/memory-lightrag.test.mjs +153 -0
- package/bizar-dash/tests/memory-protocol-drift.test.mjs +45 -0
- package/cli/banner.mjs +1 -1
- package/cli/bin.mjs +4 -4
- package/cli/bootstrap.mjs +1 -1
- package/cli/copy.mjs +22 -16
- package/cli/doctor.mjs +4 -4
- package/cli/doctor.test.mjs +2 -2
- package/cli/init.mjs +2 -2
- package/cli/install.mjs +21 -16
- package/cli/memory.mjs +710 -31
- package/cli/utils.mjs +6 -3
- package/config/AGENTS.md +7 -7
- package/config/agents/_shared/AGENT_BASELINE.md +59 -61
- package/config/opencode.json +13 -38
- package/config/skills/memory-protocol/SKILL.md +105 -0
- package/config/skills/obsidian/SKILL.md +58 -1
- package/install.sh +11 -1
- package/package.json +2 -2
- package/plugins/bizar/index.ts +7 -0
- package/plugins/bizar/src/commands.ts +42 -1
- package/plugins/bizar/src/tools/open-kb.ts +191 -0
- package/plugins/bizar/tests/commands.test.ts +36 -0
package/cli/memory.mjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* cli/memory.mjs
|
|
3
3
|
*
|
|
4
4
|
* `bizar memory` subcommands. Delegates to memory-store.mjs and memory-git.mjs.
|
|
5
|
-
* Supports: init, status, link, unlink, pull, commit, push, sync,
|
|
6
|
-
* conflicts, doctor.
|
|
5
|
+
* Supports: init, status, link, unlink, write, pull, commit, push, sync,
|
|
6
|
+
* reindex, conflicts, doctor.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import chalk from 'chalk';
|
|
@@ -18,6 +18,7 @@ const memoryStore = await import(`${SERVER_ROOT}/memory-store.mjs`).then((m) =>
|
|
|
18
18
|
const memorySchema = await import(`${SERVER_ROOT}/memory-schema.mjs`).then((m) => m);
|
|
19
19
|
const memorySecrets = await import(`${SERVER_ROOT}/memory-secrets.mjs`).then((m) => m);
|
|
20
20
|
const memoryGit = await import(`${SERVER_ROOT}/memory-git.mjs`).then((m) => m);
|
|
21
|
+
const memoryLightrag = await import(`${SERVER_ROOT}/memory-lightrag.mjs`).then((m) => m);
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Get the project root. Assumes CWD is the project root.
|
|
@@ -26,6 +27,33 @@ function getProjectRoot() {
|
|
|
26
27
|
return process.cwd();
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Validate a git remote URL. Accepts HTTPS, SSH (scp-style), and SSH URL form.
|
|
32
|
+
* Rejects file://, plain paths, and other schemes.
|
|
33
|
+
*
|
|
34
|
+
* @param {string} url
|
|
35
|
+
* @returns {{ valid: true, kind: 'ssh'|'https'|'ssh-url' } | { valid: false, error: string }}
|
|
36
|
+
*/
|
|
37
|
+
export function validateRemoteUrl(url) {
|
|
38
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
39
|
+
return { valid: false, error: 'URL must be a non-empty string' };
|
|
40
|
+
}
|
|
41
|
+
const trimmed = url.trim();
|
|
42
|
+
// SSH scp-style: git@host:path
|
|
43
|
+
if (/^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+:.+$/.test(trimmed)) {
|
|
44
|
+
return { valid: true, kind: 'ssh' };
|
|
45
|
+
}
|
|
46
|
+
// SSH URL form: ssh://[user@]host[:port]/path
|
|
47
|
+
if (/^ssh:\/\/(?:[a-zA-Z0-9._-]+@)?[a-zA-Z0-9._-]+(?::\d+)?\/.+$/.test(trimmed)) {
|
|
48
|
+
return { valid: true, kind: 'ssh-url' };
|
|
49
|
+
}
|
|
50
|
+
// HTTPS: https://host/path
|
|
51
|
+
if (/^https:\/\/[a-zA-Z0-9._-]+(?::\d+)?\/.+$/.test(trimmed)) {
|
|
52
|
+
return { valid: true, kind: 'https' };
|
|
53
|
+
}
|
|
54
|
+
return { valid: false, error: 'URL must be ssh://, https://, or git@host:path form' };
|
|
55
|
+
}
|
|
56
|
+
|
|
29
57
|
// ─── Helper formatters ────────────────────────────────────────────────────────
|
|
30
58
|
|
|
31
59
|
function success(msg) { console.log(chalk.green('✓'), msg); }
|
|
@@ -44,10 +72,178 @@ function kv(key, value) {
|
|
|
44
72
|
|
|
45
73
|
// ─── Subcommand implementations ───────────────────────────────────────────────
|
|
46
74
|
|
|
75
|
+
/**
|
|
76
|
+
* `bizar memory write <relpath> [--type …] [--status …] [--confidence …]`
|
|
77
|
+
* `[--tag <tag>]… [--body <body>|--body-file <file>]`
|
|
78
|
+
* `[--title <title>] [--json]`
|
|
79
|
+
*
|
|
80
|
+
* Write a single note to the vault. Validates the path, type, status, and
|
|
81
|
+
* confidence against the schema, builds frontmatter via `defaultFrontmatter`,
|
|
82
|
+
* then delegates to `memoryStore.writeNote`. Designed for agents (and humans)
|
|
83
|
+
* that want to write memory notes without going through the dashboard UI.
|
|
84
|
+
*/
|
|
85
|
+
async function cmdWrite(args) {
|
|
86
|
+
const projectRoot = getProjectRoot();
|
|
87
|
+
const { writeNote, loadConfig } = memoryStore;
|
|
88
|
+
const { defaultFrontmatter, VALID_TYPES, VALID_STATUSES, VALID_CONFIDENCES } = memorySchema;
|
|
89
|
+
|
|
90
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
91
|
+
console.log(`
|
|
92
|
+
Usage: bizar memory write <relpath> [options]
|
|
93
|
+
|
|
94
|
+
Write a single note to the vault. <relpath> must end in .md and is
|
|
95
|
+
resolved relative to the project namespace root (e.g.
|
|
96
|
+
decisions/0001-foo.md), NOT prefixed with the namespace.
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
--type <type> One of: ${VALID_TYPES.join(', ')}
|
|
100
|
+
(default: project_overview)
|
|
101
|
+
--status <status> One of: ${VALID_STATUSES.join(', ')}
|
|
102
|
+
(default: active)
|
|
103
|
+
--confidence <conf> One of: ${VALID_CONFIDENCES.join(', ')}
|
|
104
|
+
(default: verified)
|
|
105
|
+
--tag <tag> Tag to attach (may be passed multiple times)
|
|
106
|
+
--title <title> Frontmatter title field
|
|
107
|
+
--body <text> Note body as a string
|
|
108
|
+
--body-file <path> Path to a file containing the body
|
|
109
|
+
(exactly one of --body / --body-file is allowed)
|
|
110
|
+
--json Emit the full returned note as JSON
|
|
111
|
+
--help, -h Show this help
|
|
112
|
+
|
|
113
|
+
Exactly one of --body or --body-file is required, unless you intend
|
|
114
|
+
to write an empty body (in which case omit both).
|
|
115
|
+
`.trim());
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Parse positional relpath (first non-flag arg)
|
|
120
|
+
const positional = args.filter((a) => !a.startsWith('-'));
|
|
121
|
+
const relpath = positional[0];
|
|
122
|
+
if (!relpath) {
|
|
123
|
+
error('relpath is required: `bizar memory write <relpath>`');
|
|
124
|
+
info('run `bizar memory write --help` for usage');
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
if (!relpath.endsWith('.md')) {
|
|
128
|
+
error(`relpath must end in .md: ${relpath}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── Parse flags ──────────────────────────────────────────────────────────
|
|
133
|
+
const optVal = (name) => {
|
|
134
|
+
const i = args.indexOf(name);
|
|
135
|
+
if (i === -1) return undefined;
|
|
136
|
+
const v = args[i + 1];
|
|
137
|
+
if (!v || v.startsWith('-')) return undefined;
|
|
138
|
+
return v;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const type = optVal('--type') ?? 'project_overview';
|
|
142
|
+
if (!VALID_TYPES.includes(type)) {
|
|
143
|
+
error(`invalid --type: '${type}'`);
|
|
144
|
+
info(`must be one of: ${VALID_TYPES.join(', ')}`);
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const status = optVal('--status') ?? 'active';
|
|
149
|
+
if (!VALID_STATUSES.includes(status)) {
|
|
150
|
+
error(`invalid --status: '${status}'`);
|
|
151
|
+
info(`must be one of: ${VALID_STATUSES.join(', ')}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const confidence = optVal('--confidence') ?? 'verified';
|
|
156
|
+
if (!VALID_CONFIDENCES.includes(confidence)) {
|
|
157
|
+
error(`invalid --confidence: '${confidence}'`);
|
|
158
|
+
info(`must be one of: ${VALID_CONFIDENCES.join(', ')}`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const title = optVal('--title');
|
|
163
|
+
|
|
164
|
+
// Multi-value --tag
|
|
165
|
+
const tags = [];
|
|
166
|
+
for (let i = 0; i < args.length; i++) {
|
|
167
|
+
if (args[i] === '--tag' && args[i + 1] && !args[i + 1].startsWith('-')) {
|
|
168
|
+
tags.push(args[i + 1]);
|
|
169
|
+
i++;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const inlineBody = optVal('--body');
|
|
174
|
+
const bodyFile = optVal('--body-file');
|
|
175
|
+
if (inlineBody !== undefined && bodyFile !== undefined) {
|
|
176
|
+
error('use either --body or --body-file, not both');
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let body = '';
|
|
181
|
+
if (bodyFile) {
|
|
182
|
+
try {
|
|
183
|
+
body = readFileSync(bodyFile, 'utf8');
|
|
184
|
+
} catch (err) {
|
|
185
|
+
error(`failed to read --body-file ${bodyFile}: ${err.message}`);
|
|
186
|
+
process.exit(1);
|
|
187
|
+
}
|
|
188
|
+
} else if (inlineBody !== undefined) {
|
|
189
|
+
body = inlineBody;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ── Build frontmatter ───────────────────────────────────────────────────
|
|
193
|
+
const { config } = loadConfig(projectRoot);
|
|
194
|
+
const projectId = config.projectId || projectRoot.split('/').pop() || 'unknown';
|
|
195
|
+
|
|
196
|
+
const frontmatter = defaultFrontmatter({
|
|
197
|
+
type,
|
|
198
|
+
project_id: projectId,
|
|
199
|
+
status,
|
|
200
|
+
confidence,
|
|
201
|
+
tags,
|
|
202
|
+
});
|
|
203
|
+
if (title) frontmatter.title = title;
|
|
204
|
+
|
|
205
|
+
// ── Write ───────────────────────────────────────────────────────────────
|
|
206
|
+
let result;
|
|
207
|
+
try {
|
|
208
|
+
result = writeNote(projectRoot, relpath, { frontmatter, body });
|
|
209
|
+
} catch (err) {
|
|
210
|
+
if (err.code === 'SCHEMA_VALIDATION_FAILED') {
|
|
211
|
+
error(`schema validation failed: ${err.message.replace(/^schema validation failed: /, '')}`);
|
|
212
|
+
} else if (err.code === 'SECRET_DETECTED') {
|
|
213
|
+
error(err.message);
|
|
214
|
+
} else {
|
|
215
|
+
error(err.message);
|
|
216
|
+
}
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ── Output ──────────────────────────────────────────────────────────────
|
|
221
|
+
if (args.includes('--json')) {
|
|
222
|
+
console.log(JSON.stringify(result, null, 2));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
success(`wrote ${result.relPath}`);
|
|
227
|
+
console.log();
|
|
228
|
+
console.log(chalk.bold(' Note'));
|
|
229
|
+
console.log(' ─────────────────────────────');
|
|
230
|
+
kv('relpath', result.relPath);
|
|
231
|
+
kv('type', String(result.frontmatter.type));
|
|
232
|
+
kv('status', String(result.frontmatter.status));
|
|
233
|
+
kv('confidence', String(result.frontmatter.confidence));
|
|
234
|
+
kv('memory_id', String(result.frontmatter.memory_id));
|
|
235
|
+
if (Array.isArray(result.frontmatter.tags) && result.frontmatter.tags.length > 0) {
|
|
236
|
+
kv('tags', result.frontmatter.tags.join(', '));
|
|
237
|
+
}
|
|
238
|
+
if (title) kv('title', title);
|
|
239
|
+
kv('size', `${result.size} bytes`);
|
|
240
|
+
console.log();
|
|
241
|
+
}
|
|
242
|
+
|
|
47
243
|
/**
|
|
48
244
|
* `bizar memory init`
|
|
49
|
-
* Creates .bizar/memory.json with mode=
|
|
50
|
-
*
|
|
245
|
+
* Creates .bizar/memory.json with mode=managed (default).
|
|
246
|
+
* Pass --memory-mode local-only to use local vault storage instead.
|
|
51
247
|
*
|
|
52
248
|
* Flags (added for install.sh bootstrap):
|
|
53
249
|
* --yes Accept all defaults (skip prompts, skip if already initialized)
|
|
@@ -73,7 +269,7 @@ async function cmdInit(args) {
|
|
|
73
269
|
}
|
|
74
270
|
|
|
75
271
|
// --memory-mode takes precedence over --managed
|
|
76
|
-
let mode = '
|
|
272
|
+
let mode = 'managed';
|
|
77
273
|
const modeIdx = args.indexOf('--memory-mode');
|
|
78
274
|
if (modeIdx !== -1 && args[modeIdx + 1]) {
|
|
79
275
|
mode = args[modeIdx + 1];
|
|
@@ -157,6 +353,356 @@ async function cmdInit(args) {
|
|
|
157
353
|
}
|
|
158
354
|
}
|
|
159
355
|
|
|
356
|
+
/**
|
|
357
|
+
* `bizar memory setup` — configure or reconfigure the memory vault.
|
|
358
|
+
*
|
|
359
|
+
* v4.2.0 — first-class bootstrap entry point. Two flows:
|
|
360
|
+
* - No existing config → behaves like `init` + adds the remote (managed mode).
|
|
361
|
+
* - Existing config → optionally updates the remote URL on a managed vault.
|
|
362
|
+
*
|
|
363
|
+
* Flags:
|
|
364
|
+
* --mode <managed|local-only> Vault mode (default: managed)
|
|
365
|
+
* --remote <url> Git remote URL (required for managed)
|
|
366
|
+
* --repo-name <name> Vault directory name (default: bizar-memory)
|
|
367
|
+
* --non-interactive No prompts; use flags or defaults
|
|
368
|
+
* --yes Same as --non-interactive (backward compat)
|
|
369
|
+
*
|
|
370
|
+
* The remote URL is written to BOTH `memoryRepo.remote` (canonical,
|
|
371
|
+
* read by resolveVault) and the top-level `gitRemote` (read by cmdPush).
|
|
372
|
+
* Both must agree or `bizar memory push` will fail silently.
|
|
373
|
+
*
|
|
374
|
+
* Connectivity is checked via `memoryGit.lsRemote` after `addRemote`. A
|
|
375
|
+
* failure here is informational only — credentials may not be configured
|
|
376
|
+
* yet — and does NOT abort the setup.
|
|
377
|
+
*
|
|
378
|
+
* @param {string[]} argv
|
|
379
|
+
*/
|
|
380
|
+
export async function cmdSetup(argv) {
|
|
381
|
+
const projectRoot = getProjectRoot();
|
|
382
|
+
const { loadConfig, saveConfig, initVault, resolveVault } = memoryStore;
|
|
383
|
+
|
|
384
|
+
// ── Parse flags ──────────────────────────────────────────────────────────
|
|
385
|
+
const optVal = (name) => {
|
|
386
|
+
const i = argv.indexOf(name);
|
|
387
|
+
if (i === -1) return undefined;
|
|
388
|
+
const v = argv[i + 1];
|
|
389
|
+
if (!v || v.startsWith('-')) return undefined;
|
|
390
|
+
return v;
|
|
391
|
+
};
|
|
392
|
+
const hasFlag = (name) => argv.includes(name);
|
|
393
|
+
|
|
394
|
+
const nonInteractive = hasFlag('--non-interactive') || hasFlag('--yes');
|
|
395
|
+
|
|
396
|
+
// --mode (default managed). Accept --memory-mode alias too.
|
|
397
|
+
let mode = 'managed';
|
|
398
|
+
const modeRaw = optVal('--mode') ?? optVal('--memory-mode');
|
|
399
|
+
if (modeRaw) mode = modeRaw;
|
|
400
|
+
else if (hasFlag('--local-only')) mode = 'local-only';
|
|
401
|
+
else if (hasFlag('--managed')) mode = 'managed';
|
|
402
|
+
if (mode !== 'managed' && mode !== 'local-only') {
|
|
403
|
+
error(`invalid --mode: '${mode}' (must be 'managed' or 'local-only')`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// --remote
|
|
408
|
+
let remote = optVal('--remote');
|
|
409
|
+
if (remote !== undefined) {
|
|
410
|
+
const v = validateRemoteUrl(remote);
|
|
411
|
+
if (!v.valid) {
|
|
412
|
+
error(`invalid --remote URL: ${v.error}`);
|
|
413
|
+
info('examples:');
|
|
414
|
+
info(' --remote git@github.com:user/repo.git');
|
|
415
|
+
info(' --remote ssh://git@github.com/user/repo.git');
|
|
416
|
+
info(' --remote https://github.com/user/repo.git');
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
// Store the trimmed canonical form
|
|
420
|
+
remote = remote.trim();
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// --repo-name (or --memory-repo-name, --repo)
|
|
424
|
+
const repoName = optVal('--repo-name') ?? optVal('--memory-repo-name') ?? optVal('--repo') ?? 'bizar-memory';
|
|
425
|
+
|
|
426
|
+
// ── Help ────────────────────────────────────────────────────────────────
|
|
427
|
+
if (hasFlag('--help') || hasFlag('-h')) {
|
|
428
|
+
console.log(`
|
|
429
|
+
Usage: bizar memory setup [--remote <url>] [--mode <mode>] [--non-interactive]
|
|
430
|
+
|
|
431
|
+
Configure or reconfigure this project's memory vault.
|
|
432
|
+
|
|
433
|
+
Options:
|
|
434
|
+
--mode <managed|local-only> Vault mode (default: managed)
|
|
435
|
+
--remote <url> Git remote URL (required for managed)
|
|
436
|
+
Accepts: ssh://, https://, git@host:path
|
|
437
|
+
--repo-name <name> Vault directory name (default: bizar-memory)
|
|
438
|
+
--local-only Shortcut for --mode local-only
|
|
439
|
+
--non-interactive Skip prompts; use flags or defaults
|
|
440
|
+
--yes Alias for --non-interactive
|
|
441
|
+
--help, -h Show this help
|
|
442
|
+
|
|
443
|
+
Examples:
|
|
444
|
+
# Bootstrap: first-time init with a remote
|
|
445
|
+
bizar memory setup --non-interactive \\
|
|
446
|
+
--remote git@github.com:org/bizar-memory.git
|
|
447
|
+
|
|
448
|
+
# Reconfigure the remote on an existing managed vault
|
|
449
|
+
bizar memory setup --non-interactive \\
|
|
450
|
+
--remote https://github.com/org/bizar-memory.git
|
|
451
|
+
|
|
452
|
+
# Convert to local-only (removes remote but keeps notes)
|
|
453
|
+
bizar memory setup --non-interactive --local-only
|
|
454
|
+
`.trim());
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ── Read existing config ────────────────────────────────────────────────
|
|
459
|
+
const { config: existingCfg, exists: configExists } = loadConfig(projectRoot);
|
|
460
|
+
|
|
461
|
+
// Decide whether this is a bootstrap (no config) or reconfigure (config
|
|
462
|
+
// already exists). The branch shape drives the rest of the function.
|
|
463
|
+
const isBootstrap = !configExists;
|
|
464
|
+
|
|
465
|
+
// Reconfigure path: if no --remote was passed AND --mode wasn't changed,
|
|
466
|
+
// just print status and exit. Otherwise apply the change.
|
|
467
|
+
if (!isBootstrap) {
|
|
468
|
+
const existingMode = existingCfg.memoryRepo?.mode || existingCfg.mode || 'local-only';
|
|
469
|
+
const existingRemote = existingCfg.memoryRepo?.remote || existingCfg.gitRemote || '';
|
|
470
|
+
const modeChanged = mode !== existingMode;
|
|
471
|
+
const remoteChanged = remote !== undefined && remote !== existingRemote;
|
|
472
|
+
|
|
473
|
+
if (!modeChanged && !remoteChanged) {
|
|
474
|
+
// Nothing to do — print status and exit
|
|
475
|
+
console.log(chalk.bold('\n Memory setup (no changes)'));
|
|
476
|
+
console.log(' ─────────────────────────────');
|
|
477
|
+
success('already configured');
|
|
478
|
+
kv('mode', existingMode);
|
|
479
|
+
kv('remote', existingRemote || '(none)');
|
|
480
|
+
kv('vault', existingCfg.memoryRepo?.path || existingCfg.repoName || '(unknown)');
|
|
481
|
+
console.log();
|
|
482
|
+
info(`run \`bizar memory setup --remote <url>\` to change the remote`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// ── Reconfigure path — apply in place ───────────────────────────────
|
|
487
|
+
const updatedCfg = { ...existingCfg };
|
|
488
|
+
|
|
489
|
+
if (modeChanged && remote === undefined) {
|
|
490
|
+
// Switching modes without a new remote is fine, but warn if going
|
|
491
|
+
// managed without a URL to push to.
|
|
492
|
+
if (mode === 'managed' && !existingRemote) {
|
|
493
|
+
warn('switching to managed mode with no remote configured — push will fail');
|
|
494
|
+
info('pass --remote <url> to set one');
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (remote !== undefined) {
|
|
499
|
+
// Dual-write: top-level + memoryRepo.*
|
|
500
|
+
updatedCfg.gitRemote = remote;
|
|
501
|
+
updatedCfg.memoryRepo = {
|
|
502
|
+
...(existingCfg.memoryRepo || {}),
|
|
503
|
+
mode: mode === 'managed' ? 'managed' : (updatedCfg.memoryRepo?.mode || 'local-only'),
|
|
504
|
+
remote,
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (modeChanged) {
|
|
508
|
+
updatedCfg.mode = mode;
|
|
509
|
+
if (updatedCfg.memoryRepo) {
|
|
510
|
+
updatedCfg.memoryRepo.mode = mode;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const saveResult = saveConfig(projectRoot, updatedCfg);
|
|
515
|
+
if (!saveResult.ok) {
|
|
516
|
+
error(`failed to save config: ${saveResult.error}`);
|
|
517
|
+
process.exit(1);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// If we now have a remote on a managed vault, register it.
|
|
521
|
+
if (mode === 'managed' && remote) {
|
|
522
|
+
addRemoteToVault(projectRoot, remote);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
printSetupResult({ mode, remote, vaultPath: updatedCfg.memoryRepo?.path });
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── Bootstrap path (no existing config) ─────────────────────────────────
|
|
530
|
+
|
|
531
|
+
// For managed bootstrap, we need a remote. If missing, prompt or error.
|
|
532
|
+
if (mode === 'managed' && !remote) {
|
|
533
|
+
if (nonInteractive) {
|
|
534
|
+
error('--remote is required for --mode managed in non-interactive setup');
|
|
535
|
+
info('pass --remote <url> (or run without --non-interactive to be prompted)');
|
|
536
|
+
process.exit(1);
|
|
537
|
+
}
|
|
538
|
+
// Prompt via readline
|
|
539
|
+
const readline = await import('node:readline');
|
|
540
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
541
|
+
const answer = await new Promise((resolve) => {
|
|
542
|
+
rl.question(chalk.cyan(' remote URL (ssh://, https://, or git@host:path): '), resolve);
|
|
543
|
+
});
|
|
544
|
+
rl.close();
|
|
545
|
+
const trimmed = (answer || '').trim();
|
|
546
|
+
if (!trimmed) {
|
|
547
|
+
error('a remote URL is required for managed mode');
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
const v = validateRemoteUrl(trimmed);
|
|
551
|
+
if (!v.valid) {
|
|
552
|
+
error(`invalid remote URL: ${v.error}`);
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
remote = trimmed;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Build a config object mirroring cmdInit's shape, with the remote set.
|
|
559
|
+
const home = homedir();
|
|
560
|
+
const projectId = projectRoot.split('/').pop() || 'project';
|
|
561
|
+
const vaultPath = mode === 'managed'
|
|
562
|
+
? join(home, '.local', 'share', 'bizar', 'memory', repoName)
|
|
563
|
+
: join(projectRoot, '.obsidian');
|
|
564
|
+
|
|
565
|
+
const cfg = {
|
|
566
|
+
version: 1,
|
|
567
|
+
backend: 'bizar-local',
|
|
568
|
+
projectId,
|
|
569
|
+
memoryRepo: {
|
|
570
|
+
mode,
|
|
571
|
+
path: vaultPath,
|
|
572
|
+
remote: mode === 'managed' ? (remote || null) : null,
|
|
573
|
+
branch: 'main',
|
|
574
|
+
namespace: mode === 'managed' ? `projects/${projectId}` : null,
|
|
575
|
+
},
|
|
576
|
+
namespaces: {
|
|
577
|
+
project: `projects/${projectId}`,
|
|
578
|
+
global: 'global/bizar',
|
|
579
|
+
user: `users/${process.env.USER || process.env.USERNAME || 'local'}`,
|
|
580
|
+
},
|
|
581
|
+
lightrag: {
|
|
582
|
+
enabled: false,
|
|
583
|
+
host: '127.0.0.1',
|
|
584
|
+
port: 9621,
|
|
585
|
+
workingDir: join(projectRoot, '.bizar', 'lightrag'),
|
|
586
|
+
},
|
|
587
|
+
git: {
|
|
588
|
+
autoPullOnSessionStart: false,
|
|
589
|
+
autoCommitOnMemoryWrite: false,
|
|
590
|
+
autoPushOnSessionEnd: false,
|
|
591
|
+
commitAuthor: 'Bizar Memory <bizar-memory@local>',
|
|
592
|
+
commitMessageTemplate: `memory(${projectId}): {summary}`,
|
|
593
|
+
},
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
// Top-level gitRemote mirrors memoryRepo.remote so cmdPush works.
|
|
597
|
+
if (mode === 'managed' && remote) {
|
|
598
|
+
cfg.gitRemote = remote;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const saveResult = saveConfig(projectRoot, cfg);
|
|
602
|
+
if (!saveResult.ok) {
|
|
603
|
+
error(`failed to write config: ${saveResult.error}`);
|
|
604
|
+
process.exit(1);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// Initialize the vault on disk.
|
|
608
|
+
const vaultResult = initVault(projectRoot);
|
|
609
|
+
if (!vaultResult.ok) {
|
|
610
|
+
error(`vault init failed: ${vaultResult.error || 'unknown'}`);
|
|
611
|
+
process.exit(1);
|
|
612
|
+
}
|
|
613
|
+
if (vaultResult.created.length > 0) {
|
|
614
|
+
for (const c of vaultResult.created) info(` created: ${c}`);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// For managed bootstrap with a remote, register it on the new vault.
|
|
618
|
+
if (mode === 'managed' && remote) {
|
|
619
|
+
addRemoteToVault(projectRoot, remote);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
printSetupResult({ mode, remote, vaultPath });
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Register `remote` as `origin` on the vault's git repo, then probe
|
|
627
|
+
* connectivity via `lsRemote`. Both operations are best-effort: we
|
|
628
|
+
* warn on failure but never abort the setup.
|
|
629
|
+
*
|
|
630
|
+
* @param {string} projectRoot
|
|
631
|
+
* @param {string} remote
|
|
632
|
+
*/
|
|
633
|
+
function addRemoteToVault(projectRoot, remote) {
|
|
634
|
+
const { resolveVault } = memoryStore;
|
|
635
|
+
const { vaultRoot } = resolveVault(projectRoot);
|
|
636
|
+
|
|
637
|
+
// Sanity check: is the vault a git repo? initVault should have ensured
|
|
638
|
+
// this, but double-check before calling git remote.
|
|
639
|
+
const gitDir = join(vaultRoot, '.git');
|
|
640
|
+
if (!existsSync(gitDir)) {
|
|
641
|
+
warn('vault is not a git repo — skipping remote registration');
|
|
642
|
+
info('run `bizar memory init` or `bizar memory sync` to repair');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (typeof memoryGit.addRemote !== 'function') {
|
|
647
|
+
warn('memoryGit.addRemote is not available in this build');
|
|
648
|
+
info('(waiting for the addRemote/lsRemote stream to merge) — remote URL saved to config');
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const addResult = memoryGit.addRemote(vaultRoot, 'origin', remote);
|
|
653
|
+
if (!addResult || addResult.ok === false) {
|
|
654
|
+
warn(`failed to register remote: ${addResult?.error || 'unknown error'}`);
|
|
655
|
+
info('the remote URL is still saved in .bizar/memory.json');
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (addResult.action === 'unchanged') {
|
|
659
|
+
info(`remote 'origin' already set to ${remote}`);
|
|
660
|
+
} else if (addResult.action === 'updated') {
|
|
661
|
+
success(`updated remote 'origin' → ${remote}`);
|
|
662
|
+
} else {
|
|
663
|
+
success(`registered remote 'origin' → ${remote}`);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (typeof memoryGit.lsRemote !== 'function') {
|
|
667
|
+
info('skipping connectivity probe (lsRemote not available yet)');
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const probe = memoryGit.lsRemote(vaultRoot, 'origin', { timeoutMs: 5000 });
|
|
672
|
+
if (typeof probe === 'string' && probe.length > 0) {
|
|
673
|
+
const refCount = probe.split('\n').filter(Boolean).length;
|
|
674
|
+
success(`connectivity ok (${refCount} refs reachable)`);
|
|
675
|
+
} else {
|
|
676
|
+
warn('could not reach remote (auth, network, or unknown host)');
|
|
677
|
+
info('the vault is set up; configure credentials and run `bizar memory pull`');
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* Render the standard "setup complete" report using the existing `kv`
|
|
683
|
+
* style. The connectivity line is omitted if not relevant.
|
|
684
|
+
*
|
|
685
|
+
* @param {{ mode: string, remote: string|undefined|null, vaultPath: string }} opts
|
|
686
|
+
*/
|
|
687
|
+
function printSetupResult({ mode, remote, vaultPath }) {
|
|
688
|
+
console.log();
|
|
689
|
+
success('memory setup complete');
|
|
690
|
+
console.log(' ─────────────────────────────');
|
|
691
|
+
kv('mode', mode);
|
|
692
|
+
kv('vault', String(vaultPath || ''));
|
|
693
|
+
if (remote) kv('remote', remote);
|
|
694
|
+
console.log();
|
|
695
|
+
if (!remote) {
|
|
696
|
+
info('local-only mode — notes are stored in this project only');
|
|
697
|
+
info('run `bizar memory setup --remote <url>` to attach a remote later');
|
|
698
|
+
} else {
|
|
699
|
+
info('next steps:');
|
|
700
|
+
info(' 1. `bizar memory pull` to fetch the existing vault (if any)');
|
|
701
|
+
info(' 2. `bizar memory write <relpath>` to start adding notes');
|
|
702
|
+
info(' 3. `bizar memory push` to publish');
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
160
706
|
/**
|
|
161
707
|
* `bizar memory status`
|
|
162
708
|
*/
|
|
@@ -204,15 +750,28 @@ async function cmdStatus(_args) {
|
|
|
204
750
|
}
|
|
205
751
|
}
|
|
206
752
|
|
|
207
|
-
// LightRAG
|
|
208
|
-
|
|
753
|
+
// LightRAG status
|
|
754
|
+
try {
|
|
755
|
+
const { isLightRAGRunning, isLightRAGInstalled, resolveLightRAGConfig } = memoryStore;
|
|
756
|
+
const cfg = resolveLightRAGConfig(projectRoot);
|
|
757
|
+
if (!cfg.enabled) {
|
|
758
|
+
kv('lightrag', chalk.gray('disabled'));
|
|
759
|
+
} else if (!(await isLightRAGInstalled())) {
|
|
760
|
+
kv('lightrag', chalk.yellow('not installed — `uv tool install "lightrag-hku[api]"`'));
|
|
761
|
+
} else {
|
|
762
|
+
const running = await isLightRAGRunning(cfg);
|
|
763
|
+
kv('lightrag', running ? chalk.green(`${cfg.host}:${cfg.port} (running)`) : chalk.yellow(`${cfg.host}:${cfg.port} (not running — run \`bizar memory reindex\`)`));
|
|
764
|
+
}
|
|
765
|
+
} catch (err) {
|
|
766
|
+
kv('lightrag', chalk.gray(`status unavailable (${err.message})`));
|
|
767
|
+
}
|
|
209
768
|
|
|
210
769
|
// Last reindex attempt
|
|
211
|
-
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex
|
|
770
|
+
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex.json');
|
|
212
771
|
if (existsSync(reindexMarker)) {
|
|
213
772
|
try {
|
|
214
|
-
const {
|
|
215
|
-
kv('lastReindex', `${new Date(
|
|
773
|
+
const { finishedAt, ok, inserted, failed, noteCount } = JSON.parse(readFileSync(reindexMarker, 'utf8'));
|
|
774
|
+
kv('lastReindex', `${new Date(finishedAt).toLocaleString()} — ${ok ? chalk.green(`${inserted}/${noteCount} ok`) : chalk.red(`${failed} failed`)}`);
|
|
216
775
|
} catch { /* ignore */ }
|
|
217
776
|
}
|
|
218
777
|
|
|
@@ -543,24 +1102,115 @@ async function cmdSync(args) {
|
|
|
543
1102
|
|
|
544
1103
|
/**
|
|
545
1104
|
* `bizar memory reindex`
|
|
546
|
-
*
|
|
1105
|
+
* v4.1.0 — populates the LightRAG server with every note in the vault.
|
|
1106
|
+
*
|
|
1107
|
+
* Steps:
|
|
1108
|
+
* 1. Resolve LightRAG config from `.bizar/memory.json`.
|
|
1109
|
+
* 2. Ensure `lightrag-server` is installed (searches common paths).
|
|
1110
|
+
* 3. Ensure server is running (auto-starts if not).
|
|
1111
|
+
* 4. List all notes from the vault.
|
|
1112
|
+
* 5. Insert each note via `POST /documents` with stable id
|
|
1113
|
+
* `bizar://<projectId>/<relPath>`.
|
|
1114
|
+
* 6. Write marker file with stats.
|
|
1115
|
+
*
|
|
1116
|
+
* The reindex is idempotent — re-running re-inserts the same notes under
|
|
1117
|
+
* the same ids; LightRAG's id-keyed storage handles this gracefully.
|
|
547
1118
|
*/
|
|
548
|
-
async function cmdReindex(
|
|
1119
|
+
async function cmdReindex(args) {
|
|
549
1120
|
const projectRoot = getProjectRoot();
|
|
550
|
-
const
|
|
1121
|
+
const _atomic = await import('./atomic.mjs').then((m) => m);
|
|
551
1122
|
const cacheDir = join(projectRoot, '.bizar', 'memory-cache');
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1123
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
1124
|
+
|
|
1125
|
+
const { isLightRAGInstalled, reindexVault, resolveLightRAGConfig } = memoryStore;
|
|
1126
|
+
|
|
1127
|
+
// Preflight: is lightrag-server installed?
|
|
1128
|
+
if (!(await isLightRAGInstalled())) {
|
|
1129
|
+
error('lightrag-server not installed');
|
|
1130
|
+
info('install it with: uv tool install "lightrag-hku[api]"');
|
|
1131
|
+
info('then run `bizar memory reindex` again');
|
|
1132
|
+
process.exit(1);
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
// Preflight: is lightrag enabled in config?
|
|
1136
|
+
const config = resolveLightRAGConfig(projectRoot);
|
|
1137
|
+
if (!config.enabled) {
|
|
1138
|
+
warn('lightrag is disabled in .bizar/memory.json (lightrag.enabled = false)');
|
|
1139
|
+
info('set lightrag.enabled = true and run `bizar memory reindex` again');
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
info(`reindexing vault into LightRAG at http://${config.host}:${config.port}…`);
|
|
1144
|
+
const result = await reindexVault(projectRoot, { logger: console });
|
|
1145
|
+
|
|
1146
|
+
if (!result.ok && result.inserted === 0) {
|
|
1147
|
+
error(`reindex failed: ${result.error || 'unknown'}`);
|
|
1148
|
+
if (result.failures?.length > 0) {
|
|
1149
|
+
info(`first failure: ${result.failures[0].relPath} — ${result.failures[0].error}`);
|
|
1150
|
+
}
|
|
1151
|
+
process.exit(1);
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
if (result.started) {
|
|
1155
|
+
success(`started LightRAG server (pid ${result.pid})`);
|
|
1156
|
+
}
|
|
1157
|
+
success(`reindexed ${result.inserted}/${result.noteCount} notes in ${result.durationMs}ms`);
|
|
1158
|
+
if (result.failed > 0) {
|
|
1159
|
+
warn(`${result.failed} notes failed — see marker for details`);
|
|
1160
|
+
}
|
|
1161
|
+
info(`marker: ${result.markerPath}`);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* `bizar memory search <query>`
|
|
1166
|
+
* v4.1.0 — merged lexical + semantic search.
|
|
1167
|
+
*
|
|
1168
|
+
* - Lexical: token-frequency matching against vault notes (instant, free).
|
|
1169
|
+
* - Semantic: LightRAG `/query` with `mode: 'mix'` (combines local entity
|
|
1170
|
+
* graph + global relation graph). Skipped if LightRAG isn't running.
|
|
1171
|
+
*/
|
|
1172
|
+
async function cmdSearch(args) {
|
|
1173
|
+
const query = args.join(' ').trim();
|
|
1174
|
+
if (!query) {
|
|
1175
|
+
error('search requires a query: `bizar memory search <query>`');
|
|
1176
|
+
process.exit(1);
|
|
1177
|
+
}
|
|
1178
|
+
const projectRoot = getProjectRoot();
|
|
1179
|
+
const { searchVault } = memoryStore;
|
|
1180
|
+
|
|
1181
|
+
// Lexical
|
|
1182
|
+
const lexical = searchVault(projectRoot, query, { limit: 10 });
|
|
1183
|
+
console.log(chalk.bold('\n Lexical results (token-frequency):'));
|
|
1184
|
+
if (lexical.length === 0) {
|
|
1185
|
+
console.log(chalk.dim(' (no matches)'));
|
|
1186
|
+
} else {
|
|
1187
|
+
for (const r of lexical.slice(0, 5)) {
|
|
1188
|
+
console.log(` ${chalk.cyan(r.relPath)} ${chalk.dim(`(score ${r.score})`)}`);
|
|
1189
|
+
console.log(chalk.dim(` ${r.snippet}`));
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
// Semantic (best-effort)
|
|
1194
|
+
console.log(chalk.bold('\n Semantic answer (LightRAG):'));
|
|
1195
|
+
try {
|
|
1196
|
+
const cfg = memoryStore.resolveLightRAGConfig(projectRoot);
|
|
1197
|
+
const semantic = await memoryLightrag.query(cfg, query, { topK: 10 });
|
|
1198
|
+
if (!semantic.ok) {
|
|
1199
|
+
console.log(chalk.dim(` ${semantic.error || 'unavailable'}`));
|
|
1200
|
+
if (semantic.error?.includes('not running')) {
|
|
1201
|
+
console.log(chalk.dim(' tip: run `bizar memory reindex` to start + populate LightRAG'));
|
|
1202
|
+
}
|
|
1203
|
+
} else {
|
|
1204
|
+
const response = semantic.response?.response || semantic.response?.answer || '';
|
|
1205
|
+
if (response) {
|
|
1206
|
+
console.log(` ${response.split('\n').join('\n ')}`);
|
|
1207
|
+
} else {
|
|
1208
|
+
console.log(chalk.dim(' (no answer returned)'));
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
} catch (err) {
|
|
1212
|
+
console.log(chalk.dim(` ${err.message}`));
|
|
1213
|
+
}
|
|
564
1214
|
}
|
|
565
1215
|
|
|
566
1216
|
/**
|
|
@@ -655,17 +1305,35 @@ async function cmdDoctor(_args) {
|
|
|
655
1305
|
allPassed = false;
|
|
656
1306
|
}
|
|
657
1307
|
|
|
658
|
-
// Check 7: last
|
|
659
|
-
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex
|
|
1308
|
+
// Check 7: last reindex
|
|
1309
|
+
const reindexMarker = join(projectRoot, '.bizar', 'memory-cache', 'last-reindex.json');
|
|
660
1310
|
if (existsSync(reindexMarker)) {
|
|
661
1311
|
try {
|
|
662
|
-
const {
|
|
663
|
-
const ageMs = Date.now() - new Date(
|
|
1312
|
+
const { finishedAt, ok, inserted, failed, noteCount } = JSON.parse(readFileSync(reindexMarker, 'utf8'));
|
|
1313
|
+
const ageMs = Date.now() - new Date(finishedAt).getTime();
|
|
664
1314
|
const ageH = ageMs / 3600000;
|
|
665
|
-
|
|
1315
|
+
const summary = `${inserted}/${noteCount} inserted${failed > 0 ? `, ${failed} failed` : ''}`;
|
|
1316
|
+
const status = ok ? chalk.green(summary) : chalk.red(summary);
|
|
1317
|
+
kv('lastReindex', ageH < 24 ? `${status} ${chalk.dim(`(${Math.round(ageH)}h ago)`)}` : `${status} ${chalk.yellow(`(${Math.round(ageH)}h ago)`)}`);
|
|
666
1318
|
} catch { /* ignore */ }
|
|
667
1319
|
} else {
|
|
668
|
-
kv('lastReindex', chalk.yellow('never'));
|
|
1320
|
+
kv('lastReindex', chalk.yellow('never — run `bizar memory reindex`'));
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
// Check 8: LightRAG status
|
|
1324
|
+
try {
|
|
1325
|
+
const { isLightRAGInstalled, isLightRAGRunning, resolveLightRAGConfig } = memoryStore;
|
|
1326
|
+
const cfg = resolveLightRAGConfig(projectRoot);
|
|
1327
|
+
if (!cfg.enabled) {
|
|
1328
|
+
kv('lightrag', chalk.gray('disabled'));
|
|
1329
|
+
} else if (!(await isLightRAGInstalled())) {
|
|
1330
|
+
kv('lightrag', chalk.yellow('not installed — `uv tool install "lightrag-hku[api]"`'));
|
|
1331
|
+
} else {
|
|
1332
|
+
const running = await isLightRAGRunning(cfg);
|
|
1333
|
+
kv('lightrag', running ? chalk.green(`${cfg.host}:${cfg.port} running`) : chalk.yellow(`not running — run \`bizar memory reindex\``));
|
|
1334
|
+
}
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
kv('lightrag', chalk.gray(`status check failed: ${err.message}`));
|
|
669
1337
|
}
|
|
670
1338
|
}
|
|
671
1339
|
|
|
@@ -689,6 +1357,9 @@ export async function runMemory(subcommand, args) {
|
|
|
689
1357
|
case 'init':
|
|
690
1358
|
await cmdInit(args);
|
|
691
1359
|
break;
|
|
1360
|
+
case 'setup':
|
|
1361
|
+
await cmdSetup(args);
|
|
1362
|
+
break;
|
|
692
1363
|
case 'status':
|
|
693
1364
|
await cmdStatus(args);
|
|
694
1365
|
break;
|
|
@@ -698,6 +1369,9 @@ export async function runMemory(subcommand, args) {
|
|
|
698
1369
|
case 'unlink':
|
|
699
1370
|
await cmdUnlink(args);
|
|
700
1371
|
break;
|
|
1372
|
+
case 'write':
|
|
1373
|
+
await cmdWrite(args);
|
|
1374
|
+
break;
|
|
701
1375
|
case 'pull':
|
|
702
1376
|
await cmdPull(args);
|
|
703
1377
|
break;
|
|
@@ -713,6 +1387,9 @@ export async function runMemory(subcommand, args) {
|
|
|
713
1387
|
case 'reindex':
|
|
714
1388
|
await cmdReindex(args);
|
|
715
1389
|
break;
|
|
1390
|
+
case 'search':
|
|
1391
|
+
await cmdSearch(args);
|
|
1392
|
+
break;
|
|
716
1393
|
case 'conflicts':
|
|
717
1394
|
await cmdConflicts(args);
|
|
718
1395
|
break;
|
|
@@ -740,9 +1417,11 @@ function showHelp() {
|
|
|
740
1417
|
|
|
741
1418
|
Subcommands:
|
|
742
1419
|
init Initialize memory config for this project
|
|
1420
|
+
setup Configure or reconfigure the memory vault (mode + remote)
|
|
743
1421
|
status Show current memory configuration
|
|
744
1422
|
link <path> Link to a shared memory repo (clone if URL)
|
|
745
1423
|
unlink Revert to local-only mode
|
|
1424
|
+
write Write a single note to the vault
|
|
746
1425
|
pull Git pull from the shared repo
|
|
747
1426
|
commit [-m] Git commit staged changes
|
|
748
1427
|
push Git push to the shared repo
|