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/index.js CHANGED
@@ -1,38 +1,12 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, Option } from 'commander';
3
- import { createInterface } from 'node:readline';
4
- import { readFileSync, existsSync, mkdirSync, copyFileSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs';
3
+ import { readFileSync } from 'node:fs';
5
4
  import { join } from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
7
- import { execSync } from 'node:child_process';
8
- import { loadConfig, setConfigValue, getConfigValue, listConfig, ensureConfigDir, getConfigDir, resolveKnowledgeDir, } from './config.js';
9
6
  import { runAgent } from './agent.js';
10
- import { listKnowledgeFiles } from './knowledge.js';
11
- import { listSkills, setSkillEnabled, initSkills, importSkills } from './skills.js';
12
- import { initProject } from './project.js';
13
- import { getCurrentPlan, resetPlan } from './planner.js';
14
- import { toolDefinitions } from './tools/index.js';
15
- import { printWelcome, printError, printSuccess, printLine, printWarning } from './ui.js';
16
- import { registerKnowledgeCommand } from './knowledge-commands.js';
17
- import { registerRulesCommand } from './rules-commands.js';
18
- import { registerStructuredSkillsCommand } from './structured-skills-commands.js';
19
- import { registerStructuredTemplateCommand } from './structured-template-commands.js';
20
- import { saveSession, listSessions, loadSession, getRecentSession } from './sessions.js';
21
- import { generateInsight } from './insight.js';
22
- import { spawnSubAgent, listSubAgents, getSubAgent } from './sub-agents.js';
23
- import { enableStatusBar, disableStatusBar, updateStatusBar, setupResizeHandler, renderStatusBar } from './statusbar.js';
24
- import { slashCompleter } from './completer.js';
25
- import { runHooks, initHooksTemplate } from './hooks.js';
26
- import { initMcpServers, shutdownMcpServers, getMcpStatus } from './mcp-client.js';
27
- import { startWatch } from './watcher.js';
28
- import { runAutonomous } from './autonomous.js';
29
- import { killAllProcesses } from './tools/process-manager.js';
30
- import { cleanupClaudeSessions } from './tools/claude-code.js';
31
- import { getCompactStatus, compressHistory } from './compactor.js';
32
- import { getRouterStats, debugRoute } from './skill-router.js';
33
- import { formatCostReport } from './cost-tracker.js';
34
- import { getPreset, listPresets } from './providers.js';
35
- import chalk from 'chalk';
7
+ import { printError } from './ui.js';
8
+ import { registerCommands } from './cli.js';
9
+ import { startRepl } from './repl.js';
36
10
  const __dirname = fileURLToPath(new URL('.', import.meta.url));
37
11
  let version = '0.1.0';
38
12
  try {
@@ -79,479 +53,18 @@ program
79
53
  });
80
54
  }
81
55
  else {
82
- await runRepl({
56
+ await startRepl({
83
57
  continueSession: opts.continue,
84
58
  resumeId: opts.resume,
85
59
  model: opts.model,
86
60
  effort: opts.effort,
87
61
  permissionMode,
62
+ version,
88
63
  });
89
64
  }
90
65
  });
91
- // ─── Config subcommand ───────────────────────────────────────
92
- const configCmd = program.command('config').description('Manage configuration');
93
- configCmd
94
- .command('set <key> <value>')
95
- .description('Set a config value')
96
- .action((key, value) => {
97
- try {
98
- setConfigValue(key, value);
99
- printSuccess(`${key} = ${key === 'apiKey' ? '***' : value}`);
100
- }
101
- catch (e) {
102
- printError(e.message);
103
- process.exit(1);
104
- }
105
- });
106
- configCmd
107
- .command('get <key>')
108
- .description('Get a config value')
109
- .action((key) => {
110
- const value = getConfigValue(key);
111
- if (value === undefined) {
112
- printError(`Unknown key: ${key}`);
113
- process.exit(1);
114
- }
115
- console.log(value);
116
- });
117
- configCmd
118
- .command('list')
119
- .description('Show all configuration')
120
- .action(() => {
121
- const config = listConfig();
122
- for (const [k, v] of Object.entries(config)) {
123
- console.log(`${chalk.cyan(k)}: ${v}`);
124
- }
125
- });
126
- // ─── Init subcommand ─────────────────────────────────────────
127
- program
128
- .command('init')
129
- .description('Initialize ~/.bobo/ directory and knowledge base')
130
- .action(() => {
131
- ensureConfigDir();
132
- const config = loadConfig();
133
- const knowledgeDir = resolveKnowledgeDir(config);
134
- if (!existsSync(knowledgeDir)) {
135
- mkdirSync(knowledgeDir, { recursive: true });
136
- }
137
- const bundledDir = join(__dirname, '..', 'knowledge');
138
- if (existsSync(bundledDir)) {
139
- const files = readdirSync(bundledDir).filter(f => f.endsWith('.md'));
140
- for (const file of files) {
141
- const target = join(knowledgeDir, file);
142
- const source = join(bundledDir, file);
143
- if (!existsSync(target)) {
144
- copyFileSync(source, target);
145
- printSuccess(`Created ${target}`);
146
- }
147
- }
148
- }
149
- const memoryDir = join(getConfigDir(), 'memory');
150
- const learningsDir = join(getConfigDir(), '.learnings');
151
- const sessionsDir = join(getConfigDir(), 'sessions');
152
- const agentsDir = join(getConfigDir(), 'agents');
153
- for (const dir of [memoryDir, learningsDir, sessionsDir, agentsDir]) {
154
- if (!existsSync(dir)) {
155
- mkdirSync(dir, { recursive: true });
156
- printSuccess(`Created ${dir}`);
157
- }
158
- }
159
- initSkills();
160
- // Copy bundled skills (including scripts/ subdirs)
161
- const bundledSkillsDir = join(__dirname, '..', 'bundled-skills');
162
- const userSkillsDir = join(getConfigDir(), 'skills');
163
- if (existsSync(bundledSkillsDir)) {
164
- if (!existsSync(userSkillsDir)) {
165
- mkdirSync(userSkillsDir, { recursive: true });
166
- }
167
- let installed = 0;
168
- for (const skillName of readdirSync(bundledSkillsDir)) {
169
- const src = join(bundledSkillsDir, skillName);
170
- const dest = join(userSkillsDir, skillName);
171
- try {
172
- if (!statSync(src).isDirectory())
173
- continue;
174
- }
175
- catch {
176
- continue;
177
- }
178
- if (!existsSync(dest)) {
179
- cpSync(src, dest, { recursive: true });
180
- installed++;
181
- }
182
- }
183
- if (installed > 0) {
184
- const manifestPath = join(getConfigDir(), 'skills-manifest.json');
185
- let manifest = {};
186
- try {
187
- manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
188
- }
189
- catch {
190
- manifest = { version: 1, skills: {} };
191
- }
192
- const skills = (manifest.skills || {});
193
- for (const skillName of readdirSync(userSkillsDir)) {
194
- if (!skills[skillName]) {
195
- skills[skillName] = { enabled: true };
196
- }
197
- }
198
- manifest.skills = skills;
199
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
200
- printSuccess(`${installed} skills installed (all enabled, passive triggering)`);
201
- }
202
- }
203
- // Create BOBO.md template if not exists
204
- const boboMdPath = join(process.cwd(), 'BOBO.md');
205
- if (!existsSync(boboMdPath)) {
206
- writeFileSync(boboMdPath, `# Project Instructions
207
-
208
- <!-- Bobo reads this file at the start of every session. -->
209
- <!-- Add coding standards, architecture decisions, and project-specific rules here. -->
210
-
211
- ## Build & Test
212
- <!-- e.g.: npm run build, npm test -->
213
-
214
- ## Style Guide
215
- <!-- e.g.: Use TypeScript strict mode, prefer const over let -->
216
- `);
217
- printSuccess('Created BOBO.md (project instructions)');
218
- }
219
- printSuccess(`Initialized ${getConfigDir()}`);
220
- printLine(`Knowledge: ${knowledgeDir}`);
221
- printWarning('Configure your API key: bobo config set apiKey <your-key>');
222
- });
223
- // ─── Doctor subcommand ───────────────────────────────────────
224
- program
225
- .command('doctor')
226
- .description('Check environment dependencies for skills')
227
- .action(() => {
228
- printLine(chalk.cyan.bold('\n🩺 Bobo Doctor — Environment Check\n'));
229
- const checks = [
230
- { name: 'Node.js', cmd: 'node --version', required: true },
231
- { name: 'Python 3', cmd: 'python3 --version', required: false },
232
- { name: 'pip3', cmd: 'pip3 --version', required: false },
233
- { name: 'Git', cmd: 'git --version', required: true },
234
- { name: 'ffmpeg', cmd: 'ffmpeg -version', required: false },
235
- { name: 'npm', cmd: 'npm --version', required: true },
236
- { name: 'curl', cmd: 'curl --version', required: false },
237
- ];
238
- let allGood = true;
239
- for (const check of checks) {
240
- try {
241
- const output = execSync(check.cmd, { timeout: 5000, stdio: 'pipe' }).toString().trim().split('\n')[0];
242
- printLine(` ${chalk.green('✓')} ${check.name.padEnd(12)} ${chalk.dim(output)}`);
243
- }
244
- catch {
245
- const icon = check.required ? chalk.red('✗') : chalk.yellow('○');
246
- const label = check.required ? chalk.red('MISSING (required)') : chalk.yellow('not found (optional)');
247
- printLine(` ${icon} ${check.name.padEnd(12)} ${label}`);
248
- if (check.required)
249
- allGood = false;
250
- }
251
- }
252
- const config = loadConfig();
253
- if (config.apiKey) {
254
- printLine(` ${chalk.green('✓')} ${'API Key'.padEnd(12)} ${chalk.dim('configured')}`);
255
- }
256
- else {
257
- printLine(` ${chalk.red('✗')} ${'API Key'.padEnd(12)} ${chalk.red('not set — run: bobo config set apiKey <key>')}`);
258
- allGood = false;
259
- }
260
- // Check BOBO.md
261
- const boboMd = existsSync(join(process.cwd(), 'BOBO.md'));
262
- printLine(` ${boboMd ? chalk.green('✓') : chalk.yellow('○')} ${'BOBO.md'.padEnd(12)} ${boboMd ? chalk.dim('found') : chalk.yellow('not found — run: bobo init')}`);
263
- const skillsDir = join(getConfigDir(), 'skills');
264
- if (existsSync(skillsDir)) {
265
- const count = readdirSync(skillsDir).filter(f => {
266
- try {
267
- return statSync(join(skillsDir, f)).isDirectory();
268
- }
269
- catch {
270
- return false;
271
- }
272
- }).length;
273
- printLine(` ${chalk.green('✓')} ${'Skills'.padEnd(12)} ${chalk.dim(`${count} installed`)}`);
274
- }
275
- else {
276
- printLine(` ${chalk.yellow('○')} ${'Skills'.padEnd(12)} ${chalk.yellow('none — run: bobo init')}`);
277
- }
278
- printLine();
279
- if (allGood) {
280
- printSuccess('All required dependencies are available! 🐕');
281
- }
282
- else {
283
- printWarning('Some required dependencies are missing.');
284
- }
285
- printLine();
286
- });
287
- // ─── Spawn subcommand ────────────────────────────────────────
288
- program
289
- .command('spawn <task>')
290
- .description('Spawn a background sub-agent to run a task')
291
- .action((task) => {
292
- const result = spawnSubAgent(task);
293
- if (result.error) {
294
- printError(result.error);
295
- process.exit(1);
296
- }
297
- printSuccess(`Sub-agent ${result.id} spawned!`);
298
- printLine(chalk.dim(` Task: ${task.slice(0, 80)}${task.length > 80 ? '...' : ''}`));
299
- printLine(chalk.dim(` Check status: bobo agents`));
300
- });
301
- // ─── Agents subcommand ───────────────────────────────────────
302
- const agentsCmd = program.command('agents').description('Manage sub-agents');
303
- agentsCmd
304
- .command('list')
305
- .description('List all sub-agents')
306
- .action(() => {
307
- const agents = listSubAgents();
308
- if (agents.length === 0) {
309
- printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
310
- return;
311
- }
312
- printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
313
- for (const a of agents) {
314
- const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
315
- const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
316
- printLine(` ${icon} ${chalk.bold(a.id)} — ${task}`);
317
- printLine(` ${chalk.dim(a.startedAt)} ${chalk.dim(`[${a.status}]`)}`);
318
- }
319
- printLine();
320
- });
321
- agentsCmd
322
- .command('show <id>')
323
- .description('Show sub-agent result')
324
- .action((id) => {
325
- const agent = getSubAgent(id);
326
- if (!agent) {
327
- printError(`Sub-agent not found: ${id}`);
328
- return;
329
- }
330
- printLine(chalk.cyan.bold(`\n🤖 Sub-Agent: ${agent.id}\n`));
331
- printLine(` Status: ${agent.status}`);
332
- printLine(` Task: ${agent.task}`);
333
- printLine(` Started: ${agent.startedAt}`);
334
- if (agent.completedAt)
335
- printLine(` Done: ${agent.completedAt}`);
336
- if (agent.result) {
337
- printLine(`\n${chalk.dim('─'.repeat(50))}\n`);
338
- printLine(agent.result);
339
- }
340
- if (agent.error) {
341
- printLine(`\n${chalk.red('Error:')} ${agent.error}`);
342
- }
343
- printLine();
344
- });
345
- agentsCmd.action(() => {
346
- const agents = listSubAgents();
347
- if (agents.length === 0) {
348
- printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
349
- return;
350
- }
351
- printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
352
- for (const a of agents) {
353
- const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
354
- const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
355
- printLine(` ${icon} ${chalk.bold(a.id)} — ${task}`);
356
- }
357
- printLine();
358
- });
359
- // ─── Knowledge subcommand ────────────────────────────────────
360
- program
361
- .command('knowledge')
362
- .description('Show knowledge base files')
363
- .action(() => {
364
- const files = listKnowledgeFiles();
365
- console.log(chalk.cyan.bold('\n📚 Knowledge Base:\n'));
366
- for (const f of files) {
367
- const typeIcon = f.type === 'always' ? '🔵' : f.type === 'on-demand' ? '🟡' : '🟢';
368
- const sourceTag = f.source === 'user' ? chalk.green('user') : chalk.dim('bundled');
369
- console.log(` ${typeIcon} ${f.file} [${sourceTag}] (${f.type})`);
370
- }
371
- console.log(chalk.dim('\n 🔵 always-load 🟡 on-demand 🟢 custom\n'));
372
- });
373
- // ─── Skill subcommand ────────────────────────────────────────
374
- const skillCmd = program.command('skill').description('Manage skills');
375
- skillCmd.command('list').description('List all skills').action(() => {
376
- const skills = listSkills();
377
- console.log(chalk.cyan.bold('\n🧩 Skills:\n'));
378
- for (const s of skills) {
379
- const icon = s.enabled ? '✅' : '❌';
380
- const typeTag = s.type === 'builtin' ? chalk.dim('builtin') : chalk.green('custom');
381
- console.log(` ${icon} ${chalk.bold(s.name)} [${typeTag}] — ${s.description}`);
382
- }
383
- console.log();
384
- });
385
- skillCmd.command('enable <name>').description('Enable a skill').action((name) => {
386
- console.log(setSkillEnabled(name, true));
387
- });
388
- skillCmd.command('disable <name>').description('Disable a skill').action((name) => {
389
- console.log(setSkillEnabled(name, false));
390
- });
391
- skillCmd.command('import <path>').description('Batch import skills').action((path) => {
392
- const resolved = path.startsWith('~') ? join(process.env.HOME || '', path.slice(1)) : path;
393
- console.log(importSkills(resolved));
394
- });
395
- // ─── Watch command (file watcher / daemon mode) ─────────────
396
- program
397
- .command('watch')
398
- .description('Watch files for changes and auto-run hooks (daemon-like mode)')
399
- .option('--ignore <patterns>', 'Additional ignore patterns (comma-separated)', '')
400
- .action((opts) => {
401
- const ignore = opts.ignore ? opts.ignore.split(',').map(s => s.trim()) : [];
402
- startWatch({
403
- dir: process.cwd(),
404
- recursive: true,
405
- ignore,
406
- });
407
- });
408
- // ─── Run command (autonomous agent loop) ─────────────────────
409
- program
410
- .command('run <task>')
411
- .description('Autonomous mode: give a task, Bobo runs until done (like Claude Code agent loop)')
412
- .option('--model <model>', 'Override model')
413
- .option('--effort <level>', 'Effort level (low/medium/high)', 'high')
414
- .option('--max-iterations <n>', 'Maximum iterations', '5')
415
- .option('--log <path>', 'Log file path')
416
- .action(async (task, opts) => {
417
- await runAutonomous({
418
- task,
419
- model: opts.model,
420
- effort: (opts.effort || 'high'),
421
- permissionMode: 'auto',
422
- maxIterations: parseInt(opts.maxIterations || '5', 10),
423
- logFile: opts.log,
424
- });
425
- });
426
- // ─── Evolve command (self-improvement via Claude Code) ───────
427
- program
428
- .command('evolve [focus]')
429
- .description('Self-improvement: use Claude Code to enhance Bobo CLI itself')
430
- .option('--dry-run', 'Show what would be improved without making changes')
431
- .action(async (focus, opts) => {
432
- const { isClaudeCodeAvailable: ccAvailable, executeClaudeCodeTool: ccExec } = await import('./tools/claude-code.js');
433
- if (!ccAvailable()) {
434
- printError('Claude Code not found. Install: npm install -g @anthropic-ai/claude-code');
435
- return;
436
- }
437
- const areas = focus || 'streaming output quality, error handling, test coverage';
438
- const dryRun = opts.dryRun ? ' List the improvements but DO NOT make changes.' : '';
439
- printLine(chalk.cyan.bold('\n🧬 Bobo Evolve — Self-Improvement Mode\n'));
440
- printLine(chalk.dim(` Focus: ${areas}`));
441
- printLine(chalk.dim(` Mode: ${opts.dryRun ? 'dry-run (preview only)' : 'live (will modify code)'}`));
442
- printLine(chalk.dim(' Using Claude Code as implementation engine\n'));
443
- const task = `You are improving Bobo CLI (an AI coding assistant CLI tool).
444
- The source code is in the current directory.
445
- Focus on: ${areas}
446
-
447
- Instructions:
448
- 1. Read the relevant source files
449
- 2. Identify specific improvements
450
- 3. Implement the improvements${dryRun}
451
- 4. Run \`npm run build\` to verify
452
- 5. Summarize what you changed and why
453
-
454
- Keep changes minimal and focused. Do not break existing functionality.`;
455
- printLine(chalk.dim('Delegating to Claude Code...'));
456
- const result = ccExec('claude_code', { task, cwd: process.cwd() });
457
- printLine(result);
458
- });
459
- // ─── MCP command ─────────────────────────────────────────────
460
- const mcpCmd = program.command('mcp').description('Manage MCP (Model Context Protocol) servers');
461
- mcpCmd
462
- .command('status')
463
- .description('Show MCP server status')
464
- .action(async () => {
465
- await initMcpServers();
466
- const status = getMcpStatus();
467
- if (status.length === 0) {
468
- printLine(chalk.dim('No MCP servers configured.'));
469
- printLine(chalk.dim('Create ~/.bobo/mcp.json to add servers.'));
470
- return;
471
- }
472
- printLine(chalk.cyan.bold('\n🔌 MCP Servers:\n'));
473
- for (const s of status) {
474
- const icon = s.ready ? chalk.green('●') : chalk.red('●');
475
- printLine(` ${icon} ${chalk.bold(s.name)} [${s.transport}] — ${s.toolCount} tools`);
476
- }
477
- printLine();
478
- shutdownMcpServers();
479
- });
480
- mcpCmd
481
- .command('init')
482
- .description('Create MCP configuration template')
483
- .action(() => {
484
- const configPath = join(getConfigDir(), 'mcp.json');
485
- if (existsSync(configPath)) {
486
- printWarning('mcp.json already exists');
487
- return;
488
- }
489
- writeFileSync(configPath, JSON.stringify({
490
- servers: [
491
- {
492
- name: 'example',
493
- transport: 'stdio',
494
- command: 'npx',
495
- args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
496
- _comment: 'Replace with your MCP server',
497
- },
498
- ],
499
- }, null, 2) + '\n');
500
- printSuccess(`Created ${configPath}`);
501
- });
502
- // ─── Hooks command ───────────────────────────────────────────
503
- program
504
- .command('hooks')
505
- .description('Manage lifecycle hooks')
506
- .option('--init', 'Create hooks.json template')
507
- .action((opts) => {
508
- if (opts.init) {
509
- const hooksPath = join(getConfigDir(), 'hooks.json');
510
- if (existsSync(hooksPath)) {
511
- printWarning('hooks.json already exists');
512
- return;
513
- }
514
- writeFileSync(hooksPath, initHooksTemplate() + '\n');
515
- printSuccess(`Created ${hooksPath}`);
516
- printLine(chalk.dim(' Available hooks: pre-edit, post-edit, pre-shell, post-shell, pre-commit, post-commit, session-end'));
517
- return;
518
- }
519
- // Show current hooks
520
- const hooksPath = join(getConfigDir(), 'hooks.json');
521
- if (!existsSync(hooksPath)) {
522
- printLine(chalk.dim('No hooks configured. Run: bobo hooks --init'));
523
- return;
524
- }
525
- try {
526
- const hooks = JSON.parse(readFileSync(hooksPath, 'utf-8'));
527
- printLine(chalk.cyan.bold('\n🪝 Hooks:\n'));
528
- for (const [event, cmds] of Object.entries(hooks)) {
529
- if (event.startsWith('_'))
530
- continue;
531
- const arr = Array.isArray(cmds) ? cmds : [cmds];
532
- if (arr.length === 0)
533
- continue;
534
- printLine(` ${chalk.bold(event)}`);
535
- for (const cmd of arr) {
536
- printLine(` → ${chalk.dim(String(cmd))}`);
537
- }
538
- }
539
- printLine();
540
- }
541
- catch {
542
- printError('Failed to parse hooks.json');
543
- }
544
- });
545
- // ─── Structured knowledge commands ──────────────────────────
546
- registerKnowledgeCommand(program);
547
- registerRulesCommand(program);
548
- registerStructuredSkillsCommand(program);
549
- registerStructuredTemplateCommand(program);
550
- // ─── Project subcommand ──────────────────────────────────────
551
- const projectCmd = program.command('project').description('Manage project configuration');
552
- projectCmd.command('init').description('Initialize .bobo/ project config').action(() => {
553
- printSuccess(initProject());
554
- });
66
+ // Register all CLI subcommands
67
+ registerCommands(program);
555
68
  async function runPrintMode(prompt, opts) {
556
69
  // Read piped stdin if available
557
70
  let input = prompt;
@@ -598,644 +111,5 @@ async function runOneShot(prompt, opts) {
598
111
  }
599
112
  }
600
113
  }
601
- async function runRepl(opts) {
602
- const config = loadConfig();
603
- const skills = listSkills();
604
- const knowledgeFiles = listKnowledgeFiles();
605
- const sessionStartTime = Date.now();
606
- const matchedSkills = [];
607
- // Initialize MCP servers in background
608
- initMcpServers().catch(() => { });
609
- // Runtime overrides
610
- let currentModel = opts.model || config.model;
611
- let currentEffort = opts.effort || config.effort;
612
- let currentPermissionMode = opts.permissionMode || config.permissionMode;
613
- let sessionName = '';
614
- printWelcome({
615
- version,
616
- model: currentModel,
617
- toolCount: toolDefinitions.length,
618
- skillsActive: skills.filter(s => s.enabled).length,
619
- skillsTotal: skills.length,
620
- knowledgeCount: knowledgeFiles.length,
621
- cwd: process.cwd(),
622
- });
623
- // Check BOBO.md
624
- const boboMdExists = existsSync(join(process.cwd(), 'BOBO.md'));
625
- if (boboMdExists) {
626
- printLine(chalk.dim(' 📋 BOBO.md loaded'));
627
- }
628
- if (!config.apiKey) {
629
- printWarning('API key not configured. Run: bobo config set apiKey <your-key>');
630
- printLine();
631
- }
632
- // Restore session
633
- let history = [];
634
- if (opts.continueSession) {
635
- // -c flag: continue most recent session
636
- const recent = getRecentSession(86400000); // 24 hours
637
- if (recent && recent.messages.length > 0) {
638
- history = recent.messages;
639
- printSuccess(`Continuing session (${history.length} messages, "${recent.firstUserMessage.slice(0, 40)}...")`);
640
- }
641
- else {
642
- printWarning('No recent session found.');
643
- }
644
- }
645
- else if (opts.resumeId) {
646
- // -r flag: resume specific session
647
- const session = loadSession(opts.resumeId);
648
- if (session) {
649
- history = session.messages;
650
- printSuccess(`Resumed session ${opts.resumeId} (${history.length} messages)`);
651
- }
652
- else {
653
- printWarning(`Session not found: ${opts.resumeId}`);
654
- }
655
- }
656
- else {
657
- // Auto-resume prompt
658
- const recentSession = getRecentSession(3600000);
659
- if (recentSession && recentSession.messages.length > 0) {
660
- printLine(chalk.yellow(`💾 Found recent session (${recentSession.messageCount} messages, ${recentSession.firstUserMessage.slice(0, 50)}...)`));
661
- printLine(chalk.dim(' Resume? (y/n)'));
662
- const answer = await new Promise((resolve) => {
663
- const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
664
- tmpRl.question(chalk.green('> '), (ans) => {
665
- tmpRl.close();
666
- resolve(ans.trim().toLowerCase());
667
- });
668
- });
669
- if (answer === 'y' || answer === 'yes') {
670
- history = recentSession.messages;
671
- printSuccess(`Resumed session (${history.length} messages)`);
672
- }
673
- }
674
- }
675
- // Enable status bar
676
- if (process.stdout.isTTY) {
677
- setupResizeHandler();
678
- enableStatusBar({
679
- model: currentModel,
680
- thinkingLevel: currentEffort,
681
- skillsCount: skills.filter(s => s.enabled).length,
682
- cwd: process.cwd(),
683
- });
684
- }
685
- const rl = createInterface({
686
- input: process.stdin,
687
- output: process.stdout,
688
- prompt: chalk.green('> '),
689
- completer: slashCompleter,
690
- });
691
- let abortController = null;
692
- let lastResponse = '';
693
- let autoCompactTriggered = false;
694
- // Wrapper that renders status bar before prompt
695
- const showPrompt = () => {
696
- const bar = renderStatusBar();
697
- if (bar)
698
- printLine(bar);
699
- rl.prompt();
700
- };
701
- const autoSave = () => {
702
- if (history.length > 0) {
703
- const id = saveSession(history, process.cwd());
704
- printLine(chalk.dim(`\n💾 Session saved: ${id}`));
705
- }
706
- };
707
- rl.on('SIGINT', () => {
708
- if (abortController) {
709
- abortController.abort();
710
- abortController = null;
711
- printLine(chalk.dim('\n(cancelled)'));
712
- showPrompt();
713
- }
714
- else {
715
- printLine(chalk.dim('\n(press Ctrl+C again or Ctrl+D to exit)'));
716
- showPrompt();
717
- }
718
- });
719
- rl.on('close', () => {
720
- autoSave();
721
- runHooks('session-end');
722
- shutdownMcpServers();
723
- killAllProcesses();
724
- cleanupClaudeSessions();
725
- disableStatusBar();
726
- printLine(chalk.dim('\nGoodbye! 🐕'));
727
- process.exit(0);
728
- });
729
- showPrompt();
730
- for await (const line of rl) {
731
- const input = line.trim();
732
- if (!input) {
733
- showPrompt();
734
- continue;
735
- }
736
- // ─── Exit ───
737
- if (input === '/quit' || input === '/exit') {
738
- autoSave();
739
- runHooks('session-end');
740
- shutdownMcpServers();
741
- killAllProcesses();
742
- disableStatusBar();
743
- printLine(chalk.dim('Goodbye! 🐕'));
744
- process.exit(0);
745
- }
746
- // ─── /new, /clear ───
747
- if (input === '/clear' || input === '/new') {
748
- history = [];
749
- matchedSkills.length = 0;
750
- lastResponse = '';
751
- autoCompactTriggered = false;
752
- resetPlan();
753
- printSuccess('Conversation cleared');
754
- showPrompt();
755
- continue;
756
- }
757
- // ─── /model [name] ───
758
- if (input.startsWith('/model')) {
759
- const newModel = input.replace('/model', '').trim();
760
- if (!newModel) {
761
- printLine(chalk.cyan('Current model: ') + currentModel);
762
- printLine(chalk.dim('Usage: /model <model-name>'));
763
- printLine(chalk.dim(' Examples: claude-sonnet-4-20250514, gpt-4o, deepseek-chat'));
764
- }
765
- else {
766
- currentModel = newModel;
767
- updateStatusBar({ model: currentModel });
768
- printSuccess(`Model switched to: ${currentModel}`);
769
- }
770
- showPrompt();
771
- continue;
772
- }
773
- // ─── /effort [level] ───
774
- if (input.startsWith('/effort')) {
775
- const level = input.replace('/effort', '').trim().toLowerCase();
776
- if (!level) {
777
- printLine(chalk.cyan('Current effort: ') + currentEffort);
778
- printLine(chalk.dim(' /effort low — Quick, concise answers'));
779
- printLine(chalk.dim(' /effort medium — Balanced (default)'));
780
- printLine(chalk.dim(' /effort high — Deep analysis, thorough'));
781
- }
782
- else if (['low', 'medium', 'high'].includes(level)) {
783
- currentEffort = level;
784
- updateStatusBar({ thinkingLevel: currentEffort });
785
- printSuccess(`Effort level: ${currentEffort}`);
786
- }
787
- else {
788
- printError('Invalid effort level. Use: low, medium, high');
789
- }
790
- showPrompt();
791
- continue;
792
- }
793
- // ─── /copy [n] ───
794
- if (input.startsWith('/copy')) {
795
- const indexStr = input.replace('/copy', '').trim();
796
- let textToCopy = lastResponse;
797
- if (indexStr) {
798
- const idx = parseInt(indexStr, 10);
799
- const assistantMsgs = history.filter(m => m.role === 'assistant' && typeof m.content === 'string');
800
- if (idx > 0 && idx <= assistantMsgs.length) {
801
- textToCopy = assistantMsgs[assistantMsgs.length - idx].content;
802
- }
803
- }
804
- if (!textToCopy) {
805
- printWarning('Nothing to copy.');
806
- }
807
- else {
808
- // Try platform clipboard
809
- try {
810
- const clipCmd = process.platform === 'darwin' ? 'pbcopy'
811
- : process.platform === 'win32' ? 'clip'
812
- : 'xclip -selection clipboard';
813
- execSync(clipCmd, { input: textToCopy, timeout: 3000 });
814
- printSuccess('Copied to clipboard!');
815
- }
816
- catch {
817
- // Fallback: write to file
818
- const copyPath = join(getConfigDir(), 'last-copy.txt');
819
- writeFileSync(copyPath, textToCopy);
820
- printWarning(`Clipboard unavailable. Saved to ${copyPath}`);
821
- }
822
- }
823
- showPrompt();
824
- continue;
825
- }
826
- // ─── /context ───
827
- if (input === '/context') {
828
- const msgCount = history.length;
829
- let totalChars = 0;
830
- let toolResultChars = 0;
831
- const roleCounts = {};
832
- for (const msg of history) {
833
- const content = typeof msg.content === 'string' ? msg.content : '';
834
- totalChars += content.length;
835
- roleCounts[msg.role] = (roleCounts[msg.role] || 0) + 1;
836
- if (msg.role === 'tool')
837
- toolResultChars += content.length;
838
- }
839
- const estTokens = Math.ceil(totalChars / 3.5);
840
- const maxContext = 200000; // approximate
841
- const usage = (estTokens / maxContext * 100).toFixed(1);
842
- printLine(chalk.cyan.bold('\n📊 Context Analysis\n'));
843
- printLine(` Messages: ${msgCount}`);
844
- printLine(` Est. Tokens: ~${estTokens.toLocaleString()} / ${maxContext.toLocaleString()} (${usage}%)`);
845
- printLine('');
846
- for (const [role, count] of Object.entries(roleCounts)) {
847
- printLine(` ${role.padEnd(12)} ${count} messages`);
848
- }
849
- if (toolResultChars > totalChars * 0.6) {
850
- printLine(chalk.yellow('\n ⚠ Tool results are >60% of context. Consider /compact to free space.'));
851
- }
852
- if (estTokens > maxContext * 0.75) {
853
- printLine(chalk.red('\n 🔴 Context usage >75%. Run /compact soon!'));
854
- }
855
- else if (estTokens > maxContext * 0.5) {
856
- printLine(chalk.yellow('\n 🟡 Context usage >50%. Keep an eye on it.'));
857
- }
858
- else {
859
- printLine(chalk.green('\n 🟢 Context usage healthy.'));
860
- }
861
- printLine();
862
- showPrompt();
863
- continue;
864
- }
865
- // ─── /rename <name> ───
866
- if (input.startsWith('/rename')) {
867
- const name = input.replace('/rename', '').trim();
868
- if (!name) {
869
- printLine(chalk.dim(`Current name: ${sessionName || '(unnamed)'}`));
870
- printLine(chalk.dim('Usage: /rename <name>'));
871
- }
872
- else {
873
- sessionName = name;
874
- printSuccess(`Session renamed: ${sessionName}`);
875
- }
876
- showPrompt();
877
- continue;
878
- }
879
- if (input === '/history') {
880
- printLine(`Turns: ${history.filter(m => m.role === 'user').length}`);
881
- showPrompt();
882
- continue;
883
- }
884
- // ─── /resume ───
885
- if (input === '/resume') {
886
- const sessions = listSessions(10);
887
- if (sessions.length === 0) {
888
- printWarning('No saved sessions.');
889
- showPrompt();
890
- continue;
891
- }
892
- printLine(chalk.cyan.bold('\n💾 Recent Sessions:\n'));
893
- for (let i = 0; i < sessions.length; i++) {
894
- const s = sessions[i];
895
- const date = s.startedAt ? new Date(s.startedAt).toLocaleString() : 'unknown';
896
- printLine(` ${chalk.bold(String(i + 1).padStart(2))} ${chalk.dim(date)} — ${s.firstUserMessage.slice(0, 50)} (${s.messageCount} msgs)`);
897
- }
898
- printLine(chalk.dim('\n Enter number to restore, or press Enter to cancel:'));
899
- const pick = await new Promise((resolve) => {
900
- const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
901
- tmpRl.question(chalk.green('> '), (ans) => {
902
- tmpRl.close();
903
- resolve(ans.trim());
904
- });
905
- });
906
- const idx = parseInt(pick, 10) - 1;
907
- if (idx >= 0 && idx < sessions.length) {
908
- const session = loadSession(sessions[idx].id);
909
- if (session) {
910
- history = session.messages;
911
- printSuccess(`Restored session (${history.length} messages)`);
912
- }
913
- else {
914
- printError('Failed to load session.');
915
- }
916
- }
917
- showPrompt();
918
- continue;
919
- }
920
- // ─── /insight ───
921
- if (input === '/insight') {
922
- printLine(generateInsight(history, sessionStartTime, [...new Set(matchedSkills)]));
923
- showPrompt();
924
- continue;
925
- }
926
- // ─── /agents, /bg ───
927
- if (input === '/agents' || input === '/bg') {
928
- const agents = listSubAgents(10);
929
- if (agents.length === 0) {
930
- printLine(chalk.dim('No sub-agents. Use: /spawn <task>'));
931
- }
932
- else {
933
- printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
934
- for (const a of agents) {
935
- const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
936
- const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
937
- printLine(` ${icon} ${chalk.bold(a.id)} — ${task} ${chalk.dim(`[${a.status}]`)}`);
938
- }
939
- }
940
- printLine();
941
- showPrompt();
942
- continue;
943
- }
944
- if (input.startsWith('/agents show ')) {
945
- const id = input.replace('/agents show ', '').trim();
946
- const agent = getSubAgent(id);
947
- if (!agent) {
948
- printError(`Sub-agent not found: ${id}`);
949
- }
950
- else {
951
- printLine(chalk.cyan.bold(`\n🤖 ${agent.id} [${agent.status}]`));
952
- printLine(chalk.dim(`Task: ${agent.task}`));
953
- if (agent.result)
954
- printLine(`\n${agent.result}`);
955
- if (agent.error)
956
- printLine(chalk.red(`Error: ${agent.error}`));
957
- }
958
- printLine();
959
- showPrompt();
960
- continue;
961
- }
962
- if (input.startsWith('/spawn ')) {
963
- const task = input.replace('/spawn ', '').trim();
964
- if (!task) {
965
- printWarning('Usage: /spawn <task description>');
966
- }
967
- else {
968
- const result = spawnSubAgent(task);
969
- if (result.error) {
970
- printError(result.error);
971
- }
972
- else {
973
- printSuccess(`Sub-agent ${result.id} spawned! Check with /agents`);
974
- }
975
- }
976
- showPrompt();
977
- continue;
978
- }
979
- // ─── /compact ───
980
- if (input === '/compact') {
981
- const userCount = history.filter(m => m.role === 'user').length;
982
- if (userCount > 4) {
983
- printLine(chalk.dim('Compacting context...'));
984
- abortController = new AbortController();
985
- try {
986
- const compactResult = await runAgent('Perform a nine-section context compression. Analyze the conversation so far and produce a structured summary covering: ' +
987
- '1. Main requests/intent 2. Key technical concepts 3. Files and code 4. Errors and fixes 5. Problem resolution ' +
988
- '6. All user messages 7. Pending tasks 8. Current work state 9. Next steps (with citations). ' +
989
- 'Output the summary directly, do not call any tools.', history, { signal: abortController.signal, model: currentModel, effort: currentEffort });
990
- history = [
991
- { role: 'user', content: 'Below is a compressed summary of our prior conversation. Continue from here.' },
992
- { role: 'assistant', content: compactResult.response },
993
- ];
994
- autoCompactTriggered = false;
995
- printSuccess('Context compacted (nine-section summary)');
996
- }
997
- catch (e) {
998
- if (e.message !== 'Aborted') {
999
- history = history.slice(-8);
1000
- printSuccess('Context compacted (truncated)');
1001
- }
1002
- }
1003
- abortController = null;
1004
- }
1005
- else {
1006
- printWarning('Conversation too short to compact');
1007
- }
1008
- showPrompt();
1009
- continue;
1010
- }
1011
- // ─── /dream ───
1012
- if (input === '/dream') {
1013
- abortController = new AbortController();
1014
- try {
1015
- const result = await runAgent('Perform memory consolidation: scan recent memories and conversations, extract recurring patterns and promote to long-term memory, merge redundant entries, clean up completed tasks. Use search_memory and save_memory tools. Report what you consolidated.', history, { signal: abortController.signal, model: currentModel });
1016
- history = result.history;
1017
- }
1018
- catch (e) {
1019
- if (e.message !== 'Aborted')
1020
- printError(e.message);
1021
- }
1022
- abortController = null;
1023
- printLine();
1024
- showPrompt();
1025
- continue;
1026
- }
1027
- // ─── /status ───
1028
- if (input === '/status') {
1029
- const turns = history.filter(m => m.role === 'user').length;
1030
- const mcpServers = getMcpStatus();
1031
- const compactInfo = getCompactStatus(history);
1032
- printLine(chalk.cyan('📊 Session Status:'));
1033
- printLine(` Model: ${currentModel}`);
1034
- printLine(` Effort: ${currentEffort}`);
1035
- printLine(` Permission: ${currentPermissionMode}`);
1036
- printLine(` Turns: ${turns}`);
1037
- printLine(` Messages: ${history.length}`);
1038
- printLine(` Tokens: ~${compactInfo.tokens.toLocaleString()} (${compactInfo.urgency})`);
1039
- printLine(` CWD: ${process.cwd()}`);
1040
- if (sessionName)
1041
- printLine(` Name: ${sessionName}`);
1042
- if (mcpServers.length > 0) {
1043
- printLine(` MCP: ${mcpServers.filter(s => s.ready).length}/${mcpServers.length} servers (${mcpServers.reduce((a, s) => a + s.toolCount, 0)} tools)`);
1044
- }
1045
- printLine(chalk.dim(' ── Cost ──'));
1046
- printLine(` ${formatCostReport(currentModel).split('\n').join('\n ')}`);
1047
- showPrompt();
1048
- continue;
1049
- }
1050
- if (input === '/plan') {
1051
- printLine(getCurrentPlan());
1052
- showPrompt();
1053
- continue;
1054
- }
1055
- if (input === '/knowledge') {
1056
- const files = listKnowledgeFiles();
1057
- for (const f of files) {
1058
- const icon = f.type === 'always' ? '🔵' : f.type === 'on-demand' ? '🟡' : '🟢';
1059
- printLine(` ${icon} ${f.file} (${f.type})`);
1060
- }
1061
- showPrompt();
1062
- continue;
1063
- }
1064
- if (input === '/skills') {
1065
- const sklls = listSkills();
1066
- for (const s of sklls) {
1067
- const icon = s.enabled ? '✅' : '❌';
1068
- printLine(` ${icon} ${s.name} — ${s.description}`);
1069
- }
1070
- showPrompt();
1071
- continue;
1072
- }
1073
- // ─── /mcp ───
1074
- if (input === '/mcp') {
1075
- const mcpServers = getMcpStatus();
1076
- if (mcpServers.length === 0) {
1077
- printLine(chalk.dim('No MCP servers. Configure in ~/.bobo/mcp.json'));
1078
- printLine(chalk.dim('Run: bobo mcp init'));
1079
- }
1080
- else {
1081
- printLine(chalk.cyan.bold('\n🔌 MCP Servers:\n'));
1082
- for (const s of mcpServers) {
1083
- const icon = s.ready ? chalk.green('●') : chalk.red('●');
1084
- printLine(` ${icon} ${chalk.bold(s.name)} [${s.transport}] — ${s.toolCount} tools`);
1085
- }
1086
- }
1087
- printLine();
1088
- showPrompt();
1089
- continue;
1090
- }
1091
- // ─── /bg (background processes) ───
1092
- if (input === '/bg') {
1093
- const { executeProcessTool } = await import('./tools/process-manager.js');
1094
- printLine(executeProcessTool('bg_list', {}));
1095
- showPrompt();
1096
- continue;
1097
- }
1098
- // ─── /cost ───
1099
- if (input === '/cost') {
1100
- printLine(chalk.cyan('💰 API Cost:'));
1101
- printLine(` ${formatCostReport(currentModel).split('\n').join('\n ')}`);
1102
- showPrompt();
1103
- continue;
1104
- }
1105
- // ─── /route (skill router debug) ───
1106
- if (input.startsWith('/route ')) {
1107
- const query = input.slice(7).trim();
1108
- if (query) {
1109
- printLine(chalk.cyan('🔀 Skill Route Debug:'));
1110
- printLine(debugRoute(query));
1111
- }
1112
- else {
1113
- const stats = getRouterStats();
1114
- printLine(chalk.cyan('🔀 Skill Router Stats:'));
1115
- printLine(` Total: ${stats.totalSkills} | Kernel: ${stats.kernel} | Auto: ${stats.auto} | Manual: ${stats.manual}`);
1116
- printLine(` Intent categories: ${stats.intents}`);
1117
- }
1118
- showPrompt();
1119
- continue;
1120
- }
1121
- // ─── /provider ───
1122
- if (input.startsWith('/provider')) {
1123
- const arg = input.slice(9).trim();
1124
- if (!arg) {
1125
- printLine(chalk.cyan('🌐 Available Providers:'));
1126
- printLine(listPresets());
1127
- printLine(chalk.dim('\n Usage: /provider <name> — switch to provider'));
1128
- }
1129
- else {
1130
- const preset = getPreset(arg);
1131
- if (!preset) {
1132
- printError(`Unknown provider: ${arg}`);
1133
- }
1134
- else {
1135
- currentModel = preset.defaultModel;
1136
- // Note: baseUrl and apiKey require config set
1137
- printSuccess(`Switched model to ${preset.defaultModel}`);
1138
- printLine(chalk.dim(` Base URL: ${preset.baseUrl}`));
1139
- printLine(chalk.dim(` Set API key: bobo config set apiKey <key>`));
1140
- printLine(chalk.dim(` Set base URL: bobo config set baseUrl ${preset.baseUrl}`));
1141
- }
1142
- }
1143
- showPrompt();
1144
- continue;
1145
- }
1146
- // ─── /help ───
1147
- if (input === '/help') {
1148
- printLine(chalk.cyan.bold('Commands:'));
1149
- printLine('');
1150
- printLine(chalk.dim(' Session'));
1151
- printLine(' /new Start new conversation');
1152
- printLine(' /clear Clear conversation history');
1153
- printLine(' /compact Compress context (nine-section)');
1154
- printLine(' /resume Restore a previous session');
1155
- printLine(' /rename <n> Rename current session');
1156
- printLine(' /quit Exit');
1157
- printLine('');
1158
- printLine(chalk.dim(' Model & Effort'));
1159
- printLine(' /model <n> Switch model');
1160
- printLine(' /effort <l> Set thinking effort (low/medium/high)');
1161
- printLine('');
1162
- printLine(chalk.dim(' Analysis'));
1163
- printLine(' /insight Session analytics (tokens, tools, skills)');
1164
- printLine(' /context Context usage analysis');
1165
- printLine(' /status Session status');
1166
- printLine(' /copy [n] Copy last response to clipboard');
1167
- printLine(' /plan Show current task plan');
1168
- printLine('');
1169
- printLine(chalk.dim(' Sub-Agents'));
1170
- printLine(' /spawn <t> Run task in background sub-agent');
1171
- printLine(' /agents List sub-agents');
1172
- printLine(' /agents show <id> Show sub-agent result');
1173
- printLine('');
1174
- printLine(chalk.dim(' Knowledge & Integrations'));
1175
- printLine(' /knowledge List knowledge files');
1176
- printLine(' /skills List skills');
1177
- printLine(' /mcp MCP server status');
1178
- printLine(' /bg Background process list');
1179
- printLine(' /dream Memory consolidation');
1180
- printLine('');
1181
- printLine(chalk.dim(' CLI Commands'));
1182
- printLine(' bobo -p "q" Non-interactive (supports piping)');
1183
- printLine(' bobo -c Continue last conversation');
1184
- printLine(' bobo -r <id> Resume specific session');
1185
- printLine(' bobo --full-auto Auto-approve tool calls');
1186
- printLine(' bobo --yolo No sandbox, no approvals');
1187
- printLine(' bobo watch File watcher (daemon mode)');
1188
- printLine(' bobo run Autonomous agent loop');
1189
- printLine(' bobo mcp MCP server management');
1190
- printLine(' bobo hooks Lifecycle hook management');
1191
- printLine(' bobo doctor Environment check');
1192
- printLine('');
1193
- printLine(chalk.dim(' Debug'));
1194
- printLine(' /cost API cost this session');
1195
- printLine(' /bg Background process list');
1196
- printLine(' /route <msg> Skill router debug');
1197
- printLine(' /provider Switch AI provider');
1198
- showPrompt();
1199
- continue;
1200
- }
1201
- // ─── Run agent ───
1202
- abortController = new AbortController();
1203
- try {
1204
- const result = await runAgent(input, history, {
1205
- signal: abortController.signal,
1206
- matchedSkills,
1207
- model: currentModel,
1208
- effort: currentEffort,
1209
- permissionMode: currentPermissionMode,
1210
- onAutoCompact: () => {
1211
- if (!autoCompactTriggered) {
1212
- autoCompactTriggered = true;
1213
- printLine(chalk.yellow('\n⚠ Context is getting large. Consider running /compact to free space.\n'));
1214
- }
1215
- },
1216
- });
1217
- history = result.history;
1218
- lastResponse = result.response;
1219
- // Auto-compact check
1220
- const compactInfo = getCompactStatus(history);
1221
- if (compactInfo.urgency === 'critical') {
1222
- printWarning(`Context at ${compactInfo.tokens} tokens — auto-compressing...`);
1223
- history = compressHistory(history, 8);
1224
- printSuccess(`Compressed to ${getCompactStatus(history).tokens} tokens`);
1225
- }
1226
- else if (compactInfo.urgency === 'high') {
1227
- printWarning(`⚠ Context at ${compactInfo.tokens} tokens. Run /compact to compress.`);
1228
- }
1229
- }
1230
- catch (e) {
1231
- if (e.message !== 'Aborted') {
1232
- printError(e.message);
1233
- }
1234
- }
1235
- abortController = null;
1236
- printLine();
1237
- showPrompt();
1238
- }
1239
- }
1240
114
  program.parse();
1241
115
  //# sourceMappingURL=index.js.map