bobo-ai-cli 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent.d.ts +4 -0
- package/dist/agent.js +41 -11
- package/dist/agent.js.map +1 -1
- package/dist/completer.d.ts +16 -0
- package/dist/completer.js +41 -0
- package/dist/completer.js.map +1 -0
- package/dist/index.js +310 -9
- package/dist/index.js.map +1 -1
- package/dist/insight.d.ts +5 -0
- package/dist/insight.js +80 -0
- package/dist/insight.js.map +1 -0
- package/dist/sessions.d.ts +29 -0
- package/dist/sessions.js +106 -0
- package/dist/sessions.js.map +1 -0
- package/dist/spinner.d.ts +14 -0
- package/dist/spinner.js +38 -0
- package/dist/spinner.js.map +1 -0
- package/dist/statusbar.d.ts +26 -0
- package/dist/statusbar.js +100 -0
- package/dist/statusbar.js.map +1 -0
- package/dist/sub-agent-runner.d.ts +6 -0
- package/dist/sub-agent-runner.js +37 -0
- package/dist/sub-agent-runner.js.map +1 -0
- package/dist/sub-agents.d.ts +30 -0
- package/dist/sub-agents.js +112 -0
- package/dist/sub-agents.js.map +1 -0
- package/package.json +1 -1
package/dist/agent.d.ts
CHANGED
|
@@ -2,6 +2,10 @@ import type { ChatCompletionMessageParam } from 'openai/resources/index.js';
|
|
|
2
2
|
export interface AgentOptions {
|
|
3
3
|
onText?: (text: string) => void;
|
|
4
4
|
signal?: AbortSignal;
|
|
5
|
+
/** Track which skills were matched (mutated by caller) */
|
|
6
|
+
matchedSkills?: string[];
|
|
7
|
+
/** Suppress spinner (for sub-agents) */
|
|
8
|
+
quiet?: boolean;
|
|
5
9
|
}
|
|
6
10
|
export declare function runAgent(userMessage: string, history: ChatCompletionMessageParam[], options?: AgentOptions): Promise<{
|
|
7
11
|
response: string;
|
package/dist/agent.js
CHANGED
|
@@ -7,6 +7,7 @@ import { loadSkillPrompts } from './skills.js';
|
|
|
7
7
|
import { loadProjectKnowledge } from './project.js';
|
|
8
8
|
import { toolDefinitions, executeTool } from './tools/index.js';
|
|
9
9
|
import { printStreaming, printToolCall, printToolResult, printError, printLine } from './ui.js';
|
|
10
|
+
import { Spinner } from './spinner.js';
|
|
10
11
|
export async function runAgent(userMessage, history, options = {}) {
|
|
11
12
|
const config = loadConfig();
|
|
12
13
|
if (!config.apiKey) {
|
|
@@ -16,18 +17,25 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
16
17
|
apiKey: config.apiKey,
|
|
17
18
|
baseURL: config.baseUrl,
|
|
18
19
|
});
|
|
20
|
+
const spinner = new Spinner();
|
|
19
21
|
// Build system prompt with all context layers (order matters)
|
|
20
22
|
const extraParts = [];
|
|
21
23
|
// Layer 1: Active skills (behavior rules — highest priority after knowledge)
|
|
22
|
-
// Passive triggering: only load skills relevant to the user's message
|
|
23
24
|
const skillPrompts = loadSkillPrompts(userMessage);
|
|
24
|
-
if (skillPrompts)
|
|
25
|
+
if (skillPrompts) {
|
|
25
26
|
extraParts.push(skillPrompts);
|
|
26
|
-
|
|
27
|
+
// Track matched skills
|
|
28
|
+
if (options.matchedSkills) {
|
|
29
|
+
const matches = skillPrompts.match(/# (\S+)/g);
|
|
30
|
+
if (matches)
|
|
31
|
+
options.matchedSkills.push(...matches.map(m => m.replace('# ', '')));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// Layer 2: Persistent memory
|
|
27
35
|
const memory = loadMemory();
|
|
28
36
|
if (memory)
|
|
29
37
|
extraParts.push(`# 你的记忆\n\n${memory}`);
|
|
30
|
-
// Layer 3: Project context
|
|
38
|
+
// Layer 3: Project context
|
|
31
39
|
const projectKnowledge = loadProjectKnowledge();
|
|
32
40
|
if (projectKnowledge)
|
|
33
41
|
extraParts.push(`# 项目上下文\n\n${projectKnowledge}`);
|
|
@@ -54,9 +62,13 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
54
62
|
if (options.signal?.aborted) {
|
|
55
63
|
throw new Error('Aborted');
|
|
56
64
|
}
|
|
65
|
+
// Start thinking spinner
|
|
66
|
+
if (!options.quiet)
|
|
67
|
+
spinner.start('Thinking...');
|
|
57
68
|
try {
|
|
58
69
|
let assistantContent = '';
|
|
59
70
|
const toolCalls = new Map();
|
|
71
|
+
let firstChunkReceived = false;
|
|
60
72
|
try {
|
|
61
73
|
// Try streaming first
|
|
62
74
|
const stream = await client.chat.completions.create({
|
|
@@ -68,15 +80,21 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
68
80
|
});
|
|
69
81
|
for await (const chunk of stream) {
|
|
70
82
|
if (options.signal?.aborted) {
|
|
83
|
+
spinner.stop();
|
|
71
84
|
throw new Error('Aborted');
|
|
72
85
|
}
|
|
86
|
+
if (!firstChunkReceived) {
|
|
87
|
+
spinner.stop();
|
|
88
|
+
firstChunkReceived = true;
|
|
89
|
+
}
|
|
73
90
|
const delta = chunk.choices[0]?.delta;
|
|
74
91
|
if (!delta)
|
|
75
92
|
continue;
|
|
76
93
|
if (delta.content) {
|
|
77
94
|
assistantContent += delta.content;
|
|
78
95
|
fullResponse += delta.content;
|
|
79
|
-
|
|
96
|
+
if (!options.quiet)
|
|
97
|
+
printStreaming(delta.content);
|
|
80
98
|
}
|
|
81
99
|
if (delta.tool_calls) {
|
|
82
100
|
for (const tc of delta.tool_calls) {
|
|
@@ -98,8 +116,10 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
98
116
|
catch (streamErr) {
|
|
99
117
|
if (streamErr.message === 'Aborted')
|
|
100
118
|
throw streamErr;
|
|
119
|
+
spinner.stop();
|
|
101
120
|
// Fallback to non-streaming mode
|
|
102
|
-
|
|
121
|
+
if (!options.quiet)
|
|
122
|
+
printLine(chalk.dim('(falling back to non-streaming mode...)'));
|
|
103
123
|
const completion = await client.chat.completions.create({
|
|
104
124
|
model: config.model,
|
|
105
125
|
messages,
|
|
@@ -111,7 +131,8 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
111
131
|
if (choice?.message?.content) {
|
|
112
132
|
assistantContent = choice.message.content;
|
|
113
133
|
fullResponse += assistantContent;
|
|
114
|
-
|
|
134
|
+
if (!options.quiet)
|
|
135
|
+
printStreaming(assistantContent);
|
|
115
136
|
}
|
|
116
137
|
if (choice?.message?.tool_calls) {
|
|
117
138
|
for (let idx = 0; idx < choice.message.tool_calls.length; idx++) {
|
|
@@ -124,6 +145,8 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
124
145
|
}
|
|
125
146
|
}
|
|
126
147
|
}
|
|
148
|
+
// Ensure spinner is stopped
|
|
149
|
+
spinner.stop();
|
|
127
150
|
const assistantMsg = {
|
|
128
151
|
role: 'assistant',
|
|
129
152
|
content: assistantContent || null,
|
|
@@ -137,11 +160,11 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
137
160
|
}
|
|
138
161
|
messages.push(assistantMsg);
|
|
139
162
|
if (toolCalls.size === 0) {
|
|
140
|
-
if (assistantContent)
|
|
163
|
+
if (assistantContent && !options.quiet)
|
|
141
164
|
printLine();
|
|
142
165
|
break;
|
|
143
166
|
}
|
|
144
|
-
if (assistantContent)
|
|
167
|
+
if (assistantContent && !options.quiet)
|
|
145
168
|
printLine();
|
|
146
169
|
for (const tc of toolCalls.values()) {
|
|
147
170
|
let args = {};
|
|
@@ -151,9 +174,15 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
151
174
|
catch {
|
|
152
175
|
args = {};
|
|
153
176
|
}
|
|
154
|
-
|
|
177
|
+
// Show tool spinner
|
|
178
|
+
if (!options.quiet) {
|
|
179
|
+
spinner.start(`Running ${tc.name}...`);
|
|
180
|
+
printToolCall(tc.name, tc.arguments);
|
|
181
|
+
}
|
|
155
182
|
const result = executeTool(tc.name, args);
|
|
156
|
-
|
|
183
|
+
spinner.stop();
|
|
184
|
+
if (!options.quiet)
|
|
185
|
+
printToolResult(result);
|
|
157
186
|
messages.push({
|
|
158
187
|
role: 'tool',
|
|
159
188
|
tool_call_id: tc.id,
|
|
@@ -162,6 +191,7 @@ export async function runAgent(userMessage, history, options = {}) {
|
|
|
162
191
|
}
|
|
163
192
|
}
|
|
164
193
|
catch (e) {
|
|
194
|
+
spinner.stop();
|
|
165
195
|
if (e.message === 'Aborted')
|
|
166
196
|
throw e;
|
|
167
197
|
printError(`API Error: ${e.message}`);
|
package/dist/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,KAAK,MAAM,OAAO,CAAC;AAK1B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,eAAe,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab completion for REPL slash commands.
|
|
3
|
+
*/
|
|
4
|
+
export interface CommandDef {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Readline completer function.
|
|
10
|
+
* When user types `/` + partial, suggest matching commands.
|
|
11
|
+
*/
|
|
12
|
+
export declare function slashCompleter(line: string): [string[], string];
|
|
13
|
+
/**
|
|
14
|
+
* Get all commands for /help display.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAllCommands(): CommandDef[];
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab completion for REPL slash commands.
|
|
3
|
+
*/
|
|
4
|
+
const COMMANDS = [
|
|
5
|
+
{ name: '/help', description: 'Show available commands' },
|
|
6
|
+
{ name: '/new', description: 'Start new conversation' },
|
|
7
|
+
{ name: '/clear', description: 'Clear conversation history' },
|
|
8
|
+
{ name: '/compact', description: 'Compress context (nine-section)' },
|
|
9
|
+
{ name: '/resume', description: 'Restore a previous session' },
|
|
10
|
+
{ name: '/insight', description: 'Session analytics' },
|
|
11
|
+
{ name: '/status', description: 'Session status' },
|
|
12
|
+
{ name: '/plan', description: 'Show current task plan' },
|
|
13
|
+
{ name: '/spawn', description: 'Run task in background sub-agent' },
|
|
14
|
+
{ name: '/agents', description: 'List sub-agents' },
|
|
15
|
+
{ name: '/knowledge', description: 'List knowledge files' },
|
|
16
|
+
{ name: '/skills', description: 'List skills' },
|
|
17
|
+
{ name: '/dream', description: 'Memory consolidation' },
|
|
18
|
+
{ name: '/quit', description: 'Exit' },
|
|
19
|
+
{ name: '/exit', description: 'Exit' },
|
|
20
|
+
];
|
|
21
|
+
/**
|
|
22
|
+
* Readline completer function.
|
|
23
|
+
* When user types `/` + partial, suggest matching commands.
|
|
24
|
+
*/
|
|
25
|
+
export function slashCompleter(line) {
|
|
26
|
+
if (!line.startsWith('/')) {
|
|
27
|
+
return [[], line];
|
|
28
|
+
}
|
|
29
|
+
const input = line.toLowerCase();
|
|
30
|
+
const matches = COMMANDS
|
|
31
|
+
.filter(c => c.name.startsWith(input))
|
|
32
|
+
.map(c => c.name);
|
|
33
|
+
return [matches, line];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get all commands for /help display.
|
|
37
|
+
*/
|
|
38
|
+
export function getAllCommands() {
|
|
39
|
+
return [...COMMANDS];
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=completer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"completer.js","sourceRoot":"","sources":["../src/completer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH,MAAM,QAAQ,GAAiB;IAC7B,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,yBAAyB,EAAE;IACzD,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,wBAAwB,EAAE;IACvD,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAC7D,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,iCAAiC,EAAE;IACpE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,4BAA4B,EAAE;IAC9D,EAAE,IAAI,EAAE,UAAU,EAAE,WAAW,EAAE,mBAAmB,EAAE;IACtD,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,gBAAgB,EAAE;IAClD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,wBAAwB,EAAE;IACxD,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,kCAAkC,EAAE;IACnE,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,iBAAiB,EAAE;IACnD,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,sBAAsB,EAAE;IAC3D,EAAE,IAAI,EAAE,SAAS,EAAE,WAAW,EAAE,aAAa,EAAE;IAC/C,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE;IACvD,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE;IACtC,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE;CACvC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACpB,CAAC;IAED,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,OAAO,GAAG,QAAQ;SACrB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;SACrC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEpB,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,OAAO,CAAC,GAAG,QAAQ,CAAC,CAAC;AACvB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,7 @@ import { createInterface } from 'node:readline';
|
|
|
4
4
|
import { readFileSync, existsSync, mkdirSync, copyFileSync, writeFileSync, readdirSync, statSync, cpSync } from 'node:fs';
|
|
5
5
|
import { join } from 'node:path';
|
|
6
6
|
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
7
8
|
import { loadConfig, setConfigValue, getConfigValue, listConfig, ensureConfigDir, getConfigDir, resolveKnowledgeDir, } from './config.js';
|
|
8
9
|
import { runAgent } from './agent.js';
|
|
9
10
|
import { listKnowledgeFiles } from './knowledge.js';
|
|
@@ -16,6 +17,11 @@ import { registerKnowledgeCommand } from './knowledge-commands.js';
|
|
|
16
17
|
import { registerRulesCommand } from './rules-commands.js';
|
|
17
18
|
import { registerStructuredSkillsCommand } from './structured-skills-commands.js';
|
|
18
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, setupResizeHandler } from './statusbar.js';
|
|
24
|
+
import { slashCompleter } from './completer.js';
|
|
19
25
|
import chalk from 'chalk';
|
|
20
26
|
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
21
27
|
let version = '0.1.0';
|
|
@@ -99,14 +105,16 @@ program
|
|
|
99
105
|
}
|
|
100
106
|
const memoryDir = join(getConfigDir(), 'memory');
|
|
101
107
|
const learningsDir = join(getConfigDir(), '.learnings');
|
|
102
|
-
|
|
108
|
+
const sessionsDir = join(getConfigDir(), 'sessions');
|
|
109
|
+
const agentsDir = join(getConfigDir(), 'agents');
|
|
110
|
+
for (const dir of [memoryDir, learningsDir, sessionsDir, agentsDir]) {
|
|
103
111
|
if (!existsSync(dir)) {
|
|
104
112
|
mkdirSync(dir, { recursive: true });
|
|
105
113
|
printSuccess(`Created ${dir}`);
|
|
106
114
|
}
|
|
107
115
|
}
|
|
108
116
|
initSkills();
|
|
109
|
-
// Copy bundled skills to ~/.bobo/skills/
|
|
117
|
+
// Copy bundled skills to ~/.bobo/skills/ (including scripts/ subdirs)
|
|
110
118
|
const bundledSkillsDir = join(__dirname, '..', 'bundled-skills');
|
|
111
119
|
const userSkillsDir = join(getConfigDir(), 'skills');
|
|
112
120
|
if (existsSync(bundledSkillsDir)) {
|
|
@@ -125,6 +133,7 @@ program
|
|
|
125
133
|
continue;
|
|
126
134
|
}
|
|
127
135
|
if (!existsSync(dest)) {
|
|
136
|
+
// Use cpSync recursive — copies everything including scripts/
|
|
128
137
|
cpSync(src, dest, { recursive: true });
|
|
129
138
|
installed++;
|
|
130
139
|
}
|
|
@@ -154,6 +163,142 @@ program
|
|
|
154
163
|
printLine(`Knowledge: ${knowledgeDir}`);
|
|
155
164
|
printWarning('Configure your API key: bobo config set apiKey <your-key>');
|
|
156
165
|
});
|
|
166
|
+
// ─── Doctor subcommand ───────────────────────────────────────
|
|
167
|
+
program
|
|
168
|
+
.command('doctor')
|
|
169
|
+
.description('Check environment dependencies for skills')
|
|
170
|
+
.action(() => {
|
|
171
|
+
printLine(chalk.cyan.bold('\n🩺 Bobo Doctor — Environment Check\n'));
|
|
172
|
+
const checks = [
|
|
173
|
+
{ name: 'Node.js', cmd: 'node --version', required: true },
|
|
174
|
+
{ name: 'Python 3', cmd: 'python3 --version', required: false },
|
|
175
|
+
{ name: 'pip3', cmd: 'pip3 --version', required: false },
|
|
176
|
+
{ name: 'Git', cmd: 'git --version', required: true },
|
|
177
|
+
{ name: 'ffmpeg', cmd: 'ffmpeg -version', required: false },
|
|
178
|
+
{ name: 'npm', cmd: 'npm --version', required: true },
|
|
179
|
+
{ name: 'curl', cmd: 'curl --version', required: false },
|
|
180
|
+
];
|
|
181
|
+
let allGood = true;
|
|
182
|
+
for (const check of checks) {
|
|
183
|
+
try {
|
|
184
|
+
const output = execSync(check.cmd, { timeout: 5000, stdio: 'pipe' }).toString().trim().split('\n')[0];
|
|
185
|
+
printLine(` ${chalk.green('✓')} ${check.name.padEnd(12)} ${chalk.dim(output)}`);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
const icon = check.required ? chalk.red('✗') : chalk.yellow('○');
|
|
189
|
+
const label = check.required ? chalk.red('MISSING (required)') : chalk.yellow('not found (optional)');
|
|
190
|
+
printLine(` ${icon} ${check.name.padEnd(12)} ${label}`);
|
|
191
|
+
if (check.required)
|
|
192
|
+
allGood = false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Check API key
|
|
196
|
+
const config = loadConfig();
|
|
197
|
+
if (config.apiKey) {
|
|
198
|
+
printLine(` ${chalk.green('✓')} ${'API Key'.padEnd(12)} ${chalk.dim('configured')}`);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
printLine(` ${chalk.red('✗')} ${'API Key'.padEnd(12)} ${chalk.red('not set — run: bobo config set apiKey <key>')}`);
|
|
202
|
+
allGood = false;
|
|
203
|
+
}
|
|
204
|
+
// Check skills directory
|
|
205
|
+
const skillsDir = join(getConfigDir(), 'skills');
|
|
206
|
+
if (existsSync(skillsDir)) {
|
|
207
|
+
const count = readdirSync(skillsDir).filter(f => {
|
|
208
|
+
try {
|
|
209
|
+
return statSync(join(skillsDir, f)).isDirectory();
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
}).length;
|
|
215
|
+
printLine(` ${chalk.green('✓')} ${'Skills'.padEnd(12)} ${chalk.dim(`${count} installed`)}`);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
printLine(` ${chalk.yellow('○')} ${'Skills'.padEnd(12)} ${chalk.yellow('none — run: bobo init')}`);
|
|
219
|
+
}
|
|
220
|
+
printLine();
|
|
221
|
+
if (allGood) {
|
|
222
|
+
printSuccess('All required dependencies are available! 🐕');
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
printWarning('Some required dependencies are missing.');
|
|
226
|
+
}
|
|
227
|
+
printLine();
|
|
228
|
+
});
|
|
229
|
+
// ─── Spawn subcommand (sub-agent) ────────────────────────────
|
|
230
|
+
program
|
|
231
|
+
.command('spawn <task>')
|
|
232
|
+
.description('Spawn a background sub-agent to run a task')
|
|
233
|
+
.action((task) => {
|
|
234
|
+
const result = spawnSubAgent(task);
|
|
235
|
+
if (result.error) {
|
|
236
|
+
printError(result.error);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
printSuccess(`Sub-agent ${result.id} spawned!`);
|
|
240
|
+
printLine(chalk.dim(` Task: ${task.slice(0, 80)}${task.length > 80 ? '...' : ''}`));
|
|
241
|
+
printLine(chalk.dim(` Check status: bobo agents`));
|
|
242
|
+
});
|
|
243
|
+
// ─── Agents subcommand ───────────────────────────────────────
|
|
244
|
+
const agentsCmd = program.command('agents').description('Manage sub-agents');
|
|
245
|
+
agentsCmd
|
|
246
|
+
.command('list')
|
|
247
|
+
.description('List all sub-agents')
|
|
248
|
+
.action(() => {
|
|
249
|
+
const agents = listSubAgents();
|
|
250
|
+
if (agents.length === 0) {
|
|
251
|
+
printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
|
|
255
|
+
for (const a of agents) {
|
|
256
|
+
const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
|
|
257
|
+
const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
|
|
258
|
+
printLine(` ${icon} ${chalk.bold(a.id)} — ${task}`);
|
|
259
|
+
printLine(` ${chalk.dim(a.startedAt)} ${chalk.dim(`[${a.status}]`)}`);
|
|
260
|
+
}
|
|
261
|
+
printLine();
|
|
262
|
+
});
|
|
263
|
+
agentsCmd
|
|
264
|
+
.command('show <id>')
|
|
265
|
+
.description('Show sub-agent result')
|
|
266
|
+
.action((id) => {
|
|
267
|
+
const agent = getSubAgent(id);
|
|
268
|
+
if (!agent) {
|
|
269
|
+
printError(`Sub-agent not found: ${id}`);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
printLine(chalk.cyan.bold(`\n🤖 Sub-Agent: ${agent.id}\n`));
|
|
273
|
+
printLine(` Status: ${agent.status}`);
|
|
274
|
+
printLine(` Task: ${agent.task}`);
|
|
275
|
+
printLine(` Started: ${agent.startedAt}`);
|
|
276
|
+
if (agent.completedAt)
|
|
277
|
+
printLine(` Done: ${agent.completedAt}`);
|
|
278
|
+
if (agent.result) {
|
|
279
|
+
printLine(`\n${chalk.dim('─'.repeat(50))}\n`);
|
|
280
|
+
printLine(agent.result);
|
|
281
|
+
}
|
|
282
|
+
if (agent.error) {
|
|
283
|
+
printLine(`\n${chalk.red('Error:')} ${agent.error}`);
|
|
284
|
+
}
|
|
285
|
+
printLine();
|
|
286
|
+
});
|
|
287
|
+
// Default agents action: list
|
|
288
|
+
agentsCmd.action(() => {
|
|
289
|
+
const agents = listSubAgents();
|
|
290
|
+
if (agents.length === 0) {
|
|
291
|
+
printLine(chalk.dim('No sub-agents. Spawn one with: bobo spawn "task"'));
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
|
|
295
|
+
for (const a of agents) {
|
|
296
|
+
const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
|
|
297
|
+
const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
|
|
298
|
+
printLine(` ${icon} ${chalk.bold(a.id)} — ${task}`);
|
|
299
|
+
}
|
|
300
|
+
printLine();
|
|
301
|
+
});
|
|
157
302
|
// ─── Knowledge subcommand ────────────────────────────────────
|
|
158
303
|
program
|
|
159
304
|
.command('knowledge')
|
|
@@ -238,6 +383,8 @@ async function runRepl() {
|
|
|
238
383
|
const config = loadConfig();
|
|
239
384
|
const skills = listSkills();
|
|
240
385
|
const knowledgeFiles = listKnowledgeFiles();
|
|
386
|
+
const sessionStartTime = Date.now();
|
|
387
|
+
const matchedSkills = [];
|
|
241
388
|
printWelcome({
|
|
242
389
|
version,
|
|
243
390
|
model: config.model,
|
|
@@ -251,13 +398,49 @@ async function runRepl() {
|
|
|
251
398
|
printWarning('API key not configured. Run: bobo config set apiKey <your-key>');
|
|
252
399
|
printLine();
|
|
253
400
|
}
|
|
401
|
+
// Check for resumable session
|
|
402
|
+
let history = [];
|
|
403
|
+
const recentSession = getRecentSession(3600000); // 1 hour
|
|
404
|
+
if (recentSession && recentSession.messages.length > 0) {
|
|
405
|
+
printLine(chalk.yellow(`💾 Found recent session (${recentSession.messageCount} messages, ${recentSession.firstUserMessage.slice(0, 50)}...)`));
|
|
406
|
+
printLine(chalk.dim(' Resume? (y/n)'));
|
|
407
|
+
// Quick y/n prompt
|
|
408
|
+
const answer = await new Promise((resolve) => {
|
|
409
|
+
const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
|
|
410
|
+
tmpRl.question(chalk.green('> '), (ans) => {
|
|
411
|
+
tmpRl.close();
|
|
412
|
+
resolve(ans.trim().toLowerCase());
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
if (answer === 'y' || answer === 'yes') {
|
|
416
|
+
history = recentSession.messages;
|
|
417
|
+
printSuccess(`Resumed session (${history.length} messages)`);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
// Enable bottom status bar (Claude Code style)
|
|
421
|
+
if (process.stdout.isTTY) {
|
|
422
|
+
setupResizeHandler();
|
|
423
|
+
enableStatusBar({
|
|
424
|
+
model: config.model,
|
|
425
|
+
thinkingLevel: 'medium',
|
|
426
|
+
skillsCount: skills.filter(s => s.enabled).length,
|
|
427
|
+
cwd: process.cwd(),
|
|
428
|
+
});
|
|
429
|
+
}
|
|
254
430
|
const rl = createInterface({
|
|
255
431
|
input: process.stdin,
|
|
256
432
|
output: process.stdout,
|
|
257
433
|
prompt: chalk.green('> '),
|
|
434
|
+
completer: slashCompleter,
|
|
258
435
|
});
|
|
259
|
-
let history = [];
|
|
260
436
|
let abortController = null;
|
|
437
|
+
// Auto-save on exit
|
|
438
|
+
const autoSave = () => {
|
|
439
|
+
if (history.length > 0) {
|
|
440
|
+
const id = saveSession(history, process.cwd());
|
|
441
|
+
printLine(chalk.dim(`\n💾 Session saved: ${id}`));
|
|
442
|
+
}
|
|
443
|
+
};
|
|
261
444
|
rl.on('SIGINT', () => {
|
|
262
445
|
if (abortController) {
|
|
263
446
|
abortController.abort();
|
|
@@ -271,6 +454,8 @@ async function runRepl() {
|
|
|
271
454
|
}
|
|
272
455
|
});
|
|
273
456
|
rl.on('close', () => {
|
|
457
|
+
autoSave();
|
|
458
|
+
disableStatusBar();
|
|
274
459
|
printLine(chalk.dim('\nGoodbye! 🐕'));
|
|
275
460
|
process.exit(0);
|
|
276
461
|
});
|
|
@@ -282,11 +467,14 @@ async function runRepl() {
|
|
|
282
467
|
continue;
|
|
283
468
|
}
|
|
284
469
|
if (input === '/quit' || input === '/exit') {
|
|
470
|
+
autoSave();
|
|
471
|
+
disableStatusBar();
|
|
285
472
|
printLine(chalk.dim('Goodbye! 🐕'));
|
|
286
473
|
process.exit(0);
|
|
287
474
|
}
|
|
288
|
-
if (input === '/clear') {
|
|
475
|
+
if (input === '/clear' || input === '/new') {
|
|
289
476
|
history = [];
|
|
477
|
+
matchedSkills.length = 0;
|
|
290
478
|
resetPlan();
|
|
291
479
|
printSuccess('Conversation cleared');
|
|
292
480
|
rl.prompt();
|
|
@@ -297,6 +485,103 @@ async function runRepl() {
|
|
|
297
485
|
rl.prompt();
|
|
298
486
|
continue;
|
|
299
487
|
}
|
|
488
|
+
// ─── /resume ───
|
|
489
|
+
if (input === '/resume') {
|
|
490
|
+
const sessions = listSessions(10);
|
|
491
|
+
if (sessions.length === 0) {
|
|
492
|
+
printWarning('No saved sessions.');
|
|
493
|
+
rl.prompt();
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
printLine(chalk.cyan.bold('\n💾 Recent Sessions:\n'));
|
|
497
|
+
for (let i = 0; i < sessions.length; i++) {
|
|
498
|
+
const s = sessions[i];
|
|
499
|
+
const date = s.startedAt ? new Date(s.startedAt).toLocaleString() : 'unknown';
|
|
500
|
+
printLine(` ${chalk.bold(String(i + 1).padStart(2))} ${chalk.dim(date)} — ${s.firstUserMessage.slice(0, 50)} (${s.messageCount} msgs)`);
|
|
501
|
+
}
|
|
502
|
+
printLine(chalk.dim('\n Enter number to restore, or press Enter to cancel:'));
|
|
503
|
+
const pick = await new Promise((resolve) => {
|
|
504
|
+
const tmpRl = createInterface({ input: process.stdin, output: process.stdout });
|
|
505
|
+
tmpRl.question(chalk.green('> '), (ans) => {
|
|
506
|
+
tmpRl.close();
|
|
507
|
+
resolve(ans.trim());
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
const idx = parseInt(pick, 10) - 1;
|
|
511
|
+
if (idx >= 0 && idx < sessions.length) {
|
|
512
|
+
const session = loadSession(sessions[idx].id);
|
|
513
|
+
if (session) {
|
|
514
|
+
history = session.messages;
|
|
515
|
+
printSuccess(`Restored session (${history.length} messages)`);
|
|
516
|
+
}
|
|
517
|
+
else {
|
|
518
|
+
printError('Failed to load session.');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
rl.prompt();
|
|
522
|
+
continue;
|
|
523
|
+
}
|
|
524
|
+
// ─── /insight ───
|
|
525
|
+
if (input === '/insight') {
|
|
526
|
+
printLine(generateInsight(history, sessionStartTime, [...new Set(matchedSkills)]));
|
|
527
|
+
rl.prompt();
|
|
528
|
+
continue;
|
|
529
|
+
}
|
|
530
|
+
// ─── /agents or /bg ───
|
|
531
|
+
if (input === '/agents' || input === '/bg') {
|
|
532
|
+
const agents = listSubAgents(10);
|
|
533
|
+
if (agents.length === 0) {
|
|
534
|
+
printLine(chalk.dim('No sub-agents. Use: bobo spawn "task" or type: /spawn <task>'));
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
printLine(chalk.cyan.bold('\n🤖 Sub-Agents:\n'));
|
|
538
|
+
for (const a of agents) {
|
|
539
|
+
const icon = a.status === 'completed' ? '✅' : a.status === 'failed' ? '❌' : '⏳';
|
|
540
|
+
const task = a.task.slice(0, 60) + (a.task.length > 60 ? '...' : '');
|
|
541
|
+
printLine(` ${icon} ${chalk.bold(a.id)} — ${task} ${chalk.dim(`[${a.status}]`)}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
printLine();
|
|
545
|
+
rl.prompt();
|
|
546
|
+
continue;
|
|
547
|
+
}
|
|
548
|
+
// ─── /agents show <id> ───
|
|
549
|
+
if (input.startsWith('/agents show ')) {
|
|
550
|
+
const id = input.replace('/agents show ', '').trim();
|
|
551
|
+
const agent = getSubAgent(id);
|
|
552
|
+
if (!agent) {
|
|
553
|
+
printError(`Sub-agent not found: ${id}`);
|
|
554
|
+
}
|
|
555
|
+
else {
|
|
556
|
+
printLine(chalk.cyan.bold(`\n🤖 ${agent.id} [${agent.status}]`));
|
|
557
|
+
printLine(chalk.dim(`Task: ${agent.task}`));
|
|
558
|
+
if (agent.result)
|
|
559
|
+
printLine(`\n${agent.result}`);
|
|
560
|
+
if (agent.error)
|
|
561
|
+
printLine(chalk.red(`Error: ${agent.error}`));
|
|
562
|
+
}
|
|
563
|
+
printLine();
|
|
564
|
+
rl.prompt();
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
// ─── /spawn <task> ───
|
|
568
|
+
if (input.startsWith('/spawn ')) {
|
|
569
|
+
const task = input.replace('/spawn ', '').trim();
|
|
570
|
+
if (!task) {
|
|
571
|
+
printWarning('Usage: /spawn <task description>');
|
|
572
|
+
}
|
|
573
|
+
else {
|
|
574
|
+
const result = spawnSubAgent(task);
|
|
575
|
+
if (result.error) {
|
|
576
|
+
printError(result.error);
|
|
577
|
+
}
|
|
578
|
+
else {
|
|
579
|
+
printSuccess(`Sub-agent ${result.id} spawned! Check with /agents`);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
rl.prompt();
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
300
585
|
if (input === '/compact') {
|
|
301
586
|
const userCount = history.filter(m => m.role === 'user').length;
|
|
302
587
|
if (userCount > 4) {
|
|
@@ -377,23 +662,39 @@ async function runRepl() {
|
|
|
377
662
|
continue;
|
|
378
663
|
}
|
|
379
664
|
if (input === '/help') {
|
|
380
|
-
printLine(chalk.cyan('Commands:'));
|
|
665
|
+
printLine(chalk.cyan.bold('Commands:'));
|
|
666
|
+
printLine('');
|
|
667
|
+
printLine(chalk.dim(' Session'));
|
|
668
|
+
printLine(' /new — Start new conversation');
|
|
381
669
|
printLine(' /clear — Clear conversation history');
|
|
382
670
|
printLine(' /compact — Compress context (nine-section)');
|
|
383
|
-
printLine(' /
|
|
384
|
-
printLine(' /
|
|
671
|
+
printLine(' /resume — Restore a previous session');
|
|
672
|
+
printLine(' /quit — Exit');
|
|
673
|
+
printLine('');
|
|
674
|
+
printLine(chalk.dim(' Analysis'));
|
|
675
|
+
printLine(' /insight — Session analytics (tokens, tools, skills)');
|
|
385
676
|
printLine(' /status — Session status');
|
|
386
677
|
printLine(' /plan — Show current task plan');
|
|
678
|
+
printLine('');
|
|
679
|
+
printLine(chalk.dim(' Sub-Agents'));
|
|
680
|
+
printLine(' /spawn <task> — Run a task in background sub-agent');
|
|
681
|
+
printLine(' /agents — List sub-agents');
|
|
682
|
+
printLine(' /agents show <id> — Show sub-agent result');
|
|
683
|
+
printLine('');
|
|
684
|
+
printLine(chalk.dim(' Knowledge'));
|
|
387
685
|
printLine(' /knowledge — List knowledge files');
|
|
388
686
|
printLine(' /skills — List skills');
|
|
389
|
-
printLine(' /
|
|
687
|
+
printLine(' /dream — Memory consolidation');
|
|
390
688
|
printLine(' /help — Show this help');
|
|
391
689
|
rl.prompt();
|
|
392
690
|
continue;
|
|
393
691
|
}
|
|
394
692
|
abortController = new AbortController();
|
|
395
693
|
try {
|
|
396
|
-
const result = await runAgent(input, history, {
|
|
694
|
+
const result = await runAgent(input, history, {
|
|
695
|
+
signal: abortController.signal,
|
|
696
|
+
matchedSkills,
|
|
697
|
+
});
|
|
397
698
|
history = result.history;
|
|
398
699
|
}
|
|
399
700
|
catch (e) {
|