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.
- package/README.md +159 -200
- package/bin/clawvault.js +111 -111
- package/bin/command-registration.test.js +166 -166
- package/bin/command-runtime.js +93 -93
- package/bin/command-runtime.test.js +154 -154
- package/bin/help-contract.test.js +39 -39
- package/bin/register-config-commands.js +153 -153
- package/bin/register-config-route-commands.test.js +121 -121
- package/bin/register-core-commands.js +237 -237
- package/bin/register-kanban-commands.js +56 -56
- package/bin/register-kanban-commands.test.js +83 -83
- package/bin/register-maintenance-commands.js +282 -282
- package/bin/register-project-commands.js +209 -209
- package/bin/register-project-commands.test.js +206 -206
- package/bin/register-query-commands.js +317 -317
- package/bin/register-query-commands.test.js +65 -65
- package/bin/register-resilience-commands.js +182 -182
- package/bin/register-resilience-commands.test.js +81 -81
- package/bin/register-route-commands.js +114 -114
- package/bin/register-session-lifecycle-commands.js +206 -206
- package/bin/register-tailscale-commands.js +106 -106
- package/bin/register-task-commands.js +348 -348
- package/bin/register-task-commands.test.js +69 -69
- package/bin/register-template-commands.js +72 -72
- package/bin/register-vault-operations-commands.js +300 -300
- package/bin/test-helpers/cli-command-fixtures.js +119 -119
- package/dashboard/lib/graph-diff.js +104 -104
- package/dashboard/lib/graph-diff.test.js +75 -75
- package/dashboard/lib/vault-parser.js +556 -556
- package/dashboard/lib/vault-parser.test.js +254 -254
- package/dashboard/public/app.js +796 -796
- package/dashboard/public/index.html +52 -52
- package/dashboard/public/styles.css +221 -221
- package/dashboard/server.js +374 -374
- package/dist/{chunk-3FP5BJ42.js → chunk-4QYGFWRM.js} +1 -1
- package/dist/{chunk-M25QVSJM.js → chunk-AXKYDCNN.js} +1 -1
- package/dist/{chunk-CLE2HHNT.js → chunk-IVRIKYFE.js} +18 -11
- package/dist/{chunk-HRTPQQF2.js → chunk-IZEY5S74.js} +1 -1
- package/dist/{chunk-HWUNREDJ.js → chunk-JDLOL2PL.js} +4 -4
- package/dist/{chunk-AY4PGUVL.js → chunk-KL4NAOMO.js} +1 -1
- package/dist/{chunk-O7XHXF7F.js → chunk-MAKNAHAW.js} +4 -4
- package/dist/{chunk-PLZKZW4I.js → chunk-OSMS7QIG.js} +1 -1
- package/dist/{chunk-NZ4ZZNSR.js → chunk-THRJVD4L.js} +1 -1
- package/dist/{chunk-4GBPTBFJ.js → chunk-TIGW564L.js} +1 -1
- package/dist/{chunk-BHO7WSAY.js → chunk-W2HNZC22.js} +3 -3
- package/dist/{chunk-GFJ3LIIB.js → chunk-XAVB4GB4.js} +1 -1
- package/dist/cli/index.js +10 -10
- package/dist/commands/context.js +3 -3
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/embed.js +2 -2
- package/dist/commands/observe.js +2 -2
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.js +2 -2
- package/dist/commands/status.js +3 -3
- package/dist/commands/tailscale.js +3 -3
- package/dist/commands/wake.js +2 -2
- package/dist/index.js +12 -12
- package/dist/lib/tailscale.js +2 -2
- package/dist/lib/webdav.js +1 -1
- package/hooks/clawvault/HOOK.md +83 -74
- package/hooks/clawvault/handler.js +816 -816
- package/hooks/clawvault/handler.test.js +263 -263
- package/package.json +94 -125
- package/templates/checkpoint.md +19 -19
- package/templates/daily-note.md +19 -19
- package/templates/daily.md +19 -19
- package/templates/decision.md +17 -17
- package/templates/handoff.md +19 -19
- package/templates/lesson.md +16 -16
- package/templates/person.md +19 -19
- 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
|
+
}
|