bobo-ai-cli 2.0.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.
- package/README.md +52 -9
- package/dist/agent.js +70 -45
- package/dist/agent.js.map +1 -1
- package/dist/agents/catalog.d.ts +40 -0
- package/dist/agents/catalog.js +172 -0
- package/dist/agents/catalog.js.map +1 -0
- package/dist/agents/index.d.ts +6 -0
- package/dist/agents/index.js +4 -0
- package/dist/agents/index.js.map +1 -0
- package/dist/agents/router.d.ts +43 -0
- package/dist/agents/router.js +87 -0
- package/dist/agents/router.js.map +1 -0
- package/dist/agents/spawn.d.ts +46 -0
- package/dist/agents/spawn.js +91 -0
- package/dist/agents/spawn.js.map +1 -0
- package/dist/autonomous.js +41 -1
- package/dist/autonomous.js.map +1 -1
- package/dist/claude-bridge.d.ts +18 -0
- package/dist/claude-bridge.js +91 -0
- package/dist/claude-bridge.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +667 -0
- package/dist/cli.js.map +1 -0
- package/dist/compactor.d.ts +49 -4
- package/dist/compactor.js +164 -17
- package/dist/compactor.js.map +1 -1
- package/dist/completer.js +2 -0
- package/dist/completer.js.map +1 -1
- package/dist/cost-tracker.js +35 -2
- package/dist/cost-tracker.js.map +1 -1
- package/dist/dream.d.ts +42 -0
- package/dist/dream.js +321 -0
- package/dist/dream.js.map +1 -0
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +4 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.js +8 -1099
- package/dist/index.js.map +1 -1
- package/dist/insight.js +4 -11
- package/dist/insight.js.map +1 -1
- package/dist/knowledge.d.ts +13 -0
- package/dist/knowledge.js +13 -2
- package/dist/knowledge.js.map +1 -1
- package/dist/memory.d.ts +4 -0
- package/dist/memory.js +6 -0
- package/dist/memory.js.map +1 -1
- package/dist/repl.d.ts +16 -0
- package/dist/repl.js +702 -0
- package/dist/repl.js.map +1 -0
- package/dist/sessions.js +23 -0
- package/dist/sessions.js.map +1 -1
- package/dist/skills/composer.d.ts +18 -0
- package/dist/skills/composer.js +59 -0
- package/dist/skills/composer.js.map +1 -0
- package/dist/skills/index.d.ts +3 -0
- package/dist/skills/index.js +3 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/loader.d.ts +12 -0
- package/dist/skills/loader.js +150 -0
- package/dist/skills/loader.js.map +1 -0
- package/dist/skills/types.d.ts +28 -0
- package/dist/skills/types.js +9 -0
- package/dist/skills/types.js.map +1 -0
- package/dist/skills.d.ts +1 -0
- package/dist/skills.js +1 -0
- package/dist/skills.js.map +1 -1
- package/dist/state/artifacts.d.ts +71 -0
- package/dist/state/artifacts.js +131 -0
- package/dist/state/artifacts.js.map +1 -0
- package/dist/state/index.d.ts +9 -0
- package/dist/state/index.js +7 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/manager.d.ts +89 -0
- package/dist/state/manager.js +130 -0
- package/dist/state/manager.js.map +1 -0
- package/dist/state/project-memory.d.ts +24 -0
- package/dist/state/project-memory.js +81 -0
- package/dist/state/project-memory.js.map +1 -0
- package/dist/state/recovery.d.ts +24 -0
- package/dist/state/recovery.js +94 -0
- package/dist/state/recovery.js.map +1 -0
- package/dist/statusbar.d.ts +1 -0
- package/dist/statusbar.js +12 -1
- package/dist/statusbar.js.map +1 -1
- package/dist/sub-agent-runner.d.ts +1 -0
- package/dist/sub-agent-runner.js +29 -3
- package/dist/sub-agent-runner.js.map +1 -1
- package/dist/sub-agents.d.ts +19 -1
- package/dist/sub-agents.js +137 -2
- package/dist/sub-agents.js.map +1 -1
- package/dist/tool-governance.d.ts +77 -0
- package/dist/tool-governance.js +335 -0
- package/dist/tool-governance.js.map +1 -0
- package/dist/tools/claude-bridge-tool.d.ts +4 -0
- package/dist/tools/claude-bridge-tool.js +44 -0
- package/dist/tools/claude-bridge-tool.js.map +1 -0
- package/dist/tools/claude-code.d.ts +33 -0
- package/dist/tools/claude-code.js +279 -0
- package/dist/tools/claude-code.js.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/ui/hud.d.ts +25 -0
- package/dist/ui/hud.js +67 -0
- package/dist/ui/hud.js.map +1 -0
- package/dist/verification-agent.d.ts +46 -0
- package/dist/verification-agent.js +528 -0
- package/dist/verification-agent.js.map +1 -0
- package/dist/workflows/ask.d.ts +13 -0
- package/dist/workflows/ask.js +66 -0
- package/dist/workflows/ask.js.map +1 -0
- package/dist/workflows/index.d.ts +5 -0
- package/dist/workflows/index.js +6 -0
- package/dist/workflows/index.js.map +1 -0
- package/dist/workflows/interview.d.ts +11 -0
- package/dist/workflows/interview.js +36 -0
- package/dist/workflows/interview.js.map +1 -0
- package/dist/workflows/plan.d.ts +13 -0
- package/dist/workflows/plan.js +34 -0
- package/dist/workflows/plan.js.map +1 -0
- package/dist/workflows/team.d.ts +17 -0
- package/dist/workflows/team.js +86 -0
- package/dist/workflows/team.js.map +1 -0
- package/dist/workflows/verify.d.ts +11 -0
- package/dist/workflows/verify.js +21 -0
- package/dist/workflows/verify.js.map +1 -0
- package/package.json +2 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command registration ā all commander subcommands.
|
|
3
|
+
*/
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { readFileSync, existsSync, mkdirSync, copyFileSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { setConfigValue, getConfigValue, listConfig, ensureConfigDir, getConfigDir, resolveKnowledgeDir, loadConfig, } from './config.js';
|
|
9
|
+
import { listKnowledgeFiles } from './knowledge.js';
|
|
10
|
+
import { listSkills, setSkillEnabled, initSkills, importSkills } from './skills.js';
|
|
11
|
+
import { initProject } from './project.js';
|
|
12
|
+
import { printError, printSuccess, printLine, printWarning } from './ui.js';
|
|
13
|
+
import { registerKnowledgeCommand } from './knowledge-commands.js';
|
|
14
|
+
import { registerRulesCommand } from './rules-commands.js';
|
|
15
|
+
import { registerStructuredSkillsCommand } from './structured-skills-commands.js';
|
|
16
|
+
import { registerStructuredTemplateCommand } from './structured-template-commands.js';
|
|
17
|
+
import { spawnSubAgent, listSubAgents, getSubAgent } from './sub-agents.js';
|
|
18
|
+
import { initHooksTemplate } from './hooks.js';
|
|
19
|
+
import { initMcpServers, shutdownMcpServers, getMcpStatus } from './mcp-client.js';
|
|
20
|
+
import { startWatch } from './watcher.js';
|
|
21
|
+
import { runAutonomous } from './autonomous.js';
|
|
22
|
+
import { runTeamWorkflow, runPlanWorkflow, runVerifyWorkflow, runInterviewWorkflow, runAskWorkflow } from './workflows/index.js';
|
|
23
|
+
import { getAllAgentRoles, getAgentsByLane, AGENT_CATALOG } from './agents/catalog.js';
|
|
24
|
+
import { recoverContext } from './state/recovery.js';
|
|
25
|
+
import { addMemoryEntry, queryMemory, appendNotepad, readNotepad } from './state/project-memory.js';
|
|
26
|
+
import { renderStatusPanel } from './ui/hud.js';
|
|
27
|
+
/**
|
|
28
|
+
* Register all CLI subcommands on the program.
|
|
29
|
+
*/
|
|
30
|
+
export function registerCommands(program) {
|
|
31
|
+
// āāā Config subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
32
|
+
const configCmd = program.command('config').description('Manage configuration');
|
|
33
|
+
configCmd
|
|
34
|
+
.command('set <key> <value>')
|
|
35
|
+
.description('Set a config value')
|
|
36
|
+
.action((key, value) => {
|
|
37
|
+
try {
|
|
38
|
+
setConfigValue(key, value);
|
|
39
|
+
printSuccess(`${key} = ${key === 'apiKey' ? '***' : value}`);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
printError(e.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
configCmd
|
|
47
|
+
.command('get <key>')
|
|
48
|
+
.description('Get a config value')
|
|
49
|
+
.action((key) => {
|
|
50
|
+
const value = getConfigValue(key);
|
|
51
|
+
if (value === undefined) {
|
|
52
|
+
printError(`Unknown key: ${key}`);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
console.log(value);
|
|
56
|
+
});
|
|
57
|
+
configCmd
|
|
58
|
+
.command('list')
|
|
59
|
+
.description('Show all configuration')
|
|
60
|
+
.action(() => {
|
|
61
|
+
const config = listConfig();
|
|
62
|
+
for (const [k, v] of Object.entries(config)) {
|
|
63
|
+
console.log(`${chalk.cyan(k)}: ${v}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
// āāā Init subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
67
|
+
program
|
|
68
|
+
.command('init')
|
|
69
|
+
.description('Initialize ~/.bobo/ directory and knowledge base')
|
|
70
|
+
.action(() => {
|
|
71
|
+
ensureConfigDir();
|
|
72
|
+
const config = loadConfig();
|
|
73
|
+
const knowledgeDir = resolveKnowledgeDir(config);
|
|
74
|
+
if (!existsSync(knowledgeDir)) {
|
|
75
|
+
mkdirSync(knowledgeDir, { recursive: true });
|
|
76
|
+
}
|
|
77
|
+
// Import __dirname equivalent
|
|
78
|
+
const __dirname = new URL('.', import.meta.url).pathname;
|
|
79
|
+
const bundledDir = join(__dirname, '..', 'knowledge');
|
|
80
|
+
if (existsSync(bundledDir)) {
|
|
81
|
+
const files = readdirSync(bundledDir).filter(f => f.endsWith('.md'));
|
|
82
|
+
for (const file of files) {
|
|
83
|
+
const target = join(knowledgeDir, file);
|
|
84
|
+
const source = join(bundledDir, file);
|
|
85
|
+
if (!existsSync(target)) {
|
|
86
|
+
copyFileSync(source, target);
|
|
87
|
+
printSuccess(`Created ${target}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const memoryDir = join(getConfigDir(), 'memory');
|
|
92
|
+
const learningsDir = join(getConfigDir(), '.learnings');
|
|
93
|
+
const sessionsDir = join(getConfigDir(), 'sessions');
|
|
94
|
+
const agentsDir = join(getConfigDir(), 'agents');
|
|
95
|
+
for (const dir of [memoryDir, learningsDir, sessionsDir, agentsDir]) {
|
|
96
|
+
if (!existsSync(dir)) {
|
|
97
|
+
mkdirSync(dir, { recursive: true });
|
|
98
|
+
printSuccess(`Created ${dir}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
initSkills();
|
|
102
|
+
// Copy bundled skills
|
|
103
|
+
const bundledSkillsDir = join(__dirname, '..', 'bundled-skills');
|
|
104
|
+
const userSkillsDir = join(getConfigDir(), 'skills');
|
|
105
|
+
if (existsSync(bundledSkillsDir)) {
|
|
106
|
+
if (!existsSync(userSkillsDir)) {
|
|
107
|
+
mkdirSync(userSkillsDir, { recursive: true });
|
|
108
|
+
}
|
|
109
|
+
let installed = 0;
|
|
110
|
+
for (const skillName of readdirSync(bundledSkillsDir)) {
|
|
111
|
+
const src = join(bundledSkillsDir, skillName);
|
|
112
|
+
const dest = join(userSkillsDir, skillName);
|
|
113
|
+
try {
|
|
114
|
+
if (!statSync(src).isDirectory())
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
if (!existsSync(dest)) {
|
|
121
|
+
cpSync(src, dest, { recursive: true });
|
|
122
|
+
installed++;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (installed > 0) {
|
|
126
|
+
const manifestPath = join(getConfigDir(), 'skills-manifest.json');
|
|
127
|
+
let manifest = {};
|
|
128
|
+
try {
|
|
129
|
+
manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'));
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
manifest = { version: 1, skills: {} };
|
|
133
|
+
}
|
|
134
|
+
const skills = (manifest.skills || {});
|
|
135
|
+
for (const skillName of readdirSync(userSkillsDir)) {
|
|
136
|
+
if (!skills[skillName]) {
|
|
137
|
+
skills[skillName] = { enabled: true };
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
manifest.skills = skills;
|
|
141
|
+
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n');
|
|
142
|
+
printSuccess(`${installed} skills installed (all enabled, passive triggering)`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Create BOBO.md template
|
|
146
|
+
const boboMdPath = join(process.cwd(), 'BOBO.md');
|
|
147
|
+
if (!existsSync(boboMdPath)) {
|
|
148
|
+
writeFileSync(boboMdPath, `# Project Instructions
|
|
149
|
+
|
|
150
|
+
<!-- Bobo reads this file at the start of every session. -->
|
|
151
|
+
<!-- Add coding standards, architecture decisions, and project-specific rules here. -->
|
|
152
|
+
|
|
153
|
+
## Build & Test
|
|
154
|
+
<!-- e.g.: npm run build, npm test -->
|
|
155
|
+
|
|
156
|
+
## Style Guide
|
|
157
|
+
<!-- e.g.: Use TypeScript strict mode, prefer const over let -->
|
|
158
|
+
`);
|
|
159
|
+
printSuccess('Created BOBO.md (project instructions)');
|
|
160
|
+
}
|
|
161
|
+
printSuccess(`Initialized ${getConfigDir()}`);
|
|
162
|
+
printLine(`Knowledge: ${knowledgeDir}`);
|
|
163
|
+
printWarning('Configure your API key: bobo config set apiKey <your-key>');
|
|
164
|
+
});
|
|
165
|
+
// āāā Doctor subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
166
|
+
program
|
|
167
|
+
.command('doctor')
|
|
168
|
+
.description('Check environment dependencies for skills')
|
|
169
|
+
.action(() => {
|
|
170
|
+
printLine(chalk.cyan.bold('\n𩺠Bobo Doctor ā Environment Check\n'));
|
|
171
|
+
const checks = [
|
|
172
|
+
{ name: 'Node.js', cmd: 'node --version', required: true },
|
|
173
|
+
{ name: 'Python 3', cmd: 'python3 --version', required: false },
|
|
174
|
+
{ name: 'pip3', cmd: 'pip3 --version', required: false },
|
|
175
|
+
{ name: 'Git', cmd: 'git --version', required: true },
|
|
176
|
+
{ name: 'ffmpeg', cmd: 'ffmpeg -version', required: false },
|
|
177
|
+
{ name: 'npm', cmd: 'npm --version', required: true },
|
|
178
|
+
{ name: 'curl', cmd: 'curl --version', required: false },
|
|
179
|
+
];
|
|
180
|
+
let allGood = true;
|
|
181
|
+
for (const check of checks) {
|
|
182
|
+
try {
|
|
183
|
+
const output = execSync(check.cmd, { timeout: 5000, stdio: 'pipe' }).toString().trim().split('\n')[0];
|
|
184
|
+
printLine(` ${chalk.green('ā')} ${check.name.padEnd(12)} ${chalk.dim(output)}`);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
const icon = check.required ? chalk.red('ā') : chalk.yellow('ā');
|
|
188
|
+
const label = check.required ? chalk.red('MISSING (required)') : chalk.yellow('not found (optional)');
|
|
189
|
+
printLine(` ${icon} ${check.name.padEnd(12)} ${label}`);
|
|
190
|
+
if (check.required)
|
|
191
|
+
allGood = false;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const config = loadConfig();
|
|
195
|
+
if (config.apiKey) {
|
|
196
|
+
printLine(` ${chalk.green('ā')} ${'API Key'.padEnd(12)} ${chalk.dim('configured')}`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
printLine(` ${chalk.red('ā')} ${'API Key'.padEnd(12)} ${chalk.red('not set ā run: bobo config set apiKey <key>')}`);
|
|
200
|
+
allGood = false;
|
|
201
|
+
}
|
|
202
|
+
const boboMd = existsSync(join(process.cwd(), 'BOBO.md'));
|
|
203
|
+
printLine(` ${boboMd ? chalk.green('ā') : chalk.yellow('ā')} ${'BOBO.md'.padEnd(12)} ${boboMd ? chalk.dim('found') : chalk.yellow('not found ā run: bobo init')}`);
|
|
204
|
+
const skillsDir = join(getConfigDir(), 'skills');
|
|
205
|
+
if (existsSync(skillsDir)) {
|
|
206
|
+
const count = readdirSync(skillsDir).filter(f => {
|
|
207
|
+
try {
|
|
208
|
+
return statSync(join(skillsDir, f)).isDirectory();
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}).length;
|
|
214
|
+
printLine(` ${chalk.green('ā')} ${'Skills'.padEnd(12)} ${chalk.dim(`${count} installed`)}`);
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
printLine(` ${chalk.yellow('ā')} ${'Skills'.padEnd(12)} ${chalk.yellow('none ā run: bobo init')}`);
|
|
218
|
+
}
|
|
219
|
+
printLine();
|
|
220
|
+
if (allGood) {
|
|
221
|
+
printSuccess('All required dependencies are available! š');
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
printWarning('Some required dependencies are missing.');
|
|
225
|
+
}
|
|
226
|
+
printLine();
|
|
227
|
+
});
|
|
228
|
+
// āāā Spawn subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
229
|
+
program
|
|
230
|
+
.command('spawn <task>')
|
|
231
|
+
.description('Spawn a background sub-agent to run a task')
|
|
232
|
+
.action((task) => {
|
|
233
|
+
const result = spawnSubAgent(task);
|
|
234
|
+
if (result.error) {
|
|
235
|
+
printError(result.error);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
printSuccess(`Sub-agent ${result.id} spawned!`);
|
|
239
|
+
printLine(chalk.dim(` Task: ${task.slice(0, 80)}${task.length > 80 ? '...' : ''}`));
|
|
240
|
+
printLine(chalk.dim(` Check status: bobo agents`));
|
|
241
|
+
});
|
|
242
|
+
// āāā Agents subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
243
|
+
const agentsCmd = program.command('agents').description('Manage sub-agents');
|
|
244
|
+
agentsCmd
|
|
245
|
+
.command('list')
|
|
246
|
+
.description('List all sub-agents')
|
|
247
|
+
.action(() => {
|
|
248
|
+
const agents = listSubAgents();
|
|
249
|
+
if (agents.length === 0) {
|
|
250
|
+
printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
printLine(chalk.cyan.bold('\nš¤ Sub-Agents:\n'));
|
|
254
|
+
for (const a of agents) {
|
|
255
|
+
const icon = a.status === 'completed' ? 'ā
' : a.status === 'failed' ? 'ā' : 'ā³';
|
|
256
|
+
const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
|
|
257
|
+
printLine(` ${icon} ${chalk.bold(a.id)} ā ${task}`);
|
|
258
|
+
printLine(` ${chalk.dim(a.startedAt)} ${chalk.dim(`[${a.status}]`)}`);
|
|
259
|
+
}
|
|
260
|
+
printLine();
|
|
261
|
+
});
|
|
262
|
+
agentsCmd
|
|
263
|
+
.command('show <id>')
|
|
264
|
+
.description('Show sub-agent result')
|
|
265
|
+
.action((id) => {
|
|
266
|
+
const agent = getSubAgent(id);
|
|
267
|
+
if (!agent) {
|
|
268
|
+
printError(`Sub-agent not found: ${id}`);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
printLine(chalk.cyan.bold(`\nš¤ Sub-Agent: ${agent.id}\n`));
|
|
272
|
+
printLine(` Status: ${agent.status}`);
|
|
273
|
+
printLine(` Task: ${agent.task}`);
|
|
274
|
+
printLine(` Started: ${agent.startedAt}`);
|
|
275
|
+
if (agent.completedAt)
|
|
276
|
+
printLine(` Done: ${agent.completedAt}`);
|
|
277
|
+
if (agent.result) {
|
|
278
|
+
printLine(`\n${chalk.dim('ā'.repeat(50))}\n`);
|
|
279
|
+
printLine(agent.result);
|
|
280
|
+
}
|
|
281
|
+
if (agent.error) {
|
|
282
|
+
printLine(`\n${chalk.red('Error:')} ${agent.error}`);
|
|
283
|
+
}
|
|
284
|
+
printLine();
|
|
285
|
+
});
|
|
286
|
+
agentsCmd.action(() => {
|
|
287
|
+
const agents = listSubAgents();
|
|
288
|
+
if (agents.length === 0) {
|
|
289
|
+
printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
printLine(chalk.cyan.bold('\nš¤ Sub-Agents:\n'));
|
|
293
|
+
for (const a of agents) {
|
|
294
|
+
const icon = a.status === 'completed' ? 'ā
' : a.status === 'failed' ? 'ā' : 'ā³';
|
|
295
|
+
const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
|
|
296
|
+
printLine(` ${icon} ${chalk.bold(a.id)} ā ${task}`);
|
|
297
|
+
}
|
|
298
|
+
printLine();
|
|
299
|
+
});
|
|
300
|
+
// āāā Knowledge subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
301
|
+
program
|
|
302
|
+
.command('knowledge')
|
|
303
|
+
.description('Show knowledge base files')
|
|
304
|
+
.action(() => {
|
|
305
|
+
const files = listKnowledgeFiles();
|
|
306
|
+
console.log(chalk.cyan.bold('\nš Knowledge Base:\n'));
|
|
307
|
+
for (const f of files) {
|
|
308
|
+
const typeIcon = f.type === 'always' ? 'šµ' : f.type === 'on-demand' ? 'š”' : 'š¢';
|
|
309
|
+
const sourceTag = f.source === 'user' ? chalk.green('user') : chalk.dim('bundled');
|
|
310
|
+
console.log(` ${typeIcon} ${f.file} [${sourceTag}] (${f.type})`);
|
|
311
|
+
}
|
|
312
|
+
console.log(chalk.dim('\n šµ always-load š” on-demand š¢ custom\n'));
|
|
313
|
+
});
|
|
314
|
+
// āāā Skill subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
315
|
+
const skillCmd = program.command('skill').description('Manage skills');
|
|
316
|
+
skillCmd.command('list').description('List all skills').action(() => {
|
|
317
|
+
const skills = listSkills();
|
|
318
|
+
console.log(chalk.cyan.bold('\nš§© Skills:\n'));
|
|
319
|
+
for (const s of skills) {
|
|
320
|
+
const icon = s.enabled ? 'ā
' : 'ā';
|
|
321
|
+
const typeTag = s.type === 'builtin' ? chalk.dim('builtin') : chalk.green('custom');
|
|
322
|
+
console.log(` ${icon} ${chalk.bold(s.name)} [${typeTag}] ā ${s.description}`);
|
|
323
|
+
}
|
|
324
|
+
console.log();
|
|
325
|
+
});
|
|
326
|
+
skillCmd.command('enable <name>').description('Enable a skill').action((name) => {
|
|
327
|
+
console.log(setSkillEnabled(name, true));
|
|
328
|
+
});
|
|
329
|
+
skillCmd.command('disable <name>').description('Disable a skill').action((name) => {
|
|
330
|
+
console.log(setSkillEnabled(name, false));
|
|
331
|
+
});
|
|
332
|
+
skillCmd.command('import <path>').description('Batch import skills').action((path) => {
|
|
333
|
+
const resolved = path.startsWith('~') ? join(process.env.HOME || '', path.slice(1)) : path;
|
|
334
|
+
console.log(importSkills(resolved));
|
|
335
|
+
});
|
|
336
|
+
// āāā Workflow commands āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
337
|
+
program
|
|
338
|
+
.command('team <spec> <task>')
|
|
339
|
+
.description('Run a team workflow with multiple agents, e.g. bobo team 3:executor "build REST API"')
|
|
340
|
+
.action(async (spec, task) => {
|
|
341
|
+
const [countRaw, roleRaw] = spec.split(':');
|
|
342
|
+
const teamSize = Number.parseInt(countRaw, 10);
|
|
343
|
+
const roles = getAllAgentRoles();
|
|
344
|
+
if (!Number.isFinite(teamSize) || teamSize <= 0) {
|
|
345
|
+
printError('Invalid team size. Use format: <N>:<role>');
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
if (roleRaw && !roles.includes(roleRaw)) {
|
|
349
|
+
printError(`Invalid role: ${roleRaw}. Available: ${roles.join(', ')}`);
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
const result = await runTeamWorkflow(task, teamSize, roleRaw);
|
|
353
|
+
printSuccess(result.summary);
|
|
354
|
+
printLine(`Plan: ${result.planPath}`);
|
|
355
|
+
printLine(`PRD: ${result.prdPath}`);
|
|
356
|
+
printLine(`Team report: ${result.teamPath}`);
|
|
357
|
+
if (result.agentIds.length > 0) {
|
|
358
|
+
printLine(chalk.dim(`Spawned ${result.agentIds.length} agents. Check status: bobo agents`));
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
program
|
|
362
|
+
.command('plan <task>')
|
|
363
|
+
.description('Generate a structured execution plan using planner agent')
|
|
364
|
+
.action(async (task) => {
|
|
365
|
+
const result = await runPlanWorkflow(task);
|
|
366
|
+
printSuccess(`Plan created with ${result.role} (${result.model})`);
|
|
367
|
+
printLine(`Plan: ${result.path}`);
|
|
368
|
+
if (result.agentId) {
|
|
369
|
+
printLine(chalk.dim(`Planner agent ${result.agentId} running in background. Check: bobo agent ${result.agentId}`));
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
program
|
|
373
|
+
.command('verify [target]')
|
|
374
|
+
.description('Run adversarial verification (build/test/lint checks)')
|
|
375
|
+
.action(async (target) => {
|
|
376
|
+
const result = await runVerifyWorkflow(target);
|
|
377
|
+
const icon = result.verdict === 'PASS' ? 'ā' : result.verdict === 'FAIL' ? 'ā' : 'ā';
|
|
378
|
+
printSuccess(`Verification ${icon} ${result.verdict}`);
|
|
379
|
+
printLine(`Report: ${result.path}`);
|
|
380
|
+
});
|
|
381
|
+
program
|
|
382
|
+
.command('interview <topic>')
|
|
383
|
+
.description('Generate Socratic interview questions for a topic')
|
|
384
|
+
.action(async (topic) => {
|
|
385
|
+
const result = await runInterviewWorkflow(topic);
|
|
386
|
+
printSuccess('Interview questions generated');
|
|
387
|
+
printLine(`Report: ${result.path}`);
|
|
388
|
+
printLine(chalk.bold('\nBase questions:'));
|
|
389
|
+
result.questions.forEach((q, i) => printLine(chalk.dim(`${i + 1}.`) + ` ${q}`));
|
|
390
|
+
if (result.agentId) {
|
|
391
|
+
printLine(chalk.dim(`\nAgent ${result.agentId} generating deeper questions. Check: bobo agent ${result.agentId}`));
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
program
|
|
395
|
+
.command('ask <model> <prompt>')
|
|
396
|
+
.description('Query a specific AI model (provider name or model ID)')
|
|
397
|
+
.action(async (model, prompt) => {
|
|
398
|
+
const result = await runAskWorkflow(model, prompt);
|
|
399
|
+
if (result.error) {
|
|
400
|
+
printError(`Ask failed: ${result.error}`);
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
printSuccess(`Response from ${result.model}`);
|
|
404
|
+
if (result.response) {
|
|
405
|
+
printLine(chalk.dim('ā'.repeat(60)));
|
|
406
|
+
printLine(result.response);
|
|
407
|
+
printLine(chalk.dim('ā'.repeat(60)));
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
printLine(`Full report: ${result.path}`);
|
|
411
|
+
});
|
|
412
|
+
// āāā Watch command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
413
|
+
program
|
|
414
|
+
.command('watch')
|
|
415
|
+
.description('Watch files for changes and auto-run hooks (daemon-like mode)')
|
|
416
|
+
.option('--ignore <patterns>', 'Additional ignore patterns (comma-separated)', '')
|
|
417
|
+
.action((opts) => {
|
|
418
|
+
const ignore = opts.ignore ? opts.ignore.split(',').map(s => s.trim()) : [];
|
|
419
|
+
startWatch({
|
|
420
|
+
dir: process.cwd(),
|
|
421
|
+
recursive: true,
|
|
422
|
+
ignore,
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
// āāā Run command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
426
|
+
program
|
|
427
|
+
.command('run <task>')
|
|
428
|
+
.description('Autonomous mode: give a task, Bobo runs until done (like Claude Code agent loop)')
|
|
429
|
+
.option('--model <model>', 'Override model')
|
|
430
|
+
.option('--effort <level>', 'Effort level (low/medium/high)', 'high')
|
|
431
|
+
.option('--max-iterations <n>', 'Maximum iterations', '5')
|
|
432
|
+
.option('--log <path>', 'Log file path')
|
|
433
|
+
.action(async (task, opts) => {
|
|
434
|
+
await runAutonomous({
|
|
435
|
+
task,
|
|
436
|
+
model: opts.model,
|
|
437
|
+
effort: (opts.effort || 'high'),
|
|
438
|
+
permissionMode: 'auto',
|
|
439
|
+
maxIterations: parseInt(opts.maxIterations || '5', 10),
|
|
440
|
+
logFile: opts.log,
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
// āāā Evolve command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
444
|
+
program
|
|
445
|
+
.command('evolve [focus]')
|
|
446
|
+
.description('Self-improvement: use Claude Code to enhance Bobo CLI itself')
|
|
447
|
+
.option('--dry-run', 'Show what would be improved without making changes')
|
|
448
|
+
.action(async (focus, opts) => {
|
|
449
|
+
const { isClaudeCodeAvailable: ccAvailable, executeClaudeCodeTool: ccExec } = await import('./tools/claude-code.js');
|
|
450
|
+
if (!ccAvailable()) {
|
|
451
|
+
printError('Claude Code not found. Install: npm install -g @anthropic-ai/claude-code');
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const areas = focus || 'streaming output quality, error handling, test coverage';
|
|
455
|
+
const dryRun = opts.dryRun ? ' List the improvements but DO NOT make changes.' : '';
|
|
456
|
+
printLine(chalk.cyan.bold('\n𧬠Bobo Evolve ā Self-Improvement Mode\n'));
|
|
457
|
+
printLine(chalk.dim(` Focus: ${areas}`));
|
|
458
|
+
printLine(chalk.dim(` Mode: ${opts.dryRun ? 'dry-run (preview only)' : 'live (will modify code)'}`));
|
|
459
|
+
printLine(chalk.dim(' Using Claude Code as implementation engine\n'));
|
|
460
|
+
const task = `You are improving Bobo CLI (an AI coding assistant CLI tool).
|
|
461
|
+
The source code is in the current directory.
|
|
462
|
+
Focus on: ${areas}
|
|
463
|
+
|
|
464
|
+
Instructions:
|
|
465
|
+
1. Read the relevant source files
|
|
466
|
+
2. Identify specific improvements
|
|
467
|
+
3. Implement the improvements${dryRun}
|
|
468
|
+
4. Run \`npm run build\` to verify
|
|
469
|
+
5. Summarize what you changed and why
|
|
470
|
+
|
|
471
|
+
Keep changes minimal and focused. Do not break existing functionality.`;
|
|
472
|
+
printLine(chalk.dim('Delegating to Claude Code...'));
|
|
473
|
+
const result = ccExec('claude_code', { task, cwd: process.cwd() });
|
|
474
|
+
printLine(result);
|
|
475
|
+
});
|
|
476
|
+
// āāā MCP command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
477
|
+
const mcpCmd = program.command('mcp').description('Manage MCP (Model Context Protocol) servers');
|
|
478
|
+
mcpCmd
|
|
479
|
+
.command('status')
|
|
480
|
+
.description('Show MCP server status')
|
|
481
|
+
.action(async () => {
|
|
482
|
+
await initMcpServers();
|
|
483
|
+
const status = getMcpStatus();
|
|
484
|
+
if (status.length === 0) {
|
|
485
|
+
printLine(chalk.dim('No MCP servers configured.'));
|
|
486
|
+
printLine(chalk.dim('Create ~/.bobo/mcp.json to add servers.'));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
printLine(chalk.cyan.bold('\nš MCP Servers:\n'));
|
|
490
|
+
for (const s of status) {
|
|
491
|
+
const icon = s.ready ? chalk.green('ā') : chalk.red('ā');
|
|
492
|
+
printLine(` ${icon} ${chalk.bold(s.name)} [${s.transport}] ā ${s.toolCount} tools`);
|
|
493
|
+
}
|
|
494
|
+
printLine();
|
|
495
|
+
shutdownMcpServers();
|
|
496
|
+
});
|
|
497
|
+
mcpCmd
|
|
498
|
+
.command('init')
|
|
499
|
+
.description('Create MCP configuration template')
|
|
500
|
+
.action(() => {
|
|
501
|
+
const configPath = join(getConfigDir(), 'mcp.json');
|
|
502
|
+
if (existsSync(configPath)) {
|
|
503
|
+
printWarning('mcp.json already exists');
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
writeFileSync(configPath, JSON.stringify({
|
|
507
|
+
servers: [
|
|
508
|
+
{
|
|
509
|
+
name: 'example',
|
|
510
|
+
transport: 'stdio',
|
|
511
|
+
command: 'npx',
|
|
512
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
|
|
513
|
+
_comment: 'Replace with your MCP server',
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
}, null, 2) + '\n');
|
|
517
|
+
printSuccess(`Created ${configPath}`);
|
|
518
|
+
});
|
|
519
|
+
// āāā Hooks command āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
520
|
+
program
|
|
521
|
+
.command('hooks')
|
|
522
|
+
.description('Manage lifecycle hooks')
|
|
523
|
+
.option('--init', 'Create hooks.json template')
|
|
524
|
+
.action((opts) => {
|
|
525
|
+
if (opts.init) {
|
|
526
|
+
const hooksPath = join(getConfigDir(), 'hooks.json');
|
|
527
|
+
if (existsSync(hooksPath)) {
|
|
528
|
+
printWarning('hooks.json already exists');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
writeFileSync(hooksPath, initHooksTemplate() + '\n');
|
|
532
|
+
printSuccess(`Created ${hooksPath}`);
|
|
533
|
+
printLine(chalk.dim(' Available hooks: pre-edit, post-edit, pre-shell, post-shell, pre-commit, post-commit, session-end'));
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
// Show current hooks
|
|
537
|
+
const hooksPath = join(getConfigDir(), 'hooks.json');
|
|
538
|
+
if (!existsSync(hooksPath)) {
|
|
539
|
+
printLine(chalk.dim('No hooks configured. Run: bobo hooks --init'));
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
try {
|
|
543
|
+
const hooks = JSON.parse(readFileSync(hooksPath, 'utf-8'));
|
|
544
|
+
printLine(chalk.cyan.bold('\nšŖ Hooks:\n'));
|
|
545
|
+
for (const [event, cmds] of Object.entries(hooks)) {
|
|
546
|
+
if (event.startsWith('_'))
|
|
547
|
+
continue;
|
|
548
|
+
const arr = Array.isArray(cmds) ? cmds : [cmds];
|
|
549
|
+
if (arr.length === 0)
|
|
550
|
+
continue;
|
|
551
|
+
printLine(` ${chalk.bold(event)}`);
|
|
552
|
+
for (const cmd of arr) {
|
|
553
|
+
printLine(` ā ${chalk.dim(String(cmd))}`);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
printLine();
|
|
557
|
+
}
|
|
558
|
+
catch {
|
|
559
|
+
printError('Failed to parse hooks.json');
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
// āāā Structured knowledge commands āāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
563
|
+
registerKnowledgeCommand(program);
|
|
564
|
+
registerRulesCommand(program);
|
|
565
|
+
registerStructuredSkillsCommand(program);
|
|
566
|
+
registerStructuredTemplateCommand(program);
|
|
567
|
+
// āāā Project subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
568
|
+
const projectCmd = program.command('project').description('Manage project configuration');
|
|
569
|
+
projectCmd.command('init').description('Initialize .bobo/ project config').action(() => {
|
|
570
|
+
printSuccess(initProject());
|
|
571
|
+
});
|
|
572
|
+
// āāā Memory subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
573
|
+
const memCmd = program.command('memory').description('Project-level memory');
|
|
574
|
+
memCmd.command('list')
|
|
575
|
+
.description('List project memory entries')
|
|
576
|
+
.option('--category <cat>', 'Filter by category')
|
|
577
|
+
.option('--keyword <kw>', 'Search keyword')
|
|
578
|
+
.action((opts) => {
|
|
579
|
+
const entries = queryMemory(opts.category, opts.keyword);
|
|
580
|
+
if (entries.length === 0) {
|
|
581
|
+
printLine(chalk.dim('No memory entries. Add with: bobo memory add <key> <value>'));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
printLine(chalk.cyan.bold('\nš Project Memory:\n'));
|
|
585
|
+
for (const e of entries) {
|
|
586
|
+
printLine(` ${chalk.bold(e.key)} [${chalk.dim(e.category)}]`);
|
|
587
|
+
printLine(` ${e.value}`);
|
|
588
|
+
}
|
|
589
|
+
printLine();
|
|
590
|
+
});
|
|
591
|
+
memCmd.command('add <key> <value>')
|
|
592
|
+
.description('Add or update a memory entry')
|
|
593
|
+
.option('--category <cat>', 'Category: architecture|decision|convention|gotcha|todo', 'convention')
|
|
594
|
+
.action((key, value, opts) => {
|
|
595
|
+
addMemoryEntry({ key, value, category: (opts.category || 'convention') });
|
|
596
|
+
printSuccess(`Memory: ${key} = ${value}`);
|
|
597
|
+
});
|
|
598
|
+
memCmd.action(() => {
|
|
599
|
+
const entries = queryMemory();
|
|
600
|
+
if (entries.length === 0) {
|
|
601
|
+
printLine(chalk.dim('No memory entries. Add with: bobo memory add <key> <value>'));
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
printLine(chalk.cyan.bold('\nš Project Memory:\n'));
|
|
605
|
+
for (const e of entries) {
|
|
606
|
+
printLine(` ${chalk.bold(e.key)} [${chalk.dim(e.category)}] ā ${e.value.slice(0, 80)}`);
|
|
607
|
+
}
|
|
608
|
+
printLine();
|
|
609
|
+
});
|
|
610
|
+
// āāā Notepad subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
611
|
+
program
|
|
612
|
+
.command('note <text>')
|
|
613
|
+
.description('Quick note to .bobo/notepad.md')
|
|
614
|
+
.action((text) => {
|
|
615
|
+
appendNotepad(text);
|
|
616
|
+
printSuccess('Note saved.');
|
|
617
|
+
});
|
|
618
|
+
program
|
|
619
|
+
.command('notes')
|
|
620
|
+
.description('Show notepad')
|
|
621
|
+
.action(() => {
|
|
622
|
+
const content = readNotepad();
|
|
623
|
+
if (!content.trim()) {
|
|
624
|
+
printLine(chalk.dim('Notepad empty. Add with: bobo note "text"'));
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
printLine(content);
|
|
628
|
+
});
|
|
629
|
+
// āāā Status subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
630
|
+
program
|
|
631
|
+
.command('status')
|
|
632
|
+
.description('Show current status: agents, workflows, recovery context')
|
|
633
|
+
.action(() => {
|
|
634
|
+
const recovery = recoverContext();
|
|
635
|
+
const state = {
|
|
636
|
+
sessionId: recovery.lastSession?.sessionId ?? 'none',
|
|
637
|
+
model: recovery.lastSession?.model ?? 'unknown',
|
|
638
|
+
effort: recovery.lastSession?.effort ?? 'high',
|
|
639
|
+
activeAgents: [],
|
|
640
|
+
completedTasks: 0,
|
|
641
|
+
totalTasks: 0,
|
|
642
|
+
tokenUsage: recovery.lastSession?.tokenUsage ?? { input: 0, output: 0 },
|
|
643
|
+
};
|
|
644
|
+
printLine(renderStatusPanel(state));
|
|
645
|
+
if (recovery.activeWorkflows.length > 0) {
|
|
646
|
+
printLine(`\n${chalk.yellow('Active workflows:')} ${recovery.activeWorkflows.length}`);
|
|
647
|
+
}
|
|
648
|
+
printLine(`\n${chalk.dim(recovery.summary)}`);
|
|
649
|
+
});
|
|
650
|
+
// āāā Catalog subcommand āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
651
|
+
program
|
|
652
|
+
.command('catalog')
|
|
653
|
+
.description('Show agent catalog (roles and capabilities)')
|
|
654
|
+
.action(() => {
|
|
655
|
+
const lanes = getAgentsByLane();
|
|
656
|
+
printLine(chalk.cyan.bold('\nš¤ Agent Catalog:\n'));
|
|
657
|
+
for (const [lane, roles] of Object.entries(lanes)) {
|
|
658
|
+
printLine(chalk.bold(` ${lane.toUpperCase()} Lane:`));
|
|
659
|
+
for (const role of roles) {
|
|
660
|
+
const agent = AGENT_CATALOG[role];
|
|
661
|
+
printLine(` ${chalk.cyan(role.padEnd(12))} ${chalk.dim(agent.model.padEnd(8))} ${agent.description}`);
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
printLine();
|
|
665
|
+
});
|
|
666
|
+
}
|
|
667
|
+
//# sourceMappingURL=cli.js.map
|