apexbot 1.0.9 → 1.1.1

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.
@@ -6,15 +6,23 @@
6
6
  *
7
7
  * Enhanced with Moltbot-style brain system for personality and memory.
8
8
  */
9
+ var __importDefault = (this && this.__importDefault) || function (mod) {
10
+ return (mod && mod.__esModule) ? mod : { "default": mod };
11
+ };
9
12
  Object.defineProperty(exports, "__esModule", { value: true });
10
13
  exports.AgentManager = void 0;
11
14
  const generative_ai_1 = require("@google/generative-ai");
15
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
16
+ const openai_1 = __importDefault(require("openai"));
12
17
  const tools_1 = require("../tools");
13
18
  const brain_1 = require("../brain");
14
19
  const toolExecutor_1 = require("./toolExecutor");
20
+ const debug_1 = require("../core/debug");
15
21
  class AgentManager {
16
22
  config = null;
17
23
  googleClient = null;
24
+ anthropicClient = null;
25
+ openaiClient = null;
18
26
  brain = null;
19
27
  // Moltbot-style model fallback chain
20
28
  fallbackModels = [
@@ -65,7 +73,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
65
73
  */
66
74
  setBrain(brain) {
67
75
  this.brain = brain;
68
- console.log('[Agent] Brain connected');
76
+ (0, debug_1.debug)('Agent', 'Brain connected');
69
77
  }
70
78
  configure(config) {
71
79
  this.config = {
@@ -87,11 +95,31 @@ You are running locally on the user's machine. No data leaves their computer. Yo
87
95
  console.warn('[Agent] Google provider selected but no API key provided — Gemini will be unavailable until configured.');
88
96
  }
89
97
  }
98
+ // Initialize Anthropic Claude client
99
+ if (config.provider === 'anthropic') {
100
+ const claudeKey = config.apiKey || process.env.ANTHROPIC_API_KEY;
101
+ if (claudeKey) {
102
+ this.anthropicClient = new sdk_1.default({ apiKey: claudeKey });
103
+ }
104
+ else {
105
+ console.warn('[Agent] Anthropic provider selected but no API key provided — Claude will be unavailable.');
106
+ }
107
+ }
108
+ // Initialize OpenAI client
109
+ if (config.provider === 'openai') {
110
+ const openaiKey = config.apiKey || process.env.OPENAI_API_KEY;
111
+ if (openaiKey) {
112
+ this.openaiClient = new openai_1.default({ apiKey: openaiKey });
113
+ }
114
+ else {
115
+ console.warn('[Agent] OpenAI provider selected but no API key provided — GPT will be unavailable.');
116
+ }
117
+ }
90
118
  // Note: Kimi provider uses a generic HTTP endpoint (apiUrl) and apiKey
91
119
  if (config.provider === 'kimi') {
92
120
  // nothing to initialize here; processWithKimi will use fetch + config.apiUrl
93
121
  }
94
- console.log(`[Agent] Configured with ${config.provider} (${config.model || 'default model'})`);
122
+ (0, debug_1.debug)('Agent', `Configured with ${config.provider} (${config.model || 'default model'})`);
95
123
  }
96
124
  async process(session, message) {
97
125
  if (!this.config) {
@@ -99,7 +127,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
99
127
  return { text: 'Agent not configured. Please set up an AI provider.' };
100
128
  }
101
129
  const userText = message.text || '';
102
- console.log(`[Agent] Processing message: ${userText.slice(0, 50)}...`);
130
+ (0, debug_1.debug)('Agent', `Processing message: ${userText.slice(0, 50)}...`);
103
131
  // Handle slash commands
104
132
  if (userText.startsWith('/')) {
105
133
  return this.handleCommand(session, userText);
@@ -123,9 +151,9 @@ You are running locally on the user's machine. No data leaves their computer. Yo
123
151
  if (fallback.provider === this.config.provider)
124
152
  continue;
125
153
  try {
126
- console.log(`[Agent] Trying fallback: ${fallback.provider}/${fallback.model}`);
154
+ (0, debug_1.debug)('Agent', `Trying fallback: ${fallback.provider}/${fallback.model}`);
127
155
  response = await this.processWithProvider(fallback.provider, fallback.model, history);
128
- console.log(`[Agent] Fallback ${fallback.provider} succeeded!`);
156
+ (0, debug_1.debug)('Agent', `Fallback ${fallback.provider} succeeded!`);
129
157
  lastError = null;
130
158
  break;
131
159
  }
@@ -145,7 +173,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
145
173
  if (this.config.enableTools) {
146
174
  const toolCalls = (0, toolExecutor_1.parseToolCalls)(response.text);
147
175
  if (toolCalls.length > 0) {
148
- console.log(`[Agent] Found ${toolCalls.length} tool calls:`, toolCalls.map(t => t.name));
176
+ (0, debug_1.debug)('Agent', `Found ${toolCalls.length} tool calls:`, toolCalls.map(t => t.name));
149
177
  // Build tool context
150
178
  const context = (0, toolExecutor_1.buildToolContext)(session.id, message.userId || message.from || 'unknown', message.channel, { workspaceDir: process.cwd() });
151
179
  // Execute tools
@@ -153,9 +181,9 @@ You are running locally on the user's machine. No data leaves their computer. Yo
153
181
  response.toolCalls = toolResults;
154
182
  // Log results
155
183
  for (const { call, result } of toolResults) {
156
- console.log(`[Agent] Tool ${call.name}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
184
+ (0, debug_1.debug)('Agent', `Tool ${call.name}: ${result.success ? 'SUCCESS' : 'FAILED'}`);
157
185
  if (!result.success)
158
- console.log(`[Agent] Error: ${result.error}`);
186
+ (0, debug_1.debug)('Agent', `Error: ${result.error}`);
159
187
  }
160
188
  // Format results and continue conversation
161
189
  const resultsText = (0, toolExecutor_1.formatToolResults)(toolResults);
@@ -193,7 +221,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
193
221
  }
194
222
  }
195
223
  }
196
- console.log(`[Agent] Generated response: ${response.text.slice(0, 50)}...`);
224
+ (0, debug_1.debug)('Agent', `Generated response: ${response.text.slice(0, 50)}...`);
197
225
  // Save to session (initialize messages array if needed)
198
226
  if (!session.messages)
199
227
  session.messages = [];
@@ -351,7 +379,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
351
379
  const apiUrl = cfg.apiUrl || 'http://localhost:11434';
352
380
  const model = cfg.model || 'llama3.2';
353
381
  const temperature = cfg.temperature ?? 0.7;
354
- console.log(`[Agent] Calling Ollama at ${apiUrl} with model ${model}...`);
382
+ (0, debug_1.debug)('Agent', `Calling Ollama at ${apiUrl} with model ${model}...`);
355
383
  // Build messages array for Ollama chat API
356
384
  const messages = history.map(m => ({
357
385
  role: m.role,
@@ -380,7 +408,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
380
408
  }
381
409
  const data = await res.json();
382
410
  const text = data.message?.content || '';
383
- console.log(`[Agent] Ollama response received: ${text.slice(0, 50)}...`);
411
+ (0, debug_1.debug)('Agent', `Ollama response received: ${text.slice(0, 50)}...`);
384
412
  return {
385
413
  text: String(text).trim() || 'No response from model',
386
414
  usage: {
@@ -432,7 +460,7 @@ You are running locally on the user's machine. No data leaves their computer. Yo
432
460
  try {
433
461
  // Build full system prompt from brain files
434
462
  systemPrompt = await this.brain.buildSystemPrompt(this.defaultSystemPrompt);
435
- console.log('[Agent] Using brain context for system prompt');
463
+ (0, debug_1.debug)('Agent', 'Using brain context for system prompt');
436
464
  }
437
465
  catch (e) {
438
466
  console.warn('[Agent] Failed to load brain context, using default');
@@ -494,14 +522,77 @@ You are running locally on the user's machine. No data leaves their computer. Yo
494
522
  };
495
523
  }
496
524
  async processWithClaude(history) {
497
- // TODO: Implement Anthropic Claude API
498
- // Requires @anthropic-ai/sdk
499
- return { text: 'Claude integration not yet implemented. Use Google/Gemini for now.' };
525
+ if (!this.anthropicClient) {
526
+ return { text: 'Claude API key not configured. Set ANTHROPIC_API_KEY environment variable.' };
527
+ }
528
+ // Separate system prompt from messages
529
+ const systemPrompt = this.config?.systemPrompt || this.defaultSystemPrompt;
530
+ // Convert messages to Claude format
531
+ const messages = history
532
+ .filter(m => m.role !== 'system')
533
+ .map(m => ({
534
+ role: m.role === 'user' ? 'user' : 'assistant',
535
+ content: m.content,
536
+ }));
537
+ try {
538
+ const response = await this.anthropicClient.messages.create({
539
+ model: this.config?.model || 'claude-sonnet-4-20250514',
540
+ max_tokens: this.config?.maxTokens || 4096,
541
+ system: systemPrompt,
542
+ messages: messages,
543
+ });
544
+ // Extract text from response
545
+ const textContent = response.content.find(c => c.type === 'text');
546
+ const text = textContent && 'text' in textContent ? textContent.text : '';
547
+ return {
548
+ text,
549
+ usage: {
550
+ inputTokens: response.usage.input_tokens,
551
+ outputTokens: response.usage.output_tokens,
552
+ },
553
+ };
554
+ }
555
+ catch (error) {
556
+ console.error('[Agent] Claude API error:', error.message);
557
+ return { text: `Claude error: ${error.message}` };
558
+ }
500
559
  }
501
560
  async processWithOpenAI(history) {
502
- // TODO: Implement OpenAI API
503
- // Requires openai package
504
- return { text: 'OpenAI integration not yet implemented. Use Google/Gemini for now.' };
561
+ if (!this.openaiClient) {
562
+ return { text: 'OpenAI API key not configured. Set OPENAI_API_KEY environment variable.' };
563
+ }
564
+ // Convert messages to OpenAI format
565
+ const systemPrompt = this.config?.systemPrompt || this.defaultSystemPrompt;
566
+ const messages = [
567
+ { role: 'system', content: systemPrompt },
568
+ ...history
569
+ .filter(m => m.role !== 'system')
570
+ .map(m => ({
571
+ role: m.role === 'user' ? 'user' : 'assistant',
572
+ content: m.content,
573
+ })),
574
+ ];
575
+ try {
576
+ const response = await this.openaiClient.chat.completions.create({
577
+ model: this.config?.model || 'gpt-4o',
578
+ max_tokens: this.config?.maxTokens || 4096,
579
+ temperature: this.config?.temperature || 0.7,
580
+ messages: messages,
581
+ });
582
+ const choice = response.choices[0];
583
+ const text = choice?.message?.content || '';
584
+ return {
585
+ text,
586
+ usage: {
587
+ inputTokens: response.usage?.prompt_tokens || 0,
588
+ outputTokens: response.usage?.completion_tokens || 0,
589
+ },
590
+ };
591
+ }
592
+ catch (error) {
593
+ console.error('[Agent] OpenAI API error:', error.message);
594
+ return { text: `OpenAI error: ${error.message}` };
595
+ }
505
596
  }
506
597
  handleCommand(session, command) {
507
598
  const [cmd, ...args] = command.slice(1).split(' ');
@@ -22,6 +22,7 @@ const allowlist_1 = require("../safety/allowlist");
22
22
  const unifiedInbox_1 = require("../channels/unifiedInbox");
23
23
  const lobster_1 = require("../lobster");
24
24
  const agentManager_1 = require("./agentManager");
25
+ const debug_1 = require("../core/debug");
25
26
  const tools_1 = require("../tools");
26
27
  /**
27
28
  * Agent Orchestrator - Main control loop
@@ -94,7 +95,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
94
95
  // ─────────────────────────────────────────
95
96
  // PHASE 1: INGEST
96
97
  // ─────────────────────────────────────────
97
- console.log('[Orchestrator] Phase 1: INGEST');
98
+ (0, debug_1.debugPhase)(1, 'INGEST', { message, sessionId });
98
99
  this.emit('phase:ingest', { message, sessionId });
99
100
  // Get or create session
100
101
  const session = await this.sessionStore.getOrCreate(sessionId, userId, channel);
@@ -107,7 +108,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
107
108
  // ─────────────────────────────────────────
108
109
  // PHASE 2: CONTEXT
109
110
  // ─────────────────────────────────────────
110
- console.log('[Orchestrator] Phase 2: CONTEXT');
111
+ (0, debug_1.debugPhase)(2, 'CONTEXT', { sessionId });
111
112
  this.emit('phase:context', { sessionId });
112
113
  // Get brain context
113
114
  const brainContext = await this.brain.buildSystemPrompt();
@@ -118,7 +119,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
118
119
  if (this.config.enableRAG) {
119
120
  const searchResults = await this.vectorStore.search(message, this.config.ragTopK, this.config.ragThreshold);
120
121
  memories = searchResults.map(r => r.entry.content);
121
- console.log(`[Orchestrator] Found ${memories.length} relevant memories`);
122
+ (0, debug_1.debug)('Orchestrator', `Found ${memories.length} relevant memories`);
122
123
  }
123
124
  const context = {
124
125
  sessionId,
@@ -134,7 +135,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
134
135
  // ─────────────────────────────────────────
135
136
  // PHASE 3: INFER
136
137
  // ─────────────────────────────────────────
137
- console.log('[Orchestrator] Phase 3: INFER');
138
+ (0, debug_1.debugPhase)(3, 'INFER', null);
138
139
  this.emit('phase:infer', { context });
139
140
  // Build enhanced prompt with memories
140
141
  let enhancedMessage = message;
@@ -172,7 +173,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
172
173
  // ─────────────────────────────────────────
173
174
  // PHASE 4: EXECUTE (handled in agent)
174
175
  // ─────────────────────────────────────────
175
- console.log('[Orchestrator] Phase 4: EXECUTE');
176
+ (0, debug_1.debugPhase)(4, 'EXECUTE', { toolsExecuted: result.toolsExecuted });
176
177
  this.emit('phase:execute', { toolsExecuted: result.toolsExecuted });
177
178
  // Check for workflow triggers
178
179
  const workflowMatch = message.match(/^\/(run|workflow)\s+(\w+)/i);
@@ -190,7 +191,7 @@ class AgentOrchestrator extends events_1.EventEmitter {
190
191
  // ─────────────────────────────────────────
191
192
  // PHASE 5: RESPOND
192
193
  // ─────────────────────────────────────────
193
- console.log('[Orchestrator] Phase 5: RESPOND');
194
+ (0, debug_1.debugPhase)(5, 'RESPOND', null);
194
195
  this.emit('phase:respond', { response: result.response });
195
196
  // Store assistant response in session
196
197
  await this.sessionStore.appendMessage(sessionId, {
@@ -159,18 +159,76 @@ async function executeToolCalls(calls, context) {
159
159
  }
160
160
  /**
161
161
  * Format tool results for inclusion in AI response
162
+ * Provides clean, readable formatting for different data types
162
163
  */
163
164
  function formatToolResults(results) {
164
165
  if (results.length === 0)
165
166
  return '';
166
167
  return results.map(({ call, result }) => {
167
- const status = result.success ? 'SUCCESS' : 'ERROR';
168
- const data = result.success
169
- ? JSON.stringify(result.data, null, 2)
170
- : result.error;
171
- return `[Tool: ${call.name}] ${status}\n${data}`;
168
+ const status = result.success ? '' : '';
169
+ if (!result.success) {
170
+ return `${status} [${call.name}] Error: ${result.error}`;
171
+ }
172
+ // Format data based on type
173
+ const data = formatToolData(result.data, call.name);
174
+ return `${status} [${call.name}]\n${data}`;
172
175
  }).join('\n\n');
173
176
  }
177
+ /**
178
+ * Format tool data in a human-readable way
179
+ */
180
+ function formatToolData(data, toolName) {
181
+ if (data === null || data === undefined) {
182
+ return 'No data returned';
183
+ }
184
+ // Handle specific tool outputs
185
+ if (toolName === 'get_weather' && data.current) {
186
+ return `🌡️ ${data.location}: ${data.current.temp}°C, ${data.current.condition}\n` +
187
+ `💧 Humidity: ${data.current.humidity}%, 💨 Wind: ${data.current.wind} km/h`;
188
+ }
189
+ if (toolName === 'search_web' && Array.isArray(data.results)) {
190
+ return data.results.slice(0, 5).map((r, i) => `${i + 1}. **${r.title}**\n ${r.snippet || r.description || ''}\n ${r.url || ''}`).join('\n');
191
+ }
192
+ if (toolName === 'get_reminders' && Array.isArray(data)) {
193
+ if (data.length === 0)
194
+ return 'No active reminders';
195
+ return data.map((r) => `⏰ ${r.message} - ${new Date(r.triggerAt).toLocaleString()}`).join('\n');
196
+ }
197
+ if (toolName === 'get_calendar_events' && Array.isArray(data)) {
198
+ if (data.length === 0)
199
+ return 'No upcoming events';
200
+ return data.map((e) => `📅 ${e.title} - ${new Date(e.start).toLocaleString()}`).join('\n');
201
+ }
202
+ // Handle arrays
203
+ if (Array.isArray(data)) {
204
+ if (data.length === 0)
205
+ return 'Empty result';
206
+ if (data.length <= 5) {
207
+ return data.map((item) => typeof item === 'object' ? JSON.stringify(item, null, 2) : String(item)).join('\n');
208
+ }
209
+ return `${data.length} items:\n` + data.slice(0, 5).map((item) => typeof item === 'object' ? JSON.stringify(item) : String(item)).join('\n') + `\n... and ${data.length - 5} more`;
210
+ }
211
+ // Handle objects
212
+ if (typeof data === 'object') {
213
+ // Try to extract meaningful summary
214
+ if (data.message)
215
+ return String(data.message);
216
+ if (data.result)
217
+ return String(data.result);
218
+ if (data.output)
219
+ return String(data.output);
220
+ if (data.text)
221
+ return String(data.text);
222
+ // Compact JSON for small objects
223
+ const json = JSON.stringify(data, null, 2);
224
+ if (json.length < 500)
225
+ return json;
226
+ // Summary for large objects
227
+ const keys = Object.keys(data);
228
+ return `Object with ${keys.length} keys: ${keys.slice(0, 5).join(', ')}${keys.length > 5 ? '...' : ''}`;
229
+ }
230
+ return String(data);
231
+ }
174
232
  /**
175
233
  * Get the system prompt addition for tool usage
176
234
  */
@@ -180,47 +238,48 @@ function getToolsSystemPrompt() {
180
238
  return '';
181
239
  }
182
240
  const toolDescriptions = tools.map(t => {
183
- const params = t.parameters.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`).join('\n');
184
- return `- **${t.name}**: ${t.description}`;
241
+ const params = t.parameters.map(p => ` - ${p.name} (${p.type}${p.required ? ', required' : ''}): ${p.description}`).join('\n');
242
+ return `- **${t.name}**: ${t.description}\n Parameters:\n${params}`;
185
243
  }).join('\n');
244
+ const toolNames = tools.map(t => t.name).join(', ');
186
245
  return `
187
- ## TOOLS - IMPORTANT!
246
+ ## TOOLS - CRITICAL INSTRUCTIONS
188
247
 
189
- You have tools to interact with the real world. **USE THEM AUTOMATICALLY** when the user asks for something that requires them.
248
+ You have access to specific tools listed below. Use them to perform real actions.
190
249
 
191
- **HOW TO USE A TOOL:**
192
- Include this EXACT format in your response (the system will execute it automatically):
250
+ **⚠️ CRITICAL RULE:**
251
+ You can ONLY use tools from this exact list: [${toolNames}]
252
+ DO NOT invent, guess, or fabricate tool names. If a tool doesn't exist in the list, say "I don't have a tool for that" instead of making one up.
193
253
 
254
+ **FORMAT (exact JSON required):**
194
255
  \`\`\`json
195
- {"tool": "tool_name", "args": {"param": "value"}}
256
+ {"tool": "exact_tool_name_from_list", "args": {"param": "value"}}
196
257
  \`\`\`
197
258
 
198
- **WHEN TO USE TOOLS (do it automatically, don't ask!):**
199
- - "what's the weather" → use \`weather\` tool
200
- - "set a reminder" → use \`reminder_set\` tool
201
- - "what time is it" → use \`datetime\` tool
202
- - "how much RAM" / system info → use \`system_info\` tool
203
- - "calculate X" / math → use \`math\` tool
204
- - "read file X" → use \`read_file\` tool
205
- - "run command X" → use \`shell\` tool
206
- - "search for X" → use \`web_search\` tool
207
- - "play music" / spotify → use \`spotify_*\` tools
208
- - "create playlist" → use \`spotify_create_playlist\` tool
209
- - "my notes" / obsidian → use \`obsidian_*\` tools
259
+ **WHEN TO USE TOOLS (do it automatically):**
260
+ - Weather request → use \`weather\`
261
+ - Time/date → use \`datetime\`
262
+ - Math → use \`math\`
263
+ - System info → use \`system_info\`
264
+ - Set reminder → use \`reminder_set\`
265
+ - Spotify controls → use \`spotify_*\` (only if listed)
266
+ - Notes/Obsidian → use \`obsidian_*\` (only if listed)
267
+ - Web search → use \`search\` or \`web_search\`
268
+ - Shell commands → use \`shell\`
269
+ - Read/write files → use \`read_file\`, \`write_file\`
210
270
 
211
- **AVAILABLE TOOLS:**
271
+ **AVAILABLE TOOLS (ONLY these exist):**
212
272
  ${toolDescriptions}
213
273
 
214
274
  **RULES:**
215
- 1. ALWAYS use a tool when the user's request matches a tool's capability
216
- 2. Do NOT explain how to use tools - just USE them
217
- 3. Do NOT show code examples - execute the tool directly
218
- 4. After tool execution, summarize the result in natural language
219
- 5. If a tool fails, explain why and suggest alternatives
275
+ 1. USE tools from the above list automatically
276
+ 2. NEVER invent tool names that aren't listed
277
+ 3. NEVER show code examples - execute tools directly
278
+ 4. If tool doesn't exist, say "I don't have that capability"
279
+ 5. After execution, explain results in natural language
220
280
 
221
281
  **EXAMPLE:**
222
282
  User: "What's the weather in Tokyo?"
223
- Your response should include:
224
283
  \`\`\`json
225
284
  {"tool": "weather", "args": {"location": "Tokyo"}}
226
285
  \`\`\`
@@ -4,11 +4,33 @@
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ChannelManager = void 0;
7
+ const eventBus_1 = require("../core/eventBus");
7
8
  class ChannelManager {
8
9
  channels = new Map();
9
10
  gateway;
11
+ activeUsers = new Map(); // channelName -> lastActiveUserId
10
12
  constructor(gateway) {
11
13
  this.gateway = gateway;
14
+ this.setupBroadcastHandler();
15
+ }
16
+ setupBroadcastHandler() {
17
+ // Handle broadcast to all channels (for calendar notifications etc)
18
+ (0, eventBus_1.on)('channel:broadcast', async (msg) => {
19
+ console.log(`[Channels] Broadcasting to all: ${msg.text.slice(0, 50)}...`);
20
+ // Send to all channels with known active users
21
+ for (const [channelName, userId] of this.activeUsers) {
22
+ try {
23
+ await this.send(channelName, userId, msg.text);
24
+ }
25
+ catch (e) {
26
+ console.error(`[Channels] Broadcast to ${channelName}:${userId} failed:`, e);
27
+ }
28
+ }
29
+ });
30
+ }
31
+ // Track active users for broadcasts
32
+ trackActiveUser(channelName, userId) {
33
+ this.activeUsers.set(channelName, userId);
12
34
  }
13
35
  register(channel) {
14
36
  this.channels.set(channel.name, channel);
package/dist/cli/chat.js CHANGED
@@ -81,16 +81,13 @@ const FOX_THINKING = `${colors.orange}
81
81
  ${colors.orange} / - \\${colors.reset}
82
82
  `;
83
83
  const BANNER = `
84
- ${colors.rust}╔═══════════════════════════════════════════════════════════╗${colors.reset}
85
- ${colors.rust}║${colors.reset} ${colors.orange}█████╗ ██████╗ ███████╗██╗ ██╗${colors.amber} ██████╗ ██████╗ ████████╗${colors.reset} ${colors.rust}║${colors.reset}
86
- ${colors.rust}║${colors.reset} ${colors.orange}██╔══██╗██╔══██╗██╔════╝╚██╗██╔╝${colors.amber} ██╔══██╗██╔═══██╗╚══██╔══╝${colors.reset} ${colors.rust}║${colors.reset}
87
- ${colors.rust}║${colors.reset} ${colors.orange}███████║██████╔╝█████╗ ╚███╔╝${colors.amber} ██████╔╝██║ ██║ ██║${colors.reset} ${colors.rust}║${colors.reset}
88
- ${colors.rust}║${colors.reset} ${colors.orange}██╔══██║██╔═══╝ ██╔══╝ ██╔██╗${colors.amber} ██╔══██╗██║ ██║ ██║${colors.reset} ${colors.rust}║${colors.reset}
89
- ${colors.rust}║${colors.reset} ${colors.orange}██║ ██║██║ ███████╗██╔╝ ██╗${colors.amber} ██████╔╝╚██████╔╝ ██║${colors.reset} ${colors.rust}║${colors.reset}
90
- ${colors.rust}║${colors.reset} ${colors.orange}╚═╝ ╚═╝╚═╝ ╚══════╝╚═╝ ╚═╝${colors.amber} ╚═════╝ ╚═════╝ ╚═╝${colors.reset} ${colors.rust}║${colors.reset}
91
- ${colors.rust}╠═══════════════════════════════════════════════════════════╣${colors.reset}
92
- ${colors.rust}║${colors.reset} ${colors.gold}🦊 Free • Private • Local-First AI${colors.reset} ${colors.gray}v2026.1${colors.reset} ${colors.rust}║${colors.reset}
93
- ${colors.rust}╚═══════════════════════════════════════════════════════════╝${colors.reset}
84
+ ${colors.orange}${colors.bold} _ ____ _______ ______ ___ _____${colors.reset}
85
+ ${colors.orange}${colors.bold} / \\ | _ \\| ____\\ \\/ / __ ) / _ \\_ _|${colors.reset}
86
+ ${colors.amber}${colors.bold} / _ \\ | |_) | _| \\ /| _ \\| | | || |${colors.reset}
87
+ ${colors.amber}${colors.bold} / ___ \\| __/| |___ / \\| |_) | |_| || |${colors.reset}
88
+ ${colors.rust}${colors.bold} /_/ \\_\\_| |_____/_/\\_\\____/ \\___/ |_|${colors.reset}
89
+
90
+ ${colors.gold}🦊 Free • Private • Local-First AI${colors.reset} ${colors.gray}v2026.1${colors.reset}
94
91
  `;
95
92
  const SPINNER_FRAMES = ['🦊', '🔥', '✨', '💫', '⭐', '🌟', '✨', '🔥'];
96
93
  /**
@@ -104,9 +101,45 @@ class CLIChat extends events_1.EventEmitter {
104
101
  currentSpinnerFrame = 0;
105
102
  isThinking = false;
106
103
  username;
104
+ commandHistory = [];
105
+ historyFile;
107
106
  constructor(options = {}) {
108
107
  super();
109
108
  this.username = options.username || process.env.USER || 'You';
109
+ this.historyFile = path.join(process.env.HOME || process.env.USERPROFILE || '', '.apexbot', 'cli_history.json');
110
+ this.loadCommandHistory();
111
+ }
112
+ loadCommandHistory() {
113
+ try {
114
+ if (fs.existsSync(this.historyFile)) {
115
+ const data = JSON.parse(fs.readFileSync(this.historyFile, 'utf-8'));
116
+ this.commandHistory = data.commands || [];
117
+ }
118
+ }
119
+ catch {
120
+ // Ignore errors, start with empty history
121
+ }
122
+ }
123
+ saveCommandHistory() {
124
+ try {
125
+ const dir = path.dirname(this.historyFile);
126
+ if (!fs.existsSync(dir)) {
127
+ fs.mkdirSync(dir, { recursive: true });
128
+ }
129
+ // Keep last 500 commands
130
+ const commands = this.commandHistory.slice(-500);
131
+ fs.writeFileSync(this.historyFile, JSON.stringify({ commands }, null, 2));
132
+ }
133
+ catch {
134
+ // Ignore errors
135
+ }
136
+ }
137
+ saveCommandToHistory(input) {
138
+ // Don't add duplicates of the last command
139
+ if (this.commandHistory[this.commandHistory.length - 1] !== input) {
140
+ this.commandHistory.push(input);
141
+ this.saveCommandHistory();
142
+ }
110
143
  }
111
144
  setMessageHandler(handler) {
112
145
  this.messageHandler = handler;
@@ -128,6 +161,8 @@ class CLIChat extends events_1.EventEmitter {
128
161
  this.rl?.prompt();
129
162
  return;
130
163
  }
164
+ // Save to persistent history
165
+ this.saveCommandToHistory(input);
131
166
  // Handle prefixes like Claude Code
132
167
  if (input.startsWith('/')) {
133
168
  await this.handleCommand(input);
@@ -253,6 +288,9 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
253
288
  case 'history':
254
289
  this.showHistory(parseInt(args[0]) || 10);
255
290
  break;
291
+ case 'commands':
292
+ this.showCommandHistory(parseInt(args[0]) || 20);
293
+ break;
256
294
  case 'reset':
257
295
  this.history = [];
258
296
  console.log(`${colors.green}✓ Conversation reset${colors.reset}`);
@@ -270,6 +308,27 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
270
308
  case 'tools':
271
309
  this.emit('command:tools');
272
310
  break;
311
+ case 'model':
312
+ if (args[0]) {
313
+ this.emit('command:model', args[0]);
314
+ console.log(`${colors.green}✓ Model changed to: ${colors.gold}${args[0]}${colors.reset}`);
315
+ }
316
+ else {
317
+ this.emit('command:model:show');
318
+ }
319
+ break;
320
+ case 'provider':
321
+ if (args[0]) {
322
+ this.emit('command:provider', args[0]);
323
+ console.log(`${colors.green}✓ Provider changed to: ${colors.gold}${args[0]}${colors.reset}`);
324
+ }
325
+ else {
326
+ console.log(`${colors.amber}Usage: /provider <ollama|google|anthropic|openai|kimi>${colors.reset}`);
327
+ }
328
+ break;
329
+ case 'config':
330
+ this.emit('command:config');
331
+ break;
273
332
  case 'exit':
274
333
  case 'quit':
275
334
  case 'bye':
@@ -285,15 +344,20 @@ ${colors.orange}${colors.bold}Input Prefixes${colors.reset} ${co
285
344
  ${colors.orange}${colors.bold}🦊 ApexBot Commands${colors.reset}
286
345
  ${colors.gray}${'─'.repeat(40)}${colors.reset}
287
346
 
288
- ${colors.orange}/help${colors.reset} Show this help
289
- ${colors.orange}/clear${colors.reset} Clear screen
290
- ${colors.orange}/reset${colors.reset} Reset conversation
291
- ${colors.orange}/history${colors.reset} Show recent messages
292
- ${colors.orange}/status${colors.reset} Show bot status
293
- ${colors.orange}/skills${colors.reset} List enabled skills
294
- ${colors.orange}/tools${colors.reset} List available tools
295
- ${colors.orange}/fox${colors.reset} Show fox mascot 🦊
296
- ${colors.orange}/exit${colors.reset} Exit ApexBot
347
+ ${colors.orange}/help${colors.reset} Show this help
348
+ ${colors.orange}/clear${colors.reset} Clear screen
349
+ ${colors.orange}/reset${colors.reset} Reset conversation
350
+ ${colors.orange}/history${colors.reset} Show recent messages
351
+ ${colors.orange}/commands${colors.reset} Show command history
352
+ ${colors.orange}/status${colors.reset} Show bot status
353
+ ${colors.orange}/skills${colors.reset} List enabled skills
354
+ ${colors.orange}/tools${colors.reset} List available tools
355
+ ${colors.orange}/model${colors.reset} Show/change model
356
+ ${colors.orange}/model${colors.reset} ${colors.gray}<name>${colors.reset} Change to specified model
357
+ ${colors.orange}/provider${colors.reset} Change AI provider
358
+ ${colors.orange}/config${colors.reset} Show configuration
359
+ ${colors.orange}/fox${colors.reset} Show fox mascot 🦊
360
+ ${colors.orange}/exit${colors.reset} Exit ApexBot
297
361
 
298
362
  ${colors.gray}Shortcuts: Ctrl+C (exit), Ctrl+L (clear), ↑↓ (history)${colors.reset}
299
363
  `);
@@ -315,6 +379,19 @@ ${colors.gray}Shortcuts: Ctrl+C (exit), Ctrl+L (clear), ↑↓ (history)${colors
315
379
  }
316
380
  console.log();
317
381
  }
382
+ showCommandHistory(count) {
383
+ const recent = this.commandHistory.slice(-count);
384
+ if (recent.length === 0) {
385
+ console.log(`${colors.gray}No command history yet${colors.reset}`);
386
+ return;
387
+ }
388
+ console.log(`\n${colors.orange}${colors.bold}Recent Commands (${recent.length})${colors.reset}\n`);
389
+ recent.forEach((cmd, i) => {
390
+ const num = this.commandHistory.length - count + i + 1;
391
+ console.log(`${colors.gray}${String(num).padStart(4)}.${colors.reset} ${cmd}`);
392
+ });
393
+ console.log(`\n${colors.gray}Total: ${this.commandHistory.length} commands in history${colors.reset}\n`);
394
+ }
318
395
  startSpinner(text) {
319
396
  this.isThinking = true;
320
397
  this.currentSpinnerFrame = 0;