clawvault 1.11.2 → 2.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.
Files changed (52) hide show
  1. package/README.md +135 -1
  2. package/bin/clawvault.js +51 -1252
  3. package/bin/command-registration.test.js +148 -0
  4. package/bin/command-runtime.js +42 -0
  5. package/bin/command-runtime.test.js +102 -0
  6. package/bin/help-contract.test.js +23 -0
  7. package/bin/register-core-commands.js +139 -0
  8. package/bin/register-maintenance-commands.js +137 -0
  9. package/bin/register-query-commands.js +225 -0
  10. package/bin/register-resilience-commands.js +147 -0
  11. package/bin/register-session-lifecycle-commands.js +204 -0
  12. package/bin/register-template-commands.js +72 -0
  13. package/bin/register-vault-operations-commands.js +295 -0
  14. package/bin/test-helpers/cli-command-fixtures.js +94 -0
  15. package/dashboard/lib/graph-diff.js +3 -1
  16. package/dashboard/lib/graph-diff.test.js +19 -0
  17. package/dashboard/lib/vault-parser.js +330 -26
  18. package/dashboard/lib/vault-parser.test.js +191 -11
  19. package/dashboard/public/app.js +22 -9
  20. package/dist/chunk-MXSSG3QU.js +42 -0
  21. package/dist/chunk-O5V7SD5C.js +398 -0
  22. package/dist/chunk-PAYUH64O.js +284 -0
  23. package/dist/{chunk-3HFB7EMU.js → chunk-QFBKWDYR.js} +12 -0
  24. package/dist/{chunk-UBRYOIII.js → chunk-TBVI4N53.js} +210 -21
  25. package/dist/chunk-TXO34J3O.js +56 -0
  26. package/dist/commands/compat.d.ts +28 -0
  27. package/dist/commands/compat.js +10 -0
  28. package/dist/commands/context.d.ts +2 -33
  29. package/dist/commands/context.js +3 -2
  30. package/dist/commands/doctor.js +61 -3
  31. package/dist/commands/entities.d.ts +1 -0
  32. package/dist/commands/entities.js +4 -4
  33. package/dist/commands/graph.d.ts +21 -0
  34. package/dist/commands/graph.js +10 -0
  35. package/dist/commands/link.d.ts +1 -0
  36. package/dist/commands/link.js +14 -5
  37. package/dist/commands/sleep.js +7 -6
  38. package/dist/commands/status.d.ts +6 -0
  39. package/dist/commands/status.js +63 -3
  40. package/dist/commands/wake.js +5 -4
  41. package/dist/context-COo8oq1k.d.ts +45 -0
  42. package/dist/index.d.ts +63 -2
  43. package/dist/index.js +53 -15
  44. package/dist/lib/config.d.ts +6 -1
  45. package/dist/lib/config.js +7 -3
  46. package/hooks/clawvault/HOOK.md +6 -1
  47. package/hooks/clawvault/handler.js +44 -3
  48. package/hooks/clawvault/handler.test.js +161 -0
  49. package/package.json +34 -2
  50. package/dashboard/public/graph.js +0 -376
  51. package/dashboard/public/style.css +0 -154
  52. package/dist/chunk-4KDZZW4X.js +0 -13
package/bin/clawvault.js CHANGED
@@ -9,14 +9,22 @@ import { Command } from 'commander';
9
9
  import chalk from 'chalk';
10
10
  import * as fs from 'fs';
11
11
  import * as path from 'path';
12
- import { spawn } from 'child_process';
13
- import { createInterface } from 'readline/promises';
12
+ import { registerMaintenanceCommands } from './register-maintenance-commands.js';
13
+ import { registerCoreCommands } from './register-core-commands.js';
14
+ import { registerQueryCommands } from './register-query-commands.js';
15
+ import { registerResilienceCommands } from './register-resilience-commands.js';
16
+ import { registerSessionLifecycleCommands } from './register-session-lifecycle-commands.js';
17
+ import { registerTemplateCommands } from './register-template-commands.js';
18
+ import { registerVaultOperationsCommands } from './register-vault-operations-commands.js';
14
19
  import {
15
- ClawVault,
16
- createVault,
17
- findVault,
18
- QmdUnavailableError,
19
- QMD_INSTALL_COMMAND
20
+ getVault,
21
+ resolveVaultPath,
22
+ runQmd,
23
+ printQmdMissing,
24
+ QmdUnavailableError
25
+ } from './command-runtime.js';
26
+ import {
27
+ createVault
20
28
  } from '../dist/index.js';
21
29
 
22
30
  const program = new Command();
@@ -31,1257 +39,48 @@ const CLI_VERSION = (() => {
31
39
  }
32
40
  })();
33
41
 
34
- // Helper to get vault (required for most commands)
35
- // Checks: 1) explicit path, 2) CLAWVAULT_PATH env, 3) walk up from cwd
36
- async function getVault(vaultPath) {
37
- // Explicit path takes priority
38
- if (vaultPath) {
39
- const vault = new ClawVault(path.resolve(vaultPath));
40
- await vault.load();
41
- return vault;
42
- }
43
-
44
- // Check environment variable
45
- const envPath = process.env.CLAWVAULT_PATH;
46
- if (envPath) {
47
- const vault = new ClawVault(path.resolve(envPath));
48
- await vault.load();
49
- return vault;
50
- }
51
-
52
- // Walk up from cwd
53
- const vault = await findVault();
54
- if (!vault) {
55
- console.error(chalk.red('Error: No ClawVault found. Run `clawvault init` first.'));
56
- console.log(chalk.dim('Tip: Set CLAWVAULT_PATH environment variable to your vault path'));
57
- process.exit(1);
58
- }
59
- return vault;
60
- }
61
-
62
- function resolveVaultPath(vaultPath) {
63
- if (vaultPath) {
64
- return path.resolve(vaultPath);
65
- }
66
- if (process.env.CLAWVAULT_PATH) {
67
- return path.resolve(process.env.CLAWVAULT_PATH);
68
- }
69
- let current = process.cwd();
70
- while (true) {
71
- if (fs.existsSync(path.join(current, '.clawvault.json'))) {
72
- return current;
73
- }
74
- const parent = path.dirname(current);
75
- if (parent === current) {
76
- console.error(chalk.red('Error: No ClawVault found. Run `clawvault init` first.'));
77
- console.log(chalk.dim('Tip: Set CLAWVAULT_PATH environment variable to your vault path'));
78
- process.exit(1);
79
- }
80
- current = parent;
81
- }
82
- }
83
-
84
- async function runQmd(args) {
85
- return new Promise((resolve, reject) => {
86
- const proc = spawn('qmd', args, { stdio: 'inherit' });
87
- proc.on('close', (code) => {
88
- if (code === 0) resolve();
89
- else reject(new Error(`qmd exited with code ${code}`));
90
- });
91
- proc.on('error', (err) => {
92
- if (err?.code === 'ENOENT') {
93
- reject(new QmdUnavailableError());
94
- } else {
95
- reject(err);
96
- }
97
- });
98
- });
99
- }
100
-
101
- function printQmdMissing() {
102
- console.error(chalk.red('Error: ClawVault requires qmd.'));
103
- console.log(chalk.dim(`Install: ${QMD_INSTALL_COMMAND}`));
104
- }
105
-
106
- function parseBooleanInput(value, defaultValue = true) {
107
- const normalized = value.trim().toLowerCase();
108
- if (!normalized) {
109
- return defaultValue;
110
- }
111
- if (['y', 'yes', 'true', '1'].includes(normalized)) {
112
- return true;
113
- }
114
- if (['n', 'no', 'false', '0'].includes(normalized)) {
115
- return false;
116
- }
117
- return null;
118
- }
119
-
120
42
  program
121
43
  .name('clawvault')
122
44
  .description('🐘 An elephant never forgets. Structured memory for AI agents.')
123
45
  .version(CLI_VERSION);
124
46
 
125
- // === INIT ===
126
- program
127
- .command('init [path]')
128
- .description('Initialize a new ClawVault')
129
- .option('-n, --name <name>', 'Vault name')
130
- .option('--qmd', 'Set up qmd semantic search collection')
131
- .option('--qmd-collection <name>', 'qmd collection name (defaults to vault name)')
132
- .action(async (vaultPath, options) => {
133
- const targetPath = vaultPath || '.';
134
- console.log(chalk.cyan(`\n🐘 Initializing ClawVault at ${path.resolve(targetPath)}...\n`));
135
-
136
- try {
137
- const vault = await createVault(targetPath, {
138
- name: options.name || path.basename(path.resolve(targetPath)),
139
- qmdCollection: options.qmdCollection
140
- });
141
-
142
- console.log(chalk.green('✓ Vault created'));
143
- console.log(chalk.dim(` Categories: ${vault.getCategories().join(', ')}`));
144
-
145
- // Always set up qmd collection (qmd is required)
146
- console.log(chalk.cyan('\nSetting up qmd collection...'));
147
- try {
148
- await runQmd([
149
- 'collection',
150
- 'add',
151
- vault.getQmdRoot(),
152
- '--name',
153
- vault.getQmdCollection(),
154
- '--mask',
155
- '**/*.md'
156
- ]);
157
- console.log(chalk.green('✓ qmd collection created'));
158
- } catch (err) {
159
- // Collection might already exist
160
- console.log(chalk.yellow('⚠ qmd collection may already exist'));
161
- }
162
-
163
- console.log(chalk.green('\n✅ ClawVault ready!\n'));
164
- console.log(chalk.dim('Next steps:'));
165
- console.log(chalk.dim(' clawvault store --category inbox --title "My note" --content "Hello world"'));
166
- console.log(chalk.dim(' clawvault search "hello"'));
167
- console.log();
168
- } catch (err) {
169
- console.error(chalk.red(`Error: ${err.message}`));
170
- process.exit(1);
171
- }
172
- });
173
-
174
- // === SETUP ===
175
- program
176
- .command('setup')
177
- .description('Auto-discover and configure a ClawVault')
178
- .action(async () => {
179
- try {
180
- const { setupCommand } = await import('../dist/commands/setup.js');
181
- await setupCommand();
182
- } catch (err) {
183
- console.error(chalk.red(`Error: ${err.message}`));
184
- process.exit(1);
185
- }
186
- });
187
-
188
- // === STORE ===
189
- program
190
- .command('store')
191
- .description('Store a new memory')
192
- .requiredOption('-c, --category <category>', 'Category (preferences, decisions, patterns, people, projects, goals, transcripts, inbox)')
193
- .requiredOption('-t, --title <title>', 'Document title')
194
- .option('--content <content>', 'Content body')
195
- .option('-f, --file <file>', 'Read content from file')
196
- .option('--stdin', 'Read content from stdin')
197
- .option('--overwrite', 'Overwrite if exists')
198
- .option('--no-index', 'Skip qmd index update (auto-updates by default)')
199
- .option('--embed', 'Also update qmd embeddings for vector search')
200
- .option('-v, --vault <path>', 'Vault path (default: find nearest)')
201
- .action(async (options) => {
202
- try {
203
- const vault = await getVault(options.vault);
204
-
205
- let content = options.content || '';
206
-
207
- if (options.file) {
208
- content = fs.readFileSync(options.file, 'utf-8');
209
- } else if (options.stdin) {
210
- content = fs.readFileSync(0, 'utf-8');
211
- }
212
-
213
- const doc = await vault.store({
214
- category: options.category,
215
- title: options.title,
216
- content,
217
- overwrite: options.overwrite
218
- });
219
-
220
- console.log(chalk.green(`✓ Stored: ${doc.id}`));
221
- console.log(chalk.dim(` Path: ${doc.path}`));
222
-
223
- // Auto-update qmd index unless --no-index
224
- if (options.index !== false) {
225
- const collection = vault.getQmdCollection();
226
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
227
- if (options.embed) {
228
- await runQmd(collection ? ['embed', '-c', collection] : ['embed']);
229
- }
230
- }
231
- } catch (err) {
232
- console.error(chalk.red(`Error: ${err.message}`));
233
- process.exit(1);
234
- }
235
- });
236
-
237
- // === CAPTURE ===
238
- program
239
- .command('capture <note>')
240
- .description('Quick capture to inbox')
241
- .option('-t, --title <title>', 'Note title')
242
- .option('-v, --vault <path>', 'Vault path')
243
- .option('--no-index', 'Skip qmd index update')
244
- .action(async (note, options) => {
245
- try {
246
- const vault = await getVault(options.vault);
247
- const doc = await vault.capture(note, options.title);
248
- console.log(chalk.green(`✓ Captured: ${doc.id}`));
249
-
250
- // Auto-update qmd index unless --no-index
251
- if (options.index !== false) {
252
- const collection = vault.getQmdCollection();
253
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
254
- }
255
- } catch (err) {
256
- console.error(chalk.red(`Error: ${err.message}`));
257
- process.exit(1);
258
- }
259
- });
260
-
261
- // === SEARCH ===
262
- program
263
- .command('search <query>')
264
- .description('Search the vault via qmd (BM25)')
265
- .option('-n, --limit <n>', 'Max results', '10')
266
- .option('-c, --category <category>', 'Filter by category')
267
- .option('--tags <tags>', 'Filter by tags (comma-separated)')
268
- .option('--recent', 'Boost recent documents')
269
- .option('--full', 'Include full content in results')
270
- .option('-v, --vault <path>', 'Vault path')
271
- .option('--json', 'Output as JSON')
272
- .action(async (query, options) => {
273
- try {
274
- const vault = await getVault(options.vault);
275
-
276
- const results = await vault.find(query, {
277
- limit: parseInt(options.limit),
278
- category: options.category,
279
- tags: options.tags?.split(',').map(t => t.trim()),
280
- fullContent: options.full,
281
- temporalBoost: options.recent
282
- });
283
-
284
- if (options.json) {
285
- console.log(JSON.stringify(results, null, 2));
286
- return;
287
- }
288
-
289
- if (results.length === 0) {
290
- console.log(chalk.yellow('No results found.'));
291
- return;
292
- }
293
-
294
- console.log(chalk.cyan(`\n🔍 Found ${results.length} result(s) for "${query}":\n`));
295
-
296
- for (const result of results) {
297
- const scoreBar = '█'.repeat(Math.round(result.score * 10)).padEnd(10, '░');
298
- console.log(chalk.green(`📄 ${result.document.title}`));
299
- console.log(chalk.dim(` ${result.document.category}/${result.document.id.split('/').pop()}`));
300
- console.log(chalk.dim(` Score: ${scoreBar} ${(result.score * 100).toFixed(0)}%`));
301
- if (result.snippet) {
302
- console.log(chalk.white(` ${result.snippet.split('\n')[0].slice(0, 80)}...`));
303
- }
304
- console.log();
305
- }
306
- } catch (err) {
307
- if (err instanceof QmdUnavailableError) {
308
- printQmdMissing();
309
- process.exit(1);
310
- }
311
- console.error(chalk.red(`Error: ${err.message}`));
312
- process.exit(1);
313
- }
314
- });
315
-
316
- // === VSEARCH (qmd semantic search) ===
317
- program
318
- .command('vsearch <query>')
319
- .description('Semantic search via qmd (requires qmd installed)')
320
- .option('-n, --limit <n>', 'Max results', '5')
321
- .option('-c, --category <category>', 'Filter by category')
322
- .option('--tags <tags>', 'Filter by tags (comma-separated)')
323
- .option('--recent', 'Boost recent documents')
324
- .option('--full', 'Include full content in results')
325
- .option('-v, --vault <path>', 'Vault path')
326
- .option('--json', 'Output as JSON')
327
- .action(async (query, options) => {
328
- try {
329
- const vault = await getVault(options.vault);
330
-
331
- const results = await vault.vsearch(query, {
332
- limit: parseInt(options.limit),
333
- category: options.category,
334
- tags: options.tags?.split(',').map(t => t.trim()),
335
- fullContent: options.full,
336
- temporalBoost: options.recent
337
- });
338
-
339
- if (options.json) {
340
- console.log(JSON.stringify(results, null, 2));
341
- return;
342
- }
343
-
344
- if (results.length === 0) {
345
- console.log(chalk.yellow('No results found.'));
346
- return;
347
- }
348
-
349
- console.log(chalk.cyan(`\n🧠 Found ${results.length} result(s) for "${query}":\n`));
350
-
351
- for (const result of results) {
352
- const scoreBar = '█'.repeat(Math.round(result.score * 10)).padEnd(10, '░');
353
- console.log(chalk.green(`📄 ${result.document.title}`));
354
- console.log(chalk.dim(` ${result.document.category}/${result.document.id.split('/').pop()}`));
355
- console.log(chalk.dim(` Score: ${scoreBar} ${(result.score * 100).toFixed(0)}%`));
356
- if (result.snippet) {
357
- console.log(chalk.white(` ${result.snippet.split('\n')[0].slice(0, 80)}...`));
358
- }
359
- console.log();
360
- }
361
- } catch (err) {
362
- if (err instanceof QmdUnavailableError) {
363
- printQmdMissing();
364
- process.exit(1);
365
- }
366
- console.error(chalk.red(`Error: ${err.message}`));
367
- console.log(chalk.dim(`\nTip: Install qmd: ${QMD_INSTALL_COMMAND}`));
368
- process.exit(1);
369
- }
370
- });
371
-
372
- // === CONTEXT ===
373
- program
374
- .command('context <task>')
375
- .description('Generate task-relevant context for prompt injection')
376
- .option('-n, --limit <n>', 'Max results', '5')
377
- .option('--format <format>', 'Output format (markdown|json)', 'markdown')
378
- .option('--recent', 'Boost recent documents (enabled by default)', true)
379
- .option('--include-observations', 'Include observation memories in output', true)
380
- .option('--budget <number>', 'Optional token budget for assembled context')
381
- .option('-v, --vault <path>', 'Vault path')
382
- .action(async (task, options) => {
383
- try {
384
- const vaultPath = resolveVaultPath(options.vault);
385
- const format = options.format === 'json' ? 'json' : 'markdown';
386
- const parsedBudget = options.budget ? Number.parseInt(options.budget, 10) : undefined;
387
- if (options.budget && (!Number.isFinite(parsedBudget) || parsedBudget <= 0)) {
388
- throw new Error(`Invalid --budget value: ${options.budget}`);
389
- }
390
-
391
- const { contextCommand } = await import('../dist/commands/context.js');
392
- await contextCommand(task, {
393
- vaultPath,
394
- limit: parseInt(options.limit),
395
- format,
396
- recent: options.recent,
397
- includeObservations: options.includeObservations,
398
- budget: parsedBudget
399
- });
400
- } catch (err) {
401
- if (err instanceof QmdUnavailableError) {
402
- printQmdMissing();
403
- process.exit(1);
404
- }
405
- console.error(chalk.red(`Error: ${err.message}`));
406
- process.exit(1);
407
- }
408
- });
409
-
410
- // === OBSERVE ===
411
- program
412
- .command('observe')
413
- .description('Observe session files and build observational memory')
414
- .option('--watch <path>', 'Watch session file or directory')
415
- .option('--threshold <n>', 'Compression token threshold', '30000')
416
- .option('--reflect-threshold <n>', 'Reflection token threshold', '40000')
417
- .option('--model <model>', 'LLM model override')
418
- .option('--compress <file>', 'One-shot compression for a conversation file')
419
- .option('--daemon', 'Run in detached background mode')
420
- .option('-v, --vault <path>', 'Vault path')
421
- .action(async (options) => {
422
- try {
423
- const { observeCommand } = await import('../dist/commands/observe.js');
424
- const threshold = Number.parseInt(options.threshold, 10);
425
- const reflectThreshold = Number.parseInt(options.reflectThreshold, 10);
426
- if (Number.isNaN(threshold) || threshold <= 0) {
427
- throw new Error(`Invalid --threshold value: ${options.threshold}`);
428
- }
429
- if (Number.isNaN(reflectThreshold) || reflectThreshold <= 0) {
430
- throw new Error(`Invalid --reflect-threshold value: ${options.reflectThreshold}`);
431
- }
432
-
433
- await observeCommand({
434
- watch: options.watch,
435
- threshold,
436
- reflectThreshold,
437
- model: options.model,
438
- compress: options.compress,
439
- daemon: options.daemon,
440
- vaultPath: resolveVaultPath(options.vault)
441
- });
442
- } catch (err) {
443
- console.error(chalk.red(`Error: ${err.message}`));
444
- process.exit(1);
445
- }
446
- });
447
-
448
- // === SESSION-RECAP ===
449
- program
450
- .command('session-recap <sessionKey>')
451
- .description('Generate recap from a specific OpenClaw session transcript')
452
- .option('-n, --limit <n>', 'Number of messages to include', '15')
453
- .option('--format <format>', 'Output format (markdown|json)', 'markdown')
454
- .option('-a, --agent <id>', 'Agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
455
- .action(async (sessionKey, options) => {
456
- try {
457
- const { sessionRecapCommand } = await import('../dist/commands/session-recap.js');
458
- const format = options.format === 'json' ? 'json' : 'markdown';
459
- const parsedLimit = Number.parseInt(options.limit, 10);
460
- await sessionRecapCommand(sessionKey, {
461
- limit: Number.isNaN(parsedLimit) ? 15 : parsedLimit,
462
- format,
463
- agentId: options.agent
464
- });
465
- } catch (err) {
466
- console.error(chalk.red(`Error: ${err.message}`));
467
- process.exit(1);
468
- }
469
- });
470
-
471
- // === LIST ===
472
- program
473
- .command('list [category]')
474
- .description('List documents')
475
- .option('-v, --vault <path>', 'Vault path')
476
- .option('--json', 'Output as JSON')
477
- .action(async (category, options) => {
478
- try {
479
- const vault = await getVault(options.vault);
480
- const docs = await vault.list(category);
481
-
482
- if (options.json) {
483
- console.log(JSON.stringify(docs.map(d => ({
484
- id: d.id,
485
- title: d.title,
486
- category: d.category,
487
- tags: d.tags,
488
- modified: d.modified
489
- })), null, 2));
490
- return;
491
- }
492
-
493
- if (docs.length === 0) {
494
- console.log(chalk.yellow('No documents found.'));
495
- return;
496
- }
497
-
498
- console.log(chalk.cyan(`\n📚 ${docs.length} document(s)${category ? ` in ${category}` : ''}:\n`));
499
-
500
- // Group by category
501
- const grouped = {};
502
- for (const doc of docs) {
503
- grouped[doc.category] = grouped[doc.category] || [];
504
- grouped[doc.category].push(doc);
505
- }
506
-
507
- for (const [cat, catDocs] of Object.entries(grouped)) {
508
- console.log(chalk.yellow(`${cat}/`));
509
- for (const doc of catDocs) {
510
- console.log(chalk.dim(` - ${doc.title}`));
511
- }
512
- }
513
- console.log();
514
- } catch (err) {
515
- console.error(chalk.red(`Error: ${err.message}`));
516
- process.exit(1);
517
- }
518
- });
519
-
520
- // === GET ===
521
- program
522
- .command('get <id>')
523
- .description('Get a document by ID')
524
- .option('-v, --vault <path>', 'Vault path')
525
- .option('--json', 'Output as JSON')
526
- .action(async (id, options) => {
527
- try {
528
- const vault = await getVault(options.vault);
529
- const doc = await vault.get(id);
530
-
531
- if (!doc) {
532
- console.error(chalk.red(`Document not found: ${id}`));
533
- process.exit(1);
534
- }
535
-
536
- if (options.json) {
537
- console.log(JSON.stringify(doc, null, 2));
538
- return;
539
- }
540
-
541
- console.log(chalk.cyan(`\n📄 ${doc.title}\n`));
542
- console.log(chalk.dim(`Category: ${doc.category}`));
543
- console.log(chalk.dim(`Path: ${doc.path}`));
544
- console.log(chalk.dim(`Tags: ${doc.tags.join(', ') || 'none'}`));
545
- console.log(chalk.dim(`Links: ${doc.links.join(', ') || 'none'}`));
546
- console.log(chalk.dim(`Modified: ${doc.modified.toISOString()}`));
547
- console.log(chalk.dim('---'));
548
- console.log(doc.content);
549
- } catch (err) {
550
- console.error(chalk.red(`Error: ${err.message}`));
551
- process.exit(1);
552
- }
553
- });
554
-
555
- // === STATS ===
556
- program
557
- .command('stats')
558
- .description('Show vault statistics')
559
- .option('-v, --vault <path>', 'Vault path')
560
- .option('--json', 'Output as JSON')
561
- .action(async (options) => {
562
- try {
563
- const vault = await getVault(options.vault);
564
- const stats = await vault.stats();
565
-
566
- if (options.json) {
567
- console.log(JSON.stringify(stats, null, 2));
568
- return;
569
- }
570
-
571
- console.log(chalk.cyan(`\n🐘 ${vault.getName()} Stats\n`));
572
- console.log(chalk.dim(`Path: ${vault.getPath()}`));
573
- console.log(`Documents: ${chalk.green(stats.documents)}`);
574
- console.log(`Links: ${chalk.blue(stats.links)}`);
575
- console.log(`Tags: ${chalk.yellow(stats.tags.length)}`);
576
- console.log();
577
- console.log(chalk.dim('By category:'));
578
- for (const [cat, count] of Object.entries(stats.categories)) {
579
- console.log(chalk.dim(` ${cat}: ${count}`));
580
- }
581
- console.log();
582
- } catch (err) {
583
- console.error(chalk.red(`Error: ${err.message}`));
584
- process.exit(1);
585
- }
586
- });
587
-
588
- // === SYNC (vault file sync only) ===
589
- program
590
- .command('sync <target>')
591
- .description('Sync vault files to target path')
592
- .option('--delete', 'Delete orphan files in target')
593
- .option('--dry-run', "Show what would be synced without syncing")
594
- .option('-v, --vault <path>', 'Vault path')
595
- .action(async (target, options) => {
596
- try {
597
- const vault = await getVault(options.vault);
598
-
599
- console.log(chalk.cyan(`\n🔄 Syncing to ${target}...\n`));
600
-
601
- const result = await vault.sync({
602
- target,
603
- deleteOrphans: options.delete,
604
- dryRun: options.dryRun
605
- });
606
-
607
- if (options.dryRun) {
608
- console.log(chalk.yellow('DRY RUN - no changes made\n'));
609
- }
610
-
611
- if (result.copied.length > 0) {
612
- console.log(chalk.green(`Copied: ${result.copied.length} files`));
613
- for (const f of result.copied.slice(0, 5)) {
614
- console.log(chalk.dim(` + ${f}`));
615
- }
616
- if (result.copied.length > 5) {
617
- console.log(chalk.dim(` ... and ${result.copied.length - 5} more`));
618
- }
619
- }
620
-
621
- if (result.deleted.length > 0) {
622
- console.log(chalk.red(`Deleted: ${result.deleted.length} files`));
623
- }
624
-
625
- if (result.unchanged.length > 0) {
626
- console.log(chalk.dim(`Unchanged: ${result.unchanged.length} files`));
627
- }
628
-
629
- if (result.errors.length > 0) {
630
- console.log(chalk.red(`\nErrors:`));
631
- for (const e of result.errors) {
632
- console.log(chalk.red(` ${e}`));
633
- }
634
- }
635
-
636
- console.log();
637
- } catch (err) {
638
- console.error(chalk.red(`Error: ${err.message}`));
639
- process.exit(1);
640
- }
641
- });
642
-
643
- // === REINDEX ===
644
- program
645
- .command('reindex')
646
- .description('Rebuild the search index')
647
- .option('-v, --vault <path>', 'Vault path')
648
- .option('--qmd', 'Also update qmd embeddings')
649
- .action(async (options) => {
650
- try {
651
- const vault = await getVault(options.vault);
652
-
653
- console.log(chalk.cyan('\n🔄 Reindexing...\n'));
654
-
655
- const count = await vault.reindex();
656
- console.log(chalk.green(`✓ Indexed ${count} documents`));
657
-
658
- if (options.qmd) {
659
- console.log(chalk.cyan('Updating qmd embeddings...'));
660
- const collection = vault.getQmdCollection();
661
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
662
- console.log(chalk.green('✓ qmd updated'));
663
- }
664
-
665
- console.log();
666
- } catch (err) {
667
- console.error(chalk.red(`Error: ${err.message}`));
668
- process.exit(1);
669
- }
670
- });
671
-
672
- // === REMEMBER (type-based storage) ===
673
- program
674
- .command('remember <type> <title>')
675
- .description('Store a memory with type classification (fact|feeling|decision|lesson|commitment|preference|relationship|project)')
676
- .option('--content <content>', 'Content body')
677
- .option('-f, --file <file>', 'Read content from file')
678
- .option('--stdin', 'Read content from stdin')
679
- .option('-v, --vault <path>', 'Vault path')
680
- .option('--no-index', 'Skip qmd index update')
681
- .action(async (type, title, options) => {
682
- const validTypes = ['fact', 'feeling', 'decision', 'lesson', 'commitment', 'preference', 'relationship', 'project'];
683
- if (!validTypes.includes(type)) {
684
- console.error(chalk.red(`Invalid type: ${type}`));
685
- console.error(chalk.dim(`Valid types: ${validTypes.join(', ')}`));
686
- process.exit(1);
687
- }
688
-
689
- try {
690
- const vault = await getVault(options.vault);
691
-
692
- let content = options.content || '';
693
- if (options.file) {
694
- content = fs.readFileSync(options.file, 'utf-8');
695
- } else if (options.stdin) {
696
- content = fs.readFileSync(0, 'utf-8');
697
- }
698
-
699
- const doc = await vault.remember(type, title, content);
700
- console.log(chalk.green(`✓ Remembered (${type}): ${doc.id}`));
701
-
702
- // Auto-update qmd index unless --no-index
703
- if (options.index !== false) {
704
- const collection = vault.getQmdCollection();
705
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
706
- }
707
- } catch (err) {
708
- console.error(chalk.red(`Error: ${err.message}`));
709
- process.exit(1);
710
- }
711
- });
712
-
713
- // === WAKE (session start) ===
714
- program
715
- .command('wake')
716
- .description('Start a session (recover + recap + summary)')
717
- .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include', '3')
718
- .option('--full', 'Show full recap (default: brief)')
719
- .option('-v, --vault <path>', 'Vault path')
720
- .action(async (options) => {
721
- try {
722
- const vaultPath = resolveVaultPath(options.vault);
723
- const { wake } = await import('../dist/commands/wake.js');
724
- const { formatRecoveryInfo } = await import('../dist/commands/recover.js');
725
- const result = await wake({
726
- vaultPath,
727
- handoffLimit: parseInt(options.handoffLimit),
728
- brief: !options.full
729
- });
730
-
731
- console.log(chalk.cyan('\n🌅 ClawVault Wake\n'));
732
- console.log(formatRecoveryInfo(result.recovery));
733
- console.log();
734
- console.log(chalk.cyan('Recap'));
735
- console.log(result.recapMarkdown.trim());
736
- console.log();
737
- console.log(chalk.green(`You were working on: ${result.summary}`));
738
-
739
- process.exitCode = result.recovery.died ? 1 : 0;
740
- } catch (err) {
741
- if (err instanceof QmdUnavailableError) {
742
- printQmdMissing();
743
- process.exit(1);
744
- }
745
- console.error(chalk.red(`Error: ${err.message}`));
746
- process.exit(1);
747
- }
748
- });
749
-
750
- // === SLEEP (session end) ===
751
- program
752
- .command('sleep <summary>')
753
- .description('End a session with a handoff (and optional git commit)')
754
- .option('-n, --next <items>', 'Next steps (comma-separated)')
755
- .option('-b, --blocked <items>', 'Blocked items (comma-separated)')
756
- .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
757
- .option('-q, --questions <items>', 'Open questions (comma-separated)')
758
- .option('-f, --feeling <state>', 'Emotional/energy state')
759
- .option('-s, --session <key>', 'Session key')
760
- .option('--session-transcript <path>', 'Session transcript path for auto-observe')
761
- .option('--index', 'Update qmd index after handoff')
762
- .option('--no-git', 'Skip git commit prompt')
763
- .option('-v, --vault <path>', 'Vault path')
764
- .action(async (summary, options) => {
765
- try {
766
- const vaultPath = resolveVaultPath(options.vault);
767
- const { sleep } = await import('../dist/commands/sleep.js');
768
- const result = await sleep({
769
- workingOn: summary,
770
- next: options.next,
771
- blocked: options.blocked,
772
- decisions: options.decisions,
773
- questions: options.questions,
774
- feeling: options.feeling,
775
- sessionKey: options.session,
776
- sessionTranscript: options.sessionTranscript,
777
- vaultPath,
778
- index: options.index,
779
- git: options.git
780
- });
781
-
782
- console.log(chalk.green(`✓ Handoff saved: ${result.document.id}`));
783
- console.log(chalk.dim(` Path: ${result.document.path}`));
784
- console.log(chalk.dim(` Working on: ${result.handoff.workingOn.join(', ')}`));
785
- if (result.handoff.nextSteps.length > 0) {
786
- console.log(chalk.dim(` Next: ${result.handoff.nextSteps.join(', ')}`));
787
- } else {
788
- console.log(chalk.dim(' Next: (none)'));
789
- }
790
- if (result.handoff.blocked.length > 0) {
791
- console.log(chalk.dim(` Blocked: ${result.handoff.blocked.join(', ')}`));
792
- } else {
793
- console.log(chalk.dim(' Blocked: (none)'));
794
- }
795
- if (result.handoff.decisions?.length) {
796
- console.log(chalk.dim(` Decisions: ${result.handoff.decisions.join(', ')}`));
797
- }
798
- if (result.handoff.openQuestions?.length) {
799
- console.log(chalk.dim(` Questions: ${result.handoff.openQuestions.join(', ')}`));
800
- }
801
- if (result.handoff.feeling) {
802
- console.log(chalk.dim(` Feeling: ${result.handoff.feeling}`));
803
- }
804
- if (options.index) {
805
- console.log(chalk.dim(' qmd: index updated'));
806
- }
807
- if (result.git) {
808
- if (result.git.committed) {
809
- console.log(chalk.green(`✓ Git commit created${result.git.message ? `: ${result.git.message}` : ''}`));
810
- } else if (result.git.skippedReason === 'clean') {
811
- console.log(chalk.dim(' Git: clean'));
812
- } else if (result.git.skippedReason === 'declined') {
813
- console.log(chalk.dim(' Git: commit skipped'));
814
- }
815
- }
816
- if (result.observationRoutingSummary) {
817
- console.log(chalk.dim(` Observe: ${result.observationRoutingSummary}`));
818
- }
819
- } catch (err) {
820
- if (err instanceof QmdUnavailableError) {
821
- printQmdMissing();
822
- process.exit(1);
823
- }
824
- console.error(chalk.red(`Error: ${err.message}`));
825
- process.exit(1);
826
- }
827
- });
828
-
829
- // === HANDOFF (session bridge) ===
830
- program
831
- .command('handoff')
832
- .description('Create a session handoff document')
833
- .requiredOption('-w, --working-on <items>', 'What I was working on (comma-separated)')
834
- .option('-b, --blocked <items>', 'What is blocked (comma-separated)')
835
- .option('-n, --next <items>', 'What comes next (comma-separated)')
836
- .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
837
- .option('-q, --questions <items>', 'Open questions (comma-separated)')
838
- .option('-f, --feeling <state>', 'Emotional/energy state')
839
- .option('-s, --session <key>', 'Session key')
840
- .option('-v, --vault <path>', 'Vault path')
841
- .option('--no-index', 'Skip qmd index update (auto-updates by default)')
842
- .option('--json', 'Output as JSON')
843
- .action(async (options) => {
844
- try {
845
- const vault = await getVault(options.vault);
846
-
847
- const handoff = {
848
- workingOn: options.workingOn.split(',').map(s => s.trim()),
849
- blocked: options.blocked ? options.blocked.split(',').map(s => s.trim()) : [],
850
- nextSteps: options.next ? options.next.split(',').map(s => s.trim()) : [],
851
- decisions: options.decisions ? options.decisions.split(',').map(s => s.trim()) : undefined,
852
- openQuestions: options.questions ? options.questions.split(',').map(s => s.trim()) : undefined,
853
- feeling: options.feeling,
854
- sessionKey: options.session
855
- };
856
-
857
- const doc = await vault.createHandoff(handoff);
858
-
859
- if (!options.json) {
860
- console.log(chalk.green(`✓ Handoff created: ${doc.id}`));
861
- console.log(chalk.dim(` Path: ${doc.path}`));
862
- }
863
-
864
- // Auto-update qmd index unless --no-index
865
- if (options.index !== false) {
866
- const collection = vault.getQmdCollection();
867
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
868
- }
869
-
870
- if (options.json) {
871
- console.log(JSON.stringify({ id: doc.id, path: doc.path, handoff }, null, 2));
872
- }
873
- } catch (err) {
874
- console.error(chalk.red(`Error: ${err.message}`));
875
- process.exit(1);
876
- }
877
- });
878
-
879
- // === RECAP (session bootstrap) ===
880
- program
881
- .command('recap')
882
- .description('Generate a session recap - who I was (bootstrap hook)')
883
- .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include', '3')
884
- .option('-v, --vault <path>', 'Vault path')
885
- .option('--json', 'Output as JSON')
886
- .option('--markdown', 'Output as markdown (default)')
887
- .option('--brief', 'Minimal output for token savings')
888
- .action(async (options) => {
889
- try {
890
- const vault = await getVault(options.vault);
891
-
892
- const recap = await vault.generateRecap({
893
- handoffLimit: parseInt(options.handoffLimit),
894
- brief: options.brief
895
- });
896
-
897
- if (options.json) {
898
- console.log(JSON.stringify(recap, null, 2));
899
- return;
900
- }
901
-
902
- // Output as markdown (default)
903
- const md = vault.formatRecap(recap, { brief: options.brief });
904
- console.log(md);
905
- } catch (err) {
906
- console.error(chalk.red(`Error: ${err.message}`));
907
- process.exit(1);
908
- }
909
- });
910
-
911
- // === SHELL INIT ===
912
- program
913
- .command('shell-init')
914
- .description('Output shell integration for ClawVault')
915
- .action(async () => {
916
- try {
917
- const { shellInit } = await import('../dist/commands/shell-init.js');
918
- console.log(shellInit());
919
- } catch (err) {
920
- console.error(chalk.red(`Error: ${err.message}`));
921
- process.exit(1);
922
- }
923
- });
924
-
925
- // === TEMPLATE ===
926
- const template = program
927
- .command('template')
928
- .description('Manage templates');
929
-
930
- template
931
- .command('list')
932
- .description('List available templates')
933
- .option('-v, --vault <path>', 'Vault path')
934
- .action(async (options) => {
935
- try {
936
- const { listTemplates } = await import('../dist/commands/template.js');
937
- const templates = listTemplates({ vaultPath: options.vault });
938
- if (templates.length === 0) {
939
- console.log(chalk.yellow('No templates found.'));
940
- return;
941
- }
942
- console.log(chalk.cyan('\n📄 Templates:\n'));
943
- for (const name of templates) {
944
- console.log(`- ${name}`);
945
- }
946
- console.log();
947
- } catch (err) {
948
- console.error(chalk.red(`Error: ${err.message}`));
949
- process.exit(1);
950
- }
951
- });
952
-
953
- template
954
- .command('create <name>')
955
- .description('Create a file from a template')
956
- .option('-t, --title <title>', 'Document title')
957
- .option('-v, --vault <path>', 'Vault path')
958
- .action(async (name, options) => {
959
- try {
960
- const { createFromTemplate } = await import('../dist/commands/template.js');
961
- const result = createFromTemplate(name, {
962
- title: options.title,
963
- vaultPath: options.vault
964
- });
965
- console.log(chalk.green(`✓ Created from template: ${name}`));
966
- console.log(chalk.dim(` Output: ${result.outputPath}`));
967
- } catch (err) {
968
- console.error(chalk.red(`Error: ${err.message}`));
969
- process.exit(1);
970
- }
971
- });
972
-
973
- template
974
- .command('add <file>')
975
- .description('Add a custom template')
976
- .requiredOption('--name <name>', 'Template name')
977
- .option('-v, --vault <path>', 'Vault path')
978
- .action(async (file, options) => {
979
- try {
980
- const { addTemplate } = await import('../dist/commands/template.js');
981
- const result = addTemplate(file, {
982
- name: options.name,
983
- vaultPath: options.vault
984
- });
985
- console.log(chalk.green(`✓ Template added: ${result.name}`));
986
- console.log(chalk.dim(` Path: ${result.templatePath}`));
987
- } catch (err) {
988
- console.error(chalk.red(`Error: ${err.message}`));
989
- process.exit(1);
990
- }
991
- });
992
-
993
- // === DOCTOR (health check) ===
994
- program
995
- .command('doctor')
996
- .description('Check ClawVault setup health')
997
- .option('-v, --vault <path>', 'Vault path')
998
- .action(async (options) => {
999
- try {
1000
- const { doctor } = await import('../dist/commands/doctor.js');
1001
- const report = await doctor(options.vault);
1002
-
1003
- console.log(chalk.cyan('\n🩺 ClawVault Health Check\n'));
1004
- if (report.vaultPath) {
1005
- console.log(chalk.dim(`Vault: ${report.vaultPath}`));
1006
- console.log();
1007
- }
1008
-
1009
- for (const check of report.checks) {
1010
- const prefix = check.status === 'ok'
1011
- ? chalk.green('✓')
1012
- : check.status === 'warn'
1013
- ? chalk.yellow('⚠')
1014
- : chalk.red('✗');
1015
- const detail = check.detail ? ` — ${check.detail}` : '';
1016
- console.log(`${prefix} ${check.label}${detail}`);
1017
- if (check.hint) {
1018
- console.log(chalk.dim(` ${check.hint}`));
1019
- }
1020
- }
1021
-
1022
- const issues = report.warnings + report.errors;
1023
- console.log();
1024
- if (issues === 0) {
1025
- console.log(chalk.green('✅ ClawVault is healthy!\n'));
1026
- } else {
1027
- console.log(chalk.yellow(`⚠ ${issues} issue(s) found\n`));
1028
- }
1029
- } catch (err) {
1030
- console.error(chalk.red(`Error: ${err.message}`));
1031
- process.exit(1);
1032
- }
1033
- });
1034
-
1035
- // === ENTITIES ===
1036
- program
1037
- .command('entities')
1038
- .description('List all linkable entities in the vault')
1039
- .option('-v, --vault <path>', 'Vault path')
1040
- .option('--json', 'Output as JSON')
1041
- .action(async (options) => {
1042
- try {
1043
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1044
- if (!vaultPath) {
1045
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1046
- process.exit(1);
1047
- }
1048
-
1049
- const { entitiesCommand } = await import('../dist/commands/entities.js');
1050
- await entitiesCommand({ json: options.json });
1051
- } catch (err) {
1052
- console.error(chalk.red(`Error: ${err.message}`));
1053
- process.exit(1);
1054
- }
1055
- });
1056
-
1057
- // === LINK ===
1058
- program
1059
- .command('link [file]')
1060
- .description('Auto-link entity mentions in markdown files')
1061
- .option('--all', 'Link all files in vault')
1062
- .option('--backlinks <file>', 'Show backlinks to a file')
1063
- .option('--dry-run', 'Show what would be linked without changing files')
1064
- .option('--orphans', 'List broken wiki-links')
1065
- .option('--rebuild', 'Rebuild backlinks index')
1066
- .option('-v, --vault <path>', 'Vault path')
1067
- .action(async (file, options) => {
1068
- try {
1069
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1070
- if (!vaultPath) {
1071
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1072
- process.exit(1);
1073
- }
1074
-
1075
- const { linkCommand } = await import('../dist/commands/link.js');
1076
- await linkCommand(file, {
1077
- all: options.all,
1078
- dryRun: options.dryRun,
1079
- backlinks: options.backlinks,
1080
- orphans: options.orphans,
1081
- rebuild: options.rebuild
1082
- });
1083
- } catch (err) {
1084
- console.error(chalk.red(`Error: ${err.message}`));
1085
- process.exit(1);
1086
- }
1087
- });
1088
-
1089
- // === CHECKPOINT ===
1090
- program
1091
- .command('checkpoint')
1092
- .description('Quick state checkpoint for context death resilience')
1093
- .option('--working-on <text>', 'What you are currently working on')
1094
- .option('--focus <text>', 'Current focus area')
1095
- .option('--blocked <text>', 'What is blocking progress')
1096
- .option('--urgent', 'Trigger OpenClaw wake after checkpoint')
1097
- .option('-v, --vault <path>', 'Vault path')
1098
- .option('--json', 'Output as JSON')
1099
- .action(async (options) => {
1100
- try {
1101
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1102
- if (!vaultPath) {
1103
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1104
- process.exit(1);
1105
- }
1106
-
1107
- const { checkpoint } = await import('../dist/commands/checkpoint.js');
1108
- const data = await checkpoint({
1109
- vaultPath: path.resolve(vaultPath),
1110
- workingOn: options.workingOn,
1111
- focus: options.focus,
1112
- blocked: options.blocked,
1113
- urgent: options.urgent
1114
- });
1115
-
1116
- if (options.json) {
1117
- console.log(JSON.stringify(data, null, 2));
1118
- } else {
1119
- console.log(chalk.green('✓ Checkpoint saved'));
1120
- console.log(chalk.dim(` Timestamp: ${data.timestamp}`));
1121
- if (data.workingOn) console.log(chalk.dim(` Working on: ${data.workingOn}`));
1122
- if (data.focus) console.log(chalk.dim(` Focus: ${data.focus}`));
1123
- if (data.blocked) console.log(chalk.dim(` Blocked: ${data.blocked}`));
1124
- if (data.urgent) console.log(chalk.dim(' Urgent: yes'));
1125
- }
1126
- } catch (err) {
1127
- console.error(chalk.red(`Error: ${err.message}`));
1128
- process.exit(1);
1129
- }
1130
- });
1131
-
1132
- // === RECOVER ===
1133
- program
1134
- .command('recover')
1135
- .description('Check for context death and recover state')
1136
- .option('--clear', 'Clear the dirty death flag after recovery')
1137
- .option('--verbose', 'Show full checkpoint and handoff content')
1138
- .option('-v, --vault <path>', 'Vault path')
1139
- .option('--json', 'Output as JSON')
1140
- .action(async (options) => {
1141
- try {
1142
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1143
- if (!vaultPath) {
1144
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1145
- process.exit(1);
1146
- }
1147
-
1148
- const { recover, formatRecoveryInfo } = await import('../dist/commands/recover.js');
1149
- const info = await recover(path.resolve(vaultPath), {
1150
- clearFlag: options.clear,
1151
- verbose: options.verbose
1152
- });
1153
-
1154
- if (options.json) {
1155
- console.log(JSON.stringify(info, null, 2));
1156
- } else {
1157
- console.log(formatRecoveryInfo(info, { verbose: options.verbose }));
1158
- }
1159
- } catch (err) {
1160
- console.error(chalk.red(`Error: ${err.message}`));
1161
- process.exit(1);
1162
- }
1163
- });
1164
-
1165
- // === STATUS ===
1166
- program
1167
- .command('status')
1168
- .description('Show vault health and status')
1169
- .option('-v, --vault <path>', 'Vault path')
1170
- .option('--json', 'Output as JSON')
1171
- .action(async (options) => {
1172
- try {
1173
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1174
- if (!vaultPath) {
1175
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1176
- process.exit(1);
1177
- }
1178
-
1179
- const { statusCommand } = await import('../dist/commands/status.js');
1180
- await statusCommand(path.resolve(vaultPath), { json: options.json });
1181
- } catch (err) {
1182
- console.error(chalk.red(`Error: ${err.message}`));
1183
- process.exit(1);
1184
- }
1185
- });
1186
-
1187
- // === CLEAN-EXIT ===
1188
- program
1189
- .command('clean-exit')
1190
- .description('Mark session as cleanly exited (clears dirty death flag)')
1191
- .option('-v, --vault <path>', 'Vault path')
1192
- .action(async (options) => {
1193
- try {
1194
- const vaultPath = options.vault || process.env.CLAWVAULT_PATH;
1195
- if (!vaultPath) {
1196
- console.error(chalk.red('Error: No vault path. Set CLAWVAULT_PATH or use -v'));
1197
- process.exit(1);
1198
- }
1199
-
1200
- const { cleanExit } = await import('../dist/commands/checkpoint.js');
1201
- await cleanExit(path.resolve(vaultPath));
1202
- console.log(chalk.green('✓ Clean exit recorded'));
1203
- } catch (err) {
1204
- console.error(chalk.red(`Error: ${err.message}`));
1205
- process.exit(1);
1206
- }
1207
- });
1208
-
1209
- // === REPAIR-SESSION ===
1210
- program
1211
- .command('repair-session')
1212
- .description('Repair corrupted OpenClaw session transcripts')
1213
- .option('-s, --session <id>', 'Session ID (defaults to current main session)')
1214
- .option('-a, --agent <id>', 'Agent ID (defaults to configured agent)')
1215
- .option('--backup', 'Create backup before repair (default: true)', true)
1216
- .option('--no-backup', 'Skip backup creation')
1217
- .option('--dry-run', 'Show what would be repaired without writing')
1218
- .option('--list', 'List available sessions')
1219
- .option('--json', 'Output as JSON')
1220
- .action(async (options) => {
1221
- try {
1222
- const {
1223
- repairSessionCommand,
1224
- formatRepairResult,
1225
- listAgentSessions
1226
- } = await import('../dist/commands/repair-session.js');
1227
-
1228
- // List mode
1229
- if (options.list) {
1230
- console.log(listAgentSessions(options.agent));
1231
- return;
1232
- }
1233
-
1234
- const result = await repairSessionCommand({
1235
- sessionId: options.session,
1236
- agentId: options.agent,
1237
- backup: options.backup,
1238
- dryRun: options.dryRun
1239
- });
1240
-
1241
- if (options.json) {
1242
- console.log(JSON.stringify(result, null, 2));
1243
- } else {
1244
- console.log(formatRepairResult(result, { dryRun: options.dryRun }));
1245
- }
1246
-
1247
- // Exit with code 1 if corruption was found but not fixed (dry-run)
1248
- if (result.corruptedEntries.length > 0 && !result.repaired) {
1249
- process.exit(1);
1250
- }
1251
- } catch (err) {
1252
- console.error(chalk.red(`Error: ${err.message}`));
1253
- process.exit(1);
1254
- }
1255
- });
1256
-
1257
- // === DASHBOARD ===
1258
- program
1259
- .command('dashboard')
1260
- .description('Run local vault graph dashboard')
1261
- .option('-p, --port <port>', 'Dashboard port', '3377')
1262
- .option('-v, --vault <path>', 'Vault path')
1263
- .action(async (options) => {
1264
- try {
1265
- const parsedPort = Number.parseInt(options.port, 10);
1266
- if (Number.isNaN(parsedPort)) {
1267
- console.error(chalk.red(`Error: Invalid port: ${options.port}`));
1268
- process.exit(1);
1269
- }
1270
-
1271
- const vaultPath = options.vault
1272
- ? path.resolve(options.vault)
1273
- : resolveVaultPath(undefined);
47
+ registerCoreCommands(program, {
48
+ chalk,
49
+ path,
50
+ fs,
51
+ createVault,
52
+ getVault,
53
+ runQmd
54
+ });
55
+
56
+ registerQueryCommands(program, {
57
+ chalk,
58
+ getVault,
59
+ resolveVaultPath,
60
+ QmdUnavailableError,
61
+ printQmdMissing
62
+ });
1274
63
 
1275
- const { startDashboard } = await import('../dashboard/server.js');
1276
- await startDashboard({
1277
- port: parsedPort,
1278
- vaultPath
1279
- });
1280
- } catch (err) {
1281
- console.error(chalk.red(`Error: ${err.message}`));
1282
- process.exit(1);
1283
- }
1284
- });
64
+ registerSessionLifecycleCommands(program, {
65
+ chalk,
66
+ resolveVaultPath,
67
+ QmdUnavailableError,
68
+ printQmdMissing,
69
+ getVault,
70
+ runQmd
71
+ });
72
+
73
+ registerTemplateCommands(program, { chalk });
74
+ registerMaintenanceCommands(program, { chalk });
75
+ registerResilienceCommands(program, { chalk, resolveVaultPath });
76
+ registerVaultOperationsCommands(program, {
77
+ chalk,
78
+ fs,
79
+ getVault,
80
+ runQmd,
81
+ resolveVaultPath,
82
+ path
83
+ });
1285
84
 
1286
85
  // Parse and run
1287
86
  program.parse();