clawvault 2.5.3 → 2.6.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 (95) hide show
  1. package/README.md +159 -159
  2. package/bin/clawvault.js +111 -111
  3. package/bin/command-registration.test.js +166 -166
  4. package/bin/command-runtime.js +93 -93
  5. package/bin/command-runtime.test.js +154 -154
  6. package/bin/help-contract.test.js +39 -39
  7. package/bin/register-config-commands.js +153 -153
  8. package/bin/register-config-route-commands.test.js +121 -121
  9. package/bin/register-core-commands.js +237 -237
  10. package/bin/register-kanban-commands.js +56 -56
  11. package/bin/register-kanban-commands.test.js +83 -83
  12. package/bin/register-maintenance-commands.js +282 -282
  13. package/bin/register-project-commands.js +209 -209
  14. package/bin/register-project-commands.test.js +206 -206
  15. package/bin/register-query-commands.js +317 -317
  16. package/bin/register-query-commands.test.js +65 -65
  17. package/bin/register-resilience-commands.js +182 -182
  18. package/bin/register-resilience-commands.test.js +81 -81
  19. package/bin/register-route-commands.js +114 -114
  20. package/bin/register-session-lifecycle-commands.js +206 -206
  21. package/bin/register-tailscale-commands.js +106 -106
  22. package/bin/register-task-commands.js +348 -348
  23. package/bin/register-task-commands.test.js +69 -69
  24. package/bin/register-template-commands.js +75 -72
  25. package/bin/register-template-commands.test.js +87 -0
  26. package/bin/register-vault-operations-commands.js +300 -300
  27. package/bin/test-helpers/cli-command-fixtures.js +119 -119
  28. package/dashboard/lib/graph-diff.js +104 -104
  29. package/dashboard/lib/graph-diff.test.js +75 -75
  30. package/dashboard/lib/vault-parser.js +556 -556
  31. package/dashboard/lib/vault-parser.test.js +254 -254
  32. package/dashboard/public/app.js +796 -796
  33. package/dashboard/public/index.html +52 -52
  34. package/dashboard/public/styles.css +221 -221
  35. package/dashboard/server.js +374 -374
  36. package/dist/{chunk-J5EMBUPK.js → chunk-4OXMU5S2.js} +1 -1
  37. package/dist/{chunk-3FP5BJ42.js → chunk-4QYGFWRM.js} +1 -1
  38. package/dist/{chunk-4IV3R2F5.js → chunk-4TE4JMLA.js} +1 -1
  39. package/dist/{chunk-5GZFTAL7.js → chunk-AZYOKJYC.js} +128 -42
  40. package/dist/{chunk-FG6RJMCN.js → chunk-HA5M6KJB.js} +4 -4
  41. package/dist/{chunk-IZEY5S74.js → chunk-IEVLHNLU.js} +1 -1
  42. package/dist/{chunk-CLE2HHNT.js → chunk-IVRIKYFE.js} +18 -11
  43. package/dist/{chunk-AY4PGUVL.js → chunk-KL4NAOMO.js} +1 -1
  44. package/dist/{chunk-O7XHXF7F.js → chunk-MAKNAHAW.js} +4 -4
  45. package/dist/{chunk-OSMS7QIG.js → chunk-ME37YNW3.js} +2 -2
  46. package/dist/chunk-MFAWT5O5.js +301 -0
  47. package/dist/{chunk-TPDH3JPP.js → chunk-PBEE567J.js} +1 -1
  48. package/dist/{chunk-S2IG7VNM.js → chunk-Q2J5YTUF.js} +2 -2
  49. package/dist/{chunk-IOALNTAN.js → chunk-QWQ3TIKS.js} +103 -29
  50. package/dist/{chunk-YCVDVI5B.js → chunk-R2MIW5G7.js} +1 -1
  51. package/dist/{chunk-M25QVSJM.js → chunk-RVYA52PY.js} +1 -1
  52. package/dist/{chunk-NZ4ZZNSR.js → chunk-THRJVD4L.js} +1 -1
  53. package/dist/{chunk-4GBPTBFJ.js → chunk-TIGW564L.js} +1 -1
  54. package/dist/{chunk-LMEMZGUV.js → chunk-UEOUADMO.js} +3 -3
  55. package/dist/{chunk-GFJ3LIIB.js → chunk-XAVB4GB4.js} +1 -1
  56. package/dist/cli/index.js +15 -13
  57. package/dist/commands/backlog.js +3 -1
  58. package/dist/commands/blocked.js +3 -1
  59. package/dist/commands/canvas.js +3 -1
  60. package/dist/commands/context.js +3 -3
  61. package/dist/commands/doctor.js +9 -7
  62. package/dist/commands/embed.js +2 -2
  63. package/dist/commands/kanban.js +4 -2
  64. package/dist/commands/observe.js +7 -5
  65. package/dist/commands/project.js +5 -3
  66. package/dist/commands/rebuild.js +6 -4
  67. package/dist/commands/replay.js +6 -4
  68. package/dist/commands/setup.js +2 -2
  69. package/dist/commands/sleep.js +7 -5
  70. package/dist/commands/status.js +8 -6
  71. package/dist/commands/tailscale.js +3 -3
  72. package/dist/commands/task.js +4 -2
  73. package/dist/commands/template.d.ts +10 -1
  74. package/dist/commands/template.js +47 -55
  75. package/dist/commands/wake.js +2 -2
  76. package/dist/index.js +23 -22
  77. package/dist/lib/project-utils.js +4 -2
  78. package/dist/lib/tailscale.js +2 -2
  79. package/dist/lib/task-utils.d.ts +14 -13
  80. package/dist/lib/task-utils.js +3 -1
  81. package/dist/lib/template-engine.d.ts +1 -0
  82. package/dist/lib/webdav.js +1 -1
  83. package/hooks/clawvault/HOOK.md +83 -83
  84. package/hooks/clawvault/handler.js +816 -816
  85. package/hooks/clawvault/handler.test.js +263 -263
  86. package/package.json +94 -94
  87. package/templates/checkpoint.md +34 -19
  88. package/templates/daily-note.md +34 -19
  89. package/templates/daily.md +34 -19
  90. package/templates/decision.md +39 -17
  91. package/templates/handoff.md +34 -19
  92. package/templates/lesson.md +31 -16
  93. package/templates/person.md +37 -19
  94. package/templates/project.md +84 -23
  95. package/templates/task.md +81 -0
@@ -1,114 +1,114 @@
1
- /**
2
- * Route command registrations for custom observation entity routing.
3
- */
4
-
5
- function printRoutesTable(routes) {
6
- if (routes.length === 0) {
7
- console.log('No custom routes configured.');
8
- return;
9
- }
10
-
11
- const priorityWidth = Math.max(
12
- 'PRIORITY'.length,
13
- ...routes.map((route) => String(route.priority).length)
14
- );
15
- const patternWidth = Math.max(
16
- 'PATTERN'.length,
17
- ...routes.map((route) => route.pattern.length)
18
- );
19
- const targetWidth = Math.max(
20
- 'TARGET'.length,
21
- ...routes.map((route) => route.target.length)
22
- );
23
-
24
- const header = `${'PRIORITY'.padEnd(priorityWidth)} ${'PATTERN'.padEnd(patternWidth)} ${'TARGET'.padEnd(targetWidth)}`;
25
- const divider = `${'-'.repeat(priorityWidth)} ${'-'.repeat(patternWidth)} ${'-'.repeat(targetWidth)}`;
26
-
27
- console.log(header);
28
- console.log(divider);
29
- for (const route of routes) {
30
- console.log(
31
- `${String(route.priority).padEnd(priorityWidth)} ${route.pattern.padEnd(patternWidth)} ${route.target}`
32
- );
33
- }
34
- }
35
-
36
- export function registerRouteCommands(program, { chalk, resolveVaultPath }) {
37
- const route = program
38
- .command('route')
39
- .description('Manage custom observation routing rules');
40
-
41
- route
42
- .command('list')
43
- .description('List custom routing rules')
44
- .option('-v, --vault <path>', 'Vault path')
45
- .action(async (options) => {
46
- try {
47
- const { listRouteRules } = await import('../dist/index.js');
48
- const rules = listRouteRules(resolveVaultPath(options.vault));
49
- printRoutesTable(rules);
50
- } catch (err) {
51
- console.error(chalk.red(`Error: ${err.message}`));
52
- process.exit(1);
53
- }
54
- });
55
-
56
- route
57
- .command('add <pattern> <target>')
58
- .description('Add a custom routing rule')
59
- .option('-v, --vault <path>', 'Vault path')
60
- .action(async (pattern, target, options) => {
61
- try {
62
- const { addRouteRule } = await import('../dist/index.js');
63
- const rule = addRouteRule(resolveVaultPath(options.vault), pattern, target);
64
- console.log(chalk.green('✓ Route added'));
65
- console.log(chalk.dim(` Pattern: ${rule.pattern}`));
66
- console.log(chalk.dim(` Target: ${rule.target}`));
67
- console.log(chalk.dim(` Priority: ${rule.priority}`));
68
- } catch (err) {
69
- console.error(chalk.red(`Error: ${err.message}`));
70
- process.exit(1);
71
- }
72
- });
73
-
74
- route
75
- .command('remove <pattern>')
76
- .description('Remove a custom routing rule by pattern')
77
- .option('-v, --vault <path>', 'Vault path')
78
- .action(async (pattern, options) => {
79
- try {
80
- const { removeRouteRule } = await import('../dist/index.js');
81
- const removed = removeRouteRule(resolveVaultPath(options.vault), pattern);
82
- if (!removed) {
83
- console.log(chalk.yellow(`No route found for pattern: ${pattern}`));
84
- return;
85
- }
86
- console.log(chalk.green(`✓ Removed route: ${pattern}`));
87
- } catch (err) {
88
- console.error(chalk.red(`Error: ${err.message}`));
89
- process.exit(1);
90
- }
91
- });
92
-
93
- route
94
- .command('test <text>')
95
- .description('Test custom routes against text')
96
- .option('-v, --vault <path>', 'Vault path')
97
- .action(async (text, options) => {
98
- try {
99
- const { testRouteRule } = await import('../dist/index.js');
100
- const match = testRouteRule(resolveVaultPath(options.vault), text);
101
- if (!match) {
102
- console.log('No route matched.');
103
- return;
104
- }
105
- console.log(chalk.green('✓ Route matched'));
106
- console.log(chalk.dim(` Pattern: ${match.pattern}`));
107
- console.log(chalk.dim(` Target: ${match.target}`));
108
- console.log(chalk.dim(` Priority: ${match.priority}`));
109
- } catch (err) {
110
- console.error(chalk.red(`Error: ${err.message}`));
111
- process.exit(1);
112
- }
113
- });
114
- }
1
+ /**
2
+ * Route command registrations for custom observation entity routing.
3
+ */
4
+
5
+ function printRoutesTable(routes) {
6
+ if (routes.length === 0) {
7
+ console.log('No custom routes configured.');
8
+ return;
9
+ }
10
+
11
+ const priorityWidth = Math.max(
12
+ 'PRIORITY'.length,
13
+ ...routes.map((route) => String(route.priority).length)
14
+ );
15
+ const patternWidth = Math.max(
16
+ 'PATTERN'.length,
17
+ ...routes.map((route) => route.pattern.length)
18
+ );
19
+ const targetWidth = Math.max(
20
+ 'TARGET'.length,
21
+ ...routes.map((route) => route.target.length)
22
+ );
23
+
24
+ const header = `${'PRIORITY'.padEnd(priorityWidth)} ${'PATTERN'.padEnd(patternWidth)} ${'TARGET'.padEnd(targetWidth)}`;
25
+ const divider = `${'-'.repeat(priorityWidth)} ${'-'.repeat(patternWidth)} ${'-'.repeat(targetWidth)}`;
26
+
27
+ console.log(header);
28
+ console.log(divider);
29
+ for (const route of routes) {
30
+ console.log(
31
+ `${String(route.priority).padEnd(priorityWidth)} ${route.pattern.padEnd(patternWidth)} ${route.target}`
32
+ );
33
+ }
34
+ }
35
+
36
+ export function registerRouteCommands(program, { chalk, resolveVaultPath }) {
37
+ const route = program
38
+ .command('route')
39
+ .description('Manage custom observation routing rules');
40
+
41
+ route
42
+ .command('list')
43
+ .description('List custom routing rules')
44
+ .option('-v, --vault <path>', 'Vault path')
45
+ .action(async (options) => {
46
+ try {
47
+ const { listRouteRules } = await import('../dist/index.js');
48
+ const rules = listRouteRules(resolveVaultPath(options.vault));
49
+ printRoutesTable(rules);
50
+ } catch (err) {
51
+ console.error(chalk.red(`Error: ${err.message}`));
52
+ process.exit(1);
53
+ }
54
+ });
55
+
56
+ route
57
+ .command('add <pattern> <target>')
58
+ .description('Add a custom routing rule')
59
+ .option('-v, --vault <path>', 'Vault path')
60
+ .action(async (pattern, target, options) => {
61
+ try {
62
+ const { addRouteRule } = await import('../dist/index.js');
63
+ const rule = addRouteRule(resolveVaultPath(options.vault), pattern, target);
64
+ console.log(chalk.green('✓ Route added'));
65
+ console.log(chalk.dim(` Pattern: ${rule.pattern}`));
66
+ console.log(chalk.dim(` Target: ${rule.target}`));
67
+ console.log(chalk.dim(` Priority: ${rule.priority}`));
68
+ } catch (err) {
69
+ console.error(chalk.red(`Error: ${err.message}`));
70
+ process.exit(1);
71
+ }
72
+ });
73
+
74
+ route
75
+ .command('remove <pattern>')
76
+ .description('Remove a custom routing rule by pattern')
77
+ .option('-v, --vault <path>', 'Vault path')
78
+ .action(async (pattern, options) => {
79
+ try {
80
+ const { removeRouteRule } = await import('../dist/index.js');
81
+ const removed = removeRouteRule(resolveVaultPath(options.vault), pattern);
82
+ if (!removed) {
83
+ console.log(chalk.yellow(`No route found for pattern: ${pattern}`));
84
+ return;
85
+ }
86
+ console.log(chalk.green(`✓ Removed route: ${pattern}`));
87
+ } catch (err) {
88
+ console.error(chalk.red(`Error: ${err.message}`));
89
+ process.exit(1);
90
+ }
91
+ });
92
+
93
+ route
94
+ .command('test <text>')
95
+ .description('Test custom routes against text')
96
+ .option('-v, --vault <path>', 'Vault path')
97
+ .action(async (text, options) => {
98
+ try {
99
+ const { testRouteRule } = await import('../dist/index.js');
100
+ const match = testRouteRule(resolveVaultPath(options.vault), text);
101
+ if (!match) {
102
+ console.log('No route matched.');
103
+ return;
104
+ }
105
+ console.log(chalk.green('✓ Route matched'));
106
+ console.log(chalk.dim(` Pattern: ${match.pattern}`));
107
+ console.log(chalk.dim(` Target: ${match.target}`));
108
+ console.log(chalk.dim(` Priority: ${match.priority}`));
109
+ } catch (err) {
110
+ console.error(chalk.red(`Error: ${err.message}`));
111
+ process.exit(1);
112
+ }
113
+ });
114
+ }
@@ -1,206 +1,206 @@
1
- /**
2
- * Session lifecycle command registrations (wake/sleep/handoff/recap).
3
- */
4
-
5
- export function registerSessionLifecycleCommands(
6
- program,
7
- { chalk, resolveVaultPath, QmdUnavailableError, printQmdMissing, getVault, runQmd }
8
- ) {
9
- // === WAKE (session start) ===
10
- program
11
- .command('wake')
12
- .description('Start a session (recover + recap + summary)')
13
- .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include (default: 3)', '3')
14
- .option('--full', 'Show full recap (default: brief)')
15
- .option('-v, --vault <path>', 'Vault path')
16
- .action(async (options) => {
17
- try {
18
- const vaultPath = resolveVaultPath(options.vault);
19
- const { wake } = await import('../dist/commands/wake.js');
20
- const { formatRecoveryInfo } = await import('../dist/commands/recover.js');
21
- const result = await wake({
22
- vaultPath,
23
- handoffLimit: parseInt(options.handoffLimit, 10),
24
- brief: !options.full
25
- });
26
-
27
- console.log(chalk.cyan('\n🌅 ClawVault Wake\n'));
28
- console.log(formatRecoveryInfo(result.recovery));
29
- console.log();
30
- console.log(chalk.cyan('Recap'));
31
- console.log(result.recapMarkdown.trim());
32
- console.log();
33
- console.log(chalk.green(`You were working on: ${result.summary}`));
34
-
35
- process.exitCode = result.recovery.died ? 1 : 0;
36
- } catch (err) {
37
- if (err instanceof QmdUnavailableError) {
38
- printQmdMissing();
39
- process.exit(1);
40
- }
41
- console.error(chalk.red(`Error: ${err.message}`));
42
- process.exit(1);
43
- }
44
- });
45
-
46
- // === SLEEP (session end) ===
47
- program
48
- .command('sleep <summary>')
49
- .description('End a session with a handoff (and optional git commit)')
50
- .option('-n, --next <items>', 'Next steps (comma-separated)')
51
- .option('-b, --blocked <items>', 'Blocked items (comma-separated)')
52
- .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
53
- .option('-q, --questions <items>', 'Open questions (comma-separated)')
54
- .option('-f, --feeling <state>', 'Emotional/energy state')
55
- .option('-s, --session <key>', 'Session key')
56
- .option('--session-transcript <path>', 'Session transcript path for auto-observe')
57
- .option('--reflect', 'Run weekly reflection pass after sleep handoff')
58
- .option('--index', 'Update qmd index after handoff (default: disabled)')
59
- .option('--no-git', 'Skip git commit prompt')
60
- .option('-v, --vault <path>', 'Vault path')
61
- .action(async (summary, options) => {
62
- try {
63
- const vaultPath = resolveVaultPath(options.vault);
64
- const { sleep } = await import('../dist/commands/sleep.js');
65
- const result = await sleep({
66
- workingOn: summary,
67
- next: options.next,
68
- blocked: options.blocked,
69
- decisions: options.decisions,
70
- questions: options.questions,
71
- feeling: options.feeling,
72
- sessionKey: options.session,
73
- sessionTranscript: options.sessionTranscript,
74
- reflect: options.reflect,
75
- vaultPath,
76
- index: options.index,
77
- git: options.git
78
- });
79
-
80
- console.log(chalk.green(`✓ Handoff saved: ${result.document.id}`));
81
- console.log(chalk.dim(` Path: ${result.document.path}`));
82
- console.log(chalk.dim(` Working on: ${result.handoff.workingOn.join(', ')}`));
83
- if (result.handoff.nextSteps.length > 0) {
84
- console.log(chalk.dim(` Next: ${result.handoff.nextSteps.join(', ')}`));
85
- } else {
86
- console.log(chalk.dim(' Next: (none)'));
87
- }
88
- if (result.handoff.blocked.length > 0) {
89
- console.log(chalk.dim(` Blocked: ${result.handoff.blocked.join(', ')}`));
90
- } else {
91
- console.log(chalk.dim(' Blocked: (none)'));
92
- }
93
- if (result.handoff.decisions?.length) {
94
- console.log(chalk.dim(` Decisions: ${result.handoff.decisions.join(', ')}`));
95
- }
96
- if (result.handoff.openQuestions?.length) {
97
- console.log(chalk.dim(` Questions: ${result.handoff.openQuestions.join(', ')}`));
98
- }
99
- if (result.handoff.feeling) {
100
- console.log(chalk.dim(` Feeling: ${result.handoff.feeling}`));
101
- }
102
- if (options.index) {
103
- console.log(chalk.dim(' qmd: index updated'));
104
- }
105
- if (result.git) {
106
- if (result.git.committed) {
107
- console.log(chalk.green(`✓ Git commit created${result.git.message ? `: ${result.git.message}` : ''}`));
108
- } else if (result.git.skippedReason === 'clean') {
109
- console.log(chalk.dim(' Git: clean'));
110
- } else if (result.git.skippedReason === 'declined') {
111
- console.log(chalk.dim(' Git: commit skipped'));
112
- }
113
- }
114
- if (result.observationRoutingSummary) {
115
- console.log(chalk.dim(` Observe: ${result.observationRoutingSummary}`));
116
- }
117
- } catch (err) {
118
- if (err instanceof QmdUnavailableError) {
119
- printQmdMissing();
120
- process.exit(1);
121
- }
122
- console.error(chalk.red(`Error: ${err.message}`));
123
- process.exit(1);
124
- }
125
- });
126
-
127
- // === HANDOFF (session bridge) ===
128
- program
129
- .command('handoff')
130
- .description('Create a session handoff document')
131
- .requiredOption('-w, --working-on <items>', 'What I was working on (comma-separated)')
132
- .option('-b, --blocked <items>', 'What is blocked (comma-separated)')
133
- .option('-n, --next <items>', 'What comes next (comma-separated)')
134
- .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
135
- .option('-q, --questions <items>', 'Open questions (comma-separated)')
136
- .option('-f, --feeling <state>', 'Emotional/energy state')
137
- .option('-s, --session <key>', 'Session key')
138
- .option('-v, --vault <path>', 'Vault path')
139
- .option('--no-index', 'Skip qmd index update (auto-updates by default)')
140
- .option('--json', 'Output as JSON')
141
- .action(async (options) => {
142
- try {
143
- const vault = await getVault(options.vault);
144
-
145
- const handoff = {
146
- workingOn: options.workingOn.split(',').map((item) => item.trim()),
147
- blocked: options.blocked ? options.blocked.split(',').map((item) => item.trim()) : [],
148
- nextSteps: options.next ? options.next.split(',').map((item) => item.trim()) : [],
149
- decisions: options.decisions ? options.decisions.split(',').map((item) => item.trim()) : undefined,
150
- openQuestions: options.questions ? options.questions.split(',').map((item) => item.trim()) : undefined,
151
- feeling: options.feeling,
152
- sessionKey: options.session
153
- };
154
-
155
- const doc = await vault.createHandoff(handoff);
156
-
157
- if (!options.json) {
158
- console.log(chalk.green(`✓ Handoff created: ${doc.id}`));
159
- console.log(chalk.dim(` Path: ${doc.path}`));
160
- }
161
-
162
- if (options.index !== false) {
163
- const collection = vault.getQmdCollection();
164
- await runQmd(collection ? ['update', '-c', collection] : ['update']);
165
- }
166
-
167
- if (options.json) {
168
- console.log(JSON.stringify({ id: doc.id, path: doc.path, handoff }, null, 2));
169
- }
170
- } catch (err) {
171
- console.error(chalk.red(`Error: ${err.message}`));
172
- process.exit(1);
173
- }
174
- });
175
-
176
- // === RECAP (session bootstrap) ===
177
- program
178
- .command('recap')
179
- .description('Generate a session recap - who I was (bootstrap hook)')
180
- .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include (default: 3)', '3')
181
- .option('-v, --vault <path>', 'Vault path')
182
- .option('--json', 'Output as JSON')
183
- .option('--markdown', 'Output as markdown (default)')
184
- .option('--brief', 'Minimal output for token savings')
185
- .action(async (options) => {
186
- try {
187
- const vault = await getVault(options.vault);
188
-
189
- const recap = await vault.generateRecap({
190
- handoffLimit: parseInt(options.handoffLimit, 10),
191
- brief: options.brief
192
- });
193
-
194
- if (options.json) {
195
- console.log(JSON.stringify(recap, null, 2));
196
- return;
197
- }
198
-
199
- const markdown = vault.formatRecap(recap, { brief: options.brief });
200
- console.log(markdown);
201
- } catch (err) {
202
- console.error(chalk.red(`Error: ${err.message}`));
203
- process.exit(1);
204
- }
205
- });
206
- }
1
+ /**
2
+ * Session lifecycle command registrations (wake/sleep/handoff/recap).
3
+ */
4
+
5
+ export function registerSessionLifecycleCommands(
6
+ program,
7
+ { chalk, resolveVaultPath, QmdUnavailableError, printQmdMissing, getVault, runQmd }
8
+ ) {
9
+ // === WAKE (session start) ===
10
+ program
11
+ .command('wake')
12
+ .description('Start a session (recover + recap + summary)')
13
+ .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include (default: 3)', '3')
14
+ .option('--full', 'Show full recap (default: brief)')
15
+ .option('-v, --vault <path>', 'Vault path')
16
+ .action(async (options) => {
17
+ try {
18
+ const vaultPath = resolveVaultPath(options.vault);
19
+ const { wake } = await import('../dist/commands/wake.js');
20
+ const { formatRecoveryInfo } = await import('../dist/commands/recover.js');
21
+ const result = await wake({
22
+ vaultPath,
23
+ handoffLimit: parseInt(options.handoffLimit, 10),
24
+ brief: !options.full
25
+ });
26
+
27
+ console.log(chalk.cyan('\n🌅 ClawVault Wake\n'));
28
+ console.log(formatRecoveryInfo(result.recovery));
29
+ console.log();
30
+ console.log(chalk.cyan('Recap'));
31
+ console.log(result.recapMarkdown.trim());
32
+ console.log();
33
+ console.log(chalk.green(`You were working on: ${result.summary}`));
34
+
35
+ process.exitCode = result.recovery.died ? 1 : 0;
36
+ } catch (err) {
37
+ if (err instanceof QmdUnavailableError) {
38
+ printQmdMissing();
39
+ process.exit(1);
40
+ }
41
+ console.error(chalk.red(`Error: ${err.message}`));
42
+ process.exit(1);
43
+ }
44
+ });
45
+
46
+ // === SLEEP (session end) ===
47
+ program
48
+ .command('sleep <summary>')
49
+ .description('End a session with a handoff (and optional git commit)')
50
+ .option('-n, --next <items>', 'Next steps (comma-separated)')
51
+ .option('-b, --blocked <items>', 'Blocked items (comma-separated)')
52
+ .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
53
+ .option('-q, --questions <items>', 'Open questions (comma-separated)')
54
+ .option('-f, --feeling <state>', 'Emotional/energy state')
55
+ .option('-s, --session <key>', 'Session key')
56
+ .option('--session-transcript <path>', 'Session transcript path for auto-observe')
57
+ .option('--reflect', 'Run weekly reflection pass after sleep handoff')
58
+ .option('--index', 'Update qmd index after handoff (default: disabled)')
59
+ .option('--no-git', 'Skip git commit prompt')
60
+ .option('-v, --vault <path>', 'Vault path')
61
+ .action(async (summary, options) => {
62
+ try {
63
+ const vaultPath = resolveVaultPath(options.vault);
64
+ const { sleep } = await import('../dist/commands/sleep.js');
65
+ const result = await sleep({
66
+ workingOn: summary,
67
+ next: options.next,
68
+ blocked: options.blocked,
69
+ decisions: options.decisions,
70
+ questions: options.questions,
71
+ feeling: options.feeling,
72
+ sessionKey: options.session,
73
+ sessionTranscript: options.sessionTranscript,
74
+ reflect: options.reflect,
75
+ vaultPath,
76
+ index: options.index,
77
+ git: options.git
78
+ });
79
+
80
+ console.log(chalk.green(`✓ Handoff saved: ${result.document.id}`));
81
+ console.log(chalk.dim(` Path: ${result.document.path}`));
82
+ console.log(chalk.dim(` Working on: ${result.handoff.workingOn.join(', ')}`));
83
+ if (result.handoff.nextSteps.length > 0) {
84
+ console.log(chalk.dim(` Next: ${result.handoff.nextSteps.join(', ')}`));
85
+ } else {
86
+ console.log(chalk.dim(' Next: (none)'));
87
+ }
88
+ if (result.handoff.blocked.length > 0) {
89
+ console.log(chalk.dim(` Blocked: ${result.handoff.blocked.join(', ')}`));
90
+ } else {
91
+ console.log(chalk.dim(' Blocked: (none)'));
92
+ }
93
+ if (result.handoff.decisions?.length) {
94
+ console.log(chalk.dim(` Decisions: ${result.handoff.decisions.join(', ')}`));
95
+ }
96
+ if (result.handoff.openQuestions?.length) {
97
+ console.log(chalk.dim(` Questions: ${result.handoff.openQuestions.join(', ')}`));
98
+ }
99
+ if (result.handoff.feeling) {
100
+ console.log(chalk.dim(` Feeling: ${result.handoff.feeling}`));
101
+ }
102
+ if (options.index) {
103
+ console.log(chalk.dim(' qmd: index updated'));
104
+ }
105
+ if (result.git) {
106
+ if (result.git.committed) {
107
+ console.log(chalk.green(`✓ Git commit created${result.git.message ? `: ${result.git.message}` : ''}`));
108
+ } else if (result.git.skippedReason === 'clean') {
109
+ console.log(chalk.dim(' Git: clean'));
110
+ } else if (result.git.skippedReason === 'declined') {
111
+ console.log(chalk.dim(' Git: commit skipped'));
112
+ }
113
+ }
114
+ if (result.observationRoutingSummary) {
115
+ console.log(chalk.dim(` Observe: ${result.observationRoutingSummary}`));
116
+ }
117
+ } catch (err) {
118
+ if (err instanceof QmdUnavailableError) {
119
+ printQmdMissing();
120
+ process.exit(1);
121
+ }
122
+ console.error(chalk.red(`Error: ${err.message}`));
123
+ process.exit(1);
124
+ }
125
+ });
126
+
127
+ // === HANDOFF (session bridge) ===
128
+ program
129
+ .command('handoff')
130
+ .description('Create a session handoff document')
131
+ .requiredOption('-w, --working-on <items>', 'What I was working on (comma-separated)')
132
+ .option('-b, --blocked <items>', 'What is blocked (comma-separated)')
133
+ .option('-n, --next <items>', 'What comes next (comma-separated)')
134
+ .option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
135
+ .option('-q, --questions <items>', 'Open questions (comma-separated)')
136
+ .option('-f, --feeling <state>', 'Emotional/energy state')
137
+ .option('-s, --session <key>', 'Session key')
138
+ .option('-v, --vault <path>', 'Vault path')
139
+ .option('--no-index', 'Skip qmd index update (auto-updates by default)')
140
+ .option('--json', 'Output as JSON')
141
+ .action(async (options) => {
142
+ try {
143
+ const vault = await getVault(options.vault);
144
+
145
+ const handoff = {
146
+ workingOn: options.workingOn.split(',').map((item) => item.trim()),
147
+ blocked: options.blocked ? options.blocked.split(',').map((item) => item.trim()) : [],
148
+ nextSteps: options.next ? options.next.split(',').map((item) => item.trim()) : [],
149
+ decisions: options.decisions ? options.decisions.split(',').map((item) => item.trim()) : undefined,
150
+ openQuestions: options.questions ? options.questions.split(',').map((item) => item.trim()) : undefined,
151
+ feeling: options.feeling,
152
+ sessionKey: options.session
153
+ };
154
+
155
+ const doc = await vault.createHandoff(handoff);
156
+
157
+ if (!options.json) {
158
+ console.log(chalk.green(`✓ Handoff created: ${doc.id}`));
159
+ console.log(chalk.dim(` Path: ${doc.path}`));
160
+ }
161
+
162
+ if (options.index !== false) {
163
+ const collection = vault.getQmdCollection();
164
+ await runQmd(collection ? ['update', '-c', collection] : ['update']);
165
+ }
166
+
167
+ if (options.json) {
168
+ console.log(JSON.stringify({ id: doc.id, path: doc.path, handoff }, null, 2));
169
+ }
170
+ } catch (err) {
171
+ console.error(chalk.red(`Error: ${err.message}`));
172
+ process.exit(1);
173
+ }
174
+ });
175
+
176
+ // === RECAP (session bootstrap) ===
177
+ program
178
+ .command('recap')
179
+ .description('Generate a session recap - who I was (bootstrap hook)')
180
+ .option('-n, --handoff-limit <n>', 'Number of recent handoffs to include (default: 3)', '3')
181
+ .option('-v, --vault <path>', 'Vault path')
182
+ .option('--json', 'Output as JSON')
183
+ .option('--markdown', 'Output as markdown (default)')
184
+ .option('--brief', 'Minimal output for token savings')
185
+ .action(async (options) => {
186
+ try {
187
+ const vault = await getVault(options.vault);
188
+
189
+ const recap = await vault.generateRecap({
190
+ handoffLimit: parseInt(options.handoffLimit, 10),
191
+ brief: options.brief
192
+ });
193
+
194
+ if (options.json) {
195
+ console.log(JSON.stringify(recap, null, 2));
196
+ return;
197
+ }
198
+
199
+ const markdown = vault.formatRecap(recap, { brief: options.brief });
200
+ console.log(markdown);
201
+ } catch (err) {
202
+ console.error(chalk.red(`Error: ${err.message}`));
203
+ process.exit(1);
204
+ }
205
+ });
206
+ }