clawvault 1.11.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -0,0 +1,295 @@
1
+ /**
2
+ * Vault operation command registrations (browse/sync/reindex/remember/shell-init/dashboard).
3
+ */
4
+
5
+ export function registerVaultOperationsCommands(
6
+ program,
7
+ {
8
+ chalk,
9
+ fs,
10
+ getVault,
11
+ runQmd,
12
+ resolveVaultPath,
13
+ path
14
+ }
15
+ ) {
16
+ // === LIST ===
17
+ program
18
+ .command('list [category]')
19
+ .description('List documents')
20
+ .option('-v, --vault <path>', 'Vault path')
21
+ .option('--json', 'Output as JSON')
22
+ .action(async (category, options) => {
23
+ try {
24
+ const vault = await getVault(options.vault);
25
+ const docs = await vault.list(category);
26
+
27
+ if (options.json) {
28
+ console.log(JSON.stringify(docs.map((doc) => ({
29
+ id: doc.id,
30
+ title: doc.title,
31
+ category: doc.category,
32
+ tags: doc.tags,
33
+ modified: doc.modified
34
+ })), null, 2));
35
+ return;
36
+ }
37
+
38
+ if (docs.length === 0) {
39
+ console.log(chalk.yellow('No documents found.'));
40
+ return;
41
+ }
42
+
43
+ console.log(chalk.cyan(`\n📚 ${docs.length} document(s)${category ? ` in ${category}` : ''}:\n`));
44
+
45
+ const grouped = {};
46
+ for (const doc of docs) {
47
+ grouped[doc.category] = grouped[doc.category] || [];
48
+ grouped[doc.category].push(doc);
49
+ }
50
+
51
+ for (const [cat, catDocs] of Object.entries(grouped)) {
52
+ console.log(chalk.yellow(`${cat}/`));
53
+ for (const doc of catDocs) {
54
+ console.log(chalk.dim(` - ${doc.title}`));
55
+ }
56
+ }
57
+ console.log();
58
+ } catch (err) {
59
+ console.error(chalk.red(`Error: ${err.message}`));
60
+ process.exit(1);
61
+ }
62
+ });
63
+
64
+ // === GET ===
65
+ program
66
+ .command('get <id>')
67
+ .description('Get a document by ID')
68
+ .option('-v, --vault <path>', 'Vault path')
69
+ .option('--json', 'Output as JSON')
70
+ .action(async (id, options) => {
71
+ try {
72
+ const vault = await getVault(options.vault);
73
+ const doc = await vault.get(id);
74
+
75
+ if (!doc) {
76
+ console.error(chalk.red(`Document not found: ${id}`));
77
+ process.exit(1);
78
+ }
79
+
80
+ if (options.json) {
81
+ console.log(JSON.stringify(doc, null, 2));
82
+ return;
83
+ }
84
+
85
+ console.log(chalk.cyan(`\n📄 ${doc.title}\n`));
86
+ console.log(chalk.dim(`Category: ${doc.category}`));
87
+ console.log(chalk.dim(`Path: ${doc.path}`));
88
+ console.log(chalk.dim(`Tags: ${doc.tags.join(', ') || 'none'}`));
89
+ console.log(chalk.dim(`Links: ${doc.links.join(', ') || 'none'}`));
90
+ console.log(chalk.dim(`Modified: ${doc.modified.toISOString()}`));
91
+ console.log(chalk.dim('---'));
92
+ console.log(doc.content);
93
+ } catch (err) {
94
+ console.error(chalk.red(`Error: ${err.message}`));
95
+ process.exit(1);
96
+ }
97
+ });
98
+
99
+ // === STATS ===
100
+ program
101
+ .command('stats')
102
+ .description('Show vault statistics')
103
+ .option('-v, --vault <path>', 'Vault path')
104
+ .option('--json', 'Output as JSON')
105
+ .action(async (options) => {
106
+ try {
107
+ const vault = await getVault(options.vault);
108
+ const stats = await vault.stats();
109
+
110
+ if (options.json) {
111
+ console.log(JSON.stringify(stats, null, 2));
112
+ return;
113
+ }
114
+
115
+ console.log(chalk.cyan(`\n🐘 ${vault.getName()} Stats\n`));
116
+ console.log(chalk.dim(`Path: ${vault.getPath()}`));
117
+ console.log(`Documents: ${chalk.green(stats.documents)}`);
118
+ console.log(`Links: ${chalk.blue(stats.links)}`);
119
+ console.log(`Tags: ${chalk.yellow(stats.tags.length)}`);
120
+ console.log();
121
+ console.log(chalk.dim('By category:'));
122
+ for (const [cat, count] of Object.entries(stats.categories)) {
123
+ console.log(chalk.dim(` ${cat}: ${count}`));
124
+ }
125
+ console.log();
126
+ } catch (err) {
127
+ console.error(chalk.red(`Error: ${err.message}`));
128
+ process.exit(1);
129
+ }
130
+ });
131
+
132
+ // === SYNC ===
133
+ program
134
+ .command('sync <target>')
135
+ .description('Sync vault files to target path')
136
+ .option('--delete', 'Delete orphan files in target')
137
+ .option('--dry-run', 'Show what would be synced without syncing')
138
+ .option('-v, --vault <path>', 'Vault path')
139
+ .action(async (target, options) => {
140
+ try {
141
+ const vault = await getVault(options.vault);
142
+ console.log(chalk.cyan(`\n🔄 Syncing to ${target}...\n`));
143
+
144
+ const result = await vault.sync({
145
+ target,
146
+ deleteOrphans: options.delete,
147
+ dryRun: options.dryRun
148
+ });
149
+
150
+ if (options.dryRun) {
151
+ console.log(chalk.yellow('DRY RUN - no changes made\n'));
152
+ }
153
+
154
+ if (result.copied.length > 0) {
155
+ console.log(chalk.green(`Copied: ${result.copied.length} files`));
156
+ for (const filePath of result.copied.slice(0, 5)) {
157
+ console.log(chalk.dim(` + ${filePath}`));
158
+ }
159
+ if (result.copied.length > 5) {
160
+ console.log(chalk.dim(` ... and ${result.copied.length - 5} more`));
161
+ }
162
+ }
163
+
164
+ if (result.deleted.length > 0) {
165
+ console.log(chalk.red(`Deleted: ${result.deleted.length} files`));
166
+ }
167
+
168
+ if (result.unchanged.length > 0) {
169
+ console.log(chalk.dim(`Unchanged: ${result.unchanged.length} files`));
170
+ }
171
+
172
+ if (result.errors.length > 0) {
173
+ console.log(chalk.red('\nErrors:'));
174
+ for (const error of result.errors) {
175
+ console.log(chalk.red(` ${error}`));
176
+ }
177
+ }
178
+
179
+ console.log();
180
+ } catch (err) {
181
+ console.error(chalk.red(`Error: ${err.message}`));
182
+ process.exit(1);
183
+ }
184
+ });
185
+
186
+ // === REINDEX ===
187
+ program
188
+ .command('reindex')
189
+ .description('Rebuild the search index')
190
+ .option('-v, --vault <path>', 'Vault path')
191
+ .option('--qmd', 'Also update qmd embeddings')
192
+ .action(async (options) => {
193
+ try {
194
+ const vault = await getVault(options.vault);
195
+ console.log(chalk.cyan('\n🔄 Reindexing...\n'));
196
+
197
+ const count = await vault.reindex();
198
+ console.log(chalk.green(`✓ Indexed ${count} documents`));
199
+
200
+ if (options.qmd) {
201
+ console.log(chalk.cyan('Updating qmd embeddings...'));
202
+ const collection = vault.getQmdCollection();
203
+ await runQmd(collection ? ['update', '-c', collection] : ['update']);
204
+ console.log(chalk.green('✓ qmd updated'));
205
+ }
206
+
207
+ console.log();
208
+ } catch (err) {
209
+ console.error(chalk.red(`Error: ${err.message}`));
210
+ process.exit(1);
211
+ }
212
+ });
213
+
214
+ // === REMEMBER ===
215
+ program
216
+ .command('remember <type> <title>')
217
+ .description('Store a memory with type classification (fact|feeling|decision|lesson|commitment|preference|relationship|project)')
218
+ .option('--content <content>', 'Content body')
219
+ .option('-f, --file <file>', 'Read content from file')
220
+ .option('--stdin', 'Read content from stdin')
221
+ .option('-v, --vault <path>', 'Vault path')
222
+ .option('--no-index', 'Skip qmd index update')
223
+ .action(async (type, title, options) => {
224
+ const validTypes = ['fact', 'feeling', 'decision', 'lesson', 'commitment', 'preference', 'relationship', 'project'];
225
+ if (!validTypes.includes(type)) {
226
+ console.error(chalk.red(`Invalid type: ${type}`));
227
+ console.error(chalk.dim(`Valid types: ${validTypes.join(', ')}`));
228
+ process.exit(1);
229
+ }
230
+
231
+ try {
232
+ const vault = await getVault(options.vault);
233
+ let content = options.content || '';
234
+ if (options.file) {
235
+ content = fs.readFileSync(options.file, 'utf-8');
236
+ } else if (options.stdin) {
237
+ content = fs.readFileSync(0, 'utf-8');
238
+ }
239
+
240
+ const doc = await vault.remember(type, title, content);
241
+ console.log(chalk.green(`✓ Remembered (${type}): ${doc.id}`));
242
+
243
+ if (options.index !== false) {
244
+ const collection = vault.getQmdCollection();
245
+ await runQmd(collection ? ['update', '-c', collection] : ['update']);
246
+ }
247
+ } catch (err) {
248
+ console.error(chalk.red(`Error: ${err.message}`));
249
+ process.exit(1);
250
+ }
251
+ });
252
+
253
+ // === SHELL INIT ===
254
+ program
255
+ .command('shell-init')
256
+ .description('Output shell integration for ClawVault')
257
+ .action(async () => {
258
+ try {
259
+ const { shellInit } = await import('../dist/commands/shell-init.js');
260
+ console.log(shellInit());
261
+ } catch (err) {
262
+ console.error(chalk.red(`Error: ${err.message}`));
263
+ process.exit(1);
264
+ }
265
+ });
266
+
267
+ // === DASHBOARD ===
268
+ program
269
+ .command('dashboard')
270
+ .description('Run local vault graph dashboard')
271
+ .option('-p, --port <port>', 'Dashboard port', '3377')
272
+ .option('-v, --vault <path>', 'Vault path')
273
+ .action(async (options) => {
274
+ try {
275
+ const parsedPort = Number.parseInt(options.port, 10);
276
+ if (Number.isNaN(parsedPort)) {
277
+ console.error(chalk.red(`Error: Invalid port: ${options.port}`));
278
+ process.exit(1);
279
+ }
280
+
281
+ const vaultPath = options.vault
282
+ ? path.resolve(options.vault)
283
+ : resolveVaultPath(undefined);
284
+
285
+ const { startDashboard } = await import('../dashboard/server.js');
286
+ await startDashboard({
287
+ port: parsedPort,
288
+ vaultPath
289
+ });
290
+ } catch (err) {
291
+ console.error(chalk.red(`Error: ${err.message}`));
292
+ process.exit(1);
293
+ }
294
+ });
295
+ }
@@ -0,0 +1,94 @@
1
+ import { Command } from 'commander';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import { registerCoreCommands } from '../register-core-commands.js';
5
+ import { registerMaintenanceCommands } from '../register-maintenance-commands.js';
6
+ import { registerQueryCommands } from '../register-query-commands.js';
7
+ import { registerResilienceCommands } from '../register-resilience-commands.js';
8
+ import { registerSessionLifecycleCommands } from '../register-session-lifecycle-commands.js';
9
+ import { registerTemplateCommands } from '../register-template-commands.js';
10
+ import { registerVaultOperationsCommands } from '../register-vault-operations-commands.js';
11
+
12
+ export const chalkStub = {
13
+ cyan: (value) => value,
14
+ green: (value) => value,
15
+ red: (value) => value,
16
+ dim: (value) => value,
17
+ yellow: (value) => value,
18
+ white: (value) => value
19
+ };
20
+
21
+ export function stubResolveVaultPath(value) {
22
+ return value ?? '/vault';
23
+ }
24
+
25
+ export function createVaultStub(overrides = {}) {
26
+ return {
27
+ store: async () => ({}),
28
+ capture: async () => ({}),
29
+ find: async () => [],
30
+ vsearch: async () => [],
31
+ list: async () => [],
32
+ get: async () => null,
33
+ stats: async () => ({ tags: [], categories: {} }),
34
+ sync: async () => ({ copied: [], deleted: [], unchanged: [], errors: [] }),
35
+ reindex: async () => 0,
36
+ remember: async () => ({ id: '' }),
37
+ getQmdCollection: () => '',
38
+ createHandoff: async () => ({ id: '', path: '' }),
39
+ generateRecap: async () => ({}),
40
+ formatRecap: () => '',
41
+ ...overrides
42
+ };
43
+ }
44
+
45
+ export function createGetVaultStub(overrides = {}) {
46
+ return async () => createVaultStub(overrides);
47
+ }
48
+
49
+ export function registerAllCommandModules(program = new Command()) {
50
+ const getVault = createGetVaultStub();
51
+
52
+ registerCoreCommands(program, {
53
+ chalk: chalkStub,
54
+ path,
55
+ fs,
56
+ createVault: async () => ({ getCategories: () => [], getQmdRoot: () => '', getQmdCollection: () => '' }),
57
+ getVault,
58
+ runQmd: async () => {}
59
+ });
60
+
61
+ registerQueryCommands(program, {
62
+ chalk: chalkStub,
63
+ getVault,
64
+ resolveVaultPath: stubResolveVaultPath,
65
+ QmdUnavailableError: class extends Error {},
66
+ printQmdMissing: () => {}
67
+ });
68
+
69
+ registerVaultOperationsCommands(program, {
70
+ chalk: chalkStub,
71
+ fs,
72
+ getVault,
73
+ runQmd: async () => {},
74
+ resolveVaultPath: stubResolveVaultPath,
75
+ path
76
+ });
77
+
78
+ registerMaintenanceCommands(program, { chalk: chalkStub });
79
+ registerResilienceCommands(program, {
80
+ chalk: chalkStub,
81
+ resolveVaultPath: stubResolveVaultPath
82
+ });
83
+ registerSessionLifecycleCommands(program, {
84
+ chalk: chalkStub,
85
+ resolveVaultPath: stubResolveVaultPath,
86
+ QmdUnavailableError: class extends Error {},
87
+ printQmdMissing: () => {},
88
+ getVault,
89
+ runQmd: async () => {}
90
+ });
91
+ registerTemplateCommands(program, { chalk: chalkStub });
92
+
93
+ return program;
94
+ }
@@ -10,7 +10,9 @@ function toNodeSignature(node) {
10
10
  }
11
11
 
12
12
  function toEdgeKey(edge) {
13
- return `${edge.source}=>${edge.target}`;
13
+ const type = edge.type ?? '';
14
+ const label = edge.label ?? '';
15
+ return `${edge.source}=>${edge.target}:${type}:${label}`;
14
16
  }
15
17
 
16
18
  /**
@@ -53,4 +53,23 @@ describe('diffGraphs', () => {
53
53
  expect(patch.addedEdges).toEqual([]);
54
54
  expect(patch.removedEdges).toEqual([]);
55
55
  });
56
+
57
+ it('treats edge type changes as edge diff', () => {
58
+ const previous = {
59
+ nodes: [
60
+ { id: 'a', title: 'A', category: 'root', tags: [], path: 'a.md', missing: false, degree: 1 },
61
+ { id: 'b', title: 'B', category: 'root', tags: [], path: 'b.md', missing: false, degree: 1 }
62
+ ],
63
+ edges: [{ source: 'a', target: 'b', type: 'wiki_link' }]
64
+ };
65
+ const next = {
66
+ nodes: previous.nodes,
67
+ edges: [{ source: 'a', target: 'b', type: 'frontmatter_relation', label: 'related' }]
68
+ };
69
+
70
+ const patch = diffGraphs(previous, next);
71
+ expect(patch.addedEdges).toEqual([{ source: 'a', target: 'b', type: 'frontmatter_relation', label: 'related' }]);
72
+ expect(patch.removedEdges).toEqual([{ source: 'a', target: 'b', type: 'wiki_link' }]);
73
+ expect(patch.hasChanges).toBe(true);
74
+ });
56
75
  });