@kernel.chat/kbot 3.62.0 → 3.64.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/dist/agent.js CHANGED
@@ -16,6 +16,8 @@ import { formatContextForPrompt } from './context.js';
16
16
  import { getMatrixSystemPrompt } from './matrix.js';
17
17
  import { buildFullLearningContext, findPattern, recordPattern, cacheSolution, updateProfile, classifyTask, extractKeywords, learnFromExchange, updateProjectMemory, shouldAutoTrain, selfTrain, } from './learning.js';
18
18
  import { getMemoryPrompt, addTurn, getPreviousMessages, getHistory } from './memory.js';
19
+ import { getDreamPrompt, dreamAfterSession } from './dream.js';
20
+ import { notifyTurn, startMemoryScanner, stopMemoryScanner } from './memory-scanner.js';
19
21
  import { autoCompact, compressToolResult } from './context-manager.js';
20
22
  import { learnedRoute, recordRoute } from './learned-router.js';
21
23
  import { buildCacheablePrompt, createPromptSections } from './prompt-cache.js';
@@ -746,7 +748,9 @@ export async function runAgent(message, options = {}) {
746
748
  const localResult = await tryLocalFirst(message);
747
749
  if (localResult !== null) {
748
750
  addTurn({ role: 'user', content: message }, memSession);
751
+ notifyTurn({ role: 'user', content: message }, memSession);
749
752
  addTurn({ role: 'assistant', content: localResult }, memSession);
753
+ notifyTurn({ role: 'assistant', content: localResult }, memSession);
750
754
  ui.onInfo('(handled locally — 0 tokens used)');
751
755
  return { content: localResult, agent: 'local', model: 'none', toolCalls: 0 };
752
756
  }
@@ -775,7 +779,9 @@ export async function runAgent(message, options = {}) {
775
779
  }, { autoApprove: false, onApproval: async () => true });
776
780
  const summary = formatPlanSummary(plan);
777
781
  addTurn({ role: 'user', content: message }, memSession);
782
+ notifyTurn({ role: 'user', content: message }, memSession);
778
783
  addTurn({ role: 'assistant', content: summary }, memSession);
784
+ notifyTurn({ role: 'assistant', content: summary }, memSession);
779
785
  return {
780
786
  content: summary,
781
787
  agent: options.agent || 'coder',
@@ -965,7 +971,7 @@ Always quote file paths that contain spaces. Never reference internal system nam
965
971
  persona: PERSONA,
966
972
  matrixPrompt: matrixPrompt || undefined,
967
973
  contextSnippet: (contextSnippet || '') + repoMapSnippet + graphSnippet + skillsSnippet + skillLibrarySnippet || undefined,
968
- memorySnippet: (memorySnippet || '') + reflectionSnippet || undefined,
974
+ memorySnippet: (memorySnippet || '') + getDreamPrompt(8) + reflectionSnippet || undefined,
969
975
  learningContext: ((learningContext || '') + (synthesisSnippet ? '\n\n' + synthesisSnippet : '') + (correctionsSnippet ? '\n\n' + correctionsSnippet : '')) || undefined,
970
976
  });
971
977
  const provider = byokProvider || 'anthropic';
@@ -997,6 +1003,8 @@ Always quote file paths that contain spaces. Never reference internal system nam
997
1003
  model: options.model || 'auto',
998
1004
  message: originalMessage.slice(0, 200),
999
1005
  });
1006
+ // Start passive memory scanner for this session
1007
+ startMemoryScanner();
1000
1008
  // ── Gödel limits: detect undecidable loops and hand off to human ──
1001
1009
  const loopDetector = new LoopDetector({
1002
1010
  maxToolRepeats: 5,
@@ -1292,7 +1300,9 @@ Always quote file paths that contain spaces. Never reference internal system nam
1292
1300
  catch { /* self-eval errors are non-critical */ }
1293
1301
  }
1294
1302
  addTurn({ role: 'user', content: originalMessage }, memSession);
1303
+ notifyTurn({ role: 'user', content: originalMessage }, memSession);
1295
1304
  addTurn({ role: 'assistant', content }, memSession);
1305
+ notifyTurn({ role: 'assistant', content }, memSession);
1296
1306
  // ── Recursive Learning: record what worked (async — non-blocking) ──
1297
1307
  const totalTokens = lastResponse.usage
1298
1308
  ? (lastResponse.usage.input_tokens || 0) + (lastResponse.usage.output_tokens || 0)
@@ -1603,6 +1613,10 @@ Always quote file paths that contain spaces. Never reference internal system nam
1603
1613
  reason: 'loop_exhausted',
1604
1614
  });
1605
1615
  telemetry.destroy().catch(() => { });
1616
+ // ── Memory Scanner: stop and persist session stats ──
1617
+ stopMemoryScanner();
1618
+ // ── Dream Engine: consolidate session memories (non-blocking, $0 via Ollama) ──
1619
+ dreamAfterSession(sessionId);
1606
1620
  const content = lastResponse?.content || 'Reached maximum tool iterations.';
1607
1621
  return {
1608
1622
  content,
@@ -0,0 +1,33 @@
1
+ export type BuddySpecies = 'fox' | 'owl' | 'cat' | 'robot' | 'ghost' | 'mushroom' | 'octopus' | 'dragon';
2
+ export type BuddyMood = 'idle' | 'thinking' | 'success' | 'error' | 'learning';
3
+ export interface BuddyState {
4
+ species: BuddySpecies;
5
+ name: string;
6
+ mood: BuddyMood;
7
+ }
8
+ /** Get the buddy's current state (species, name, mood) */
9
+ export declare function getBuddy(): BuddyState;
10
+ /** Set the buddy's mood */
11
+ export declare function setBuddyMood(mood: BuddyMood): void;
12
+ /** Get the ASCII sprite for the buddy in the given mood (defaults to current) */
13
+ export declare function getBuddySprite(mood?: BuddyMood): string[];
14
+ /** Get a random greeting for the buddy */
15
+ export declare function getBuddyGreeting(): string;
16
+ /** Rename the buddy (persisted to ~/.kbot/buddy.json) */
17
+ export declare function renameBuddy(newName: string): void;
18
+ /**
19
+ * Format the buddy with a speech bubble and status message.
20
+ * Returns a multi-line string ready for terminal output.
21
+ *
22
+ * .----------------.
23
+ * | Status message |
24
+ * '----------------'
25
+ * /\ /\
26
+ * ( o . o )
27
+ * > ^ <
28
+ * /| |\
29
+ * (_| |_)
30
+ * ~ Patch the fox ~
31
+ */
32
+ export declare function formatBuddyStatus(message?: string): string;
33
+ //# sourceMappingURL=buddy.d.ts.map
package/dist/buddy.js ADDED
@@ -0,0 +1,468 @@
1
+ // kbot Buddy System — Terminal companion sprites
2
+ //
3
+ // Deterministic companion assignment based on config path hash.
4
+ // Same user always gets the same buddy. Mood changes based on session activity.
5
+ // Pure ASCII art, max 5 lines tall, 15 chars wide. Tamagotchi energy.
6
+ //
7
+ // Persists buddy name to ~/.kbot/buddy.json
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
+ import { createHash } from 'node:crypto';
12
+ // ── Paths ──
13
+ const KBOT_DIR = join(homedir(), '.kbot');
14
+ const BUDDY_FILE = join(KBOT_DIR, 'buddy.json');
15
+ const CONFIG_PATH = join(KBOT_DIR, 'config.json');
16
+ // ── Species list (order matters — index = hash result) ──
17
+ const SPECIES = [
18
+ 'fox', 'owl', 'cat', 'robot', 'ghost', 'mushroom', 'octopus', 'dragon',
19
+ ];
20
+ // ── Default names per species ──
21
+ const DEFAULT_NAMES = {
22
+ fox: 'Patch',
23
+ owl: 'Hoot',
24
+ cat: 'Pixel',
25
+ robot: 'Bolt',
26
+ ghost: 'Wisp',
27
+ mushroom: 'Spore',
28
+ octopus: 'Ink',
29
+ dragon: 'Ember',
30
+ };
31
+ // ── ASCII Sprites ──
32
+ // Each sprite is an array of strings (lines). Max 5 lines, max 15 chars wide.
33
+ // Pure ASCII only — no unicode box drawing.
34
+ const SPRITES = {
35
+ fox: {
36
+ idle: [
37
+ ' /\\ /\\ ',
38
+ ' ( o . o ) ',
39
+ ' > ^ < ',
40
+ ' /| |\\ ',
41
+ '(_| |_) ',
42
+ ],
43
+ thinking: [
44
+ ' /\\ /\\ ',
45
+ ' ( o . o ) ',
46
+ ' > ~ < ..',
47
+ ' /| |\\ ',
48
+ '(_| |_) ',
49
+ ],
50
+ success: [
51
+ ' /\\ /\\ ',
52
+ ' ( ^ . ^ ) ',
53
+ ' > w < ',
54
+ ' \\| |/ ',
55
+ ' | | ',
56
+ ],
57
+ error: [
58
+ ' /\\ /\\ ',
59
+ ' ( ; . ; ) ',
60
+ ' > n < ',
61
+ ' /| |\\ ',
62
+ '(_| |_) ',
63
+ ],
64
+ learning: [
65
+ ' /\\ /\\ ',
66
+ ' ( o . o ) ',
67
+ ' > - < |] ',
68
+ ' /| |\\ ',
69
+ '(_| |_) ',
70
+ ],
71
+ },
72
+ owl: {
73
+ idle: [
74
+ ' {o,o} ',
75
+ ' /)__) ',
76
+ ' -"--"-- ',
77
+ ' | | ',
78
+ ' ^ ^ ',
79
+ ],
80
+ thinking: [
81
+ ' {o,o} ',
82
+ ' /)__)..',
83
+ ' -"--"-- ',
84
+ ' | | ',
85
+ ' ^ ^ ',
86
+ ],
87
+ success: [
88
+ ' {^,^} ',
89
+ ' /)__) ',
90
+ ' -"--"-- ',
91
+ ' \\| |/ ',
92
+ ' ^ ^ ',
93
+ ],
94
+ error: [
95
+ ' {;,;} ',
96
+ ' /)__) ',
97
+ ' -"--"-- ',
98
+ ' | | ',
99
+ ' ^ ^ ',
100
+ ],
101
+ learning: [
102
+ ' {o,o} ',
103
+ ' /)__)|]',
104
+ ' -"--"-- ',
105
+ ' | | ',
106
+ ' ^ ^ ',
107
+ ],
108
+ },
109
+ cat: {
110
+ idle: [
111
+ ' /\\_/\\ ',
112
+ '( o.o ) ',
113
+ ' > ^ < ',
114
+ ' | | ',
115
+ ' ~ ~ ',
116
+ ],
117
+ thinking: [
118
+ ' /\\_/\\ ',
119
+ '( o.o )..',
120
+ ' > ^ < ',
121
+ ' | | ',
122
+ ' ~ ~ ',
123
+ ],
124
+ success: [
125
+ ' /\\_/\\ ',
126
+ '( ^.^ ) ',
127
+ ' > w < ',
128
+ ' \\ / ',
129
+ ' ~ ~ ',
130
+ ],
131
+ error: [
132
+ ' /\\_/\\ ',
133
+ '( ;.; ) ',
134
+ ' > n < ',
135
+ ' | | ',
136
+ ' ~ ~ ',
137
+ ],
138
+ learning: [
139
+ ' /\\_/\\ ',
140
+ '( o.o ) ',
141
+ ' > - <|] ',
142
+ ' | | ',
143
+ ' ~ ~ ',
144
+ ],
145
+ },
146
+ robot: {
147
+ idle: [
148
+ ' [=====] ',
149
+ ' |[o o]| ',
150
+ ' | _ | ',
151
+ ' |_____| ',
152
+ ' || || ',
153
+ ],
154
+ thinking: [
155
+ ' [=====] ',
156
+ ' |[o o]|.',
157
+ ' | _ |.',
158
+ ' |_____| ',
159
+ ' || || ',
160
+ ],
161
+ success: [
162
+ ' [=====] ',
163
+ ' |[^ ^]| ',
164
+ ' | \\_/ | ',
165
+ ' |_____| ',
166
+ ' \\|| ||/ ',
167
+ ],
168
+ error: [
169
+ ' [=====] ',
170
+ ' |[x x]| ',
171
+ ' | ~ | ',
172
+ ' |_____| ',
173
+ ' || || ',
174
+ ],
175
+ learning: [
176
+ ' [=====] ',
177
+ ' |[o o]| ',
178
+ ' | _ | ',
179
+ ' |_____|]',
180
+ ' || || ',
181
+ ],
182
+ },
183
+ ghost: {
184
+ idle: [
185
+ ' .---. ',
186
+ ' / o o \\ ',
187
+ '| o |',
188
+ ' | | ',
189
+ ' ~~W~~ ',
190
+ ],
191
+ thinking: [
192
+ ' .---. ',
193
+ ' / o o \\..',
194
+ '| o |',
195
+ ' | | ',
196
+ ' ~~W~~ ',
197
+ ],
198
+ success: [
199
+ ' .---. ',
200
+ ' / ^ ^ \\ ',
201
+ '| v |',
202
+ ' | | ',
203
+ ' ~~W~~ ',
204
+ ],
205
+ error: [
206
+ ' .---. ',
207
+ ' / ; ; \\ ',
208
+ '| ~ |',
209
+ ' | | ',
210
+ ' ~~W~~ ',
211
+ ],
212
+ learning: [
213
+ ' .---. ',
214
+ ' / o o \\ ',
215
+ '| o |]',
216
+ ' | | ',
217
+ ' ~~W~~ ',
218
+ ],
219
+ },
220
+ mushroom: {
221
+ idle: [
222
+ ' .-^-. ',
223
+ ' / o o \\ ',
224
+ '|_______|',
225
+ ' | | ',
226
+ ' |_| ',
227
+ ],
228
+ thinking: [
229
+ ' .-^-. ',
230
+ ' / o o \\..',
231
+ '|_______|',
232
+ ' | | ',
233
+ ' |_| ',
234
+ ],
235
+ success: [
236
+ ' .-^-. ',
237
+ ' / ^ ^ \\ ',
238
+ '|_______|',
239
+ ' \\| |/ ',
240
+ ' |_| ',
241
+ ],
242
+ error: [
243
+ ' .-^-. ',
244
+ ' / ; ; \\ ',
245
+ '|_______|',
246
+ ' | | ',
247
+ ' |_| ',
248
+ ],
249
+ learning: [
250
+ ' .-^-. ',
251
+ ' / o o \\ ',
252
+ '|______|]',
253
+ ' | | ',
254
+ ' |_| ',
255
+ ],
256
+ },
257
+ octopus: {
258
+ idle: [
259
+ ' .---. ',
260
+ ' / o o \\ ',
261
+ '| --- |',
262
+ ' \\/\\/\\/\\/ ',
263
+ ' ~ ~ ~ ',
264
+ ],
265
+ thinking: [
266
+ ' .---. ',
267
+ ' / o o \\..',
268
+ '| --- |',
269
+ ' \\/\\/\\/\\/ ',
270
+ ' ~ ~ ~ ',
271
+ ],
272
+ success: [
273
+ ' .---. ',
274
+ ' / ^ ^ \\ ',
275
+ '| \\_/ |',
276
+ ' \\/\\/\\/\\/ ',
277
+ ' \\~ ~ ~/ ',
278
+ ],
279
+ error: [
280
+ ' .---. ',
281
+ ' / ; ; \\ ',
282
+ '| ~~~ |',
283
+ ' \\/\\/\\/\\/ ',
284
+ ' ~ ~ ~ ',
285
+ ],
286
+ learning: [
287
+ ' .---. ',
288
+ ' / o o \\ ',
289
+ '| --- |]',
290
+ ' \\/\\/\\/\\/ ',
291
+ ' ~ ~ ~ ',
292
+ ],
293
+ },
294
+ dragon: {
295
+ idle: [
296
+ ' /\\_ ',
297
+ ' / o > ',
298
+ '| --/\\ ',
299
+ ' \\/\\/ ',
300
+ ' ^^ ',
301
+ ],
302
+ thinking: [
303
+ ' /\\_ ',
304
+ ' / o > ..',
305
+ '| --/\\ ',
306
+ ' \\/\\/ ',
307
+ ' ^^ ',
308
+ ],
309
+ success: [
310
+ ' /\\_ ',
311
+ ' / ^ >* ',
312
+ '| --/\\ ',
313
+ ' \\/\\/ ~ ',
314
+ ' ^^ ',
315
+ ],
316
+ error: [
317
+ ' /\\_ ',
318
+ ' / ; > ',
319
+ '| --/\\ ',
320
+ ' \\/\\/ ',
321
+ ' ^^ ',
322
+ ],
323
+ learning: [
324
+ ' /\\_ ',
325
+ ' / o > ',
326
+ '| --/|] ',
327
+ ' \\/\\/ ',
328
+ ' ^^ ',
329
+ ],
330
+ },
331
+ };
332
+ // ── Greetings per species ──
333
+ const GREETINGS = {
334
+ fox: ['Yip! Ready to dig in.', 'What are we hunting today?', 'Tail wagging. Lets go.'],
335
+ owl: ['Hoo! I see everything.', 'Wise choice opening kbot.', 'Night shift begins.'],
336
+ cat: ['Mrow. Fine, lets work.', 'I was napping, but ok.', 'Keyboard is warm. Go.'],
337
+ robot: ['Systems online.', 'All circuits nominal.', 'Beep boop. Ready.'],
338
+ ghost: ['Boo... I mean, hi!', 'Haunting your terminal.', 'Floating by to help.'],
339
+ mushroom: ['Sprouting up!', 'Growing ideas today.', 'Rooted and ready.'],
340
+ octopus: ['All arms on deck!', 'Eight ways to help.', 'Tentacles ready.'],
341
+ dragon: ['*tiny flame*', 'Wings stretched. Go.', 'Guarding your code.'],
342
+ };
343
+ // ── Mood messages ──
344
+ const MOOD_MESSAGES = {
345
+ idle: ['Just hanging out.', 'Waiting for action.', 'Idle but alert.'],
346
+ thinking: ['Hmm, thinking...', 'Processing...', 'Working on it...'],
347
+ success: ['Nailed it!', 'That worked!', 'Nice one!'],
348
+ error: ['Uh oh...', 'That stings.', 'Something broke.'],
349
+ learning: ['Studying patterns...', 'Learning something.', 'Getting smarter.'],
350
+ };
351
+ // ── State ──
352
+ let currentMood = 'idle';
353
+ let cachedSpecies = null;
354
+ let cachedName = null;
355
+ // ── Config persistence ──
356
+ function ensureDir() {
357
+ if (!existsSync(KBOT_DIR))
358
+ mkdirSync(KBOT_DIR, { recursive: true });
359
+ }
360
+ function loadBuddyConfig() {
361
+ if (!existsSync(BUDDY_FILE))
362
+ return {};
363
+ try {
364
+ return JSON.parse(readFileSync(BUDDY_FILE, 'utf-8'));
365
+ }
366
+ catch {
367
+ return {};
368
+ }
369
+ }
370
+ function saveBuddyConfig(config) {
371
+ ensureDir();
372
+ writeFileSync(BUDDY_FILE, JSON.stringify(config, null, 2));
373
+ }
374
+ // ── Species assignment (deterministic) ──
375
+ function hashConfigPath() {
376
+ const hash = createHash('sha256').update(CONFIG_PATH).digest();
377
+ // Use first 4 bytes as uint32, mod by species count
378
+ return hash.readUInt32BE(0) % SPECIES.length;
379
+ }
380
+ function resolveSpecies() {
381
+ if (cachedSpecies)
382
+ return cachedSpecies;
383
+ cachedSpecies = SPECIES[hashConfigPath()];
384
+ return cachedSpecies;
385
+ }
386
+ function resolveName() {
387
+ if (cachedName)
388
+ return cachedName;
389
+ const config = loadBuddyConfig();
390
+ cachedName = config.name || DEFAULT_NAMES[resolveSpecies()];
391
+ return cachedName;
392
+ }
393
+ // ── Public API ──
394
+ /** Get the buddy's current state (species, name, mood) */
395
+ export function getBuddy() {
396
+ return {
397
+ species: resolveSpecies(),
398
+ name: resolveName(),
399
+ mood: currentMood,
400
+ };
401
+ }
402
+ /** Set the buddy's mood */
403
+ export function setBuddyMood(mood) {
404
+ currentMood = mood;
405
+ }
406
+ /** Get the ASCII sprite for the buddy in the given mood (defaults to current) */
407
+ export function getBuddySprite(mood) {
408
+ const m = mood ?? currentMood;
409
+ const species = resolveSpecies();
410
+ return SPRITES[species][m];
411
+ }
412
+ /** Get a random greeting for the buddy */
413
+ export function getBuddyGreeting() {
414
+ const species = resolveSpecies();
415
+ const greetings = GREETINGS[species];
416
+ const idx = Math.floor(Math.random() * greetings.length);
417
+ return greetings[idx];
418
+ }
419
+ /** Rename the buddy (persisted to ~/.kbot/buddy.json) */
420
+ export function renameBuddy(newName) {
421
+ const config = loadBuddyConfig();
422
+ config.name = newName.trim();
423
+ saveBuddyConfig(config);
424
+ cachedName = config.name;
425
+ }
426
+ /** Pick a random message for the current mood */
427
+ function moodMessage() {
428
+ const msgs = MOOD_MESSAGES[currentMood];
429
+ return msgs[Math.floor(Math.random() * msgs.length)];
430
+ }
431
+ /**
432
+ * Format the buddy with a speech bubble and status message.
433
+ * Returns a multi-line string ready for terminal output.
434
+ *
435
+ * .----------------.
436
+ * | Status message |
437
+ * '----------------'
438
+ * /\ /\
439
+ * ( o . o )
440
+ * > ^ <
441
+ * /| |\
442
+ * (_| |_)
443
+ * ~ Patch the fox ~
444
+ */
445
+ export function formatBuddyStatus(message) {
446
+ const name = resolveName();
447
+ const species = resolveSpecies();
448
+ const sprite = getBuddySprite();
449
+ const text = message || moodMessage();
450
+ // Build speech bubble
451
+ const inner = ` ${text} `;
452
+ const width = inner.length;
453
+ const top = '.' + '-'.repeat(width) + '.';
454
+ const mid = '|' + inner + '|';
455
+ const bot = "'" + '-'.repeat(width) + "'";
456
+ const lines = [];
457
+ lines.push(` ${top}`);
458
+ lines.push(` ${mid}`);
459
+ lines.push(` ${bot}`);
460
+ // Sprite lines
461
+ for (const line of sprite) {
462
+ lines.push(` ${line}`);
463
+ }
464
+ // Name tag
465
+ lines.push(` ~ ${name} the ${species} ~`);
466
+ return lines.join('\n');
467
+ }
468
+ //# sourceMappingURL=buddy.js.map
package/dist/cli.js CHANGED
@@ -3158,6 +3158,80 @@ async function main() {
3158
3158
  const r = await executeTool({ id: 'db_health', name: 'db_health', arguments: {} });
3159
3159
  console.log(r.result);
3160
3160
  });
3161
+ // ── Dream Engine ──
3162
+ const dreamCmd = program
3163
+ .command('dream')
3164
+ .description('Memory consolidation — consolidate session knowledge into durable insights');
3165
+ dreamCmd
3166
+ .command('run')
3167
+ .description('Run a dream cycle now (uses local Ollama)')
3168
+ .action(async () => {
3169
+ const { dream } = await import('./dream.js');
3170
+ printInfo('Dreaming... consolidating session memories with local AI');
3171
+ const result = await dream();
3172
+ if (result.success) {
3173
+ printSuccess(`Dream cycle #${result.cycle} complete`);
3174
+ console.log(` New insights: ${result.newInsights}`);
3175
+ console.log(` Reinforced: ${result.reinforced}`);
3176
+ console.log(` Archived: ${result.archived} aged-out`);
3177
+ console.log(` Duration: ${result.duration}ms`);
3178
+ }
3179
+ else {
3180
+ printWarn(result.error || 'Dream cycle failed');
3181
+ if (result.archived > 0)
3182
+ console.log(` (Still archived ${result.archived} aged-out insights)`);
3183
+ }
3184
+ });
3185
+ dreamCmd
3186
+ .command('status')
3187
+ .description('Show dream engine status and top insights')
3188
+ .action(async () => {
3189
+ const { getDreamStatus } = await import('./dream.js');
3190
+ const { state, insights, archiveCount } = getDreamStatus();
3191
+ console.log(chalk.bold('\nDream Engine'));
3192
+ console.log(chalk.dim('═══════════════════'));
3193
+ console.log(`Cycles: ${state.cycles}`);
3194
+ console.log(`Last: ${state.lastDream || 'never'}`);
3195
+ console.log(`Active: ${state.activeInsights} insights`);
3196
+ console.log(`Archived: ${state.totalArchived} (${archiveCount} files)`);
3197
+ if (insights.length > 0) {
3198
+ const avgRel = Math.round(insights.reduce((s, i) => s + i.relevance, 0) / insights.length * 100);
3199
+ console.log(`Avg relevance: ${avgRel}%`);
3200
+ console.log(chalk.bold('\nTop insights:'));
3201
+ for (const i of insights.slice(0, 8)) {
3202
+ const pct = Math.round(i.relevance * 100);
3203
+ console.log(` ${chalk.green(`${pct}%`)} [${chalk.cyan(i.category)}] ${i.content}`);
3204
+ }
3205
+ }
3206
+ else {
3207
+ console.log(chalk.dim('\nNo insights yet. Run: kbot dream run'));
3208
+ }
3209
+ });
3210
+ dreamCmd
3211
+ .command('search <query>')
3212
+ .description('Search dream insights by keyword')
3213
+ .action(async (query) => {
3214
+ const { searchDreams } = await import('./dream.js');
3215
+ const results = searchDreams(query);
3216
+ if (results.length === 0) {
3217
+ printInfo(`No insights match "${query}"`);
3218
+ return;
3219
+ }
3220
+ console.log(chalk.bold(`\n${results.length} insights matching "${query}":\n`));
3221
+ for (const i of results.slice(0, 15)) {
3222
+ const pct = Math.round(i.relevance * 100);
3223
+ console.log(` ${chalk.green(`${pct}%`)} [${chalk.cyan(i.category)}] ${i.content}`);
3224
+ console.log(chalk.dim(` ${i.keywords.join(', ')} | ${i.sessions} sessions | ${i.created.split('T')[0]}`));
3225
+ }
3226
+ });
3227
+ dreamCmd
3228
+ .command('journal')
3229
+ .description('Print the full dream journal')
3230
+ .action(async () => {
3231
+ const { getDreamPrompt } = await import('./dream.js');
3232
+ const journal = getDreamPrompt(50);
3233
+ console.log(journal || chalk.dim('Dream journal is empty. Run: kbot dream run'));
3234
+ });
3161
3235
  program.parse(process.argv);
3162
3236
  const opts = program.opts();
3163
3237
  const promptArgs = program.args;
@@ -3165,7 +3239,7 @@ async function main() {
3165
3239
  if (opts.quiet)
3166
3240
  setQuiet(true);
3167
3241
  // If a sub-command was run, we're done
3168
- if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'release', 'completions', 'automate', 'status', 'spec', 'a2a', 'init', 'email-agent', 'imessage-agent', 'consultation', 'observe', 'discovery', 'bench', 'lab', 'teach', 'sessions', 'admin', 'monitor', 'analytics', 'deploy', 'env', 'db'].includes(program.args[0]))
3242
+ if (['byok', 'auth', 'ide', 'local', 'ollama', 'kbot-local', 'pull', 'doctor', 'serve', 'agents', 'watch', 'voice', 'export', 'plugins', 'changelog', 'release', 'completions', 'automate', 'status', 'spec', 'a2a', 'init', 'email-agent', 'imessage-agent', 'consultation', 'observe', 'discovery', 'bench', 'lab', 'teach', 'sessions', 'admin', 'monitor', 'analytics', 'deploy', 'env', 'db', 'dream'].includes(program.args[0]))
3169
3243
  return;
3170
3244
  // ── Ollama Launch Integration ──
3171
3245
  // Detect when kbot is started via `ollama launch kbot` or `kbot --ollama-launch`