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.
- package/README.md +52 -9
- package/dist/agent.js +66 -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/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 -1134
- 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/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/index.js
CHANGED
|
@@ -1,38 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command, Option } from 'commander';
|
|
3
|
-
import {
|
|
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 {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
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
|
|
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
|
-
//
|
|
92
|
-
|
|
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
|