bobo-ai-cli 2.1.0 → 3.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.
Files changed (115) hide show
  1. package/README.md +52 -9
  2. package/dist/agent.js +66 -45
  3. package/dist/agent.js.map +1 -1
  4. package/dist/agents/catalog.d.ts +40 -0
  5. package/dist/agents/catalog.js +172 -0
  6. package/dist/agents/catalog.js.map +1 -0
  7. package/dist/agents/index.d.ts +6 -0
  8. package/dist/agents/index.js +4 -0
  9. package/dist/agents/index.js.map +1 -0
  10. package/dist/agents/router.d.ts +43 -0
  11. package/dist/agents/router.js +87 -0
  12. package/dist/agents/router.js.map +1 -0
  13. package/dist/agents/spawn.d.ts +46 -0
  14. package/dist/agents/spawn.js +91 -0
  15. package/dist/agents/spawn.js.map +1 -0
  16. package/dist/autonomous.js +41 -1
  17. package/dist/autonomous.js.map +1 -1
  18. package/dist/cli.d.ts +8 -0
  19. package/dist/cli.js +667 -0
  20. package/dist/cli.js.map +1 -0
  21. package/dist/compactor.d.ts +49 -4
  22. package/dist/compactor.js +164 -17
  23. package/dist/compactor.js.map +1 -1
  24. package/dist/completer.js +2 -0
  25. package/dist/completer.js.map +1 -1
  26. package/dist/cost-tracker.js +35 -2
  27. package/dist/cost-tracker.js.map +1 -1
  28. package/dist/dream.d.ts +42 -0
  29. package/dist/dream.js +321 -0
  30. package/dist/dream.js.map +1 -0
  31. package/dist/hooks.d.ts +1 -1
  32. package/dist/hooks.js +4 -0
  33. package/dist/hooks.js.map +1 -1
  34. package/dist/index.js +8 -1134
  35. package/dist/index.js.map +1 -1
  36. package/dist/insight.js +4 -11
  37. package/dist/insight.js.map +1 -1
  38. package/dist/knowledge.d.ts +13 -0
  39. package/dist/knowledge.js +13 -2
  40. package/dist/knowledge.js.map +1 -1
  41. package/dist/memory.d.ts +4 -0
  42. package/dist/memory.js +6 -0
  43. package/dist/memory.js.map +1 -1
  44. package/dist/repl.d.ts +16 -0
  45. package/dist/repl.js +702 -0
  46. package/dist/repl.js.map +1 -0
  47. package/dist/sessions.js +23 -0
  48. package/dist/sessions.js.map +1 -1
  49. package/dist/skills/composer.d.ts +18 -0
  50. package/dist/skills/composer.js +59 -0
  51. package/dist/skills/composer.js.map +1 -0
  52. package/dist/skills/index.d.ts +3 -0
  53. package/dist/skills/index.js +3 -0
  54. package/dist/skills/index.js.map +1 -0
  55. package/dist/skills/loader.d.ts +12 -0
  56. package/dist/skills/loader.js +150 -0
  57. package/dist/skills/loader.js.map +1 -0
  58. package/dist/skills/types.d.ts +28 -0
  59. package/dist/skills/types.js +9 -0
  60. package/dist/skills/types.js.map +1 -0
  61. package/dist/skills.d.ts +1 -0
  62. package/dist/skills.js +1 -0
  63. package/dist/skills.js.map +1 -1
  64. package/dist/state/artifacts.d.ts +71 -0
  65. package/dist/state/artifacts.js +131 -0
  66. package/dist/state/artifacts.js.map +1 -0
  67. package/dist/state/index.d.ts +9 -0
  68. package/dist/state/index.js +7 -0
  69. package/dist/state/index.js.map +1 -0
  70. package/dist/state/manager.d.ts +89 -0
  71. package/dist/state/manager.js +130 -0
  72. package/dist/state/manager.js.map +1 -0
  73. package/dist/state/project-memory.d.ts +24 -0
  74. package/dist/state/project-memory.js +81 -0
  75. package/dist/state/project-memory.js.map +1 -0
  76. package/dist/state/recovery.d.ts +24 -0
  77. package/dist/state/recovery.js +94 -0
  78. package/dist/state/recovery.js.map +1 -0
  79. package/dist/statusbar.d.ts +1 -0
  80. package/dist/statusbar.js +12 -1
  81. package/dist/statusbar.js.map +1 -1
  82. package/dist/sub-agent-runner.d.ts +1 -0
  83. package/dist/sub-agent-runner.js +29 -3
  84. package/dist/sub-agent-runner.js.map +1 -1
  85. package/dist/sub-agents.d.ts +19 -1
  86. package/dist/sub-agents.js +137 -2
  87. package/dist/sub-agents.js.map +1 -1
  88. package/dist/tool-governance.d.ts +77 -0
  89. package/dist/tool-governance.js +335 -0
  90. package/dist/tool-governance.js.map +1 -0
  91. package/dist/ui/hud.d.ts +25 -0
  92. package/dist/ui/hud.js +67 -0
  93. package/dist/ui/hud.js.map +1 -0
  94. package/dist/verification-agent.d.ts +46 -0
  95. package/dist/verification-agent.js +528 -0
  96. package/dist/verification-agent.js.map +1 -0
  97. package/dist/workflows/ask.d.ts +13 -0
  98. package/dist/workflows/ask.js +66 -0
  99. package/dist/workflows/ask.js.map +1 -0
  100. package/dist/workflows/index.d.ts +5 -0
  101. package/dist/workflows/index.js +6 -0
  102. package/dist/workflows/index.js.map +1 -0
  103. package/dist/workflows/interview.d.ts +11 -0
  104. package/dist/workflows/interview.js +36 -0
  105. package/dist/workflows/interview.js.map +1 -0
  106. package/dist/workflows/plan.d.ts +13 -0
  107. package/dist/workflows/plan.js +34 -0
  108. package/dist/workflows/plan.js.map +1 -0
  109. package/dist/workflows/team.d.ts +17 -0
  110. package/dist/workflows/team.js +86 -0
  111. package/dist/workflows/team.js.map +1 -0
  112. package/dist/workflows/verify.d.ts +11 -0
  113. package/dist/workflows/verify.js +21 -0
  114. package/dist/workflows/verify.js.map +1 -0
  115. package/package.json +2 -4
package/dist/repl.js ADDED
@@ -0,0 +1,702 @@
1
+ /**
2
+ * REPL mode — interactive conversation loop with slash commands.
3
+ */
4
+ import { createInterface } from 'node:readline';
5
+ import { existsSync, writeFileSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { execSync } from 'node:child_process';
8
+ import { loadConfig, getConfigDir } from './config.js';
9
+ import { runAgent } from './agent.js';
10
+ import { listKnowledgeFiles } from './knowledge.js';
11
+ import { listSkills } from './skills.js';
12
+ import { getCurrentPlan, resetPlan } from './planner.js';
13
+ import { toolDefinitions } from './tools/index.js';
14
+ import { printWelcome, printError, printSuccess, printLine, printWarning } from './ui.js';
15
+ import { saveSession, listSessions, loadSession, getRecentSession } from './sessions.js';
16
+ import { generateInsight } from './insight.js';
17
+ import { spawnSubAgent, listSubAgents, getSubAgent } from './sub-agents.js';
18
+ import { enableStatusBar, disableStatusBar, updateStatusBar, setupResizeHandler, renderStatusBar } from './statusbar.js';
19
+ import { slashCompleter } from './completer.js';
20
+ import { runHooks } from './hooks.js';
21
+ import { initMcpServers, shutdownMcpServers, getMcpStatus } from './mcp-client.js';
22
+ import { killAllProcesses } from './tools/process-manager.js';
23
+ import { cleanupClaudeSessions } from './tools/claude-code.js';
24
+ import { getCompactStatus, compressHistory } from './compactor.js';
25
+ import { getRouterStats, debugRoute } from './skill-router.js';
26
+ import { formatCostReport } from './cost-tracker.js';
27
+ import { getPreset, listPresets } from './providers.js';
28
+ import { runDream, formatDreamResult, shouldAutoDream } from './dream.js';
29
+ import { runVerification, formatVerificationResult } from './verification-agent.js';
30
+ import chalk from 'chalk';
31
+ /**
32
+ * Start interactive REPL mode.
33
+ */
34
+ export async function startRepl(opts) {
35
+ const config = loadConfig();
36
+ const skills = listSkills();
37
+ const knowledgeFiles = listKnowledgeFiles();
38
+ const sessionStartTime = Date.now();
39
+ const matchedSkills = [];
40
+ // Initialize MCP servers in background
41
+ initMcpServers().catch(() => { });
42
+ // Runtime overrides
43
+ let currentModel = opts.model || config.model;
44
+ let currentEffort = opts.effort || config.effort;
45
+ let currentPermissionMode = opts.permissionMode || config.permissionMode;
46
+ let sessionName = '';
47
+ printWelcome({
48
+ version: opts.version,
49
+ model: currentModel,
50
+ toolCount: toolDefinitions.length,
51
+ skillsActive: skills.filter(s => s.enabled).length,
52
+ skillsTotal: skills.length,
53
+ knowledgeCount: knowledgeFiles.length,
54
+ cwd: process.cwd(),
55
+ });
56
+ // Check BOBO.md
57
+ const boboMdExists = existsSync(join(process.cwd(), 'BOBO.md'));
58
+ if (boboMdExists) {
59
+ printLine(chalk.dim(' 📋 BOBO.md loaded'));
60
+ }
61
+ if (!config.apiKey) {
62
+ printWarning('API key not configured. Run: bobo config set apiKey <your-key>');
63
+ printLine();
64
+ }
65
+ // Restore session
66
+ let history = [];
67
+ if (opts.continueSession) {
68
+ // -c flag: continue most recent session
69
+ const recent = getRecentSession(86400000); // 24 hours
70
+ if (recent && recent.messages.length > 0) {
71
+ history = recent.messages;
72
+ printSuccess(`Continuing session (${history.length} messages, "${recent.firstUserMessage.slice(0, 40)}...")`);
73
+ }
74
+ else {
75
+ printWarning('No recent session found.');
76
+ }
77
+ }
78
+ else if (opts.resumeId) {
79
+ // -r flag: resume specific session
80
+ const session = loadSession(opts.resumeId);
81
+ if (session) {
82
+ history = session.messages;
83
+ printSuccess(`Resumed session ${opts.resumeId} (${history.length} messages)`);
84
+ }
85
+ else {
86
+ printWarning(`Session not found: ${opts.resumeId}`);
87
+ }
88
+ }
89
+ else {
90
+ // Auto-resume prompt
91
+ const recentSession = getRecentSession(3600000);
92
+ if (recentSession && recentSession.messages.length > 0) {
93
+ printLine(chalk.yellow(`💾 Found recent session (${recentSession.messageCount} messages, ${recentSession.firstUserMessage.slice(0, 50)}...)`));
94
+ printLine(chalk.dim(' Resume? (y/n)'));
95
+ const answer = await new Promise((resolve) => {
96
+ const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
97
+ tmpRl.question(chalk.green('> '), (ans) => {
98
+ tmpRl.close();
99
+ resolve(ans.trim().toLowerCase());
100
+ });
101
+ });
102
+ if (answer === 'y' || answer === 'yes') {
103
+ history = recentSession.messages;
104
+ printSuccess(`Resumed session (${history.length} messages)`);
105
+ }
106
+ }
107
+ }
108
+ // Enable status bar
109
+ if (process.stdout.isTTY) {
110
+ setupResizeHandler();
111
+ enableStatusBar({
112
+ model: currentModel,
113
+ thinkingLevel: currentEffort,
114
+ skillsCount: skills.filter(s => s.enabled).length,
115
+ cwd: process.cwd(),
116
+ permissionMode: currentPermissionMode,
117
+ });
118
+ }
119
+ const rl = createInterface({
120
+ input: process.stdin,
121
+ output: process.stdout,
122
+ prompt: chalk.green('> '),
123
+ completer: slashCompleter,
124
+ });
125
+ let abortController = null;
126
+ let lastResponse = '';
127
+ let autoCompactTriggered = false;
128
+ // Wrapper that renders status bar before prompt
129
+ const showPrompt = () => {
130
+ const bar = renderStatusBar();
131
+ if (bar)
132
+ printLine(bar);
133
+ rl.prompt();
134
+ };
135
+ const autoSave = () => {
136
+ if (history.length > 0) {
137
+ const id = saveSession(history, process.cwd());
138
+ printLine(chalk.dim(`\n💾 Session saved: ${id}`));
139
+ }
140
+ };
141
+ rl.on('SIGINT', () => {
142
+ if (abortController) {
143
+ abortController.abort();
144
+ abortController = null;
145
+ printLine(chalk.dim('\n(cancelled)'));
146
+ showPrompt();
147
+ }
148
+ else {
149
+ printLine(chalk.dim('\n(press Ctrl+C again or Ctrl+D to exit)'));
150
+ showPrompt();
151
+ }
152
+ });
153
+ rl.on('close', async () => {
154
+ autoSave();
155
+ // Auto-dream on session end if needed
156
+ if (shouldAutoDream()) {
157
+ printLine(chalk.dim('\n🌙 Consolidating memories...'));
158
+ try {
159
+ const dreamResult = await runDream({ verbose: false });
160
+ if (dreamResult.insights.length > 0) {
161
+ printLine(chalk.green(`✨ Extracted ${dreamResult.insights.length} insights during shutdown`));
162
+ }
163
+ }
164
+ catch {
165
+ // Silent failure on shutdown
166
+ }
167
+ }
168
+ runHooks('session-end');
169
+ shutdownMcpServers();
170
+ killAllProcesses();
171
+ cleanupClaudeSessions();
172
+ disableStatusBar();
173
+ printLine(chalk.dim('\nGoodbye! 🐕'));
174
+ process.exit(0);
175
+ });
176
+ showPrompt();
177
+ for await (const line of rl) {
178
+ const input = line.trim();
179
+ if (!input) {
180
+ showPrompt();
181
+ continue;
182
+ }
183
+ // ─── Exit ───
184
+ if (input === '/quit' || input === '/exit') {
185
+ autoSave();
186
+ runHooks('session-end');
187
+ shutdownMcpServers();
188
+ killAllProcesses();
189
+ disableStatusBar();
190
+ printLine(chalk.dim('Goodbye! 🐕'));
191
+ process.exit(0);
192
+ }
193
+ // ─── /new, /clear ───
194
+ if (input === '/clear' || input === '/new') {
195
+ history = [];
196
+ matchedSkills.length = 0;
197
+ lastResponse = '';
198
+ autoCompactTriggered = false;
199
+ resetPlan();
200
+ printSuccess('Conversation cleared');
201
+ showPrompt();
202
+ continue;
203
+ }
204
+ // ─── /model [name] ───
205
+ if (input.startsWith('/model')) {
206
+ const newModel = input.replace('/model', '').trim();
207
+ if (!newModel) {
208
+ printLine(chalk.cyan('Current model: ') + currentModel);
209
+ printLine(chalk.dim('Usage: /model <model-name>'));
210
+ printLine(chalk.dim(' Examples: claude-sonnet-4-20250514, gpt-4o, deepseek-chat'));
211
+ }
212
+ else {
213
+ currentModel = newModel;
214
+ updateStatusBar({ model: currentModel });
215
+ printSuccess(`Model switched to: ${currentModel}`);
216
+ }
217
+ showPrompt();
218
+ continue;
219
+ }
220
+ // ─── /effort [level] ───
221
+ if (input.startsWith('/effort')) {
222
+ const level = input.replace('/effort', '').trim().toLowerCase();
223
+ if (!level) {
224
+ printLine(chalk.cyan('Current effort: ') + currentEffort);
225
+ printLine(chalk.dim(' /effort low — Quick, concise answers'));
226
+ printLine(chalk.dim(' /effort medium — Balanced (default)'));
227
+ printLine(chalk.dim(' /effort high — Deep analysis, thorough'));
228
+ }
229
+ else if (['low', 'medium', 'high'].includes(level)) {
230
+ currentEffort = level;
231
+ updateStatusBar({ thinkingLevel: currentEffort });
232
+ printSuccess(`Effort level: ${currentEffort}`);
233
+ }
234
+ else {
235
+ printError('Invalid effort level. Use: low, medium, high');
236
+ }
237
+ showPrompt();
238
+ continue;
239
+ }
240
+ // ─── /copy [n] ───
241
+ if (input.startsWith('/copy')) {
242
+ const indexStr = input.replace('/copy', '').trim();
243
+ let textToCopy = lastResponse;
244
+ if (indexStr) {
245
+ const idx = parseInt(indexStr, 10);
246
+ const assistantMsgs = history.filter(m => m.role === 'assistant' && typeof m.content === 'string');
247
+ if (idx > 0 && idx <= assistantMsgs.length) {
248
+ textToCopy = assistantMsgs[assistantMsgs.length - idx].content;
249
+ }
250
+ }
251
+ if (!textToCopy) {
252
+ printWarning('Nothing to copy.');
253
+ }
254
+ else {
255
+ // Try platform clipboard
256
+ try {
257
+ const clipCmd = process.platform === 'darwin' ? 'pbcopy'
258
+ : process.platform === 'win32' ? 'clip'
259
+ : 'xclip -selection clipboard';
260
+ execSync(clipCmd, { input: textToCopy, timeout: 3000 });
261
+ printSuccess('Copied to clipboard!');
262
+ }
263
+ catch {
264
+ // Fallback: write to file
265
+ const copyPath = join(getConfigDir(), 'last-copy.txt');
266
+ writeFileSync(copyPath, textToCopy);
267
+ printWarning(`Clipboard unavailable. Saved to ${copyPath}`);
268
+ }
269
+ }
270
+ showPrompt();
271
+ continue;
272
+ }
273
+ // ─── /context ───
274
+ if (input === '/context') {
275
+ const msgCount = history.length;
276
+ let totalChars = 0;
277
+ let toolResultChars = 0;
278
+ const roleCounts = {};
279
+ for (const msg of history) {
280
+ const content = typeof msg.content === 'string' ? msg.content : '';
281
+ totalChars += content.length;
282
+ roleCounts[msg.role] = (roleCounts[msg.role] || 0) + 1;
283
+ if (msg.role === 'tool')
284
+ toolResultChars += content.length;
285
+ }
286
+ const estTokens = Math.ceil(totalChars / 3.5);
287
+ const maxContext = 200000; // approximate
288
+ const usage = (estTokens / maxContext * 100).toFixed(1);
289
+ printLine(chalk.cyan.bold('\n📊 Context Analysis\n'));
290
+ printLine(` Messages: ${msgCount}`);
291
+ printLine(` Est. Tokens: ~${estTokens.toLocaleString()} / ${maxContext.toLocaleString()} (${usage}%)`);
292
+ printLine('');
293
+ for (const [role, count] of Object.entries(roleCounts)) {
294
+ printLine(` ${role.padEnd(12)} ${count} messages`);
295
+ }
296
+ if (toolResultChars > totalChars * 0.6) {
297
+ printLine(chalk.yellow('\n ⚠ Tool results are >60% of context. Consider /compact to free space.'));
298
+ }
299
+ if (estTokens > maxContext * 0.75) {
300
+ printLine(chalk.red('\n 🔴 Context usage >75%. Run /compact soon!'));
301
+ }
302
+ else if (estTokens > maxContext * 0.5) {
303
+ printLine(chalk.yellow('\n 🟡 Context usage >50%. Keep an eye on it.'));
304
+ }
305
+ else {
306
+ printLine(chalk.green('\n 🟢 Context usage healthy.'));
307
+ }
308
+ printLine();
309
+ showPrompt();
310
+ continue;
311
+ }
312
+ // ─── /rename <name> ───
313
+ if (input.startsWith('/rename')) {
314
+ const name = input.replace('/rename', '').trim();
315
+ if (!name) {
316
+ printLine(chalk.dim(`Current name: ${sessionName || '(unnamed)'}`));
317
+ printLine(chalk.dim('Usage: /rename <name>'));
318
+ }
319
+ else {
320
+ sessionName = name;
321
+ printSuccess(`Session renamed: ${sessionName}`);
322
+ }
323
+ showPrompt();
324
+ continue;
325
+ }
326
+ // ─── /history ───
327
+ if (input === '/history') {
328
+ printLine(`Turns: ${history.filter(m => m.role === 'user').length}`);
329
+ showPrompt();
330
+ continue;
331
+ }
332
+ // ─── /resume ───
333
+ if (input === '/resume') {
334
+ const sessions = listSessions(10);
335
+ if (sessions.length === 0) {
336
+ printWarning('No saved sessions.');
337
+ showPrompt();
338
+ continue;
339
+ }
340
+ printLine(chalk.cyan.bold('\n💾 Recent Sessions:\n'));
341
+ for (let i = 0; i < sessions.length; i++) {
342
+ const s = sessions[i];
343
+ const date = s.startedAt ? new Date(s.startedAt).toLocaleString() : 'unknown';
344
+ printLine(` ${chalk.bold(String(i + 1).padStart(2))} ${chalk.dim(date)} — ${s.firstUserMessage.slice(0, 50)} (${s.messageCount} msgs)`);
345
+ }
346
+ printLine(chalk.dim('\n Enter number to restore, or press Enter to cancel:'));
347
+ const pick = await new Promise((resolve) => {
348
+ const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
349
+ tmpRl.question(chalk.green('> '), (ans) => {
350
+ tmpRl.close();
351
+ resolve(ans.trim());
352
+ });
353
+ });
354
+ const idx = parseInt(pick, 10) - 1;
355
+ if (idx >= 0 && idx < sessions.length) {
356
+ const session = loadSession(sessions[idx].id);
357
+ if (session) {
358
+ history = session.messages;
359
+ printSuccess(`Restored session (${history.length} messages)`);
360
+ }
361
+ else {
362
+ printError('Failed to load session.');
363
+ }
364
+ }
365
+ showPrompt();
366
+ continue;
367
+ }
368
+ // ─── /insight ───
369
+ if (input === '/insight') {
370
+ printLine(generateInsight(history, sessionStartTime, [...new Set(matchedSkills)]));
371
+ showPrompt();
372
+ continue;
373
+ }
374
+ // ─── /agents, /bg ───
375
+ if (input === '/agents' || input === '/bg') {
376
+ const agents = listSubAgents(10);
377
+ if (agents.length === 0) {
378
+ printLine(chalk.dim('No sub-agents. Use: /spawn <task>'));
379
+ }
380
+ else {
381
+ printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
382
+ for (const a of agents) {
383
+ const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
384
+ const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
385
+ printLine(` ${icon} ${chalk.bold(a.id)} — ${task} ${chalk.dim(`[${a.status}]`)}`);
386
+ }
387
+ }
388
+ printLine();
389
+ showPrompt();
390
+ continue;
391
+ }
392
+ if (input.startsWith('/agents show ')) {
393
+ const id = input.replace('/agents show ', '').trim();
394
+ const agent = getSubAgent(id);
395
+ if (!agent) {
396
+ printError(`Sub-agent not found: ${id}`);
397
+ }
398
+ else {
399
+ printLine(chalk.cyan.bold(`\n🤖 ${agent.id} [${agent.status}]`));
400
+ printLine(chalk.dim(`Task: ${agent.task}`));
401
+ if (agent.result)
402
+ printLine(`\n${agent.result}`);
403
+ if (agent.error)
404
+ printLine(chalk.red(`Error: ${agent.error}`));
405
+ }
406
+ printLine();
407
+ showPrompt();
408
+ continue;
409
+ }
410
+ if (input.startsWith('/spawn ')) {
411
+ const task = input.replace('/spawn ', '').trim();
412
+ if (!task) {
413
+ printWarning('Usage: /spawn <task description>');
414
+ }
415
+ else {
416
+ const result = spawnSubAgent(task);
417
+ if (result.error) {
418
+ printError(result.error);
419
+ }
420
+ else {
421
+ printSuccess(`Sub-agent ${result.id} spawned! Check with /agents`);
422
+ }
423
+ }
424
+ showPrompt();
425
+ continue;
426
+ }
427
+ // ─── /compact ───
428
+ if (input === '/compact') {
429
+ const userCount = history.filter(m => m.role === 'user').length;
430
+ if (userCount > 4) {
431
+ printLine(chalk.dim('Compacting context...'));
432
+ abortController = new AbortController();
433
+ try {
434
+ const compactResult = await runAgent('Perform a nine-section context compression. Analyze the conversation so far and produce a structured summary covering: ' +
435
+ '1. Main requests/intent 2. Key technical concepts 3. Files and code 4. Errors and fixes 5. Problem resolution ' +
436
+ '6. All user messages 7. Pending tasks 8. Current work state 9. Next steps (with citations). ' +
437
+ 'Output the summary directly, do not call any tools.', history, { signal: abortController.signal, model: currentModel, effort: currentEffort });
438
+ history = [
439
+ { role: 'user', content: 'Below is a compressed summary of our prior conversation. Continue from here.' },
440
+ { role: 'assistant', content: compactResult.response },
441
+ ];
442
+ autoCompactTriggered = false;
443
+ printSuccess('Context compacted (nine-section summary)');
444
+ }
445
+ catch (e) {
446
+ if (e.message !== 'Aborted') {
447
+ history = history.slice(-8);
448
+ printSuccess('Context compacted (truncated)');
449
+ }
450
+ }
451
+ abortController = null;
452
+ }
453
+ else {
454
+ printWarning('Conversation too short to compact');
455
+ }
456
+ showPrompt();
457
+ continue;
458
+ }
459
+ // ─── /dream (KAIROS memory consolidation) ───
460
+ if (input === '/dream') {
461
+ printLine(chalk.dim('🌙 Running KAIROS dream mode...'));
462
+ try {
463
+ const dreamResult = await runDream({ verbose: true });
464
+ printLine(formatDreamResult(dreamResult));
465
+ }
466
+ catch (e) {
467
+ printError(`Dream mode failed: ${e.message}`);
468
+ }
469
+ showPrompt();
470
+ continue;
471
+ }
472
+ // ─── /verify (verification agent) ───
473
+ if (input.startsWith('/verify')) {
474
+ const task = input.replace('/verify', '').trim() || 'Verify current project state';
475
+ printLine(chalk.dim('🔍 Running verification agent...'));
476
+ try {
477
+ const verifyResult = await runVerification(task, lastResponse || '', {
478
+ cwd: process.cwd(),
479
+ });
480
+ printLine(formatVerificationResult(verifyResult));
481
+ // If verification failed, suggest fixes
482
+ if (verifyResult.verdict === 'FAIL' && verifyResult.suggestedFixes) {
483
+ printLine(chalk.yellow('\n💡 Suggested next steps:'));
484
+ for (const fix of verifyResult.suggestedFixes) {
485
+ printLine(chalk.dim(` • ${fix}`));
486
+ }
487
+ }
488
+ }
489
+ catch (e) {
490
+ printError(`Verification failed: ${e.message}`);
491
+ }
492
+ showPrompt();
493
+ continue;
494
+ }
495
+ // ─── /status ───
496
+ if (input === '/status') {
497
+ const turns = history.filter(m => m.role === 'user').length;
498
+ const mcpServers = getMcpStatus();
499
+ const compactInfo = getCompactStatus(history);
500
+ printLine(chalk.cyan('📊 Session Status:'));
501
+ printLine(` Model: ${currentModel}`);
502
+ printLine(` Effort: ${currentEffort}`);
503
+ printLine(` Permission: ${currentPermissionMode}`);
504
+ printLine(` Turns: ${turns}`);
505
+ printLine(` Messages: ${history.length}`);
506
+ printLine(` Tokens: ~${compactInfo.tokens.toLocaleString()} (${compactInfo.urgency})`);
507
+ printLine(` CWD: ${process.cwd()}`);
508
+ if (sessionName)
509
+ printLine(` Name: ${sessionName}`);
510
+ if (mcpServers.length > 0) {
511
+ printLine(` MCP: ${mcpServers.filter(s => s.ready).length}/${mcpServers.length} servers (${mcpServers.reduce((a, s) => a + s.toolCount, 0)} tools)`);
512
+ }
513
+ printLine(chalk.dim(' ── Cost ──'));
514
+ printLine(` ${formatCostReport(currentModel).split('\n').join('\n ')}`);
515
+ showPrompt();
516
+ continue;
517
+ }
518
+ if (input === '/plan') {
519
+ printLine(getCurrentPlan());
520
+ showPrompt();
521
+ continue;
522
+ }
523
+ if (input === '/knowledge') {
524
+ const files = listKnowledgeFiles();
525
+ for (const f of files) {
526
+ const icon = f.type === 'always' ? '🔵' : f.type === 'on-demand' ? '🟡' : '🟢';
527
+ printLine(` ${icon} ${f.file} (${f.type})`);
528
+ }
529
+ showPrompt();
530
+ continue;
531
+ }
532
+ if (input === '/skills') {
533
+ const sklls = listSkills();
534
+ for (const s of sklls) {
535
+ const icon = s.enabled ? '✅' : '❌';
536
+ printLine(` ${icon} ${s.name} — ${s.description}`);
537
+ }
538
+ showPrompt();
539
+ continue;
540
+ }
541
+ // ─── /mcp ───
542
+ if (input === '/mcp') {
543
+ const mcpServers = getMcpStatus();
544
+ if (mcpServers.length === 0) {
545
+ printLine(chalk.dim('No MCP servers. Configure in ~/.bobo/mcp.json'));
546
+ printLine(chalk.dim('Run: bobo mcp init'));
547
+ }
548
+ else {
549
+ printLine(chalk.cyan.bold('\n🔌 MCP Servers:\n'));
550
+ for (const s of mcpServers) {
551
+ const icon = s.ready ? chalk.green('●') : chalk.red('●');
552
+ printLine(` ${icon} ${chalk.bold(s.name)} [${s.transport}] — ${s.toolCount} tools`);
553
+ }
554
+ }
555
+ printLine();
556
+ showPrompt();
557
+ continue;
558
+ }
559
+ // ─── /cost ───
560
+ if (input === '/cost') {
561
+ printLine(chalk.cyan('💰 API Cost:'));
562
+ printLine(` ${formatCostReport(currentModel).split('\n').join('\n ')}`);
563
+ showPrompt();
564
+ continue;
565
+ }
566
+ // ─── /route (skill router debug) ───
567
+ if (input.startsWith('/route ')) {
568
+ const query = input.slice(7).trim();
569
+ if (query) {
570
+ printLine(chalk.cyan('🔀 Skill Route Debug:'));
571
+ printLine(debugRoute(query));
572
+ }
573
+ else {
574
+ const stats = getRouterStats();
575
+ printLine(chalk.cyan('🔀 Skill Router Stats:'));
576
+ printLine(` Total: ${stats.totalSkills} | Kernel: ${stats.kernel} | Auto: ${stats.auto} | Manual: ${stats.manual}`);
577
+ printLine(` Intent categories: ${stats.intents}`);
578
+ }
579
+ showPrompt();
580
+ continue;
581
+ }
582
+ // ─── /provider ───
583
+ if (input.startsWith('/provider')) {
584
+ const arg = input.slice(9).trim();
585
+ if (!arg) {
586
+ printLine(chalk.cyan('🌐 Available Providers:'));
587
+ printLine(listPresets());
588
+ printLine(chalk.dim('\n Usage: /provider <name> — switch to provider'));
589
+ }
590
+ else {
591
+ const preset = getPreset(arg);
592
+ if (!preset) {
593
+ printError(`Unknown provider: ${arg}`);
594
+ }
595
+ else {
596
+ currentModel = preset.defaultModel;
597
+ // Note: baseUrl and apiKey require config set
598
+ printSuccess(`Switched model to ${preset.defaultModel}`);
599
+ printLine(chalk.dim(` Base URL: ${preset.baseUrl}`));
600
+ printLine(chalk.dim(` Set API key: bobo config set apiKey <key>`));
601
+ printLine(chalk.dim(` Set base URL: bobo config set baseUrl ${preset.baseUrl}`));
602
+ }
603
+ }
604
+ showPrompt();
605
+ continue;
606
+ }
607
+ // ─── /help ───
608
+ if (input === '/help') {
609
+ printLine(chalk.cyan.bold('Commands:'));
610
+ printLine('');
611
+ printLine(chalk.dim(' Session'));
612
+ printLine(' /new Start new conversation');
613
+ printLine(' /clear Clear conversation history');
614
+ printLine(' /compact Compress context (nine-section)');
615
+ printLine(' /resume Restore a previous session');
616
+ printLine(' /rename <n> Rename current session');
617
+ printLine(' /quit Exit');
618
+ printLine('');
619
+ printLine(chalk.dim(' Model & Effort'));
620
+ printLine(' /model <n> Switch model');
621
+ printLine(' /effort <l> Set thinking effort (low/medium/high)');
622
+ printLine('');
623
+ printLine(chalk.dim(' Analysis'));
624
+ printLine(' /insight Session analytics (tokens, tools, skills)');
625
+ printLine(' /context Context usage analysis');
626
+ printLine(' /status Session status');
627
+ printLine(' /copy [n] Copy last response to clipboard');
628
+ printLine(' /plan Show current task plan');
629
+ printLine(' /verify Run verification agent');
630
+ printLine(' /history Show conversation turns');
631
+ printLine('');
632
+ printLine(chalk.dim(' Sub-Agents'));
633
+ printLine(' /spawn <t> Run task in background sub-agent');
634
+ printLine(' /agents List sub-agents');
635
+ printLine(' /agents show <id> Show sub-agent result');
636
+ printLine('');
637
+ printLine(chalk.dim(' Knowledge & Integrations'));
638
+ printLine(' /knowledge List knowledge files');
639
+ printLine(' /skills List skills');
640
+ printLine(' /mcp MCP server status');
641
+ printLine(' /bg Background process list');
642
+ printLine(' /dream Memory consolidation');
643
+ printLine('');
644
+ printLine(chalk.dim(' CLI Commands'));
645
+ printLine(' bobo -p "q" Non-interactive (supports piping)');
646
+ printLine(' bobo -c Continue last conversation');
647
+ printLine(' bobo -r <id> Resume specific session');
648
+ printLine(' bobo --full-auto Auto-approve tool calls');
649
+ printLine(' bobo --yolo No sandbox, no approvals');
650
+ printLine(' bobo watch File watcher (daemon mode)');
651
+ printLine(' bobo run Autonomous agent loop');
652
+ printLine(' bobo mcp MCP server management');
653
+ printLine(' bobo hooks Lifecycle hook management');
654
+ printLine(' bobo doctor Environment check');
655
+ printLine('');
656
+ printLine(chalk.dim(' Debug'));
657
+ printLine(' /cost API cost this session');
658
+ printLine(' /route <msg> Skill router debug');
659
+ printLine(' /provider Switch AI provider');
660
+ showPrompt();
661
+ continue;
662
+ }
663
+ // ─── Run agent ───
664
+ abortController = new AbortController();
665
+ try {
666
+ const result = await runAgent(input, history, {
667
+ signal: abortController.signal,
668
+ matchedSkills,
669
+ model: currentModel,
670
+ effort: currentEffort,
671
+ permissionMode: currentPermissionMode,
672
+ onAutoCompact: () => {
673
+ if (!autoCompactTriggered) {
674
+ autoCompactTriggered = true;
675
+ printLine(chalk.yellow('\n⚠ Context is getting large. Consider running /compact to free space.\n'));
676
+ }
677
+ },
678
+ });
679
+ history = result.history;
680
+ lastResponse = result.response;
681
+ // Auto-compact check
682
+ const compactInfo = getCompactStatus(history);
683
+ if (compactInfo.urgency === 'critical') {
684
+ printWarning(`Context at ${compactInfo.tokens} tokens — auto-compressing...`);
685
+ history = compressHistory(history, 8);
686
+ printSuccess(`Compressed to ${getCompactStatus(history).tokens} tokens`);
687
+ }
688
+ else if (compactInfo.urgency === 'high') {
689
+ printWarning(`⚠ Context at ${compactInfo.tokens} tokens. Run /compact to compress.`);
690
+ }
691
+ }
692
+ catch (e) {
693
+ if (e.message !== 'Aborted') {
694
+ printError(e.message);
695
+ }
696
+ }
697
+ abortController = null;
698
+ printLine();
699
+ showPrompt();
700
+ }
701
+ }
702
+ //# sourceMappingURL=repl.js.map