clawvault 3.2.0 → 3.3.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 (112) hide show
  1. package/README.md +54 -14
  2. package/bin/clawvault.js +0 -2
  3. package/bin/command-registration.test.js +13 -1
  4. package/bin/help-contract.test.js +14 -0
  5. package/bin/register-core-commands.js +88 -0
  6. package/bin/register-core-commands.test.js +80 -0
  7. package/bin/register-maintenance-commands.js +57 -6
  8. package/bin/register-query-commands.js +10 -28
  9. package/bin/test-helpers/cli-command-fixtures.js +1 -0
  10. package/dist/chunk-2PKBIKDH.js +130 -0
  11. package/dist/{chunk-2JQ3O2YL.js → chunk-5EFSWZO6.js} +3 -3
  12. package/dist/{chunk-77Q5CSPJ.js → chunk-7SWP5FKU.js} +33 -701
  13. package/dist/{chunk-URXDAUVH.js → chunk-AXSJIFOJ.js} +174 -1
  14. package/dist/{chunk-23YDQ3QU.js → chunk-BLQXXX7Q.js} +6 -6
  15. package/dist/chunk-CSHO3PJB.js +684 -0
  16. package/dist/{chunk-SLXOR3CC.js → chunk-DOIUYIXV.js} +2 -2
  17. package/dist/{chunk-NCKFNBHJ.js → chunk-DVOUSOR3.js} +79 -5
  18. package/dist/{chunk-CLJTREDS.js → chunk-ECGJYWNA.js} +193 -41
  19. package/dist/{chunk-BUEW6IIK.js → chunk-EL6UBSX5.js} +5 -5
  20. package/dist/{chunk-6FH3IULF.js → chunk-FZ5I2NF7.js} +1 -1
  21. package/dist/{chunk-ZN54U2OZ.js → chunk-GFCHWMGD.js} +3 -3
  22. package/dist/{chunk-GNJL4YGR.js → chunk-GJO3CFUN.js} +30 -6
  23. package/dist/chunk-H3JZIB5O.js +322 -0
  24. package/dist/chunk-HEHO7SMV.js +51 -0
  25. package/dist/{chunk-STCQGCEQ.js → chunk-HGDDW24U.js} +3 -3
  26. package/dist/chunk-J3YUXVID.js +907 -0
  27. package/dist/{chunk-Y6VJKXGL.js → chunk-KCYWJDDW.js} +1 -1
  28. package/dist/{chunk-W4SPAEE7.js → chunk-OFOCU2V4.js} +5 -4
  29. package/dist/chunk-PTWPPVC7.js +972 -0
  30. package/dist/{chunk-QSHD36LH.js → chunk-QFWERBDP.js} +2 -2
  31. package/dist/{chunk-QSRRMEYM.js → chunk-S7N7HI5E.js} +1 -1
  32. package/dist/{chunk-PBACDKKP.js → chunk-T7E764W3.js} +3 -3
  33. package/dist/chunk-TDWFBDAQ.js +1016 -0
  34. package/dist/{chunk-ESVS6K2B.js → chunk-TWMI3SNN.js} +6 -5
  35. package/dist/{chunk-2RAZ4ZFE.js → chunk-VBILES4B.js} +1 -1
  36. package/dist/{chunk-ESFLMDRB.js → chunk-VXAGOLDP.js} +3 -3
  37. package/dist/chunk-YCUVAOFC.js +158 -0
  38. package/dist/{chunk-SS4B7P7V.js → chunk-YIDV4VV2.js} +1 -1
  39. package/dist/chunk-ZKWPCBYT.js +600 -0
  40. package/dist/cli/index.js +24 -24
  41. package/dist/commands/archive.js +2 -2
  42. package/dist/commands/benchmark.d.ts +12 -0
  43. package/dist/commands/benchmark.js +12 -0
  44. package/dist/commands/context.js +6 -5
  45. package/dist/commands/doctor.d.ts +8 -3
  46. package/dist/commands/doctor.js +6 -20
  47. package/dist/commands/embed.js +5 -4
  48. package/dist/commands/entities.js +1 -1
  49. package/dist/commands/graph.js +2 -2
  50. package/dist/commands/inbox.d.ts +23 -0
  51. package/dist/commands/inbox.js +11 -0
  52. package/dist/commands/inject.d.ts +1 -1
  53. package/dist/commands/inject.js +3 -3
  54. package/dist/commands/link.js +6 -6
  55. package/dist/commands/maintain.d.ts +32 -0
  56. package/dist/commands/maintain.js +12 -0
  57. package/dist/commands/migrate-observations.js +2 -2
  58. package/dist/commands/observe.js +9 -8
  59. package/dist/commands/rebuild-embeddings.js +47 -16
  60. package/dist/commands/rebuild.js +7 -6
  61. package/dist/commands/reflect.js +5 -5
  62. package/dist/commands/replay.js +8 -7
  63. package/dist/commands/setup.js +3 -2
  64. package/dist/commands/sleep.d.ts +1 -1
  65. package/dist/commands/sleep.js +17 -15
  66. package/dist/commands/status.js +26 -24
  67. package/dist/commands/sync-bd.js +2 -2
  68. package/dist/commands/tailscale.js +2 -2
  69. package/dist/commands/wake.d.ts +1 -1
  70. package/dist/commands/wake.js +8 -7
  71. package/dist/index.d.ts +168 -16
  72. package/dist/index.js +271 -108
  73. package/dist/{inject-DYUrDqQO.d.ts → inject-DEb_jpLi.d.ts} +3 -1
  74. package/dist/lib/config.js +1 -1
  75. package/dist/{types-BbWJoC1c.d.ts → types-DslKvCaj.d.ts} +51 -1
  76. package/hooks/clawvault/HOOK.md +22 -5
  77. package/hooks/clawvault/handler.js +213 -78
  78. package/hooks/clawvault/handler.test.js +109 -43
  79. package/hooks/clawvault/integrity.js +112 -0
  80. package/hooks/clawvault/integrity.test.js +32 -0
  81. package/hooks/clawvault/openclaw.plugin.json +133 -15
  82. package/openclaw.plugin.json +126 -20
  83. package/package.json +2 -2
  84. package/bin/register-workgraph-commands.js +0 -1368
  85. package/dist/chunk-33VSQP4J.js +0 -37
  86. package/dist/chunk-4BQTQMJP.js +0 -93
  87. package/dist/chunk-EK6S23ZB.js +0 -469
  88. package/dist/chunk-GAOWA7GR.js +0 -501
  89. package/dist/chunk-GGA32J2R.js +0 -784
  90. package/dist/chunk-MM6QGW3P.js +0 -207
  91. package/dist/chunk-QVEERJSP.js +0 -152
  92. package/dist/chunk-U4O6C46S.js +0 -154
  93. package/dist/chunk-VSL7KY3M.js +0 -189
  94. package/dist/chunk-WMGIIABP.js +0 -15
  95. package/dist/commands/workgraph.d.ts +0 -124
  96. package/dist/commands/workgraph.js +0 -38
  97. package/dist/ledger-B7g7jhqG.d.ts +0 -44
  98. package/dist/registry-BR4326o0.d.ts +0 -30
  99. package/dist/store-CA-6sKCJ.d.ts +0 -34
  100. package/dist/thread-B9LhXNU0.d.ts +0 -41
  101. package/dist/workgraph/index.d.ts +0 -5
  102. package/dist/workgraph/index.js +0 -23
  103. package/dist/workgraph/ledger.d.ts +0 -2
  104. package/dist/workgraph/ledger.js +0 -25
  105. package/dist/workgraph/registry.d.ts +0 -2
  106. package/dist/workgraph/registry.js +0 -19
  107. package/dist/workgraph/store.d.ts +0 -2
  108. package/dist/workgraph/store.js +0 -25
  109. package/dist/workgraph/thread.d.ts +0 -2
  110. package/dist/workgraph/thread.js +0 -25
  111. package/dist/workgraph/types.d.ts +0 -54
  112. package/dist/workgraph/types.js +0 -7
@@ -1,1368 +0,0 @@
1
- /**
2
- * Workgraph CLI commands — multi-agent coordination primitives.
3
- *
4
- * Commands under 'wg' namespace (beautiful, agent-native):
5
- * wg status Agent morning briefing
6
- * wg thread create Create a new thread
7
- * wg thread list List threads with filters
8
- * wg thread claim Claim a thread
9
- * wg thread done Mark thread complete
10
- * wg thread block Block thread on dependency
11
- * wg thread release Release thread back to pool
12
- * wg thread decompose Break thread into sub-threads
13
- * wg ledger View coordination history
14
- * wg define Define new primitive type
15
- * wg types List all primitive types
16
- * wg create Create any primitive
17
- * wg board Terminal kanban board
18
- *
19
- * Legacy commands (still available):
20
- * thread create <title> Create a new thread
21
- * thread list List threads (filterable by status)
22
- * thread show <path> Show thread details + ledger history
23
- * thread claim <path> Claim a thread for this agent
24
- * thread release <path> Release a claimed thread
25
- * thread done <path> Mark thread complete
26
- * thread block <path> Mark thread blocked
27
- * thread unblock <path> Unblock a thread
28
- * thread decompose <path> Break into sub-threads
29
- * primitive define <name> Define a new primitive type
30
- * primitive list List all primitive types
31
- * primitive create <type> Create an instance of any type
32
- * ledger show Show recent ledger entries
33
- * ledger history <path> Show history of a specific file
34
- */
35
-
36
- import * as os from 'os';
37
- import * as path from 'path';
38
-
39
- export function registerWorkgraphCommands(program, { chalk, resolveVaultPath }) {
40
- // Register the new 'wg' namespace with beautiful, agent-native commands
41
- registerWgCommands(program, { chalk, resolveVaultPath });
42
-
43
- // Keep legacy commands for backward compatibility
44
- const agentName = process.env.CLAWVAULT_AGENT || process.env.USER || 'anonymous';
45
-
46
- // =========================================================================
47
- // thread
48
- // =========================================================================
49
- const threadCmd = program
50
- .command('thread')
51
- .description('Coordinate work through threads (workgraph core)');
52
-
53
- threadCmd
54
- .command('create <title>')
55
- .description('Create a new thread')
56
- .requiredOption('-g, --goal <goal>', 'What success looks like')
57
- .option('-v, --vault <path>', 'Vault path')
58
- .option('-a, --actor <name>', 'Agent name', agentName)
59
- .option('-p, --priority <level>', 'urgent | high | medium | low', 'medium')
60
- .option('--deps <paths>', 'Comma-separated dependency thread paths')
61
- .option('--parent <path>', 'Parent thread path')
62
- .option('--context <refs>', 'Comma-separated vault doc refs for context')
63
- .option('--tags <tags>', 'Comma-separated tags')
64
- .action(async (title, opts) => {
65
- try {
66
- const vaultPath = resolveVaultPath(opts.vault);
67
- const { thread } = await import('../dist/workgraph/index.js');
68
- const t = thread.createThread(vaultPath, title, opts.goal, opts.actor, {
69
- priority: opts.priority,
70
- deps: csv(opts.deps),
71
- parent: opts.parent,
72
- context_refs: csv(opts.context),
73
- tags: csv(opts.tags),
74
- });
75
- console.log(chalk.green(`✓ Thread created: ${t.path}`));
76
- console.log(` Title: ${t.fields.title}`);
77
- console.log(` Status: ${t.fields.status}`);
78
- console.log(` Priority: ${t.fields.priority}`);
79
- } catch (err) {
80
- console.error(chalk.red(`Error: ${err.message}`));
81
- process.exit(1);
82
- }
83
- });
84
-
85
- threadCmd
86
- .command('list')
87
- .description('List threads')
88
- .option('-v, --vault <path>', 'Vault path')
89
- .option('-s, --status <status>', 'Filter: open | active | blocked | done | cancelled')
90
- .option('--json', 'Output as JSON')
91
- .action(async (opts) => {
92
- try {
93
- const vaultPath = resolveVaultPath(opts.vault);
94
- const { store } = await import('../dist/workgraph/index.js');
95
- let threads = store.list(vaultPath, 'thread');
96
- if (opts.status) threads = threads.filter(t => t.fields.status === opts.status);
97
-
98
- if (opts.json) {
99
- console.log(JSON.stringify(threads.map(t => ({ path: t.path, ...t.fields })), null, 2));
100
- return;
101
- }
102
-
103
- if (threads.length === 0) {
104
- console.log(chalk.dim('No threads found.'));
105
- return;
106
- }
107
-
108
- const statusColor = { open: 'blue', active: 'yellow', blocked: 'red', done: 'green', cancelled: 'dim' };
109
- for (const t of threads) {
110
- const s = String(t.fields.status);
111
- const colorFn = chalk[statusColor[s]] || chalk.white;
112
- const owner = t.fields.owner ? chalk.dim(` (${t.fields.owner})`) : '';
113
- console.log(` ${colorFn(`[${s}]`)} ${t.fields.title}${owner}`);
114
- console.log(chalk.dim(` ${t.path}`));
115
- }
116
- console.log(chalk.dim(`\n${threads.length} thread(s)`));
117
- } catch (err) {
118
- console.error(chalk.red(`Error: ${err.message}`));
119
- process.exit(1);
120
- }
121
- });
122
-
123
- threadCmd
124
- .command('show <path>')
125
- .description('Show thread details and ledger history')
126
- .option('-v, --vault <path>', 'Vault path')
127
- .action(async (threadPath, opts) => {
128
- try {
129
- const vaultPath = resolveVaultPath(opts.vault);
130
- const { store, ledger } = await import('../dist/workgraph/index.js');
131
- const t = store.read(vaultPath, threadPath);
132
- if (!t) { console.error(chalk.red(`Not found: ${threadPath}`)); process.exit(1); }
133
-
134
- console.log(chalk.bold(String(t.fields.title)));
135
- console.log(chalk.dim('─'.repeat(50)));
136
- console.log(`Status: ${t.fields.status}`);
137
- console.log(`Owner: ${t.fields.owner || chalk.dim('unclaimed')}`);
138
- console.log(`Priority: ${t.fields.priority}`);
139
- if (t.fields.deps?.length) console.log(`Deps: ${(t.fields.deps).join(', ')}`);
140
- if (t.fields.parent) console.log(`Parent: ${t.fields.parent}`);
141
- if (t.fields.tags?.length) console.log(`Tags: ${(t.fields.tags).join(', ')}`);
142
- console.log(`Path: ${t.path}`);
143
- console.log();
144
- if (t.body) console.log(t.body);
145
-
146
- const history = ledger.historyOf(vaultPath, threadPath);
147
- if (history.length > 0) {
148
- console.log(chalk.dim('\n─── Ledger History ───'));
149
- for (const e of history) {
150
- const time = new Date(e.ts).toLocaleTimeString();
151
- const data = e.data ? chalk.dim(` ${JSON.stringify(e.data)}`) : '';
152
- console.log(` ${chalk.dim(time)} ${chalk.cyan(e.op)} by ${e.actor}${data}`);
153
- }
154
- }
155
- } catch (err) {
156
- console.error(chalk.red(`Error: ${err.message}`));
157
- process.exit(1);
158
- }
159
- });
160
-
161
- threadCmd
162
- .command('claim <path>')
163
- .description('Claim a thread — only you can work on it')
164
- .option('-v, --vault <path>', 'Vault path')
165
- .option('-a, --actor <name>', 'Agent name', agentName)
166
- .action(async (threadPath, opts) => {
167
- try {
168
- const vaultPath = resolveVaultPath(opts.vault);
169
- const { thread } = await import('../dist/workgraph/index.js');
170
- const t = thread.claim(vaultPath, threadPath, opts.actor);
171
- console.log(chalk.green(`✓ Claimed: ${threadPath}`));
172
- console.log(` Owner: ${opts.actor}`);
173
- } catch (err) {
174
- console.error(chalk.red(`Error: ${err.message}`));
175
- process.exit(1);
176
- }
177
- });
178
-
179
- threadCmd
180
- .command('release <path>')
181
- .description('Release a claimed thread back to open')
182
- .option('-v, --vault <path>', 'Vault path')
183
- .option('-a, --actor <name>', 'Agent name', agentName)
184
- .option('--reason <reason>', 'Why you are releasing')
185
- .action(async (threadPath, opts) => {
186
- try {
187
- const vaultPath = resolveVaultPath(opts.vault);
188
- const { thread } = await import('../dist/workgraph/index.js');
189
- thread.release(vaultPath, threadPath, opts.actor, opts.reason);
190
- console.log(chalk.green(`✓ Released: ${threadPath}`));
191
- } catch (err) {
192
- console.error(chalk.red(`Error: ${err.message}`));
193
- process.exit(1);
194
- }
195
- });
196
-
197
- threadCmd
198
- .command('done <path>')
199
- .description('Mark thread complete')
200
- .option('-v, --vault <path>', 'Vault path')
201
- .option('-a, --actor <name>', 'Agent name', agentName)
202
- .option('-o, --output <text>', 'Output/result summary')
203
- .action(async (threadPath, opts) => {
204
- try {
205
- const vaultPath = resolveVaultPath(opts.vault);
206
- const { thread } = await import('../dist/workgraph/index.js');
207
- thread.done(vaultPath, threadPath, opts.actor, opts.output);
208
- console.log(chalk.green(`✓ Done: ${threadPath}`));
209
- } catch (err) {
210
- console.error(chalk.red(`Error: ${err.message}`));
211
- process.exit(1);
212
- }
213
- });
214
-
215
- threadCmd
216
- .command('block <path>')
217
- .description('Mark thread blocked on a dependency')
218
- .option('-v, --vault <path>', 'Vault path')
219
- .option('-a, --actor <name>', 'Agent name', agentName)
220
- .requiredOption('-b, --blocked-by <dep>', 'What is blocking this thread')
221
- .option('--reason <reason>', 'Why it is blocked')
222
- .action(async (threadPath, opts) => {
223
- try {
224
- const vaultPath = resolveVaultPath(opts.vault);
225
- const { thread } = await import('../dist/workgraph/index.js');
226
- thread.block(vaultPath, threadPath, opts.actor, opts.blockedBy, opts.reason);
227
- console.log(chalk.red(`⊘ Blocked: ${threadPath}`));
228
- console.log(` Blocked by: ${opts.blockedBy}`);
229
- } catch (err) {
230
- console.error(chalk.red(`Error: ${err.message}`));
231
- process.exit(1);
232
- }
233
- });
234
-
235
- threadCmd
236
- .command('unblock <path>')
237
- .description('Unblock a thread')
238
- .option('-v, --vault <path>', 'Vault path')
239
- .option('-a, --actor <name>', 'Agent name', agentName)
240
- .action(async (threadPath, opts) => {
241
- try {
242
- const vaultPath = resolveVaultPath(opts.vault);
243
- const { thread } = await import('../dist/workgraph/index.js');
244
- thread.unblock(vaultPath, threadPath, opts.actor);
245
- console.log(chalk.green(`✓ Unblocked: ${threadPath}`));
246
- } catch (err) {
247
- console.error(chalk.red(`Error: ${err.message}`));
248
- process.exit(1);
249
- }
250
- });
251
-
252
- threadCmd
253
- .command('decompose <path>')
254
- .description('Break a thread into sub-threads')
255
- .option('-v, --vault <path>', 'Vault path')
256
- .option('-a, --actor <name>', 'Agent name', agentName)
257
- .requiredOption('--sub <specs...>', 'Sub-threads as "title|goal" pairs')
258
- .action(async (threadPath, opts) => {
259
- try {
260
- const vaultPath = resolveVaultPath(opts.vault);
261
- const { thread } = await import('../dist/workgraph/index.js');
262
- const subs = opts.sub.map(spec => {
263
- const [title, ...goalParts] = spec.split('|');
264
- return { title: title.trim(), goal: goalParts.join('|').trim() || title.trim() };
265
- });
266
- const children = thread.decompose(vaultPath, threadPath, subs, opts.actor);
267
- console.log(chalk.green(`✓ Decomposed ${threadPath} into ${children.length} sub-threads:`));
268
- for (const c of children) {
269
- console.log(` → ${c.path}`);
270
- }
271
- } catch (err) {
272
- console.error(chalk.red(`Error: ${err.message}`));
273
- process.exit(1);
274
- }
275
- });
276
-
277
- // =========================================================================
278
- // primitive
279
- // =========================================================================
280
- const primitiveCmd = program
281
- .command('primitive')
282
- .description('Manage workgraph primitive types (define new types, list, create)');
283
-
284
- primitiveCmd
285
- .command('define <name>')
286
- .description('Define a new primitive type that agents can instantiate')
287
- .requiredOption('-d, --description <desc>', 'What this type represents')
288
- .option('-v, --vault <path>', 'Vault path')
289
- .option('-a, --actor <name>', 'Agent name', agentName)
290
- .option('--fields <specs...>', 'Field definitions as "name:type" (types: string, number, boolean, list, date, ref)')
291
- .option('--dir <directory>', 'Storage directory name')
292
- .action(async (name, opts) => {
293
- try {
294
- const vaultPath = resolveVaultPath(opts.vault);
295
- const { registry } = await import('../dist/workgraph/index.js');
296
- const fields = {};
297
- if (opts.fields) {
298
- for (const spec of opts.fields) {
299
- const [fieldName, fieldType = 'string'] = spec.split(':');
300
- fields[fieldName.trim()] = { type: fieldType.trim() };
301
- }
302
- }
303
- const typeDef = registry.defineType(vaultPath, name, opts.description, fields, opts.actor, opts.dir);
304
- console.log(chalk.green(`✓ Defined type: ${typeDef.name}`));
305
- console.log(` Directory: ${typeDef.directory}/`);
306
- console.log(` Fields: ${Object.keys(typeDef.fields).join(', ')}`);
307
- } catch (err) {
308
- console.error(chalk.red(`Error: ${err.message}`));
309
- process.exit(1);
310
- }
311
- });
312
-
313
- primitiveCmd
314
- .command('list')
315
- .description('List all registered primitive types')
316
- .option('-v, --vault <path>', 'Vault path')
317
- .option('--json', 'Output as JSON')
318
- .action(async (opts) => {
319
- try {
320
- const vaultPath = resolveVaultPath(opts.vault);
321
- const { registry } = await import('../dist/workgraph/index.js');
322
- const types = registry.listTypes(vaultPath);
323
-
324
- if (opts.json) {
325
- console.log(JSON.stringify(types, null, 2));
326
- return;
327
- }
328
-
329
- for (const t of types) {
330
- const badge = t.builtIn ? chalk.dim('[built-in]') : chalk.cyan(`[${t.createdBy}]`);
331
- console.log(` ${chalk.bold(t.name)} ${badge}`);
332
- console.log(chalk.dim(` ${t.description}`));
333
- console.log(chalk.dim(` dir: ${t.directory}/ fields: ${Object.keys(t.fields).join(', ')}`));
334
- }
335
- console.log(chalk.dim(`\n${types.length} type(s) — ${types.filter(t => !t.builtIn).length} agent-defined`));
336
- } catch (err) {
337
- console.error(chalk.red(`Error: ${err.message}`));
338
- process.exit(1);
339
- }
340
- });
341
-
342
- primitiveCmd
343
- .command('create <type> <title>')
344
- .description('Create an instance of any primitive type')
345
- .option('-v, --vault <path>', 'Vault path')
346
- .option('-a, --actor <name>', 'Agent name', agentName)
347
- .option('--set <fields...>', 'Set fields as "key=value" pairs')
348
- .option('--body <text>', 'Markdown body content', '')
349
- .action(async (type, title, opts) => {
350
- try {
351
- const vaultPath = resolveVaultPath(opts.vault);
352
- const { store } = await import('../dist/workgraph/index.js');
353
- const fields = { title };
354
- if (opts.set) {
355
- for (const pair of opts.set) {
356
- const eqIdx = pair.indexOf('=');
357
- if (eqIdx === -1) continue;
358
- const key = pair.slice(0, eqIdx).trim();
359
- let val = pair.slice(eqIdx + 1).trim();
360
- if (val.includes(',')) val = val.split(',').map(s => s.trim());
361
- fields[key] = val;
362
- }
363
- }
364
- const inst = store.create(vaultPath, type, fields, opts.body, opts.actor);
365
- console.log(chalk.green(`✓ Created ${type}: ${inst.path}`));
366
- } catch (err) {
367
- console.error(chalk.red(`Error: ${err.message}`));
368
- process.exit(1);
369
- }
370
- });
371
-
372
- // =========================================================================
373
- // ledger
374
- // =========================================================================
375
- const ledgerCmd = program
376
- .command('ledger')
377
- .description('View the workgraph audit trail');
378
-
379
- ledgerCmd
380
- .command('show')
381
- .description('Show recent ledger entries')
382
- .option('-v, --vault <path>', 'Vault path')
383
- .option('-n, --count <n>', 'Number of entries', '20')
384
- .option('--actor <name>', 'Filter by actor')
385
- .option('--json', 'Output as JSON')
386
- .action(async (opts) => {
387
- try {
388
- const vaultPath = resolveVaultPath(opts.vault);
389
- const { ledger } = await import('../dist/workgraph/index.js');
390
- let entries = ledger.recent(vaultPath, parseInt(opts.count));
391
- if (opts.actor) entries = entries.filter(e => e.actor === opts.actor);
392
-
393
- if (opts.json) {
394
- console.log(JSON.stringify(entries, null, 2));
395
- return;
396
- }
397
-
398
- if (entries.length === 0) {
399
- console.log(chalk.dim('No ledger entries.'));
400
- return;
401
- }
402
-
403
- const opColor = { create: 'green', claim: 'yellow', release: 'blue', done: 'green', block: 'red', unblock: 'cyan', cancel: 'dim', update: 'white', delete: 'red', define: 'magenta', decompose: 'cyan' };
404
- for (const e of entries) {
405
- const time = new Date(e.ts).toLocaleString();
406
- const colorFn = chalk[opColor[e.op]] || chalk.white;
407
- const data = e.data ? chalk.dim(` ${JSON.stringify(e.data)}`) : '';
408
- console.log(` ${chalk.dim(time)} ${colorFn(e.op.padEnd(10))} ${e.actor.padEnd(15)} ${e.target}${data}`);
409
- }
410
- } catch (err) {
411
- console.error(chalk.red(`Error: ${err.message}`));
412
- process.exit(1);
413
- }
414
- });
415
-
416
- ledgerCmd
417
- .command('history <path>')
418
- .description('Show full history of a specific file')
419
- .option('-v, --vault <path>', 'Vault path')
420
- .action(async (targetPath, opts) => {
421
- try {
422
- const vaultPath = resolveVaultPath(opts.vault);
423
- const { ledger } = await import('../dist/workgraph/index.js');
424
- const history = ledger.historyOf(vaultPath, targetPath);
425
-
426
- if (history.length === 0) {
427
- console.log(chalk.dim(`No history for ${targetPath}`));
428
- return;
429
- }
430
-
431
- console.log(chalk.bold(`History: ${targetPath}`));
432
- console.log(chalk.dim('─'.repeat(50)));
433
- for (const e of history) {
434
- const time = new Date(e.ts).toLocaleString();
435
- const data = e.data ? chalk.dim(` ${JSON.stringify(e.data)}`) : '';
436
- console.log(` ${chalk.dim(time)} ${chalk.cyan(e.op)} by ${e.actor}${data}`);
437
- }
438
- } catch (err) {
439
- console.error(chalk.red(`Error: ${err.message}`));
440
- process.exit(1);
441
- }
442
- });
443
-
444
- ledgerCmd
445
- .command('claims')
446
- .description('Show all active claims')
447
- .option('-v, --vault <path>', 'Vault path')
448
- .action(async (opts) => {
449
- try {
450
- const vaultPath = resolveVaultPath(opts.vault);
451
- const { ledger } = await import('../dist/workgraph/index.js');
452
- const claims = ledger.allClaims(vaultPath);
453
-
454
- if (claims.size === 0) {
455
- console.log(chalk.dim('No active claims.'));
456
- return;
457
- }
458
-
459
- for (const [target, owner] of claims) {
460
- console.log(` ${chalk.yellow(owner.padEnd(20))} → ${target}`);
461
- }
462
- console.log(chalk.dim(`\n${claims.size} active claim(s)`));
463
- } catch (err) {
464
- console.error(chalk.red(`Error: ${err.message}`));
465
- process.exit(1);
466
- }
467
- });
468
- }
469
-
470
- function csv(value) {
471
- if (!value) return undefined;
472
- return String(value).split(',').map(s => s.trim()).filter(Boolean);
473
- }
474
-
475
- // ─────────────────────────────────────────────────────────────────────────────
476
- // Beautiful, agent-native 'wg' namespace
477
- // ─────────────────────────────────────────────────────────────────────────────
478
-
479
- const BOX = {
480
- topLeft: '╭',
481
- topRight: '╮',
482
- bottomLeft: '╰',
483
- bottomRight: '╯',
484
- horizontal: '─',
485
- vertical: '│',
486
- };
487
-
488
- const PRIORITY_CONFIG = {
489
- urgent: { symbol: '🔴', label: 'URGENT' },
490
- high: { symbol: '🟠', label: 'HIGH' },
491
- medium: { symbol: '🔵', label: 'MEDIUM' },
492
- low: { symbol: '⚪', label: 'LOW' },
493
- };
494
-
495
- const STATUS_CONFIG = {
496
- open: { symbol: '○', label: 'Open' },
497
- active: { symbol: '●', label: 'Active' },
498
- blocked: { symbol: '⊘', label: 'Blocked' },
499
- done: { symbol: '✓', label: 'Done' },
500
- cancelled: { symbol: '✗', label: 'Cancelled' },
501
- };
502
-
503
- const OP_COLORS = {
504
- create: 'green',
505
- update: 'blue',
506
- delete: 'red',
507
- claim: 'yellow',
508
- release: 'cyan',
509
- block: 'red',
510
- unblock: 'green',
511
- done: 'greenBright',
512
- cancel: 'gray',
513
- define: 'magenta',
514
- decompose: 'cyan',
515
- };
516
-
517
- function getAgentName() {
518
- return process.env.CLAWVAULT_AGENT || os.hostname();
519
- }
520
-
521
- function formatRelativeTime(isoTimestamp) {
522
- const now = Date.now();
523
- const then = new Date(isoTimestamp).getTime();
524
- const diffMs = now - then;
525
- if (diffMs < 0) return 'just now';
526
- const seconds = Math.floor(diffMs / 1000);
527
- const minutes = Math.floor(seconds / 60);
528
- const hours = Math.floor(minutes / 60);
529
- const days = Math.floor(hours / 24);
530
- const weeks = Math.floor(days / 7);
531
- if (weeks > 0) return `${weeks}w ago`;
532
- if (days > 0) return `${days}d ago`;
533
- if (hours > 0) return `${hours}h ago`;
534
- if (minutes > 0) return `${minutes}m ago`;
535
- if (seconds > 0) return `${seconds}s ago`;
536
- return 'just now';
537
- }
538
-
539
- function truncate(str, maxLen) {
540
- if (str.length <= maxLen) return str;
541
- return str.slice(0, maxLen - 1) + '…';
542
- }
543
-
544
- function stripAnsi(str) {
545
- return str.replace(/\x1B\[[0-9;]*m/g, '');
546
- }
547
-
548
- function drawLine(width, title) {
549
- if (!title) return BOX.horizontal.repeat(width);
550
- const titlePadded = ` ${title} `;
551
- const remaining = width - titlePadded.length - 2;
552
- const left = Math.floor(remaining / 2);
553
- const right = remaining - left;
554
- return BOX.horizontal.repeat(left) + titlePadded + BOX.horizontal.repeat(right);
555
- }
556
-
557
- function drawBox(chalk, title, lines, width = 60) {
558
- const innerWidth = width - 2;
559
- const output = [];
560
- output.push(BOX.topLeft + drawLine(innerWidth, title) + BOX.topRight);
561
- for (const line of lines) {
562
- const stripped = stripAnsi(line);
563
- const padding = innerWidth - stripped.length;
564
- output.push(BOX.vertical + line + ' '.repeat(Math.max(0, padding)) + BOX.vertical);
565
- }
566
- output.push(BOX.bottomLeft + BOX.horizontal.repeat(innerWidth) + BOX.bottomRight);
567
- return output.join('\n');
568
- }
569
-
570
- function getGreeting(hour) {
571
- if (hour < 12) return '☀️ Good morning';
572
- if (hour < 17) return '🌤️ Good afternoon';
573
- return '🌙 Good evening';
574
- }
575
-
576
- function sortByPriority(threads) {
577
- const priorityOrder = { urgent: 0, high: 1, medium: 2, low: 3 };
578
- return [...threads].sort((a, b) => {
579
- const pa = priorityOrder[String(a.fields.priority || 'medium')] ?? 2;
580
- const pb = priorityOrder[String(b.fields.priority || 'medium')] ?? 2;
581
- return pa - pb;
582
- });
583
- }
584
-
585
- function formatThreadLine(chalk, inst, showOwner = true) {
586
- const status = inst.fields.status;
587
- const priority = inst.fields.priority || 'medium';
588
- const title = truncate(String(inst.fields.title || inst.path), 40);
589
- const owner = inst.fields.owner;
590
- const statusCfg = STATUS_CONFIG[status] || STATUS_CONFIG.open;
591
- const priorityCfg = PRIORITY_CONFIG[priority] || PRIORITY_CONFIG.medium;
592
- const statusColors = { open: 'cyan', active: 'green', blocked: 'red', done: 'gray', cancelled: 'dim' };
593
- const priorityColors = { urgent: 'red', high: 'yellow', medium: 'blue', low: 'gray' };
594
- const statusColor = chalk[statusColors[status]] || chalk.white;
595
- let line = `${statusColor(statusCfg.symbol)} ${priorityCfg.symbol} ${chalk.white(title)}`;
596
- if (showOwner && owner) {
597
- line += chalk.dim(` @${owner}`);
598
- }
599
- return line;
600
- }
601
-
602
- function formatPriority(chalk, priority) {
603
- const cfg = PRIORITY_CONFIG[priority] || PRIORITY_CONFIG.medium;
604
- const colors = { urgent: 'red', high: 'yellow', medium: 'blue', low: 'gray' };
605
- const colorFn = chalk[colors[priority]] || chalk.blue;
606
- return colorFn(`${cfg.symbol} ${cfg.label}`);
607
- }
608
-
609
- function normalizeThreadPath(input) {
610
- if (input.startsWith('threads/')) return input;
611
- if (input.endsWith('.md')) return `threads/${input}`;
612
- return `threads/${input}.md`;
613
- }
614
-
615
- function formatError(chalk, what, why, fix) {
616
- return [
617
- '',
618
- chalk.red.bold('✗ Error: ') + chalk.red(what),
619
- '',
620
- chalk.dim('Why: ') + why,
621
- chalk.dim('Fix: ') + chalk.cyan(fix),
622
- '',
623
- ].join('\n');
624
- }
625
-
626
- function registerWgCommands(program, { chalk, resolveVaultPath }) {
627
- const wg = program
628
- .command('wg')
629
- .description('Workgraph — beautiful, agent-native multi-agent coordination');
630
-
631
- // wg status
632
- wg.command('status')
633
- .description('Agent morning briefing with active work, available tasks, and team status')
634
- .option('-v, --vault <path>', 'Vault path')
635
- .action(async (opts) => {
636
- try {
637
- const vaultPath = resolveVaultPath(opts.vault);
638
- const { store, ledger } = await import('../dist/workgraph/index.js');
639
- const agentName = getAgentName();
640
- const now = new Date();
641
- const greeting = getGreeting(now.getHours());
642
-
643
- console.log('');
644
- console.log(chalk.bold.cyan(`${greeting}, ${agentName}!`));
645
- console.log(chalk.dim(`${now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })}`));
646
- console.log('');
647
-
648
- const allThreads = store.list(vaultPath, 'thread');
649
- const activeThreads = allThreads.filter(t => t.fields.status === 'active');
650
- const openThreads = allThreads.filter(t => t.fields.status === 'open');
651
- const blockedThreads = allThreads.filter(t => t.fields.status === 'blocked');
652
- const myActiveThreads = activeThreads.filter(t => t.fields.owner === agentName);
653
-
654
- if (myActiveThreads.length > 0) {
655
- const activeLines = myActiveThreads.map(t => formatThreadLine(chalk, t, false));
656
- console.log(drawBox(chalk, '🔥 Your Active Work', activeLines, 65));
657
- console.log('');
658
- }
659
-
660
- if (openThreads.length > 0) {
661
- const sorted = sortByPriority(openThreads);
662
- const availableLines = sorted.slice(0, 5).map(t => formatThreadLine(chalk, t, false));
663
- if (sorted.length > 5) {
664
- availableLines.push(chalk.dim(` ... and ${sorted.length - 5} more`));
665
- }
666
- console.log(drawBox(chalk, '📋 Available Work', availableLines, 65));
667
- console.log('');
668
- }
669
-
670
- if (blockedThreads.length > 0) {
671
- const blockedLines = blockedThreads.slice(0, 3).map(t => {
672
- const title = truncate(String(t.fields.title || t.path), 35);
673
- const deps = t.fields.deps || [];
674
- const depStr = deps.length > 0 ? chalk.dim(` → ${deps[0]}`) : '';
675
- const statusColor = chalk.red;
676
- return `${statusColor(STATUS_CONFIG.blocked.symbol)} ${title}${depStr}`;
677
- });
678
- console.log(drawBox(chalk, '⛔ Blocked', blockedLines, 65));
679
- console.log('');
680
- }
681
-
682
- const recentEntries = ledger.recent(vaultPath, 8);
683
- if (recentEntries.length > 0) {
684
- const activityLines = recentEntries.reverse().map(e => {
685
- const opColorName = OP_COLORS[e.op] || 'white';
686
- const opColor = chalk[opColorName] || chalk.white;
687
- const target = truncate(path.basename(e.target, '.md'), 25);
688
- const time = formatRelativeTime(e.ts);
689
- return `${opColor(e.op.padEnd(8))} ${chalk.white(target)} ${chalk.dim(time)}`;
690
- });
691
- console.log(drawBox(chalk, '📜 Recent Activity', activityLines, 65));
692
- console.log('');
693
- }
694
-
695
- const claims = ledger.allClaims(vaultPath);
696
- const teamMembers = new Map();
697
- for (const [target, owner] of claims) {
698
- const current = teamMembers.get(owner) || [];
699
- current.push(target);
700
- teamMembers.set(owner, current);
701
- }
702
-
703
- if (teamMembers.size > 0) {
704
- const teamLines = [];
705
- for (const [member, threads] of teamMembers) {
706
- const isYou = member === agentName;
707
- const name = isYou ? chalk.green(`${member} (you)`) : chalk.white(member);
708
- teamLines.push(`${chalk.cyan('●')} ${name}: ${chalk.dim(`${threads.length} active`)}`);
709
- }
710
- console.log(drawBox(chalk, '👥 Team Status', teamLines, 65));
711
- console.log('');
712
- }
713
-
714
- const summaryParts = [
715
- chalk.green(`${activeThreads.length} active`),
716
- chalk.cyan(`${openThreads.length} open`),
717
- chalk.red(`${blockedThreads.length} blocked`),
718
- ];
719
- console.log(chalk.dim('Summary: ') + summaryParts.join(chalk.dim(' · ')));
720
- console.log('');
721
- } catch (err) {
722
- console.error(chalk.red(`Error: ${err.message}`));
723
- process.exit(1);
724
- }
725
- });
726
-
727
- // wg thread subcommands
728
- const threadCmd = wg
729
- .command('thread')
730
- .description('Thread lifecycle operations');
731
-
732
- threadCmd
733
- .command('create <title>')
734
- .description('Create a new thread')
735
- .option('--goal <goal>', 'What success looks like')
736
- .option('--priority <priority>', 'urgent | high | medium | low', 'medium')
737
- .option('--deps <deps>', 'Comma-separated dependency paths')
738
- .option('--tags <tags>', 'Comma-separated tags')
739
- .option('-v, --vault <path>', 'Vault path')
740
- .action(async (title, opts) => {
741
- try {
742
- const vaultPath = resolveVaultPath(opts.vault);
743
- const { thread } = await import('../dist/workgraph/index.js');
744
- const agentName = getAgentName();
745
- const goal = opts.goal || `Complete: ${title}`;
746
- const priority = opts.priority || 'medium';
747
- const deps = csv(opts.deps) || [];
748
- const tags = csv(opts.tags) || [];
749
-
750
- const inst = thread.createThread(vaultPath, title, goal, agentName, {
751
- priority,
752
- deps,
753
- tags,
754
- });
755
-
756
- console.log('');
757
- console.log(chalk.green.bold('✓ Thread created'));
758
- console.log('');
759
- console.log(chalk.dim(' Path: ') + chalk.white(inst.path));
760
- console.log(chalk.dim(' Title: ') + chalk.white(inst.fields.title));
761
- console.log(chalk.dim(' Goal: ') + chalk.white(inst.fields.goal));
762
- console.log(chalk.dim(' Priority: ') + formatPriority(chalk, priority));
763
- if (deps.length > 0) {
764
- console.log(chalk.dim(' Deps: ') + chalk.cyan(deps.join(', ')));
765
- }
766
- if (tags.length > 0) {
767
- console.log(chalk.dim(' Tags: ') + chalk.magenta(tags.join(', ')));
768
- }
769
- console.log('');
770
- console.log(chalk.dim(`Claim it: ${chalk.cyan(`clawvault wg thread claim ${inst.path}`)}`));
771
- console.log('');
772
- } catch (err) {
773
- console.error(formatError(chalk,
774
- 'Failed to create thread',
775
- err.message,
776
- 'Check the title is unique and vault path is correct'
777
- ));
778
- process.exit(1);
779
- }
780
- });
781
-
782
- threadCmd
783
- .command('list')
784
- .description('List threads with optional filters')
785
- .option('--status <status>', 'Filter by status: open | active | blocked | done | cancelled')
786
- .option('--owner <owner>', 'Filter by owner (use "me" for current agent)')
787
- .option('--json', 'Output as JSON')
788
- .option('-v, --vault <path>', 'Vault path')
789
- .action(async (opts) => {
790
- try {
791
- const vaultPath = resolveVaultPath(opts.vault);
792
- const { store } = await import('../dist/workgraph/index.js');
793
- let threads = store.list(vaultPath, 'thread');
794
-
795
- if (opts.status) {
796
- threads = threads.filter(t => t.fields.status === opts.status);
797
- }
798
-
799
- if (opts.owner) {
800
- const ownerFilter = opts.owner === 'me' ? getAgentName() : opts.owner;
801
- threads = threads.filter(t => t.fields.owner === ownerFilter);
802
- }
803
-
804
- if (opts.json) {
805
- console.log(JSON.stringify(threads, null, 2));
806
- return;
807
- }
808
-
809
- if (threads.length === 0) {
810
- console.log('');
811
- console.log(chalk.dim('No threads found matching filters.'));
812
- console.log('');
813
- return;
814
- }
815
-
816
- console.log('');
817
- console.log(chalk.bold(`Threads (${threads.length})`));
818
- console.log(chalk.dim('─'.repeat(70)));
819
-
820
- const sorted = sortByPriority(threads);
821
- for (const t of sorted) {
822
- const status = t.fields.status;
823
- const priority = t.fields.priority || 'medium';
824
- const title = truncate(String(t.fields.title || t.path), 35);
825
- const owner = t.fields.owner;
826
- const updated = formatRelativeTime(String(t.fields.updated));
827
-
828
- const statusCfg = STATUS_CONFIG[status] || STATUS_CONFIG.open;
829
- const priorityCfg = PRIORITY_CONFIG[priority] || PRIORITY_CONFIG.medium;
830
- const statusColors = { open: 'cyan', active: 'green', blocked: 'red', done: 'gray', cancelled: 'dim' };
831
- const statusColor = chalk[statusColors[status]] || chalk.white;
832
-
833
- let line = `${statusColor(statusCfg.symbol.padEnd(2))}`;
834
- line += `${priorityCfg.symbol} `;
835
- line += chalk.white(title.padEnd(37));
836
- line += owner ? chalk.cyan(`@${owner}`.padEnd(15)) : ' '.repeat(15);
837
- line += chalk.dim(updated);
838
-
839
- console.log(line);
840
- }
841
-
842
- console.log(chalk.dim('─'.repeat(70)));
843
- console.log('');
844
- } catch (err) {
845
- console.error(chalk.red(`Error: ${err.message}`));
846
- process.exit(1);
847
- }
848
- });
849
-
850
- threadCmd
851
- .command('claim <path>')
852
- .description('Claim a thread and show work brief')
853
- .option('-v, --vault <path>', 'Vault path')
854
- .action(async (threadPath, opts) => {
855
- try {
856
- const vaultPath = resolveVaultPath(opts.vault);
857
- const { thread } = await import('../dist/workgraph/index.js');
858
- const agentName = getAgentName();
859
- const normalizedPath = normalizeThreadPath(threadPath);
860
-
861
- const inst = thread.claim(vaultPath, normalizedPath, agentName);
862
-
863
- console.log('');
864
- console.log(chalk.green.bold('✓ Thread claimed'));
865
- console.log('');
866
-
867
- const briefLines = [
868
- chalk.dim('Title: ') + chalk.white.bold(inst.fields.title),
869
- chalk.dim('Goal: ') + chalk.white(inst.fields.goal),
870
- chalk.dim('Priority: ') + formatPriority(chalk, String(inst.fields.priority || 'medium')),
871
- ];
872
-
873
- const deps = inst.fields.deps || [];
874
- if (deps.length > 0) {
875
- briefLines.push(chalk.dim('Deps: ') + chalk.cyan(deps.join(', ')));
876
- }
877
-
878
- const contextRefs = inst.fields.context_refs || [];
879
- if (contextRefs.length > 0) {
880
- briefLines.push(chalk.dim('Context: ') + chalk.magenta(contextRefs.join(', ')));
881
- }
882
-
883
- console.log(drawBox(chalk, '📋 Work Brief', briefLines, 65));
884
- console.log('');
885
-
886
- if (inst.body && inst.body.trim()) {
887
- console.log(chalk.dim('─'.repeat(65)));
888
- console.log(chalk.dim('Notes:'));
889
- console.log(inst.body.trim().split('\n').slice(0, 10).join('\n'));
890
- console.log(chalk.dim('─'.repeat(65)));
891
- console.log('');
892
- }
893
-
894
- console.log(chalk.dim(`When done: ${chalk.cyan(`clawvault wg thread done ${normalizedPath}`)}`));
895
- console.log('');
896
- } catch (err) {
897
- console.error(formatError(chalk,
898
- 'Failed to claim thread',
899
- err.message,
900
- 'Ensure the thread exists and is in "open" status'
901
- ));
902
- process.exit(1);
903
- }
904
- });
905
-
906
- threadCmd
907
- .command('done <path>')
908
- .description('Mark thread as complete')
909
- .option('--output <output>', 'Completion summary or output')
910
- .option('-v, --vault <path>', 'Vault path')
911
- .action(async (threadPath, opts) => {
912
- try {
913
- const vaultPath = resolveVaultPath(opts.vault);
914
- const { thread } = await import('../dist/workgraph/index.js');
915
- const agentName = getAgentName();
916
- const normalizedPath = normalizeThreadPath(threadPath);
917
-
918
- const inst = thread.done(vaultPath, normalizedPath, agentName, opts.output);
919
-
920
- console.log('');
921
- console.log(chalk.green.bold('✓ Thread completed!'));
922
- console.log('');
923
- console.log(chalk.dim(' Title: ') + chalk.white(inst.fields.title));
924
- console.log(chalk.dim(' Status: ') + chalk.green('done'));
925
- if (opts.output) {
926
- console.log(chalk.dim(' Output: ') + chalk.white(truncate(opts.output, 50)));
927
- }
928
- console.log('');
929
- console.log(chalk.dim('Great work! 🎉'));
930
- console.log('');
931
- } catch (err) {
932
- console.error(formatError(chalk,
933
- 'Failed to complete thread',
934
- err.message,
935
- 'Ensure you own the thread and it is in "active" status'
936
- ));
937
- process.exit(1);
938
- }
939
- });
940
-
941
- threadCmd
942
- .command('block <path>')
943
- .description('Block thread on a dependency')
944
- .requiredOption('--by <blocker>', 'What is blocking this thread')
945
- .option('--reason <reason>', 'Additional context')
946
- .option('-v, --vault <path>', 'Vault path')
947
- .action(async (threadPath, opts) => {
948
- try {
949
- const vaultPath = resolveVaultPath(opts.vault);
950
- const { thread } = await import('../dist/workgraph/index.js');
951
- const agentName = getAgentName();
952
- const normalizedPath = normalizeThreadPath(threadPath);
953
-
954
- const inst = thread.block(vaultPath, normalizedPath, agentName, opts.by, opts.reason);
955
-
956
- console.log('');
957
- console.log(chalk.yellow.bold('⊘ Thread blocked'));
958
- console.log('');
959
- console.log(chalk.dim(' Title: ') + chalk.white(inst.fields.title));
960
- console.log(chalk.dim(' Blocked by: ') + chalk.red(opts.by));
961
- if (opts.reason) {
962
- console.log(chalk.dim(' Reason: ') + chalk.white(opts.reason));
963
- }
964
- console.log('');
965
- } catch (err) {
966
- console.error(formatError(chalk,
967
- 'Failed to block thread',
968
- err.message,
969
- 'Ensure the thread exists and is in "active" status'
970
- ));
971
- process.exit(1);
972
- }
973
- });
974
-
975
- threadCmd
976
- .command('release <path>')
977
- .description('Release thread back to the pool')
978
- .option('--reason <reason>', 'Why releasing')
979
- .option('-v, --vault <path>', 'Vault path')
980
- .action(async (threadPath, opts) => {
981
- try {
982
- const vaultPath = resolveVaultPath(opts.vault);
983
- const { thread } = await import('../dist/workgraph/index.js');
984
- const agentName = getAgentName();
985
- const normalizedPath = normalizeThreadPath(threadPath);
986
-
987
- const inst = thread.release(vaultPath, normalizedPath, agentName, opts.reason);
988
-
989
- console.log('');
990
- console.log(chalk.cyan.bold('↩ Thread released'));
991
- console.log('');
992
- console.log(chalk.dim(' Title: ') + chalk.white(inst.fields.title));
993
- console.log(chalk.dim(' Status: ') + chalk.cyan('open'));
994
- if (opts.reason) {
995
- console.log(chalk.dim(' Reason: ') + chalk.white(opts.reason));
996
- }
997
- console.log('');
998
- console.log(chalk.dim('Thread is now available for others to claim.'));
999
- console.log('');
1000
- } catch (err) {
1001
- console.error(formatError(chalk,
1002
- 'Failed to release thread',
1003
- err.message,
1004
- 'Ensure you own the thread'
1005
- ));
1006
- process.exit(1);
1007
- }
1008
- });
1009
-
1010
- threadCmd
1011
- .command('decompose <path>')
1012
- .description('Break thread into sub-threads')
1013
- .option('--into <titles...>', 'Sub-thread titles')
1014
- .option('-v, --vault <path>', 'Vault path')
1015
- .action(async (threadPath, opts) => {
1016
- try {
1017
- const vaultPath = resolveVaultPath(opts.vault);
1018
- const { thread, store } = await import('../dist/workgraph/index.js');
1019
- const agentName = getAgentName();
1020
- const normalizedPath = normalizeThreadPath(threadPath);
1021
-
1022
- if (!opts.into || opts.into.length === 0) {
1023
- console.error(formatError(chalk,
1024
- 'Missing --into option',
1025
- 'You must specify sub-thread titles',
1026
- 'clawvault wg thread decompose <path> --into "sub1" --into "sub2"'
1027
- ));
1028
- process.exit(1);
1029
- }
1030
-
1031
- const parent = store.read(vaultPath, normalizedPath);
1032
- if (!parent) {
1033
- throw new Error(`Thread not found: ${normalizedPath}`);
1034
- }
1035
-
1036
- const subthreads = opts.into.map(title => ({
1037
- title,
1038
- goal: `Sub-task of: ${parent.fields.title}`,
1039
- }));
1040
-
1041
- const created = thread.decompose(vaultPath, normalizedPath, subthreads, agentName);
1042
-
1043
- console.log('');
1044
- console.log(chalk.green.bold('✓ Thread decomposed'));
1045
- console.log('');
1046
- console.log(chalk.dim(' Parent: ') + chalk.white(parent.fields.title));
1047
- console.log(chalk.dim(' Created sub-threads:'));
1048
- for (const sub of created) {
1049
- console.log(chalk.cyan(` → ${sub.fields.title}`));
1050
- }
1051
- console.log('');
1052
- } catch (err) {
1053
- console.error(formatError(chalk,
1054
- 'Failed to decompose thread',
1055
- err.message,
1056
- 'Ensure the thread exists'
1057
- ));
1058
- process.exit(1);
1059
- }
1060
- });
1061
-
1062
- // wg ledger
1063
- wg.command('ledger')
1064
- .description('View coordination history')
1065
- .option('--last <n>', 'Number of entries to show', '20')
1066
- .option('--actor <actor>', 'Filter by actor (use "me" for current agent)')
1067
- .option('--target <target>', 'Filter by target path substring')
1068
- .option('--json', 'Output as JSON')
1069
- .option('-v, --vault <path>', 'Vault path')
1070
- .action(async (opts) => {
1071
- try {
1072
- const vaultPath = resolveVaultPath(opts.vault);
1073
- const { ledger } = await import('../dist/workgraph/index.js');
1074
- let entries = ledger.readAll(vaultPath);
1075
-
1076
- if (opts.actor) {
1077
- const actorFilter = opts.actor === 'me' ? getAgentName() : opts.actor;
1078
- entries = entries.filter(e => e.actor === actorFilter);
1079
- }
1080
-
1081
- if (opts.target) {
1082
- entries = entries.filter(e => e.target.includes(opts.target));
1083
- }
1084
-
1085
- const limit = parseInt(opts.last) || 20;
1086
- entries = entries.slice(-limit);
1087
-
1088
- if (opts.json) {
1089
- console.log(JSON.stringify(entries, null, 2));
1090
- return;
1091
- }
1092
-
1093
- if (entries.length === 0) {
1094
- console.log('');
1095
- console.log(chalk.dim('No ledger entries found.'));
1096
- console.log('');
1097
- return;
1098
- }
1099
-
1100
- console.log('');
1101
- console.log(chalk.bold(`Ledger (last ${entries.length} entries)`));
1102
- console.log(chalk.dim('─'.repeat(80)));
1103
-
1104
- for (const entry of entries.reverse()) {
1105
- const opColorName = OP_COLORS[entry.op] || 'white';
1106
- const opColor = chalk[opColorName] || chalk.white;
1107
- const time = formatRelativeTime(entry.ts);
1108
- const target = truncate(entry.target, 30);
1109
- const actor = entry.actor;
1110
-
1111
- let line = chalk.dim(time.padEnd(10));
1112
- line += opColor(entry.op.toUpperCase().padEnd(10));
1113
- line += chalk.white(target.padEnd(32));
1114
- line += chalk.cyan(`@${actor}`);
1115
-
1116
- console.log(line);
1117
-
1118
- if (entry.data && Object.keys(entry.data).length > 0) {
1119
- const dataStr = JSON.stringify(entry.data);
1120
- console.log(chalk.dim(` ${truncate(dataStr, 68)}`));
1121
- }
1122
- }
1123
-
1124
- console.log(chalk.dim('─'.repeat(80)));
1125
- console.log('');
1126
- } catch (err) {
1127
- console.error(chalk.red(`Error: ${err.message}`));
1128
- process.exit(1);
1129
- }
1130
- });
1131
-
1132
- // wg define
1133
- wg.command('define <type>')
1134
- .description('Define a new primitive type')
1135
- .option('--fields <fields>', 'Comma-separated field definitions (name:type)')
1136
- .option('--dir <directory>', 'Custom directory for instances')
1137
- .option('--description <desc>', 'Type description')
1138
- .option('-v, --vault <path>', 'Vault path')
1139
- .action(async (typeName, opts) => {
1140
- try {
1141
- const vaultPath = resolveVaultPath(opts.vault);
1142
- const { registry } = await import('../dist/workgraph/index.js');
1143
- const agentName = getAgentName();
1144
- const description = opts.description || `Custom type: ${typeName}`;
1145
-
1146
- const fields = {};
1147
- if (opts.fields) {
1148
- const fieldPairs = opts.fields.split(',');
1149
- for (const pair of fieldPairs) {
1150
- const [name, type] = pair.split(':').map(s => s.trim());
1151
- if (name && type) {
1152
- fields[name] = { type };
1153
- }
1154
- }
1155
- }
1156
-
1157
- const typeDef = registry.defineType(
1158
- vaultPath,
1159
- typeName,
1160
- description,
1161
- fields,
1162
- agentName,
1163
- opts.dir
1164
- );
1165
-
1166
- console.log('');
1167
- console.log(chalk.green.bold('✓ Type defined'));
1168
- console.log('');
1169
- console.log(chalk.dim(' Name: ') + chalk.magenta(typeDef.name));
1170
- console.log(chalk.dim(' Directory: ') + chalk.white(typeDef.directory));
1171
- console.log(chalk.dim(' Fields:'));
1172
- for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) {
1173
- const required = fieldDef.required ? chalk.red('*') : ' ';
1174
- console.log(chalk.dim(` ${required} ${fieldName}: ${fieldDef.type}`));
1175
- }
1176
- console.log('');
1177
- console.log(chalk.dim(`Create instances: ${chalk.cyan(`clawvault wg create ${typeDef.name} "title"`)}`));
1178
- console.log('');
1179
- } catch (err) {
1180
- console.error(formatError(chalk,
1181
- 'Failed to define type',
1182
- err.message,
1183
- 'Ensure the type name is unique and not a built-in type'
1184
- ));
1185
- process.exit(1);
1186
- }
1187
- });
1188
-
1189
- // wg types
1190
- wg.command('types')
1191
- .description('List all primitive types with their fields')
1192
- .option('--json', 'Output as JSON')
1193
- .option('-v, --vault <path>', 'Vault path')
1194
- .action(async (opts) => {
1195
- try {
1196
- const vaultPath = resolveVaultPath(opts.vault);
1197
- const { registry } = await import('../dist/workgraph/index.js');
1198
- const types = registry.listTypes(vaultPath);
1199
-
1200
- if (opts.json) {
1201
- console.log(JSON.stringify(types, null, 2));
1202
- return;
1203
- }
1204
-
1205
- console.log('');
1206
- console.log(chalk.bold(`Primitive Types (${types.length})`));
1207
- console.log(chalk.dim('─'.repeat(70)));
1208
-
1209
- for (const typeDef of types) {
1210
- const builtInBadge = typeDef.builtIn ? chalk.cyan(' [built-in]') : chalk.magenta(' [custom]');
1211
- console.log('');
1212
- console.log(chalk.white.bold(typeDef.name) + builtInBadge);
1213
- console.log(chalk.dim(` ${typeDef.description}`));
1214
- console.log(chalk.dim(` Directory: ${typeDef.directory}/`));
1215
- console.log(chalk.dim(' Fields:'));
1216
-
1217
- const fieldEntries = Object.entries(typeDef.fields);
1218
- for (const [fieldName, fieldDef] of fieldEntries) {
1219
- const required = fieldDef.required ? chalk.red('*') : ' ';
1220
- const defaultVal = fieldDef.default !== undefined ? chalk.dim(` = ${JSON.stringify(fieldDef.default)}`) : '';
1221
- const desc = fieldDef.description ? chalk.dim(` — ${fieldDef.description}`) : '';
1222
- console.log(` ${required} ${chalk.cyan(fieldName)}: ${fieldDef.type}${defaultVal}${desc}`);
1223
- }
1224
- }
1225
-
1226
- console.log('');
1227
- console.log(chalk.dim('─'.repeat(70)));
1228
- console.log('');
1229
- } catch (err) {
1230
- console.error(chalk.red(`Error: ${err.message}`));
1231
- process.exit(1);
1232
- }
1233
- });
1234
-
1235
- // wg create
1236
- wg.command('create <type> <title>')
1237
- .description('Create any primitive instance')
1238
- .option('--body <body>', 'Markdown body content')
1239
- .option('-v, --vault <path>', 'Vault path')
1240
- .allowUnknownOption(true)
1241
- .action(async (typeName, title, opts) => {
1242
- try {
1243
- const vaultPath = resolveVaultPath(opts.vault);
1244
- const { store } = await import('../dist/workgraph/index.js');
1245
- const agentName = getAgentName();
1246
- const body = opts.body || '';
1247
-
1248
- const fields = { title };
1249
- const knownOptions = new Set(['body', 'vault']);
1250
- for (const [key, value] of Object.entries(opts)) {
1251
- if (!knownOptions.has(key) && value !== undefined) {
1252
- fields[key] = value;
1253
- }
1254
- }
1255
-
1256
- const inst = store.create(vaultPath, typeName, fields, body, agentName);
1257
-
1258
- console.log('');
1259
- console.log(chalk.green.bold(`✓ ${typeName} created`));
1260
- console.log('');
1261
- console.log(chalk.dim(' Path: ') + chalk.white(inst.path));
1262
- console.log(chalk.dim(' Title: ') + chalk.white(inst.fields.title));
1263
- console.log('');
1264
- } catch (err) {
1265
- console.error(formatError(chalk,
1266
- `Failed to create ${typeName}`,
1267
- err.message,
1268
- `Ensure the type "${typeName}" exists. Run: clawvault wg types`
1269
- ));
1270
- process.exit(1);
1271
- }
1272
- });
1273
-
1274
- // wg board
1275
- wg.command('board')
1276
- .description('Terminal kanban board view')
1277
- .option('--width <width>', 'Terminal width override')
1278
- .option('-v, --vault <path>', 'Vault path')
1279
- .action(async (opts) => {
1280
- try {
1281
- const vaultPath = resolveVaultPath(opts.vault);
1282
- const { store } = await import('../dist/workgraph/index.js');
1283
- const threads = store.list(vaultPath, 'thread');
1284
- const termWidth = opts.width ? parseInt(opts.width) : (process.stdout.columns || 120);
1285
-
1286
- const columns = {
1287
- open: [],
1288
- active: [],
1289
- blocked: [],
1290
- done: [],
1291
- cancelled: [],
1292
- };
1293
-
1294
- for (const t of threads) {
1295
- const status = t.fields.status;
1296
- if (columns[status]) {
1297
- columns[status].push(t);
1298
- }
1299
- }
1300
-
1301
- for (const status of Object.keys(columns)) {
1302
- columns[status] = sortByPriority(columns[status]);
1303
- }
1304
-
1305
- const visibleStatuses = ['open', 'active', 'blocked', 'done'];
1306
- const colWidth = Math.floor((termWidth - visibleStatuses.length - 1) / visibleStatuses.length);
1307
- const cardWidth = colWidth - 4;
1308
-
1309
- console.log('');
1310
- console.log(chalk.bold.cyan('╔' + '═'.repeat(termWidth - 2) + '╗'));
1311
- console.log(chalk.bold.cyan('║') + chalk.bold(' WORKGRAPH BOARD').padEnd(termWidth - 2) + chalk.bold.cyan('║'));
1312
- console.log(chalk.bold.cyan('╚' + '═'.repeat(termWidth - 2) + '╝'));
1313
- console.log('');
1314
-
1315
- let headerLine = '';
1316
- const statusColors = { open: 'cyan', active: 'green', blocked: 'red', done: 'gray', cancelled: 'dim' };
1317
- for (const status of visibleStatuses) {
1318
- const cfg = STATUS_CONFIG[status];
1319
- const header = `${cfg.symbol} ${cfg.label} (${columns[status].length})`;
1320
- const padded = header.padEnd(colWidth);
1321
- const colorFn = chalk[statusColors[status]] || chalk.white;
1322
- headerLine += colorFn(padded);
1323
- }
1324
- console.log(headerLine);
1325
- console.log(chalk.dim('─'.repeat(termWidth)));
1326
-
1327
- const maxRows = Math.max(...visibleStatuses.map(s => columns[s].length), 1);
1328
-
1329
- for (let row = 0; row < Math.min(maxRows, 15); row++) {
1330
- let line = '';
1331
- for (const status of visibleStatuses) {
1332
- const t = columns[status][row];
1333
- if (t) {
1334
- const priority = t.fields.priority || 'medium';
1335
- const title = truncate(String(t.fields.title || t.path), cardWidth - 4);
1336
- const owner = t.fields.owner;
1337
- const priorityCfg = PRIORITY_CONFIG[priority] || PRIORITY_CONFIG.medium;
1338
-
1339
- let card = `${priorityCfg.symbol} ${title}`;
1340
- if (owner) {
1341
- card += chalk.dim(` @${truncate(owner, 8)}`);
1342
- }
1343
- line += card.padEnd(colWidth);
1344
- } else {
1345
- line += ' '.repeat(colWidth);
1346
- }
1347
- }
1348
- console.log(line);
1349
- }
1350
-
1351
- if (maxRows > 15) {
1352
- console.log(chalk.dim(`... and ${maxRows - 15} more rows`));
1353
- }
1354
-
1355
- console.log('');
1356
- console.log(chalk.dim('─'.repeat(termWidth)));
1357
-
1358
- const legendParts = Object.entries(PRIORITY_CONFIG).map(([key, cfg]) =>
1359
- `${cfg.symbol} ${cfg.label}`
1360
- );
1361
- console.log(chalk.dim('Priority: ') + legendParts.join(chalk.dim(' · ')));
1362
- console.log('');
1363
- } catch (err) {
1364
- console.error(chalk.red(`Error: ${err.message}`));
1365
- process.exit(1);
1366
- }
1367
- });
1368
- }