clawvault 2.5.2 → 2.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +159 -200
- package/bin/clawvault.js +111 -111
- package/bin/command-registration.test.js +166 -166
- package/bin/command-runtime.js +93 -93
- package/bin/command-runtime.test.js +154 -154
- package/bin/help-contract.test.js +39 -39
- package/bin/register-config-commands.js +153 -153
- package/bin/register-config-route-commands.test.js +121 -121
- package/bin/register-core-commands.js +237 -237
- package/bin/register-kanban-commands.js +56 -56
- package/bin/register-kanban-commands.test.js +83 -83
- package/bin/register-maintenance-commands.js +282 -282
- package/bin/register-project-commands.js +209 -209
- package/bin/register-project-commands.test.js +206 -206
- package/bin/register-query-commands.js +317 -317
- package/bin/register-query-commands.test.js +65 -65
- package/bin/register-resilience-commands.js +182 -182
- package/bin/register-resilience-commands.test.js +81 -81
- package/bin/register-route-commands.js +114 -114
- package/bin/register-session-lifecycle-commands.js +206 -206
- package/bin/register-tailscale-commands.js +106 -106
- package/bin/register-task-commands.js +348 -348
- package/bin/register-task-commands.test.js +69 -69
- package/bin/register-template-commands.js +72 -72
- package/bin/register-vault-operations-commands.js +300 -300
- package/bin/test-helpers/cli-command-fixtures.js +119 -119
- package/dashboard/lib/graph-diff.js +104 -104
- package/dashboard/lib/graph-diff.test.js +75 -75
- package/dashboard/lib/vault-parser.js +556 -556
- package/dashboard/lib/vault-parser.test.js +254 -254
- package/dashboard/public/app.js +796 -796
- package/dashboard/public/index.html +52 -52
- package/dashboard/public/styles.css +221 -221
- package/dashboard/server.js +374 -374
- package/dist/{chunk-3FP5BJ42.js → chunk-4QYGFWRM.js} +1 -1
- package/dist/{chunk-M25QVSJM.js → chunk-AXKYDCNN.js} +1 -1
- package/dist/{chunk-CLE2HHNT.js → chunk-IVRIKYFE.js} +18 -11
- package/dist/{chunk-HRTPQQF2.js → chunk-IZEY5S74.js} +1 -1
- package/dist/{chunk-HWUNREDJ.js → chunk-JDLOL2PL.js} +4 -4
- package/dist/{chunk-AY4PGUVL.js → chunk-KL4NAOMO.js} +1 -1
- package/dist/{chunk-O7XHXF7F.js → chunk-MAKNAHAW.js} +4 -4
- package/dist/{chunk-PLZKZW4I.js → chunk-OSMS7QIG.js} +1 -1
- package/dist/{chunk-NZ4ZZNSR.js → chunk-THRJVD4L.js} +1 -1
- package/dist/{chunk-4GBPTBFJ.js → chunk-TIGW564L.js} +1 -1
- package/dist/{chunk-BHO7WSAY.js → chunk-W2HNZC22.js} +3 -3
- package/dist/{chunk-GFJ3LIIB.js → chunk-XAVB4GB4.js} +1 -1
- package/dist/cli/index.js +10 -10
- package/dist/commands/context.js +3 -3
- package/dist/commands/doctor.js +4 -4
- package/dist/commands/embed.js +2 -2
- package/dist/commands/observe.js +2 -2
- package/dist/commands/setup.js +2 -2
- package/dist/commands/sleep.js +2 -2
- package/dist/commands/status.js +3 -3
- package/dist/commands/tailscale.js +3 -3
- package/dist/commands/wake.js +2 -2
- package/dist/index.js +12 -12
- package/dist/lib/tailscale.js +2 -2
- package/dist/lib/webdav.js +1 -1
- package/hooks/clawvault/HOOK.md +83 -74
- package/hooks/clawvault/handler.js +816 -816
- package/hooks/clawvault/handler.test.js +263 -263
- package/package.json +94 -125
- package/templates/checkpoint.md +19 -19
- package/templates/daily-note.md +19 -19
- package/templates/daily.md +19 -19
- package/templates/decision.md +17 -17
- package/templates/handoff.md +19 -19
- package/templates/lesson.md +16 -16
- package/templates/person.md +19 -19
- package/templates/project.md +23 -23
|
@@ -1,317 +1,317 @@
|
|
|
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 (default: 10)', '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 (default: 5)', '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 (default: 5)', '5')
|
|
130
|
-
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
131
|
-
.option('--recent', 'Boost recent documents (enabled by default)', true)
|
|
132
|
-
.option('--include-observations', 'Include observation memories in output (enabled by default)', true)
|
|
133
|
-
.option('--budget <number>', 'Optional token budget for assembled context')
|
|
134
|
-
.option('--profile <profile>', 'Context profile (default|planning|incident|handoff|auto) (default: default)', 'default')
|
|
135
|
-
.option('--max-hops <n>', 'Maximum graph expansion hops (default: 2)', '2')
|
|
136
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
137
|
-
.action(async (task, options) => {
|
|
138
|
-
try {
|
|
139
|
-
const vaultPath = resolveVaultPath(options.vault);
|
|
140
|
-
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
141
|
-
const parsedBudget = options.budget ? Number.parseInt(options.budget, 10) : undefined;
|
|
142
|
-
const parsedMaxHops = Number.parseInt(options.maxHops, 10);
|
|
143
|
-
if (options.budget && (!Number.isFinite(parsedBudget) || parsedBudget <= 0)) {
|
|
144
|
-
throw new Error(`Invalid --budget value: ${options.budget}`);
|
|
145
|
-
}
|
|
146
|
-
if (!Number.isFinite(parsedMaxHops) || parsedMaxHops <= 0) {
|
|
147
|
-
throw new Error(`Invalid --max-hops value: ${options.maxHops}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const { contextCommand } = await import('../dist/commands/context.js');
|
|
151
|
-
await contextCommand(task, {
|
|
152
|
-
vaultPath,
|
|
153
|
-
limit: parseInt(options.limit, 10),
|
|
154
|
-
format,
|
|
155
|
-
recent: options.recent,
|
|
156
|
-
includeObservations: options.includeObservations,
|
|
157
|
-
budget: parsedBudget,
|
|
158
|
-
profile: options.profile,
|
|
159
|
-
maxHops: parsedMaxHops
|
|
160
|
-
});
|
|
161
|
-
} catch (err) {
|
|
162
|
-
if (err instanceof QmdUnavailableError) {
|
|
163
|
-
printQmdMissing();
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
// === INJECT ===
|
|
172
|
-
program
|
|
173
|
-
.command('inject <message>')
|
|
174
|
-
.description('Inject relevant rules, decisions, and preferences into prompt context')
|
|
175
|
-
.option('-n, --max-results <n>', 'Maximum injected items (default: config inject.maxResults, fallback 8)')
|
|
176
|
-
.option('--scope <scope>', 'Comma-separated scope filter override (default: config inject.scope, fallback global)')
|
|
177
|
-
.option('--enable-llm', 'Enable LLM fuzzy intent matching (overrides config inject.useLlm)')
|
|
178
|
-
.option('--disable-llm', 'Disable LLM fuzzy intent matching (overrides config inject.useLlm)')
|
|
179
|
-
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
180
|
-
.option('--model <model>', 'Override LLM model when fuzzy matching is enabled')
|
|
181
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
182
|
-
.action(async (message, options) => {
|
|
183
|
-
try {
|
|
184
|
-
const parsedMaxResults = options.maxResults
|
|
185
|
-
? Number.parseInt(options.maxResults, 10)
|
|
186
|
-
: undefined;
|
|
187
|
-
if (options.maxResults && (!Number.isFinite(parsedMaxResults) || parsedMaxResults <= 0)) {
|
|
188
|
-
throw new Error(`Invalid --max-results value: ${options.maxResults}`);
|
|
189
|
-
}
|
|
190
|
-
const useLlm = options.enableLlm
|
|
191
|
-
? true
|
|
192
|
-
: options.disableLlm
|
|
193
|
-
? false
|
|
194
|
-
: undefined;
|
|
195
|
-
|
|
196
|
-
const { injectCommand } = await import('../dist/commands/inject.js');
|
|
197
|
-
await injectCommand(message, {
|
|
198
|
-
vaultPath: resolveVaultPath(options.vault),
|
|
199
|
-
maxResults: parsedMaxResults,
|
|
200
|
-
useLlm,
|
|
201
|
-
scope: options.scope,
|
|
202
|
-
format: options.format === 'json' ? 'json' : 'markdown',
|
|
203
|
-
model: options.model
|
|
204
|
-
});
|
|
205
|
-
} catch (err) {
|
|
206
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
207
|
-
process.exit(1);
|
|
208
|
-
}
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
// === OBSERVE ===
|
|
212
|
-
program
|
|
213
|
-
.command('observe')
|
|
214
|
-
.description('Observe session files and build observational memory')
|
|
215
|
-
.option('--watch <path>', 'Watch session file or directory')
|
|
216
|
-
.option('--active', 'Observe active OpenClaw sessions incrementally')
|
|
217
|
-
.option('--cron', 'Run one-shot active observation for cron hooks')
|
|
218
|
-
.option('--agent <id>', 'OpenClaw agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
|
|
219
|
-
.option('--min-new <bytes>', 'Override minimum new-content threshold in bytes')
|
|
220
|
-
.option('--sessions-dir <path>', 'Override OpenClaw sessions directory')
|
|
221
|
-
.option('--dry-run', 'Show active observation candidates without compressing')
|
|
222
|
-
.option('--threshold <n>', 'Compression token threshold (default: 30000)', '30000')
|
|
223
|
-
.option('--reflect-threshold <n>', 'Reflection token threshold (default: 40000)', '40000')
|
|
224
|
-
.option('--model <model>', 'LLM model override')
|
|
225
|
-
.option('--extract-tasks', 'Extract task-like observations into backlog (enabled by default)', true)
|
|
226
|
-
.option('--no-extract-tasks', 'Disable task extraction from observations')
|
|
227
|
-
.option('--compress <file>', 'One-shot compression for a conversation file')
|
|
228
|
-
.option('--daemon', 'Run in detached background mode')
|
|
229
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
230
|
-
.action(async (options) => {
|
|
231
|
-
try {
|
|
232
|
-
const { observeCommand } = await import('../dist/commands/observe.js');
|
|
233
|
-
const threshold = Number.parseInt(options.threshold, 10);
|
|
234
|
-
const reflectThreshold = Number.parseInt(options.reflectThreshold, 10);
|
|
235
|
-
const minNew = options.minNew === undefined
|
|
236
|
-
? undefined
|
|
237
|
-
: Number.parseInt(options.minNew, 10);
|
|
238
|
-
if (Number.isNaN(threshold) || threshold <= 0) {
|
|
239
|
-
throw new Error(`Invalid --threshold value: ${options.threshold}`);
|
|
240
|
-
}
|
|
241
|
-
if (Number.isNaN(reflectThreshold) || reflectThreshold <= 0) {
|
|
242
|
-
throw new Error(`Invalid --reflect-threshold value: ${options.reflectThreshold}`);
|
|
243
|
-
}
|
|
244
|
-
if (options.minNew !== undefined && (Number.isNaN(minNew) || minNew <= 0)) {
|
|
245
|
-
throw new Error(`Invalid --min-new value: ${options.minNew}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
await observeCommand({
|
|
249
|
-
watch: options.watch,
|
|
250
|
-
active: options.active,
|
|
251
|
-
cron: options.cron,
|
|
252
|
-
agent: options.agent,
|
|
253
|
-
minNew,
|
|
254
|
-
sessionsDir: options.sessionsDir,
|
|
255
|
-
dryRun: options.dryRun,
|
|
256
|
-
threshold,
|
|
257
|
-
reflectThreshold,
|
|
258
|
-
model: options.model,
|
|
259
|
-
extractTasks: options.extractTasks,
|
|
260
|
-
compress: options.compress,
|
|
261
|
-
daemon: options.daemon,
|
|
262
|
-
vaultPath: resolveVaultPath(options.vault)
|
|
263
|
-
});
|
|
264
|
-
} catch (err) {
|
|
265
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
// === REFLECT ===
|
|
271
|
-
program
|
|
272
|
-
.command('reflect')
|
|
273
|
-
.description('Promote stable observations into weekly reflections')
|
|
274
|
-
.option('--days <n>', 'Observation window in days (default: 14)', '14')
|
|
275
|
-
.option('--dry-run', 'Show reflection output candidates without writing')
|
|
276
|
-
.option('-v, --vault <path>', 'Vault path')
|
|
277
|
-
.action(async (options) => {
|
|
278
|
-
try {
|
|
279
|
-
const { reflectCommand } = await import('../dist/commands/reflect.js');
|
|
280
|
-
const days = Number.parseInt(options.days, 10);
|
|
281
|
-
if (!Number.isFinite(days) || days <= 0) {
|
|
282
|
-
throw new Error(`Invalid --days value: ${options.days}`);
|
|
283
|
-
}
|
|
284
|
-
await reflectCommand({
|
|
285
|
-
vaultPath: resolveVaultPath(options.vault),
|
|
286
|
-
days,
|
|
287
|
-
dryRun: options.dryRun
|
|
288
|
-
});
|
|
289
|
-
} catch (err) {
|
|
290
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
291
|
-
process.exit(1);
|
|
292
|
-
}
|
|
293
|
-
});
|
|
294
|
-
|
|
295
|
-
// === SESSION-RECAP ===
|
|
296
|
-
program
|
|
297
|
-
.command('session-recap <sessionKey>')
|
|
298
|
-
.description('Generate recap from a specific OpenClaw session transcript')
|
|
299
|
-
.option('-n, --limit <n>', 'Number of messages to include (default: 15)', '15')
|
|
300
|
-
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
301
|
-
.option('-a, --agent <id>', 'Agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
|
|
302
|
-
.action(async (sessionKey, options) => {
|
|
303
|
-
try {
|
|
304
|
-
const { sessionRecapCommand } = await import('../dist/commands/session-recap.js');
|
|
305
|
-
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
306
|
-
const parsedLimit = Number.parseInt(options.limit, 10);
|
|
307
|
-
await sessionRecapCommand(sessionKey, {
|
|
308
|
-
limit: Number.isNaN(parsedLimit) ? 15 : parsedLimit,
|
|
309
|
-
format,
|
|
310
|
-
agentId: options.agent
|
|
311
|
-
});
|
|
312
|
-
} catch (err) {
|
|
313
|
-
console.error(chalk.red(`Error: ${err.message}`));
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
}
|
|
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 (default: 10)', '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 (default: 5)', '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 (default: 5)', '5')
|
|
130
|
+
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
131
|
+
.option('--recent', 'Boost recent documents (enabled by default)', true)
|
|
132
|
+
.option('--include-observations', 'Include observation memories in output (enabled by default)', true)
|
|
133
|
+
.option('--budget <number>', 'Optional token budget for assembled context')
|
|
134
|
+
.option('--profile <profile>', 'Context profile (default|planning|incident|handoff|auto) (default: default)', 'default')
|
|
135
|
+
.option('--max-hops <n>', 'Maximum graph expansion hops (default: 2)', '2')
|
|
136
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
137
|
+
.action(async (task, options) => {
|
|
138
|
+
try {
|
|
139
|
+
const vaultPath = resolveVaultPath(options.vault);
|
|
140
|
+
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
141
|
+
const parsedBudget = options.budget ? Number.parseInt(options.budget, 10) : undefined;
|
|
142
|
+
const parsedMaxHops = Number.parseInt(options.maxHops, 10);
|
|
143
|
+
if (options.budget && (!Number.isFinite(parsedBudget) || parsedBudget <= 0)) {
|
|
144
|
+
throw new Error(`Invalid --budget value: ${options.budget}`);
|
|
145
|
+
}
|
|
146
|
+
if (!Number.isFinite(parsedMaxHops) || parsedMaxHops <= 0) {
|
|
147
|
+
throw new Error(`Invalid --max-hops value: ${options.maxHops}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const { contextCommand } = await import('../dist/commands/context.js');
|
|
151
|
+
await contextCommand(task, {
|
|
152
|
+
vaultPath,
|
|
153
|
+
limit: parseInt(options.limit, 10),
|
|
154
|
+
format,
|
|
155
|
+
recent: options.recent,
|
|
156
|
+
includeObservations: options.includeObservations,
|
|
157
|
+
budget: parsedBudget,
|
|
158
|
+
profile: options.profile,
|
|
159
|
+
maxHops: parsedMaxHops
|
|
160
|
+
});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err instanceof QmdUnavailableError) {
|
|
163
|
+
printQmdMissing();
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// === INJECT ===
|
|
172
|
+
program
|
|
173
|
+
.command('inject <message>')
|
|
174
|
+
.description('Inject relevant rules, decisions, and preferences into prompt context')
|
|
175
|
+
.option('-n, --max-results <n>', 'Maximum injected items (default: config inject.maxResults, fallback 8)')
|
|
176
|
+
.option('--scope <scope>', 'Comma-separated scope filter override (default: config inject.scope, fallback global)')
|
|
177
|
+
.option('--enable-llm', 'Enable LLM fuzzy intent matching (overrides config inject.useLlm)')
|
|
178
|
+
.option('--disable-llm', 'Disable LLM fuzzy intent matching (overrides config inject.useLlm)')
|
|
179
|
+
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
180
|
+
.option('--model <model>', 'Override LLM model when fuzzy matching is enabled')
|
|
181
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
182
|
+
.action(async (message, options) => {
|
|
183
|
+
try {
|
|
184
|
+
const parsedMaxResults = options.maxResults
|
|
185
|
+
? Number.parseInt(options.maxResults, 10)
|
|
186
|
+
: undefined;
|
|
187
|
+
if (options.maxResults && (!Number.isFinite(parsedMaxResults) || parsedMaxResults <= 0)) {
|
|
188
|
+
throw new Error(`Invalid --max-results value: ${options.maxResults}`);
|
|
189
|
+
}
|
|
190
|
+
const useLlm = options.enableLlm
|
|
191
|
+
? true
|
|
192
|
+
: options.disableLlm
|
|
193
|
+
? false
|
|
194
|
+
: undefined;
|
|
195
|
+
|
|
196
|
+
const { injectCommand } = await import('../dist/commands/inject.js');
|
|
197
|
+
await injectCommand(message, {
|
|
198
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
199
|
+
maxResults: parsedMaxResults,
|
|
200
|
+
useLlm,
|
|
201
|
+
scope: options.scope,
|
|
202
|
+
format: options.format === 'json' ? 'json' : 'markdown',
|
|
203
|
+
model: options.model
|
|
204
|
+
});
|
|
205
|
+
} catch (err) {
|
|
206
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// === OBSERVE ===
|
|
212
|
+
program
|
|
213
|
+
.command('observe')
|
|
214
|
+
.description('Observe session files and build observational memory')
|
|
215
|
+
.option('--watch <path>', 'Watch session file or directory')
|
|
216
|
+
.option('--active', 'Observe active OpenClaw sessions incrementally')
|
|
217
|
+
.option('--cron', 'Run one-shot active observation for cron hooks')
|
|
218
|
+
.option('--agent <id>', 'OpenClaw agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
|
|
219
|
+
.option('--min-new <bytes>', 'Override minimum new-content threshold in bytes')
|
|
220
|
+
.option('--sessions-dir <path>', 'Override OpenClaw sessions directory')
|
|
221
|
+
.option('--dry-run', 'Show active observation candidates without compressing')
|
|
222
|
+
.option('--threshold <n>', 'Compression token threshold (default: 30000)', '30000')
|
|
223
|
+
.option('--reflect-threshold <n>', 'Reflection token threshold (default: 40000)', '40000')
|
|
224
|
+
.option('--model <model>', 'LLM model override')
|
|
225
|
+
.option('--extract-tasks', 'Extract task-like observations into backlog (enabled by default)', true)
|
|
226
|
+
.option('--no-extract-tasks', 'Disable task extraction from observations')
|
|
227
|
+
.option('--compress <file>', 'One-shot compression for a conversation file')
|
|
228
|
+
.option('--daemon', 'Run in detached background mode')
|
|
229
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
230
|
+
.action(async (options) => {
|
|
231
|
+
try {
|
|
232
|
+
const { observeCommand } = await import('../dist/commands/observe.js');
|
|
233
|
+
const threshold = Number.parseInt(options.threshold, 10);
|
|
234
|
+
const reflectThreshold = Number.parseInt(options.reflectThreshold, 10);
|
|
235
|
+
const minNew = options.minNew === undefined
|
|
236
|
+
? undefined
|
|
237
|
+
: Number.parseInt(options.minNew, 10);
|
|
238
|
+
if (Number.isNaN(threshold) || threshold <= 0) {
|
|
239
|
+
throw new Error(`Invalid --threshold value: ${options.threshold}`);
|
|
240
|
+
}
|
|
241
|
+
if (Number.isNaN(reflectThreshold) || reflectThreshold <= 0) {
|
|
242
|
+
throw new Error(`Invalid --reflect-threshold value: ${options.reflectThreshold}`);
|
|
243
|
+
}
|
|
244
|
+
if (options.minNew !== undefined && (Number.isNaN(minNew) || minNew <= 0)) {
|
|
245
|
+
throw new Error(`Invalid --min-new value: ${options.minNew}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
await observeCommand({
|
|
249
|
+
watch: options.watch,
|
|
250
|
+
active: options.active,
|
|
251
|
+
cron: options.cron,
|
|
252
|
+
agent: options.agent,
|
|
253
|
+
minNew,
|
|
254
|
+
sessionsDir: options.sessionsDir,
|
|
255
|
+
dryRun: options.dryRun,
|
|
256
|
+
threshold,
|
|
257
|
+
reflectThreshold,
|
|
258
|
+
model: options.model,
|
|
259
|
+
extractTasks: options.extractTasks,
|
|
260
|
+
compress: options.compress,
|
|
261
|
+
daemon: options.daemon,
|
|
262
|
+
vaultPath: resolveVaultPath(options.vault)
|
|
263
|
+
});
|
|
264
|
+
} catch (err) {
|
|
265
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// === REFLECT ===
|
|
271
|
+
program
|
|
272
|
+
.command('reflect')
|
|
273
|
+
.description('Promote stable observations into weekly reflections')
|
|
274
|
+
.option('--days <n>', 'Observation window in days (default: 14)', '14')
|
|
275
|
+
.option('--dry-run', 'Show reflection output candidates without writing')
|
|
276
|
+
.option('-v, --vault <path>', 'Vault path')
|
|
277
|
+
.action(async (options) => {
|
|
278
|
+
try {
|
|
279
|
+
const { reflectCommand } = await import('../dist/commands/reflect.js');
|
|
280
|
+
const days = Number.parseInt(options.days, 10);
|
|
281
|
+
if (!Number.isFinite(days) || days <= 0) {
|
|
282
|
+
throw new Error(`Invalid --days value: ${options.days}`);
|
|
283
|
+
}
|
|
284
|
+
await reflectCommand({
|
|
285
|
+
vaultPath: resolveVaultPath(options.vault),
|
|
286
|
+
days,
|
|
287
|
+
dryRun: options.dryRun
|
|
288
|
+
});
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// === SESSION-RECAP ===
|
|
296
|
+
program
|
|
297
|
+
.command('session-recap <sessionKey>')
|
|
298
|
+
.description('Generate recap from a specific OpenClaw session transcript')
|
|
299
|
+
.option('-n, --limit <n>', 'Number of messages to include (default: 15)', '15')
|
|
300
|
+
.option('--format <format>', 'Output format (markdown|json) (default: markdown)', 'markdown')
|
|
301
|
+
.option('-a, --agent <id>', 'Agent ID (default: OPENCLAW_AGENT_ID or clawdious)')
|
|
302
|
+
.action(async (sessionKey, options) => {
|
|
303
|
+
try {
|
|
304
|
+
const { sessionRecapCommand } = await import('../dist/commands/session-recap.js');
|
|
305
|
+
const format = options.format === 'json' ? 'json' : 'markdown';
|
|
306
|
+
const parsedLimit = Number.parseInt(options.limit, 10);
|
|
307
|
+
await sessionRecapCommand(sessionKey, {
|
|
308
|
+
limit: Number.isNaN(parsedLimit) ? 15 : parsedLimit,
|
|
309
|
+
format,
|
|
310
|
+
agentId: options.agent
|
|
311
|
+
});
|
|
312
|
+
} catch (err) {
|
|
313
|
+
console.error(chalk.red(`Error: ${err.message}`));
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
}
|