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 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
- // Layer 2: Persistent memory (data — lower priority than rules)
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 (local project config + auto-detected files)
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
- printStreaming(delta.content);
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
- printLine(chalk.dim('(falling back to non-streaming mode...)'));
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
- printStreaming(assistantContent);
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
- printToolCall(tc.name, tc.arguments);
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
- printToolResult(result);
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;AAOhG,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,WAAmB,EACnB,OAAqC,EACrC,UAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IAEH,8DAA8D;IAC9D,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,6EAA6E;IAC7E,sEAAsE;IACtE,MAAM,YAAY,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,YAAY;QAAE,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IAEhD,gEAAgE;IAChE,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM;QAAE,UAAU,CAAC,IAAI,CAAC,aAAa,MAAM,EAAE,CAAC,CAAC;IAEnD,wEAAwE;IACxE,MAAM,gBAAgB,GAAG,oBAAoB,EAAE,CAAC;IAChD,IAAI,gBAAgB;QAAE,UAAU,CAAC,IAAI,CAAC,cAAc,gBAAgB,EAAE,CAAC,CAAC;IAExE,+CAA+C;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAChE,IAAI,OAAO,GAAG,uCAAuC,OAAO,CAAC,GAAG,EAAE,yBAAyB,SAAS,EAAE,CAAC;IACvG,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,uEAAuE,CAAC;IACrF,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEzB,MAAM,YAAY,GAAG,aAAa,CAAC;QACjC,WAAW;QACX,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;QAC7B,YAAY,EAAE,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC;KAC7C,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAiC;QAC7C,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QACzC,GAAG,OAAO;QACV,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;KACvC,CAAC;IAEF,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,MAAM,aAAa,GAAG,EAAE,CAAC;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,CAAC;YACH,IAAI,gBAAgB,GAAG,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAiE,IAAI,GAAG,EAAE,CAAC;YAE1F,IAAI,CAAC;gBACH,sBAAsB;gBACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAClD,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,QAAQ;oBACR,KAAK,EAAE,eAAe;oBACtB,UAAU,EAAE,MAAM,CAAC,SAAS;oBAC5B,MAAM,EAAE,IAAI;iBACb,CAAC,CAAC;gBAEH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBACjC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;wBAC5B,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;oBAC7B,CAAC;oBAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;oBACtC,IAAI,CAAC,KAAK;wBAAE,SAAS;oBAErB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;wBAClB,gBAAgB,IAAI,KAAK,CAAC,OAAO,CAAC;wBAClC,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC;wBAC9B,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBAChC,CAAC;oBAED,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;wBACrB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;4BAClC,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC;4BACrB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gCACxB,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;4BACxF,CAAC;4BACD,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;4BACrC,IAAI,EAAE,CAAC,EAAE;gCAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;4BAC/B,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI;gCAAE,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;4BACxD,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS;gCAAE,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;wBAC1E,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;gBACnB,IAAK,SAAmB,CAAC,OAAO,KAAK,SAAS;oBAAE,MAAM,SAAS,CAAC;gBAChE,iCAAiC;gBACjC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC,CAAC;gBAChE,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBACtD,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,QAAQ;oBACR,KAAK,EAAE,eAAe;oBACtB,UAAU,EAAE,MAAM,CAAC,SAAS;oBAC5B,MAAM,EAAE,KAAK;iBACd,CAAC,CAAC;gBAEH,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACrC,IAAI,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;oBAC7B,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1C,YAAY,IAAI,gBAAgB,CAAC;oBACjC,cAAc,CAAC,gBAAgB,CAAC,CAAC;gBACnC,CAAC;gBACD,IAAI,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;oBAChC,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;wBAChE,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;wBAC1C,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE;4BACjB,EAAE,EAAE,EAAE,CAAC,EAAE;4BACT,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI;4BACtB,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS;yBACjC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAED,MAAM,YAAY,GAAwC;gBACxD,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,gBAAgB,IAAI,IAAI;aAClC,CAAC;YAEF,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACvB,YAAY,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBAClE,EAAE,EAAE,EAAE,CAAC,EAAE;oBACT,IAAI,EAAE,UAAmB;oBACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE;iBACrD,CAAC,CAAC,CAAC;YACN,CAAC;YAED,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAE5B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,gBAAgB;oBAAE,SAAS,EAAE,CAAC;gBAClC,MAAM;YACR,CAAC;YAED,IAAI,gBAAgB;gBAAE,SAAS,EAAE,CAAC;YAElC,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpC,IAAI,IAAI,GAA4B,EAAE,CAAC;gBACvC,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;gBAClC,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,EAAE,CAAC;gBACZ,CAAC;gBAED,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;gBACrC,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC1C,eAAe,CAAC,MAAM,CAAC,CAAC;gBAExB,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,YAAY,EAAE,EAAE,CAAC,EAAE;oBACnB,OAAO,EAAE,MAAM;iBAChB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAK,CAAW,CAAC,OAAO,KAAK,SAAS;gBAAE,MAAM,CAAC,CAAC;YAChD,UAAU,CAAC,cAAe,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YACjD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAiC;QAC/C,GAAG,OAAO;QACV,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;QACtC,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;KACtC,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACzD,CAAC"}
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;AAChG,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAWvC,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,WAAmB,EACnB,OAAqC,EACrC,UAAwB,EAAE;IAE1B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAE5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;QACnB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,MAAM,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,OAAO,EAAE,MAAM,CAAC,OAAO;KACxB,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,8DAA8D;IAC9D,MAAM,UAAU,GAAa,EAAE,CAAC;IAEhC,6EAA6E;IAC7E,MAAM,YAAY,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,YAAY,EAAE,CAAC;QACjB,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QAC9B,uBAAuB;QACvB,IAAI,OAAO,CAAC,aAAa,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YAC/C,IAAI,OAAO;gBAAE,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;QACpF,CAAC;IACH,CAAC;IAED,6BAA6B;IAC7B,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,IAAI,MAAM;QAAE,UAAU,CAAC,IAAI,CAAC,aAAa,MAAM,EAAE,CAAC,CAAC;IAEnD,2BAA2B;IAC3B,MAAM,gBAAgB,GAAG,oBAAoB,EAAE,CAAC;IAChD,IAAI,gBAAgB;QAAE,UAAU,CAAC,IAAI,CAAC,cAAc,gBAAgB,EAAE,CAAC,CAAC;IAExE,+CAA+C;IAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,MAAM,CAAC;IAChE,IAAI,OAAO,GAAG,uCAAuC,OAAO,CAAC,GAAG,EAAE,yBAAyB,SAAS,EAAE,CAAC;IACvG,IAAI,SAAS,IAAI,EAAE,EAAE,CAAC;QACpB,OAAO,IAAI,uEAAuE,CAAC;IACrF,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAEzB,MAAM,YAAY,GAAG,aAAa,CAAC;QACjC,WAAW;QACX,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;QAC7B,YAAY,EAAE,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC;KAC7C,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAiC;QAC7C,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QACzC,GAAG,OAAO;QACV,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;KACvC,CAAC;IAEF,IAAI,YAAY,GAAG,EAAE,CAAC;IACtB,MAAM,aAAa,GAAG,EAAE,CAAC;IAEzB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YAC5B,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QAED,yBAAyB;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK;YAAE,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAEjD,IAAI,CAAC;YACH,IAAI,gBAAgB,GAAG,EAAE,CAAC;YAC1B,MAAM,SAAS,GAAiE,IAAI,GAAG,EAAE,CAAC;YAC1F,IAAI,kBAAkB,GAAG,KAAK,CAAC;YAE/B,IAAI,CAAC;gBACH,sBAAsB;gBACtB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBAClD,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,QAAQ;oBACR,KAAK,EAAE,eAAe;oBACtB,UAAU,EAAE,MAAM,CAAC,SAAS;oBAC5B,MAAM,EAAE,IAAI;iBACb,CAAC,CAAC;gBAEH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;oBACjC,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;wBAC5B,OAAO,CAAC,IAAI,EAAE,CAAC;wBACf,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;oBAC7B,CAAC;oBAED,IAAI,CAAC,kBAAkB,EAAE,CAAC;wBACxB,OAAO,CAAC,IAAI,EAAE,CAAC;wBACf,kBAAkB,GAAG,IAAI,CAAC;oBAC5B,CAAC;oBAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC;oBACtC,IAAI,CAAC,KAAK;wBAAE,SAAS;oBAErB,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;wBAClB,gBAAgB,IAAI,KAAK,CAAC,OAAO,CAAC;wBAClC,YAAY,IAAI,KAAK,CAAC,OAAO,CAAC;wBAC9B,IAAI,CAAC,OAAO,CAAC,KAAK;4BAAE,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;oBACpD,CAAC;oBAED,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;wBACrB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;4BAClC,MAAM,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC;4BACrB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gCACxB,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,CAAC;4BACxF,CAAC;4BACD,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAE,CAAC;4BACrC,IAAI,EAAE,CAAC,EAAE;gCAAE,QAAQ,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;4BAC/B,IAAI,EAAE,CAAC,QAAQ,EAAE,IAAI;gCAAE,QAAQ,CAAC,IAAI,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC;4BACxD,IAAI,EAAE,CAAC,QAAQ,EAAE,SAAS;gCAAE,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC;wBAC1E,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,OAAO,SAAS,EAAE,CAAC;gBACnB,IAAK,SAAmB,CAAC,OAAO,KAAK,SAAS;oBAAE,MAAM,SAAS,CAAC;gBAChE,OAAO,CAAC,IAAI,EAAE,CAAC;gBACf,iCAAiC;gBACjC,IAAI,CAAC,OAAO,CAAC,KAAK;oBAAE,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAC,CAAC;gBACpF,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;oBACtD,KAAK,EAAE,MAAM,CAAC,KAAK;oBACnB,QAAQ;oBACR,KAAK,EAAE,eAAe;oBACtB,UAAU,EAAE,MAAM,CAAC,SAAS;oBAC5B,MAAM,EAAE,KAAK;iBACd,CAAC,CAAC;gBAEH,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACrC,IAAI,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;oBAC7B,gBAAgB,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;oBAC1C,YAAY,IAAI,gBAAgB,CAAC;oBACjC,IAAI,CAAC,OAAO,CAAC,KAAK;wBAAE,cAAc,CAAC,gBAAgB,CAAC,CAAC;gBACvD,CAAC;gBACD,IAAI,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;oBAChC,KAAK,IAAI,GAAG,GAAG,CAAC,EAAE,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;wBAChE,MAAM,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;wBAC1C,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE;4BACjB,EAAE,EAAE,EAAE,CAAC,EAAE;4BACT,IAAI,EAAE,EAAE,CAAC,QAAQ,CAAC,IAAI;4BACtB,SAAS,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS;yBACjC,CAAC,CAAC;oBACL,CAAC;gBACH,CAAC;YACH,CAAC;YAED,4BAA4B;YAC5B,OAAO,CAAC,IAAI,EAAE,CAAC;YAEf,MAAM,YAAY,GAAwC;gBACxD,IAAI,EAAE,WAAW;gBACjB,OAAO,EAAE,gBAAgB,IAAI,IAAI;aAClC,CAAC;YAEF,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;gBACvB,YAAY,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;oBAClE,EAAE,EAAE,EAAE,CAAC,EAAE;oBACT,IAAI,EAAE,UAAmB;oBACzB,QAAQ,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,SAAS,EAAE;iBACrD,CAAC,CAAC,CAAC;YACN,CAAC;YAED,QAAQ,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;YAE5B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;gBACzB,IAAI,gBAAgB,IAAI,CAAC,OAAO,CAAC,KAAK;oBAAE,SAAS,EAAE,CAAC;gBACpD,MAAM;YACR,CAAC;YAED,IAAI,gBAAgB,IAAI,CAAC,OAAO,CAAC,KAAK;gBAAE,SAAS,EAAE,CAAC;YAEpD,KAAK,MAAM,EAAE,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;gBACpC,IAAI,IAAI,GAA4B,EAAE,CAAC;gBACvC,IAAI,CAAC;oBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC;gBAClC,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,GAAG,EAAE,CAAC;gBACZ,CAAC;gBAED,oBAAoB;gBACpB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;oBACnB,OAAO,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,IAAI,KAAK,CAAC,CAAC;oBACvC,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;gBACvC,CAAC;gBACD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;gBAC1C,OAAO,CAAC,IAAI,EAAE,CAAC;gBACf,IAAI,CAAC,OAAO,CAAC,KAAK;oBAAE,eAAe,CAAC,MAAM,CAAC,CAAC;gBAE5C,QAAQ,CAAC,IAAI,CAAC;oBACZ,IAAI,EAAE,MAAM;oBACZ,YAAY,EAAE,EAAE,CAAC,EAAE;oBACnB,OAAO,EAAE,MAAM;iBAChB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,IAAK,CAAW,CAAC,OAAO,KAAK,SAAS;gBAAE,MAAM,CAAC,CAAC;YAChD,UAAU,CAAC,cAAe,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;YACjD,MAAM,CAAC,CAAC;QACV,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAiC;QAC/C,GAAG,OAAO;QACV,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE;QACtC,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;KACtC,CAAC;IAEF,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACzD,CAAC"}
@@ -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
- for (const dir of [memoryDir, learningsDir]) {
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(' /dream Memory consolidation');
384
- printLine(' /history Show turn count');
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(' /quit Exit');
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, { signal: abortController.signal });
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) {