clawvault 2.5.2 → 2.5.4

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 (71) hide show
  1. package/README.md +159 -200
  2. package/bin/clawvault.js +111 -111
  3. package/bin/command-registration.test.js +166 -166
  4. package/bin/command-runtime.js +93 -93
  5. package/bin/command-runtime.test.js +154 -154
  6. package/bin/help-contract.test.js +39 -39
  7. package/bin/register-config-commands.js +153 -153
  8. package/bin/register-config-route-commands.test.js +121 -121
  9. package/bin/register-core-commands.js +237 -237
  10. package/bin/register-kanban-commands.js +56 -56
  11. package/bin/register-kanban-commands.test.js +83 -83
  12. package/bin/register-maintenance-commands.js +282 -282
  13. package/bin/register-project-commands.js +209 -209
  14. package/bin/register-project-commands.test.js +206 -206
  15. package/bin/register-query-commands.js +317 -317
  16. package/bin/register-query-commands.test.js +65 -65
  17. package/bin/register-resilience-commands.js +182 -182
  18. package/bin/register-resilience-commands.test.js +81 -81
  19. package/bin/register-route-commands.js +114 -114
  20. package/bin/register-session-lifecycle-commands.js +206 -206
  21. package/bin/register-tailscale-commands.js +106 -106
  22. package/bin/register-task-commands.js +348 -348
  23. package/bin/register-task-commands.test.js +69 -69
  24. package/bin/register-template-commands.js +72 -72
  25. package/bin/register-vault-operations-commands.js +300 -300
  26. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  27. package/dashboard/lib/graph-diff.js +104 -104
  28. package/dashboard/lib/graph-diff.test.js +75 -75
  29. package/dashboard/lib/vault-parser.js +556 -556
  30. package/dashboard/lib/vault-parser.test.js +254 -254
  31. package/dashboard/public/app.js +796 -796
  32. package/dashboard/public/index.html +52 -52
  33. package/dashboard/public/styles.css +221 -221
  34. package/dashboard/server.js +374 -374
  35. package/dist/{chunk-3FP5BJ42.js → chunk-4QYGFWRM.js} +1 -1
  36. package/dist/{chunk-M25QVSJM.js → chunk-AXKYDCNN.js} +1 -1
  37. package/dist/{chunk-CLE2HHNT.js → chunk-IVRIKYFE.js} +18 -11
  38. package/dist/{chunk-HRTPQQF2.js → chunk-IZEY5S74.js} +1 -1
  39. package/dist/{chunk-HWUNREDJ.js → chunk-JDLOL2PL.js} +4 -4
  40. package/dist/{chunk-AY4PGUVL.js → chunk-KL4NAOMO.js} +1 -1
  41. package/dist/{chunk-O7XHXF7F.js → chunk-MAKNAHAW.js} +4 -4
  42. package/dist/{chunk-PLZKZW4I.js → chunk-OSMS7QIG.js} +1 -1
  43. package/dist/{chunk-NZ4ZZNSR.js → chunk-THRJVD4L.js} +1 -1
  44. package/dist/{chunk-4GBPTBFJ.js → chunk-TIGW564L.js} +1 -1
  45. package/dist/{chunk-BHO7WSAY.js → chunk-W2HNZC22.js} +3 -3
  46. package/dist/{chunk-GFJ3LIIB.js → chunk-XAVB4GB4.js} +1 -1
  47. package/dist/cli/index.js +10 -10
  48. package/dist/commands/context.js +3 -3
  49. package/dist/commands/doctor.js +4 -4
  50. package/dist/commands/embed.js +2 -2
  51. package/dist/commands/observe.js +2 -2
  52. package/dist/commands/setup.js +2 -2
  53. package/dist/commands/sleep.js +2 -2
  54. package/dist/commands/status.js +3 -3
  55. package/dist/commands/tailscale.js +3 -3
  56. package/dist/commands/wake.js +2 -2
  57. package/dist/index.js +12 -12
  58. package/dist/lib/tailscale.js +2 -2
  59. package/dist/lib/webdav.js +1 -1
  60. package/hooks/clawvault/HOOK.md +83 -74
  61. package/hooks/clawvault/handler.js +816 -816
  62. package/hooks/clawvault/handler.test.js +263 -263
  63. package/package.json +94 -125
  64. package/templates/checkpoint.md +19 -19
  65. package/templates/daily-note.md +19 -19
  66. package/templates/daily.md +19 -19
  67. package/templates/decision.md +17 -17
  68. package/templates/handoff.md +19 -19
  69. package/templates/lesson.md +16 -16
  70. package/templates/person.md +19 -19
  71. package/templates/project.md +23 -23
@@ -1,300 +1,300 @@
1
- /**
2
- * Vault operation command registrations (browse/sync/reindex/remember/shell-init/dashboard).
3
- */
4
-
5
- import { validatePathWithinBase } from './command-runtime.js';
6
-
7
- export function registerVaultOperationsCommands(
8
- program,
9
- {
10
- chalk,
11
- fs,
12
- getVault,
13
- runQmd,
14
- resolveVaultPath,
15
- path
16
- }
17
- ) {
18
- // === LIST ===
19
- program
20
- .command('list [category]')
21
- .description('List vault documents (optionally filtered by category)')
22
- .option('-v, --vault <path>', 'Vault path')
23
- .option('--json', 'Output as JSON')
24
- .action(async (category, options) => {
25
- try {
26
- const vault = await getVault(options.vault);
27
- const docs = await vault.list(category);
28
-
29
- if (options.json) {
30
- console.log(JSON.stringify(docs.map((doc) => ({
31
- id: doc.id,
32
- title: doc.title,
33
- category: doc.category,
34
- tags: doc.tags,
35
- modified: doc.modified
36
- })), null, 2));
37
- return;
38
- }
39
-
40
- if (docs.length === 0) {
41
- console.log(chalk.yellow('No documents found.'));
42
- return;
43
- }
44
-
45
- console.log(chalk.cyan(`\n📚 ${docs.length} document(s)${category ? ` in ${category}` : ''}:\n`));
46
-
47
- const grouped = {};
48
- for (const doc of docs) {
49
- grouped[doc.category] = grouped[doc.category] || [];
50
- grouped[doc.category].push(doc);
51
- }
52
-
53
- for (const [cat, catDocs] of Object.entries(grouped)) {
54
- console.log(chalk.yellow(`${cat}/`));
55
- for (const doc of catDocs) {
56
- console.log(chalk.dim(` - ${doc.title}`));
57
- }
58
- }
59
- console.log();
60
- } catch (err) {
61
- console.error(chalk.red(`Error: ${err.message}`));
62
- process.exit(1);
63
- }
64
- });
65
-
66
- // === GET ===
67
- program
68
- .command('get <id>')
69
- .description('Get a document by ID')
70
- .option('-v, --vault <path>', 'Vault path')
71
- .option('--json', 'Output as JSON')
72
- .action(async (id, options) => {
73
- try {
74
- const vault = await getVault(options.vault);
75
- const doc = await vault.get(id);
76
-
77
- if (!doc) {
78
- console.error(chalk.red(`Document not found: ${id}`));
79
- process.exit(1);
80
- }
81
-
82
- if (options.json) {
83
- console.log(JSON.stringify(doc, null, 2));
84
- return;
85
- }
86
-
87
- console.log(chalk.cyan(`\n📄 ${doc.title}\n`));
88
- console.log(chalk.dim(`Category: ${doc.category}`));
89
- console.log(chalk.dim(`Path: ${doc.path}`));
90
- console.log(chalk.dim(`Tags: ${doc.tags.join(', ') || 'none'}`));
91
- console.log(chalk.dim(`Links: ${doc.links.join(', ') || 'none'}`));
92
- console.log(chalk.dim(`Modified: ${doc.modified.toISOString()}`));
93
- console.log(chalk.dim('---'));
94
- console.log(doc.content);
95
- } catch (err) {
96
- console.error(chalk.red(`Error: ${err.message}`));
97
- process.exit(1);
98
- }
99
- });
100
-
101
- // === STATS ===
102
- program
103
- .command('stats')
104
- .description('Show vault statistics')
105
- .option('-v, --vault <path>', 'Vault path')
106
- .option('--json', 'Output as JSON')
107
- .action(async (options) => {
108
- try {
109
- const vault = await getVault(options.vault);
110
- const stats = await vault.stats();
111
-
112
- if (options.json) {
113
- console.log(JSON.stringify(stats, null, 2));
114
- return;
115
- }
116
-
117
- console.log(chalk.cyan(`\n🐘 ${vault.getName()} Stats\n`));
118
- console.log(chalk.dim(`Path: ${vault.getPath()}`));
119
- console.log(`Documents: ${chalk.green(stats.documents)}`);
120
- console.log(`Links: ${chalk.blue(stats.links)}`);
121
- console.log(`Tags: ${chalk.yellow(stats.tags.length)}`);
122
- console.log();
123
- console.log(chalk.dim('By category:'));
124
- for (const [cat, count] of Object.entries(stats.categories)) {
125
- console.log(chalk.dim(` ${cat}: ${count}`));
126
- }
127
- console.log();
128
- } catch (err) {
129
- console.error(chalk.red(`Error: ${err.message}`));
130
- process.exit(1);
131
- }
132
- });
133
-
134
- // === SYNC ===
135
- program
136
- .command('sync <target>')
137
- .description('Sync vault files to a target path')
138
- .option('--delete', 'Delete orphan files in target')
139
- .option('--dry-run', 'Show what would be synced without syncing')
140
- .option('-v, --vault <path>', 'Vault path')
141
- .action(async (target, options) => {
142
- try {
143
- const vault = await getVault(options.vault);
144
- console.log(chalk.cyan(`\n🔄 Syncing to ${target}...\n`));
145
-
146
- const result = await vault.sync({
147
- target,
148
- deleteOrphans: options.delete,
149
- dryRun: options.dryRun
150
- });
151
-
152
- if (options.dryRun) {
153
- console.log(chalk.yellow('DRY RUN - no changes made\n'));
154
- }
155
-
156
- if (result.copied.length > 0) {
157
- console.log(chalk.green(`Copied: ${result.copied.length} files`));
158
- for (const filePath of result.copied.slice(0, 5)) {
159
- console.log(chalk.dim(` + ${filePath}`));
160
- }
161
- if (result.copied.length > 5) {
162
- console.log(chalk.dim(` ... and ${result.copied.length - 5} more`));
163
- }
164
- }
165
-
166
- if (result.deleted.length > 0) {
167
- console.log(chalk.red(`Deleted: ${result.deleted.length} files`));
168
- }
169
-
170
- if (result.unchanged.length > 0) {
171
- console.log(chalk.dim(`Unchanged: ${result.unchanged.length} files`));
172
- }
173
-
174
- if (result.errors.length > 0) {
175
- console.log(chalk.red('\nErrors:'));
176
- for (const error of result.errors) {
177
- console.log(chalk.red(` ${error}`));
178
- }
179
- }
180
-
181
- console.log();
182
- } catch (err) {
183
- console.error(chalk.red(`Error: ${err.message}`));
184
- process.exit(1);
185
- }
186
- });
187
-
188
- // === REINDEX ===
189
- program
190
- .command('reindex')
191
- .description('Rebuild the search index')
192
- .option('-v, --vault <path>', 'Vault path')
193
- .option('--qmd', 'Also update qmd embeddings')
194
- .action(async (options) => {
195
- try {
196
- const vault = await getVault(options.vault);
197
- console.log(chalk.cyan('\n🔄 Reindexing...\n'));
198
-
199
- const count = await vault.reindex();
200
- console.log(chalk.green(`✓ Indexed ${count} documents`));
201
-
202
- if (options.qmd) {
203
- console.log(chalk.cyan('Updating qmd embeddings...'));
204
- const collection = vault.getQmdCollection();
205
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
206
- console.log(chalk.green('✓ qmd updated'));
207
- }
208
-
209
- console.log();
210
- } catch (err) {
211
- console.error(chalk.red(`Error: ${err.message}`));
212
- process.exit(1);
213
- }
214
- });
215
-
216
- // === REMEMBER ===
217
- program
218
- .command('remember <type> <title>')
219
- .description('Store a typed memory (fact|feeling|decision|lesson|commitment|preference|relationship|project)')
220
- .option('--content <content>', 'Content body')
221
- .option('-f, --file <file>', 'Read content from file (validated against current working directory)')
222
- .option('--stdin', 'Read content from stdin')
223
- .option('-v, --vault <path>', 'Vault path')
224
- .option('--no-index', 'Skip qmd index update (auto-updates by default)')
225
- .action(async (type, title, options) => {
226
- const validTypes = ['fact', 'feeling', 'decision', 'lesson', 'commitment', 'preference', 'relationship', 'project'];
227
- if (!validTypes.includes(type)) {
228
- console.error(chalk.red(`Invalid type: ${type}`));
229
- console.error(chalk.dim(`Valid types: ${validTypes.join(', ')}`));
230
- process.exit(1);
231
- }
232
-
233
- try {
234
- const vault = await getVault(options.vault);
235
- let content = options.content || '';
236
- if (options.file) {
237
- // Validate file path is within current working directory to prevent path traversal
238
- const cwd = process.cwd();
239
- const resolvedFilePath = validatePathWithinBase(options.file, cwd);
240
- content = fs.readFileSync(resolvedFilePath, 'utf-8');
241
- } else if (options.stdin) {
242
- content = fs.readFileSync(0, 'utf-8');
243
- }
244
-
245
- const doc = await vault.remember(type, title, content);
246
- console.log(chalk.green(`✓ Remembered (${type}): ${doc.id}`));
247
-
248
- if (options.index !== false) {
249
- const collection = vault.getQmdCollection();
250
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
251
- }
252
- } catch (err) {
253
- console.error(chalk.red(`Error: ${err.message}`));
254
- process.exit(1);
255
- }
256
- });
257
-
258
- // === SHELL INIT ===
259
- program
260
- .command('shell-init')
261
- .description('Output shell integration for ClawVault')
262
- .action(async () => {
263
- try {
264
- const { shellInit } = await import('../dist/commands/shell-init.js');
265
- console.log(shellInit());
266
- } catch (err) {
267
- console.error(chalk.red(`Error: ${err.message}`));
268
- process.exit(1);
269
- }
270
- });
271
-
272
- // === DASHBOARD ===
273
- program
274
- .command('dashboard')
275
- .description('Run the local vault graph dashboard server')
276
- .option('-p, --port <port>', 'Dashboard port (default: 3377)', '3377')
277
- .option('-v, --vault <path>', 'Vault path')
278
- .action(async (options) => {
279
- try {
280
- const parsedPort = Number.parseInt(options.port, 10);
281
- if (Number.isNaN(parsedPort)) {
282
- console.error(chalk.red(`Error: Invalid port: ${options.port}`));
283
- process.exit(1);
284
- }
285
-
286
- const vaultPath = options.vault
287
- ? path.resolve(options.vault)
288
- : resolveVaultPath(undefined);
289
-
290
- const { startDashboard } = await import('../dashboard/server.js');
291
- await startDashboard({
292
- port: parsedPort,
293
- vaultPath
294
- });
295
- } catch (err) {
296
- console.error(chalk.red(`Error: ${err.message}`));
297
- process.exit(1);
298
- }
299
- });
300
- }
1
+ /**
2
+ * Vault operation command registrations (browse/sync/reindex/remember/shell-init/dashboard).
3
+ */
4
+
5
+ import { validatePathWithinBase } from './command-runtime.js';
6
+
7
+ export function registerVaultOperationsCommands(
8
+ program,
9
+ {
10
+ chalk,
11
+ fs,
12
+ getVault,
13
+ runQmd,
14
+ resolveVaultPath,
15
+ path
16
+ }
17
+ ) {
18
+ // === LIST ===
19
+ program
20
+ .command('list [category]')
21
+ .description('List vault documents (optionally filtered by category)')
22
+ .option('-v, --vault <path>', 'Vault path')
23
+ .option('--json', 'Output as JSON')
24
+ .action(async (category, options) => {
25
+ try {
26
+ const vault = await getVault(options.vault);
27
+ const docs = await vault.list(category);
28
+
29
+ if (options.json) {
30
+ console.log(JSON.stringify(docs.map((doc) => ({
31
+ id: doc.id,
32
+ title: doc.title,
33
+ category: doc.category,
34
+ tags: doc.tags,
35
+ modified: doc.modified
36
+ })), null, 2));
37
+ return;
38
+ }
39
+
40
+ if (docs.length === 0) {
41
+ console.log(chalk.yellow('No documents found.'));
42
+ return;
43
+ }
44
+
45
+ console.log(chalk.cyan(`\n📚 ${docs.length} document(s)${category ? ` in ${category}` : ''}:\n`));
46
+
47
+ const grouped = {};
48
+ for (const doc of docs) {
49
+ grouped[doc.category] = grouped[doc.category] || [];
50
+ grouped[doc.category].push(doc);
51
+ }
52
+
53
+ for (const [cat, catDocs] of Object.entries(grouped)) {
54
+ console.log(chalk.yellow(`${cat}/`));
55
+ for (const doc of catDocs) {
56
+ console.log(chalk.dim(` - ${doc.title}`));
57
+ }
58
+ }
59
+ console.log();
60
+ } catch (err) {
61
+ console.error(chalk.red(`Error: ${err.message}`));
62
+ process.exit(1);
63
+ }
64
+ });
65
+
66
+ // === GET ===
67
+ program
68
+ .command('get <id>')
69
+ .description('Get a document by ID')
70
+ .option('-v, --vault <path>', 'Vault path')
71
+ .option('--json', 'Output as JSON')
72
+ .action(async (id, options) => {
73
+ try {
74
+ const vault = await getVault(options.vault);
75
+ const doc = await vault.get(id);
76
+
77
+ if (!doc) {
78
+ console.error(chalk.red(`Document not found: ${id}`));
79
+ process.exit(1);
80
+ }
81
+
82
+ if (options.json) {
83
+ console.log(JSON.stringify(doc, null, 2));
84
+ return;
85
+ }
86
+
87
+ console.log(chalk.cyan(`\n📄 ${doc.title}\n`));
88
+ console.log(chalk.dim(`Category: ${doc.category}`));
89
+ console.log(chalk.dim(`Path: ${doc.path}`));
90
+ console.log(chalk.dim(`Tags: ${doc.tags.join(', ') || 'none'}`));
91
+ console.log(chalk.dim(`Links: ${doc.links.join(', ') || 'none'}`));
92
+ console.log(chalk.dim(`Modified: ${doc.modified.toISOString()}`));
93
+ console.log(chalk.dim('---'));
94
+ console.log(doc.content);
95
+ } catch (err) {
96
+ console.error(chalk.red(`Error: ${err.message}`));
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ // === STATS ===
102
+ program
103
+ .command('stats')
104
+ .description('Show vault statistics')
105
+ .option('-v, --vault <path>', 'Vault path')
106
+ .option('--json', 'Output as JSON')
107
+ .action(async (options) => {
108
+ try {
109
+ const vault = await getVault(options.vault);
110
+ const stats = await vault.stats();
111
+
112
+ if (options.json) {
113
+ console.log(JSON.stringify(stats, null, 2));
114
+ return;
115
+ }
116
+
117
+ console.log(chalk.cyan(`\n🐘 ${vault.getName()} Stats\n`));
118
+ console.log(chalk.dim(`Path: ${vault.getPath()}`));
119
+ console.log(`Documents: ${chalk.green(stats.documents)}`);
120
+ console.log(`Links: ${chalk.blue(stats.links)}`);
121
+ console.log(`Tags: ${chalk.yellow(stats.tags.length)}`);
122
+ console.log();
123
+ console.log(chalk.dim('By category:'));
124
+ for (const [cat, count] of Object.entries(stats.categories)) {
125
+ console.log(chalk.dim(` ${cat}: ${count}`));
126
+ }
127
+ console.log();
128
+ } catch (err) {
129
+ console.error(chalk.red(`Error: ${err.message}`));
130
+ process.exit(1);
131
+ }
132
+ });
133
+
134
+ // === SYNC ===
135
+ program
136
+ .command('sync <target>')
137
+ .description('Sync vault files to a target path')
138
+ .option('--delete', 'Delete orphan files in target')
139
+ .option('--dry-run', 'Show what would be synced without syncing')
140
+ .option('-v, --vault <path>', 'Vault path')
141
+ .action(async (target, options) => {
142
+ try {
143
+ const vault = await getVault(options.vault);
144
+ console.log(chalk.cyan(`\n🔄 Syncing to ${target}...\n`));
145
+
146
+ const result = await vault.sync({
147
+ target,
148
+ deleteOrphans: options.delete,
149
+ dryRun: options.dryRun
150
+ });
151
+
152
+ if (options.dryRun) {
153
+ console.log(chalk.yellow('DRY RUN - no changes made\n'));
154
+ }
155
+
156
+ if (result.copied.length > 0) {
157
+ console.log(chalk.green(`Copied: ${result.copied.length} files`));
158
+ for (const filePath of result.copied.slice(0, 5)) {
159
+ console.log(chalk.dim(` + ${filePath}`));
160
+ }
161
+ if (result.copied.length > 5) {
162
+ console.log(chalk.dim(` ... and ${result.copied.length - 5} more`));
163
+ }
164
+ }
165
+
166
+ if (result.deleted.length > 0) {
167
+ console.log(chalk.red(`Deleted: ${result.deleted.length} files`));
168
+ }
169
+
170
+ if (result.unchanged.length > 0) {
171
+ console.log(chalk.dim(`Unchanged: ${result.unchanged.length} files`));
172
+ }
173
+
174
+ if (result.errors.length > 0) {
175
+ console.log(chalk.red('\nErrors:'));
176
+ for (const error of result.errors) {
177
+ console.log(chalk.red(` ${error}`));
178
+ }
179
+ }
180
+
181
+ console.log();
182
+ } catch (err) {
183
+ console.error(chalk.red(`Error: ${err.message}`));
184
+ process.exit(1);
185
+ }
186
+ });
187
+
188
+ // === REINDEX ===
189
+ program
190
+ .command('reindex')
191
+ .description('Rebuild the search index')
192
+ .option('-v, --vault <path>', 'Vault path')
193
+ .option('--qmd', 'Also update qmd embeddings')
194
+ .action(async (options) => {
195
+ try {
196
+ const vault = await getVault(options.vault);
197
+ console.log(chalk.cyan('\n🔄 Reindexing...\n'));
198
+
199
+ const count = await vault.reindex();
200
+ console.log(chalk.green(`✓ Indexed ${count} documents`));
201
+
202
+ if (options.qmd) {
203
+ console.log(chalk.cyan('Updating qmd embeddings...'));
204
+ const collection = vault.getQmdCollection();
205
+ await runQmd(collection ? ['update', '-c', collection] : ['update']);
206
+ console.log(chalk.green('✓ qmd updated'));
207
+ }
208
+
209
+ console.log();
210
+ } catch (err) {
211
+ console.error(chalk.red(`Error: ${err.message}`));
212
+ process.exit(1);
213
+ }
214
+ });
215
+
216
+ // === REMEMBER ===
217
+ program
218
+ .command('remember <type> <title>')
219
+ .description('Store a typed memory (fact|feeling|decision|lesson|commitment|preference|relationship|project)')
220
+ .option('--content <content>', 'Content body')
221
+ .option('-f, --file <file>', 'Read content from file (validated against current working directory)')
222
+ .option('--stdin', 'Read content from stdin')
223
+ .option('-v, --vault <path>', 'Vault path')
224
+ .option('--no-index', 'Skip qmd index update (auto-updates by default)')
225
+ .action(async (type, title, options) => {
226
+ const validTypes = ['fact', 'feeling', 'decision', 'lesson', 'commitment', 'preference', 'relationship', 'project'];
227
+ if (!validTypes.includes(type)) {
228
+ console.error(chalk.red(`Invalid type: ${type}`));
229
+ console.error(chalk.dim(`Valid types: ${validTypes.join(', ')}`));
230
+ process.exit(1);
231
+ }
232
+
233
+ try {
234
+ const vault = await getVault(options.vault);
235
+ let content = options.content || '';
236
+ if (options.file) {
237
+ // Validate file path is within current working directory to prevent path traversal
238
+ const cwd = process.cwd();
239
+ const resolvedFilePath = validatePathWithinBase(options.file, cwd);
240
+ content = fs.readFileSync(resolvedFilePath, 'utf-8');
241
+ } else if (options.stdin) {
242
+ content = fs.readFileSync(0, 'utf-8');
243
+ }
244
+
245
+ const doc = await vault.remember(type, title, content);
246
+ console.log(chalk.green(`✓ Remembered (${type}): ${doc.id}`));
247
+
248
+ if (options.index !== false) {
249
+ const collection = vault.getQmdCollection();
250
+ await runQmd(collection ? ['update', '-c', collection] : ['update']);
251
+ }
252
+ } catch (err) {
253
+ console.error(chalk.red(`Error: ${err.message}`));
254
+ process.exit(1);
255
+ }
256
+ });
257
+
258
+ // === SHELL INIT ===
259
+ program
260
+ .command('shell-init')
261
+ .description('Output shell integration for ClawVault')
262
+ .action(async () => {
263
+ try {
264
+ const { shellInit } = await import('../dist/commands/shell-init.js');
265
+ console.log(shellInit());
266
+ } catch (err) {
267
+ console.error(chalk.red(`Error: ${err.message}`));
268
+ process.exit(1);
269
+ }
270
+ });
271
+
272
+ // === DASHBOARD ===
273
+ program
274
+ .command('dashboard')
275
+ .description('Run the local vault graph dashboard server')
276
+ .option('-p, --port <port>', 'Dashboard port (default: 3377)', '3377')
277
+ .option('-v, --vault <path>', 'Vault path')
278
+ .action(async (options) => {
279
+ try {
280
+ const parsedPort = Number.parseInt(options.port, 10);
281
+ if (Number.isNaN(parsedPort)) {
282
+ console.error(chalk.red(`Error: Invalid port: ${options.port}`));
283
+ process.exit(1);
284
+ }
285
+
286
+ const vaultPath = options.vault
287
+ ? path.resolve(options.vault)
288
+ : resolveVaultPath(undefined);
289
+
290
+ const { startDashboard } = await import('../dashboard/server.js');
291
+ await startDashboard({
292
+ port: parsedPort,
293
+ vaultPath
294
+ });
295
+ } catch (err) {
296
+ console.error(chalk.red(`Error: ${err.message}`));
297
+ process.exit(1);
298
+ }
299
+ });
300
+ }