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.
- package/README.md +135 -1
- package/bin/clawvault.js +51 -1252
- package/bin/command-registration.test.js +148 -0
- package/bin/command-runtime.js +42 -0
- package/bin/command-runtime.test.js +102 -0
- package/bin/help-contract.test.js +23 -0
- package/bin/register-core-commands.js +139 -0
- package/bin/register-maintenance-commands.js +137 -0
- package/bin/register-query-commands.js +225 -0
- package/bin/register-resilience-commands.js +147 -0
- package/bin/register-session-lifecycle-commands.js +204 -0
- package/bin/register-template-commands.js +72 -0
- package/bin/register-vault-operations-commands.js +295 -0
- package/bin/test-helpers/cli-command-fixtures.js +94 -0
- package/dashboard/lib/graph-diff.js +3 -1
- package/dashboard/lib/graph-diff.test.js +19 -0
- package/dashboard/lib/vault-parser.js +330 -26
- package/dashboard/lib/vault-parser.test.js +191 -11
- package/dashboard/public/app.js +22 -9
- package/dist/chunk-MXSSG3QU.js +42 -0
- package/dist/chunk-O5V7SD5C.js +398 -0
- package/dist/chunk-PAYUH64O.js +284 -0
- package/dist/{chunk-3HFB7EMU.js → chunk-QFBKWDYR.js} +12 -0
- package/dist/{chunk-UBRYOIII.js → chunk-TBVI4N53.js} +210 -21
- package/dist/chunk-TXO34J3O.js +56 -0
- package/dist/commands/compat.d.ts +28 -0
- package/dist/commands/compat.js +10 -0
- package/dist/commands/context.d.ts +2 -33
- package/dist/commands/context.js +3 -2
- package/dist/commands/doctor.js +61 -3
- package/dist/commands/entities.d.ts +1 -0
- package/dist/commands/entities.js +4 -4
- package/dist/commands/graph.d.ts +21 -0
- package/dist/commands/graph.js +10 -0
- package/dist/commands/link.d.ts +1 -0
- package/dist/commands/link.js +14 -5
- package/dist/commands/sleep.js +7 -6
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +63 -3
- package/dist/commands/wake.js +5 -4
- package/dist/context-COo8oq1k.d.ts +45 -0
- package/dist/index.d.ts +63 -2
- package/dist/index.js +53 -15
- package/dist/lib/config.d.ts +6 -1
- package/dist/lib/config.js +7 -3
- package/hooks/clawvault/HOOK.md +6 -1
- package/hooks/clawvault/handler.js +44 -3
- package/hooks/clawvault/handler.test.js +161 -0
- package/package.json +34 -2
- package/dashboard/public/graph.js +0 -376
- package/dashboard/public/style.css +0 -154
- package/dist/chunk-4KDZZW4X.js +0 -13
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query and context command registrations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function registerQueryCommands(
|
|
6
|
+
program,
|
|
7
|
+
{
|
|
8
|
+
chalk,
|
|
9
|
+
getVault,
|
|
10
|
+
resolveVaultPath,
|
|
11
|
+
QmdUnavailableError,
|
|
12
|
+
printQmdMissing
|
|
13
|
+
}
|
|
14
|
+
) {
|
|
15
|
+
// === SEARCH ===
|
|
16
|
+
program
|
|
17
|
+
.command('search <query>')
|
|
18
|
+
.description('Search the vault via qmd (BM25)')
|
|
19
|
+
.option('-n, --limit <n>', 'Max results', '10')
|
|
20
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
21
|
+
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
22
|
+
.option('--recent', 'Boost recent documents')
|
|
23
|
+
.option('--full', 'Include full content in results')
|
|
24
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
25
|
+
.option('--json', 'Output as JSON')
|
|
26
|
+
.action(async (query, options) => {
|
|
27
|
+
try {
|
|
28
|
+
const vault = await getVault(options.vault);
|
|
29
|
+
|
|
30
|
+
const results = await vault.find(query, {
|
|
31
|
+
limit: parseInt(options.limit, 10),
|
|
32
|
+
category: options.category,
|
|
33
|
+
tags: options.tags?.split(',').map((value) => value.trim()),
|
|
34
|
+
fullContent: options.full,
|
|
35
|
+
temporalBoost: options.recent
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(JSON.stringify(results, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (results.length === 0) {
|
|
44
|
+
console.log(chalk.yellow('No results found.'));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
console.log(chalk.cyan(`\n🔍 Found ${results.length} result(s) for "${query}":\n`));
|
|
49
|
+
|
|
50
|
+
for (const result of results) {
|
|
51
|
+
const scoreBar = '█'.repeat(Math.round(result.score * 10)).padEnd(10, '░');
|
|
52
|
+
console.log(chalk.green(`📄 ${result.document.title}`));
|
|
53
|
+
console.log(chalk.dim(` ${result.document.category}/${result.document.id.split('/').pop()}`));
|
|
54
|
+
console.log(chalk.dim(` Score: ${scoreBar} ${(result.score * 100).toFixed(0)}%`));
|
|
55
|
+
if (result.snippet) {
|
|
56
|
+
console.log(chalk.white(` ${result.snippet.split('\n')[0].slice(0, 80)}...`));
|
|
57
|
+
}
|
|
58
|
+
console.log();
|
|
59
|
+
}
|
|
60
|
+
} catch (err) {
|
|
61
|
+
if (err instanceof QmdUnavailableError) {
|
|
62
|
+
printQmdMissing();
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// === VSEARCH ===
|
|
71
|
+
program
|
|
72
|
+
.command('vsearch <query>')
|
|
73
|
+
.description('Semantic search via qmd (requires qmd installed)')
|
|
74
|
+
.option('-n, --limit <n>', 'Max results', '5')
|
|
75
|
+
.option('-c, --category <category>', 'Filter by category')
|
|
76
|
+
.option('--tags <tags>', 'Filter by tags (comma-separated)')
|
|
77
|
+
.option('--recent', 'Boost recent documents')
|
|
78
|
+
.option('--full', 'Include full content in results')
|
|
79
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
80
|
+
.option('--json', 'Output as JSON')
|
|
81
|
+
.action(async (query, options) => {
|
|
82
|
+
try {
|
|
83
|
+
const vault = await getVault(options.vault);
|
|
84
|
+
|
|
85
|
+
const results = await vault.vsearch(query, {
|
|
86
|
+
limit: parseInt(options.limit, 10),
|
|
87
|
+
category: options.category,
|
|
88
|
+
tags: options.tags?.split(',').map((value) => value.trim()),
|
|
89
|
+
fullContent: options.full,
|
|
90
|
+
temporalBoost: options.recent
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (options.json) {
|
|
94
|
+
console.log(JSON.stringify(results, null, 2));
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (results.length === 0) {
|
|
99
|
+
console.log(chalk.yellow('No results found.'));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
console.log(chalk.cyan(`\n🧠 Found ${results.length} result(s) for "${query}":\n`));
|
|
104
|
+
|
|
105
|
+
for (const result of results) {
|
|
106
|
+
const scoreBar = '█'.repeat(Math.round(result.score * 10)).padEnd(10, '░');
|
|
107
|
+
console.log(chalk.green(`📄 ${result.document.title}`));
|
|
108
|
+
console.log(chalk.dim(` ${result.document.category}/${result.document.id.split('/').pop()}`));
|
|
109
|
+
console.log(chalk.dim(` Score: ${scoreBar} ${(result.score * 100).toFixed(0)}%`));
|
|
110
|
+
if (result.snippet) {
|
|
111
|
+
console.log(chalk.white(` ${result.snippet.split('\n')[0].slice(0, 80)}...`));
|
|
112
|
+
}
|
|
113
|
+
console.log();
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof QmdUnavailableError) {
|
|
117
|
+
printQmdMissing();
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// === CONTEXT ===
|
|
126
|
+
program
|
|
127
|
+
.command('context <task>')
|
|
128
|
+
.description('Generate task-relevant context for prompt injection')
|
|
129
|
+
.option('-n, --limit <n>', 'Max results', '5')
|
|
130
|
+
.option('--format <format>', 'Output format (markdown|json)', 'markdown')
|
|
131
|
+
.option('--recent', 'Boost recent documents (enabled by default)', true)
|
|
132
|
+
.option('--include-observations', 'Include observation memories in output', true)
|
|
133
|
+
.option('--budget <number>', 'Optional token budget for assembled context')
|
|
134
|
+
.option('--profile <profile>', 'Context profile (default|planning|incident|handoff|auto)', 'default')
|
|
135
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
136
|
+
.action(async (task, options) => {
|
|
137
|
+
try {
|
|
138
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
139
|
+
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
140
|
+
const parsedBudget = options.budget ? Number.parseInt(options.budget, 10) : undefined;
|
|
141
|
+
if (options.budget && (!Number.isFinite(parsedBudget) || parsedBudget <= 0)) {
|
|
142
|
+
throw new Error(`Invalid --budget value: ${options.budget}`);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const { contextCommand } = await import('../dist/commands/context.js');
|
|
146
|
+
await contextCommand(task, {
|
|
147
|
+
vaultPath,
|
|
148
|
+
limit: parseInt(options.limit, 10),
|
|
149
|
+
format,
|
|
150
|
+
recent: options.recent,
|
|
151
|
+
includeObservations: options.includeObservations,
|
|
152
|
+
budget: parsedBudget,
|
|
153
|
+
profile: options.profile
|
|
154
|
+
});
|
|
155
|
+
} catch (err) {
|
|
156
|
+
if (err instanceof QmdUnavailableError) {
|
|
157
|
+
printQmdMissing();
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// === OBSERVE ===
|
|
166
|
+
program
|
|
167
|
+
.command('observe')
|
|
168
|
+
.description('Observe session files and build observational memory')
|
|
169
|
+
.option('--watch <path>', 'Watch session file or directory')
|
|
170
|
+
.option('--threshold <n>', 'Compression token threshold', '30000')
|
|
171
|
+
.option('--reflect-threshold <n>', 'Reflection token threshold', '40000')
|
|
172
|
+
.option('--model <model>', 'LLM model override')
|
|
173
|
+
.option('--compress <file>', 'One-shot compression for a conversation file')
|
|
174
|
+
.option('--daemon', 'Run in detached background mode')
|
|
175
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
176
|
+
.action(async (options) => {
|
|
177
|
+
try {
|
|
178
|
+
const { observeCommand } = await import('../dist/commands/observe.js');
|
|
179
|
+
const threshold = Number.parseInt(options.threshold, 10);
|
|
180
|
+
const reflectThreshold = Number.parseInt(options.reflectThreshold, 10);
|
|
181
|
+
if (Number.isNaN(threshold) || threshold <= 0) {
|
|
182
|
+
throw new Error(`Invalid --threshold value: ${options.threshold}`);
|
|
183
|
+
}
|
|
184
|
+
if (Number.isNaN(reflectThreshold) || reflectThreshold <= 0) {
|
|
185
|
+
throw new Error(`Invalid --reflect-threshold value: ${options.reflectThreshold}`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await observeCommand({
|
|
189
|
+
watch: options.watch,
|
|
190
|
+
threshold,
|
|
191
|
+
reflectThreshold,
|
|
192
|
+
model: options.model,
|
|
193
|
+
compress: options.compress,
|
|
194
|
+
daemon: options.daemon,
|
|
195
|
+
vaultPath: resolveVaultPath(options.vault)
|
|
196
|
+
});
|
|
197
|
+
} catch (err) {
|
|
198
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// === SESSION-RECAP ===
|
|
204
|
+
program
|
|
205
|
+
.command('session-recap <sessionKey>')
|
|
206
|
+
.description('Generate recap from a specific OpenClaw session transcript')
|
|
207
|
+
.option('-n, --limit <n>', 'Number of messages to include', '15')
|
|
208
|
+
.option('--format <format>', 'Output format (markdown|json)', 'markdown')
|
|
209
|
+
.option('-a, --agent <id>', 'Agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
|
|
210
|
+
.action(async (sessionKey, options) => {
|
|
211
|
+
try {
|
|
212
|
+
const { sessionRecapCommand } = await import('../dist/commands/session-recap.js');
|
|
213
|
+
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
214
|
+
const parsedLimit = Number.parseInt(options.limit, 10);
|
|
215
|
+
await sessionRecapCommand(sessionKey, {
|
|
216
|
+
limit: Number.isNaN(parsedLimit) ? 15 : parsedLimit,
|
|
217
|
+
format,
|
|
218
|
+
agentId: options.agent
|
|
219
|
+
});
|
|
220
|
+
} catch (err) {
|
|
221
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context-resilience and session-repair command registrations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function registerResilienceCommands(program, { chalk, resolveVaultPath }) {
|
|
6
|
+
// === CHECKPOINT ===
|
|
7
|
+
program
|
|
8
|
+
.command('checkpoint')
|
|
9
|
+
.description('Quick state checkpoint for context death resilience')
|
|
10
|
+
.option('--working-on <text>', 'What you are currently working on')
|
|
11
|
+
.option('--focus <text>', 'Current focus area')
|
|
12
|
+
.option('--blocked <text>', 'What is blocking progress')
|
|
13
|
+
.option('--urgent', 'Trigger OpenClaw wake after checkpoint')
|
|
14
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
try {
|
|
18
|
+
const { checkpoint } = await import('../dist/commands/checkpoint.js');
|
|
19
|
+
const data = await checkpoint({
|
|
20
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
21
|
+
workingOn: options.workingOn,
|
|
22
|
+
focus: options.focus,
|
|
23
|
+
blocked: options.blocked,
|
|
24
|
+
urgent: options.urgent
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
if (options.json) {
|
|
28
|
+
console.log(JSON.stringify(data, null, 2));
|
|
29
|
+
} else {
|
|
30
|
+
console.log(chalk.green('✓ Checkpoint saved'));
|
|
31
|
+
console.log(chalk.dim(` Timestamp: ${data.timestamp}`));
|
|
32
|
+
if (data.workingOn) console.log(chalk.dim(` Working on: ${data.workingOn}`));
|
|
33
|
+
if (data.focus) console.log(chalk.dim(` Focus: ${data.focus}`));
|
|
34
|
+
if (data.blocked) console.log(chalk.dim(` Blocked: ${data.blocked}`));
|
|
35
|
+
if (data.urgent) console.log(chalk.dim(' Urgent: yes'));
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// === RECOVER ===
|
|
44
|
+
program
|
|
45
|
+
.command('recover')
|
|
46
|
+
.description('Check for context death and recover state')
|
|
47
|
+
.option('--clear', 'Clear the dirty death flag after recovery')
|
|
48
|
+
.option('--verbose', 'Show full checkpoint and handoff content')
|
|
49
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
50
|
+
.option('--json', 'Output as JSON')
|
|
51
|
+
.action(async (options) => {
|
|
52
|
+
try {
|
|
53
|
+
const { recover, formatRecoveryInfo } = await import('../dist/commands/recover.js');
|
|
54
|
+
const info = await recover(resolveVaultPath(options.vault), {
|
|
55
|
+
clearFlag: options.clear,
|
|
56
|
+
verbose: options.verbose
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (options.json) {
|
|
60
|
+
console.log(JSON.stringify(info, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
console.log(formatRecoveryInfo(info, { verbose: options.verbose }));
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// === STATUS ===
|
|
71
|
+
program
|
|
72
|
+
.command('status')
|
|
73
|
+
.description('Show vault health and status')
|
|
74
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
75
|
+
.option('--json', 'Output as JSON')
|
|
76
|
+
.action(async (options) => {
|
|
77
|
+
try {
|
|
78
|
+
const { statusCommand } = await import('../dist/commands/status.js');
|
|
79
|
+
await statusCommand(resolveVaultPath(options.vault), { json: options.json });
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// === CLEAN-EXIT ===
|
|
87
|
+
program
|
|
88
|
+
.command('clean-exit')
|
|
89
|
+
.description('Mark session as cleanly exited (clears dirty death flag)')
|
|
90
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
91
|
+
.action(async (options) => {
|
|
92
|
+
try {
|
|
93
|
+
const { cleanExit } = await import('../dist/commands/checkpoint.js');
|
|
94
|
+
await cleanExit(resolveVaultPath(options.vault));
|
|
95
|
+
console.log(chalk.green('✓ Clean exit recorded'));
|
|
96
|
+
} catch (err) {
|
|
97
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// === REPAIR-SESSION ===
|
|
103
|
+
program
|
|
104
|
+
.command('repair-session')
|
|
105
|
+
.description('Repair corrupted OpenClaw session transcripts')
|
|
106
|
+
.option('-s, --session <id>', 'Session ID (defaults to current main session)')
|
|
107
|
+
.option('-a, --agent <id>', 'Agent ID (defaults to configured agent)')
|
|
108
|
+
.option('--backup', 'Create backup before repair (default: true)', true)
|
|
109
|
+
.option('--no-backup', 'Skip backup creation')
|
|
110
|
+
.option('--dry-run', 'Show what would be repaired without writing')
|
|
111
|
+
.option('--list', 'List available sessions')
|
|
112
|
+
.option('--json', 'Output as JSON')
|
|
113
|
+
.action(async (options) => {
|
|
114
|
+
try {
|
|
115
|
+
const {
|
|
116
|
+
repairSessionCommand,
|
|
117
|
+
formatRepairResult,
|
|
118
|
+
listAgentSessions
|
|
119
|
+
} = await import('../dist/commands/repair-session.js');
|
|
120
|
+
|
|
121
|
+
if (options.list) {
|
|
122
|
+
console.log(listAgentSessions(options.agent));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const result = await repairSessionCommand({
|
|
127
|
+
sessionId: options.session,
|
|
128
|
+
agentId: options.agent,
|
|
129
|
+
backup: options.backup,
|
|
130
|
+
dryRun: options.dryRun
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
if (options.json) {
|
|
134
|
+
console.log(JSON.stringify(result, null, 2));
|
|
135
|
+
} else {
|
|
136
|
+
console.log(formatRepairResult(result, { dryRun: options.dryRun }));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (result.corruptedEntries.length > 0 && !result.repaired) {
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
} catch (err) {
|
|
143
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
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', '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('--index', 'Update qmd index after handoff')
|
|
58
|
+
.option('--no-git', 'Skip git commit prompt')
|
|
59
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
60
|
+
.action(async (summary, options) => {
|
|
61
|
+
try {
|
|
62
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
63
|
+
const { sleep } = await import('../dist/commands/sleep.js');
|
|
64
|
+
const result = await sleep({
|
|
65
|
+
workingOn: summary,
|
|
66
|
+
next: options.next,
|
|
67
|
+
blocked: options.blocked,
|
|
68
|
+
decisions: options.decisions,
|
|
69
|
+
questions: options.questions,
|
|
70
|
+
feeling: options.feeling,
|
|
71
|
+
sessionKey: options.session,
|
|
72
|
+
sessionTranscript: options.sessionTranscript,
|
|
73
|
+
vaultPath,
|
|
74
|
+
index: options.index,
|
|
75
|
+
git: options.git
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
console.log(chalk.green(`✓ Handoff saved: ${result.document.id}`));
|
|
79
|
+
console.log(chalk.dim(` Path: ${result.document.path}`));
|
|
80
|
+
console.log(chalk.dim(` Working on: ${result.handoff.workingOn.join(', ')}`));
|
|
81
|
+
if (result.handoff.nextSteps.length > 0) {
|
|
82
|
+
console.log(chalk.dim(` Next: ${result.handoff.nextSteps.join(', ')}`));
|
|
83
|
+
} else {
|
|
84
|
+
console.log(chalk.dim(' Next: (none)'));
|
|
85
|
+
}
|
|
86
|
+
if (result.handoff.blocked.length > 0) {
|
|
87
|
+
console.log(chalk.dim(` Blocked: ${result.handoff.blocked.join(', ')}`));
|
|
88
|
+
} else {
|
|
89
|
+
console.log(chalk.dim(' Blocked: (none)'));
|
|
90
|
+
}
|
|
91
|
+
if (result.handoff.decisions?.length) {
|
|
92
|
+
console.log(chalk.dim(` Decisions: ${result.handoff.decisions.join(', ')}`));
|
|
93
|
+
}
|
|
94
|
+
if (result.handoff.openQuestions?.length) {
|
|
95
|
+
console.log(chalk.dim(` Questions: ${result.handoff.openQuestions.join(', ')}`));
|
|
96
|
+
}
|
|
97
|
+
if (result.handoff.feeling) {
|
|
98
|
+
console.log(chalk.dim(` Feeling: ${result.handoff.feeling}`));
|
|
99
|
+
}
|
|
100
|
+
if (options.index) {
|
|
101
|
+
console.log(chalk.dim(' qmd: index updated'));
|
|
102
|
+
}
|
|
103
|
+
if (result.git) {
|
|
104
|
+
if (result.git.committed) {
|
|
105
|
+
console.log(chalk.green(`✓ Git commit created${result.git.message ? `: ${result.git.message}` : ''}`));
|
|
106
|
+
} else if (result.git.skippedReason === 'clean') {
|
|
107
|
+
console.log(chalk.dim(' Git: clean'));
|
|
108
|
+
} else if (result.git.skippedReason === 'declined') {
|
|
109
|
+
console.log(chalk.dim(' Git: commit skipped'));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (result.observationRoutingSummary) {
|
|
113
|
+
console.log(chalk.dim(` Observe: ${result.observationRoutingSummary}`));
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof QmdUnavailableError) {
|
|
117
|
+
printQmdMissing();
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// === HANDOFF (session bridge) ===
|
|
126
|
+
program
|
|
127
|
+
.command('handoff')
|
|
128
|
+
.description('Create a session handoff document')
|
|
129
|
+
.requiredOption('-w, --working-on <items>', 'What I was working on (comma-separated)')
|
|
130
|
+
.option('-b, --blocked <items>', 'What is blocked (comma-separated)')
|
|
131
|
+
.option('-n, --next <items>', 'What comes next (comma-separated)')
|
|
132
|
+
.option('-d, --decisions <items>', 'Key decisions made (comma-separated)')
|
|
133
|
+
.option('-q, --questions <items>', 'Open questions (comma-separated)')
|
|
134
|
+
.option('-f, --feeling <state>', 'Emotional/energy state')
|
|
135
|
+
.option('-s, --session <key>', 'Session key')
|
|
136
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
137
|
+
.option('--no-index', 'Skip qmd index update (auto-updates by default)')
|
|
138
|
+
.option('--json', 'Output as JSON')
|
|
139
|
+
.action(async (options) => {
|
|
140
|
+
try {
|
|
141
|
+
const vault = await getVault(options.vault);
|
|
142
|
+
|
|
143
|
+
const handoff = {
|
|
144
|
+
workingOn: options.workingOn.split(',').map((item) => item.trim()),
|
|
145
|
+
blocked: options.blocked ? options.blocked.split(',').map((item) => item.trim()) : [],
|
|
146
|
+
nextSteps: options.next ? options.next.split(',').map((item) => item.trim()) : [],
|
|
147
|
+
decisions: options.decisions ? options.decisions.split(',').map((item) => item.trim()) : undefined,
|
|
148
|
+
openQuestions: options.questions ? options.questions.split(',').map((item) => item.trim()) : undefined,
|
|
149
|
+
feeling: options.feeling,
|
|
150
|
+
sessionKey: options.session
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const doc = await vault.createHandoff(handoff);
|
|
154
|
+
|
|
155
|
+
if (!options.json) {
|
|
156
|
+
console.log(chalk.green(`✓ Handoff created: ${doc.id}`));
|
|
157
|
+
console.log(chalk.dim(` Path: ${doc.path}`));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (options.index !== false) {
|
|
161
|
+
const collection = vault.getQmdCollection();
|
|
162
|
+
await runQmd(collection ? ['update', '-c', collection] : ['update']);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (options.json) {
|
|
166
|
+
console.log(JSON.stringify({ id: doc.id, path: doc.path, handoff }, null, 2));
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// === RECAP (session bootstrap) ===
|
|
175
|
+
program
|
|
176
|
+
.command('recap')
|
|
177
|
+
.description('Generate a session recap - who I was (bootstrap hook)')
|
|
178
|
+
.option('-n, --handoff-limit <n>', 'Number of recent handoffs to include', '3')
|
|
179
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
180
|
+
.option('--json', 'Output as JSON')
|
|
181
|
+
.option('--markdown', 'Output as markdown (default)')
|
|
182
|
+
.option('--brief', 'Minimal output for token savings')
|
|
183
|
+
.action(async (options) => {
|
|
184
|
+
try {
|
|
185
|
+
const vault = await getVault(options.vault);
|
|
186
|
+
|
|
187
|
+
const recap = await vault.generateRecap({
|
|
188
|
+
handoffLimit: parseInt(options.handoffLimit, 10),
|
|
189
|
+
brief: options.brief
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
if (options.json) {
|
|
193
|
+
console.log(JSON.stringify(recap, null, 2));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const markdown = vault.formatRecap(recap, { brief: options.brief });
|
|
198
|
+
console.log(markdown);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Template command registrations split from main CLI entrypoint.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export function registerTemplateCommands(program, { chalk }) {
|
|
6
|
+
const template = program
|
|
7
|
+
.command('template')
|
|
8
|
+
.description('Manage templates');
|
|
9
|
+
|
|
10
|
+
template
|
|
11
|
+
.command('list')
|
|
12
|
+
.description('List available templates')
|
|
13
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
try {
|
|
16
|
+
const { listTemplates } = await import('../dist/commands/template.js');
|
|
17
|
+
const templates = listTemplates({ vaultPath: options.vault });
|
|
18
|
+
if (templates.length === 0) {
|
|
19
|
+
console.log(chalk.yellow('No templates found.'));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
console.log(chalk.cyan('\n📄 Templates:\n'));
|
|
23
|
+
for (const name of templates) {
|
|
24
|
+
console.log(`- ${name}`);
|
|
25
|
+
}
|
|
26
|
+
console.log();
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
template
|
|
34
|
+
.command('create <name>')
|
|
35
|
+
.description('Create a file from a template')
|
|
36
|
+
.option('-t, --title <title>', 'Document title')
|
|
37
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
38
|
+
.action(async (name, options) => {
|
|
39
|
+
try {
|
|
40
|
+
const { createFromTemplate } = await import('../dist/commands/template.js');
|
|
41
|
+
const result = createFromTemplate(name, {
|
|
42
|
+
title: options.title,
|
|
43
|
+
vaultPath: options.vault
|
|
44
|
+
});
|
|
45
|
+
console.log(chalk.green(`✓ Created from template: ${name}`));
|
|
46
|
+
console.log(chalk.dim(` Output: ${result.outputPath}`));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
template
|
|
54
|
+
.command('add <file>')
|
|
55
|
+
.description('Add a custom template')
|
|
56
|
+
.requiredOption('--name <name>', 'Template name')
|
|
57
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
58
|
+
.action(async (file, options) => {
|
|
59
|
+
try {
|
|
60
|
+
const { addTemplate } = await import('../dist/commands/template.js');
|
|
61
|
+
const result = addTemplate(file, {
|
|
62
|
+
name: options.name,
|
|
63
|
+
vaultPath: options.vault
|
|
64
|
+
});
|
|
65
|
+
console.log(chalk.green(`✓ Template added: ${result.name}`));
|
|
66
|
+
console.log(chalk.dim(` Path: ${result.templatePath}`));
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|