morpheus-cli 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,6 +47,9 @@ morpheus status
47
47
  # Stop the agent
48
48
  morpheus stop
49
49
 
50
+ # Restart the agent
51
+ morpheus restart
52
+
50
53
  # Diagnose issues
51
54
  morpheus doctor
52
55
  ```
@@ -93,17 +96,80 @@ Local React-based UI to manage recordings, chat history, and system status acros
93
96
  #### 🔒 UI Authentication
94
97
  To protect your Web UI, use the `THE_ARCHITECT_PASS` environment variable. This ensures only authorized users can access the dashboard and API.
95
98
 
99
+ Additionally, you can use environment variables for API keys instead of storing them in the configuration file:
100
+
101
+ | Variable | Description | Required |
102
+ |----------|-------------|----------|
103
+ | `OPENAI_API_KEY` | OpenAI API key (if using GPT) | No |
104
+ | `ANTHROPIC_API_KEY` | Anthropic API key (if using Claude) | No |
105
+ | `GOOGLE_API_KEY` | Google AI key (for Gemini and Audio) | Yes (for audio) |
106
+ | `OPENROUTER_API_KEY` | OpenRouter API key (if using OpenRouter) | No |
107
+ | `THE_ARCHITECT_PASS` | Web Dashboard access password | Recommended |
108
+ | `TELEGRAM_BOT_TOKEN` | Telegram BotFather token | No |
109
+
110
+ If these environment variables are set, they will take precedence over values stored in the configuration file.
111
+
112
+ The system also supports generic environment variables that apply to all providers:
113
+
114
+ | Variable | Description | Applies To |
115
+ |----------|-------------|------------|
116
+ | `MORPHEUS_AGENT_NAME` | Name of the agent | agent.name |
117
+ | `MORPHEUS_AGENT_PERSONALITY` | Personality of the agent | agent.personality |
118
+ | `MORPHEUS_LLM_PROVIDER` | LLM provider to use | llm.provider |
119
+ | `MORPHEUS_LLM_MODEL` | Model name for LLM | llm.model |
120
+ | `MORPHEUS_LLM_TEMPERATURE` | Temperature setting for LLM | llm.temperature |
121
+ | `MORPHEUS_LLM_MAX_TOKENS` | Maximum tokens for LLM | llm.max_tokens |
122
+ | `MORPHEUS_LLM_CONTEXT_WINDOW` | Context window size for LLM | llm.context_window |
123
+ | `MORPHEUS_LLM_API_KEY` | Generic API key for LLM (lower precedence than provider-specific keys) | llm.api_key |
124
+ | `MORPHEUS_SANTI_PROVIDER` | Sati provider to use | santi.provider |
125
+ | `MORPHEUS_SANTI_MODEL` | Model name for Sati | santi.model |
126
+ | `MORPHEUS_SANTI_TEMPERATURE` | Temperature setting for Sati | santi.temperature |
127
+ | `MORPHEUS_SANTI_MAX_TOKENS` | Maximum tokens for Sati | santi.max_tokens |
128
+ | `MORPHEUS_SANTI_CONTEXT_WINDOW` | Context window size for Sati | santi.context_window |
129
+ | `MORPHEUS_SANTI_API_KEY` | Generic API key for Sati (lower precedence than provider-specific keys) | santi.api_key |
130
+ | `MORPHEUS_SANTI_MEMORY_LIMIT` | Memory retrieval limit for Sati | santi.memory_limit |
131
+ | `MORPHEUS_AUDIO_MODEL` | Model name for audio processing | audio.model |
132
+ | `MORPHEUS_AUDIO_ENABLED` | Enable/disable audio processing | audio.enabled |
133
+ | `MORPHEUS_AUDIO_API_KEY` | Generic API key for audio (lower precedence than provider-specific keys) | audio.apiKey |
134
+ | `MORPHEUS_AUDIO_MAX_DURATION` | Max duration for audio processing | audio.maxDurationSeconds |
135
+ | `MORPHEUS_TELEGRAM_ENABLED` | Enable/disable Telegram channel | channels.telegram.enabled |
136
+ | `MORPHEUS_TELEGRAM_TOKEN` | Telegram bot token | channels.telegram.token |
137
+ | `MORPHEUS_TELEGRAM_ALLOWED_USERS` | Comma-separated list of allowed Telegram user IDs | channels.telegram.allowedUsers |
138
+ | `MORPHEUS_UI_ENABLED` | Enable/disable Web UI | ui.enabled |
139
+ | `MORPHEUS_UI_PORT` | Port for Web UI | ui.port |
140
+ | `MORPHEUS_LOGGING_ENABLED` | Enable/disable logging | logging.enabled |
141
+ | `MORPHEUS_LOGGING_LEVEL` | Logging level | logging.level |
142
+ | `MORPHEUS_LOGGING_RETENTION` | Log retention period | logging.retention |
143
+
144
+ **Precedence Order**: The system follows this order of precedence when resolving configuration values:
145
+ 1. Provider-specific environment variable (e.g., `OPENAI_API_KEY`) - Highest priority
146
+ 2. Generic environment variable (e.g., `MORPHEUS_LLM_API_KEY`) - Medium priority
147
+ 3. Configuration file value (e.g., `config.llm.api_key`) - Lower priority
148
+ 4. Default value - Lowest priority
149
+
150
+ > **Note**: If `THE_ARCHITECT_PASS` is not set, the system will use the default password `iamthearchitect`. This is less secure and it's recommended to set your own password in production environments.
151
+
96
152
  **Option 1: Using a `.env` file**
97
153
  Create a `.env` file in the root of your project:
98
154
 
99
155
  ```env
156
+ OPENAI_API_KEY="your-openai-api-key"
157
+ ANTHROPIC_API_KEY="your-anthropic-api-key"
158
+ GOOGLE_API_KEY="your-google-api-key"
100
159
  THE_ARCHITECT_PASS="your-secure-password"
160
+ TELEGRAM_BOT_TOKEN="your-telegram-bot-token"
161
+ OPENROUTER_API_KEY="your-openrouter-api-key"
101
162
  ```
102
163
 
103
164
  **Option 2: Using Shell export**
104
165
 
105
166
  ```bash
167
+ export OPENAI_API_KEY="your-openai-api-key"
168
+ export ANTHROPIC_API_KEY="your-anthropic-api-key"
169
+ export GOOGLE_API_KEY="your-google-api-key"
170
+ export OPENROUTER_API_KEY="your-openrouter-api-key"
106
171
  export THE_ARCHITECT_PASS="your-secure-password"
172
+ export TELEGRAM_BOT_TOKEN="your-telegram-bot-token"
107
173
  morpheus start
108
174
  ```
109
175
 
@@ -133,6 +199,18 @@ Send voice messages directly to the Telegram bot. Morpheus will:
133
199
 
134
200
  *Requires a Google Gemini API Key.*
135
201
 
202
+ ### 🤖 Telegram Commands
203
+ The Morpheus Telegram bot supports several commands for interacting with the agent:
204
+
205
+ - `/start` - Show welcome message and available commands
206
+ - `/status` - Check the status of the Morpheus agent
207
+ - `/doctor` - Diagnose environment and configuration issues
208
+ - `/stats` - Show token usage statistics
209
+ - `/help` - Show available commands
210
+ - `/zaion` - Show system configurations
211
+ - `/sati <qnt>` - Show specific memories
212
+ - `/restart` - **New!** Restart the Morpheus agent
213
+
136
214
  ## Development Setup
137
215
 
138
216
  This guide is for developers contributing to the Morpheus codebase.
@@ -333,6 +411,131 @@ npm run test:watch
333
411
  - [ ] **Discord Adapter**: Support for Discord interactions.
334
412
  - [ ] **Plugin System**: Extend functionality via external modules.
335
413
 
414
+ ## 🕵️ Privacy Protection
415
+
416
+ The Web UI includes privacy protection headers to prevent indexing by search engines:
417
+ - HTML meta tags: `<meta name="robots" content="noindex, nofollow">`
418
+ - HTTP header: `X-Robots-Tag: noindex, nofollow`
419
+
420
+ This ensures that your private agent dashboard remains private and is not discoverable by search engines.
421
+
422
+ ## 🐳 Running with Docker
423
+
424
+ Morpheus can be easily deployed using Docker and Docker Compose. The container supports all environment variables for configuration.
425
+ The Docker image is publicly available at [Docker Hub](https://hub.docker.com/r/marcosnunesmbs/morpheus).
426
+
427
+ ### Prerequisites
428
+
429
+ - Docker Engine
430
+ - Docker Compose
431
+
432
+ ### Quick Start
433
+
434
+ 1. Create a `.env` file with your configuration:
435
+
436
+ ```bash
437
+ cp .env.example .env
438
+ # Edit .env with your actual API keys and settings
439
+ ```
440
+
441
+ 2. Build and start the container:
442
+
443
+ ```bash
444
+ docker-compose up -d
445
+ ```
446
+
447
+ 3. Access the Web UI at `http://localhost:3333`
448
+
449
+ ### Docker Compose Example
450
+
451
+ Here's a complete example of how to run Morpheus using Docker Compose:
452
+
453
+ ```yaml
454
+ version: '3.8'
455
+
456
+ services:
457
+ morpheus:
458
+ image: morpheus/morpheus-agent:latest
459
+ container_name: morpheus-agent
460
+ ports:
461
+ - "3333:3333"
462
+ volumes:
463
+ - morpheus_data:/root/.morpheus
464
+ environment:
465
+ # LLM Configuration
466
+ - MORPHEUS_LLM_PROVIDER=openai
467
+ - MORPHEUS_LLM_MODEL=gpt-4o
468
+ - MORPHEUS_LLM_TEMPERATURE=0.7
469
+
470
+ # API Keys
471
+ - OPENAI_API_KEY=your-openai-api-key
472
+ - ANTHROPIC_API_KEY=your-anthropic-api-key
473
+ - GOOGLE_API_KEY=your-google-api-key
474
+ - OPENROUTER_API_KEY=your-openrouter-api-key
475
+
476
+ # Security
477
+ - THE_ARCHITECT_PASS=your-secure-password
478
+
479
+ # Agent Configuration
480
+ - MORPHEUS_AGENT_NAME=morpheus
481
+ - MORPHEUS_AGENT_PERSONALITY=helpful_dev
482
+
483
+ # UI Configuration
484
+ - MORPHEUS_UI_ENABLED=true
485
+ - MORPHEUS_UI_PORT=3333
486
+ restart: unless-stopped
487
+ ```
488
+
489
+ ### Using Docker Directly
490
+
491
+ ```bash
492
+ # Build the image
493
+ docker build -t morpheus .
494
+
495
+ # Run with environment variables
496
+ docker run -d \
497
+ --name morpheus-agent \
498
+ -p 3333:3333 \
499
+ -v morpheus_data:/root/.morpheus \
500
+ -e MORPHEUS_LLM_PROVIDER=openai \
501
+ -e OPENAI_API_KEY=your-api-key-here \
502
+ -e THE_ARCHITECT_PASS=your-password \
503
+ morpheus
504
+ ```
505
+
506
+ ### Environment Variables in Docker
507
+
508
+ All environment variables described above work in Docker. The precedence order remains the same:
509
+ 1. Container environment variables
510
+ 2. Configuration file values
511
+ 3. Default values
512
+
513
+ ### Persistent Data
514
+
515
+ The container stores configuration and data in `/root/.morpheus`. Mount a volume to persist data between container restarts:
516
+
517
+ ```yaml
518
+ volumes:
519
+ - morpheus_data:/root/.morpheus # Recommended for persistence
520
+ ```
521
+
522
+ ### Health Check
523
+
524
+ The container includes a health check that verifies the health endpoint is accessible. The application exposes a public `/health` endpoint that doesn't require authentication:
525
+
526
+ ```bash
527
+ curl http://localhost:3333/health
528
+ ```
529
+
530
+ Response:
531
+ ```json
532
+ {
533
+ "status": "healthy",
534
+ "timestamp": "2026-02-05T21:30:00.000Z",
535
+ "uptime": 123.45
536
+ }
537
+ ```
538
+
336
539
  ## Contributing
337
540
 
338
541
  1. Fork the repository.
@@ -4,9 +4,13 @@ import chalk from 'chalk';
4
4
  import fs from 'fs-extra';
5
5
  import path from 'path';
6
6
  import os from 'os';
7
+ import { spawn } from 'child_process';
7
8
  import { ConfigManager } from '../config/manager.js';
8
9
  import { DisplayManager } from '../runtime/display.js';
9
10
  import { Telephonist } from '../runtime/telephonist.js';
11
+ import { readPid, isProcessRunning, checkStalePid } from '../runtime/lifecycle.js';
12
+ import { SQLiteChatMessageHistory } from '../runtime/memory/sqlite.js';
13
+ import { SatiRepository } from '../runtime/memory/sati/repository.js';
10
14
  export class TelegramAdapter {
11
15
  bot = null;
12
16
  isConnected = false;
@@ -40,13 +44,18 @@ export class TelegramAdapter {
40
44
  return; // Silent fail for security
41
45
  }
42
46
  this.display.log(`@${user}: ${text}`, { source: 'Telegram' });
47
+ // Handle system commands
48
+ if (text.startsWith('/')) {
49
+ await this.handleSystemCommand(ctx, text, user);
50
+ return;
51
+ }
43
52
  try {
44
53
  // Send "typing" status
45
54
  await ctx.sendChatAction('typing');
46
55
  // Process with Agent
47
56
  const response = await this.oracle.chat(text);
48
57
  if (response) {
49
- await ctx.reply(response);
58
+ await ctx.reply(response, { parse_mode: 'Markdown' });
50
59
  this.display.log(`Responded to @${user}`, { source: 'Telegram' });
51
60
  }
52
61
  }
@@ -105,7 +114,7 @@ export class TelegramAdapter {
105
114
  await ctx.reply(`🎤 *Transcription*: _"${text}"_`, { parse_mode: 'Markdown' });
106
115
  await ctx.sendChatAction('typing');
107
116
  // Process with Agent
108
- const response = await this.oracle.chat(text, usage);
117
+ const response = await this.oracle.chat(text, usage, true);
109
118
  // if (listeningMsg) {
110
119
  // try {
111
120
  // await ctx.telegram.deleteMessage(ctx.chat.id, listeningMsg.message_id);
@@ -135,6 +144,10 @@ export class TelegramAdapter {
135
144
  }
136
145
  });
137
146
  this.isConnected = true;
147
+ // Check if there's a restart notification to send
148
+ this.checkAndSendRestartNotification().catch((err) => {
149
+ this.display.log(`Failed to send restart notification: ${err.message}`, { source: 'Telegram', level: 'error' });
150
+ });
138
151
  process.once('SIGINT', () => this.disconnect());
139
152
  process.once('SIGTERM', () => this.disconnect());
140
153
  }
@@ -174,4 +187,290 @@ export class TelegramAdapter {
174
187
  this.bot = null;
175
188
  this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
176
189
  }
190
+ async handleSystemCommand(ctx, text, user) {
191
+ const command = text.split(' ')[0];
192
+ const args = text.split(' ').slice(1);
193
+ switch (command) {
194
+ case '/start':
195
+ await this.handleStartCommand(ctx, user);
196
+ break;
197
+ case '/status':
198
+ await this.handleStatusCommand(ctx, user);
199
+ break;
200
+ case '/doctor':
201
+ await this.handleDoctorCommand(ctx, user);
202
+ break;
203
+ case '/stats':
204
+ await this.handleStatsCommand(ctx, user);
205
+ break;
206
+ case '/help':
207
+ await this.handleHelpCommand(ctx, user);
208
+ break;
209
+ case '/zaion':
210
+ await this.handleZaionCommand(ctx, user);
211
+ break;
212
+ case '/sati':
213
+ await this.handleSatiCommand(ctx, user, args);
214
+ break;
215
+ case '/restart':
216
+ await this.handleRestartCommand(ctx, user);
217
+ break;
218
+ default:
219
+ await this.handleDefaultCommand(ctx, user, command);
220
+ }
221
+ }
222
+ async handleStartCommand(ctx, user) {
223
+ const welcomeMessage = `
224
+ Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
225
+
226
+ I am your local AI operator/agent. Here are the commands you can use:
227
+
228
+ /start - Show this welcome message and available commands
229
+ /status - Check the status of the Morpheus agent
230
+ /doctor - Diagnose environment and configuration issues
231
+ /stats - Show token usage statistics
232
+ /help - Show available commands
233
+ /zaion - Show system configurations
234
+ /sati <qnt> - Show specific memories
235
+ /restart - Restart the Morpheus agent
236
+
237
+ How can I assist you today?`;
238
+ await ctx.reply(welcomeMessage);
239
+ }
240
+ async handleStatusCommand(ctx, user) {
241
+ try {
242
+ await checkStalePid();
243
+ const pid = await readPid();
244
+ if (pid && isProcessRunning(pid)) {
245
+ await ctx.reply(`Morpheus is running (PID: ${pid})`);
246
+ }
247
+ else {
248
+ await ctx.reply('Morpheus is stopped.');
249
+ }
250
+ }
251
+ catch (error) {
252
+ await ctx.reply(`Failed to check status: ${error.message}`);
253
+ }
254
+ }
255
+ async handleDoctorCommand(ctx, user) {
256
+ // Implementação simplificada do diagnóstico
257
+ const config = this.config.get();
258
+ let response = '*Morpheus Doctor*\n\n';
259
+ // Verificar versão do Node.js
260
+ const nodeVersion = process.version;
261
+ const majorVersion = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
262
+ if (majorVersion >= 18) {
263
+ response += '✅ Node.js Version: ' + nodeVersion + ' (Satisfied)\n';
264
+ }
265
+ else {
266
+ response += '❌ Node.js Version: ' + nodeVersion + ' (Required: >=18)\n';
267
+ }
268
+ // Verificar configuração
269
+ if (config) {
270
+ response += '✅ Configuration: Valid\n';
271
+ // Verificar se há chave de API disponível para o provedor ativo
272
+ const llmProvider = config.llm?.provider;
273
+ if (llmProvider && llmProvider !== 'ollama') {
274
+ const hasLlmApiKey = config.llm?.api_key ||
275
+ (llmProvider === 'openai' && process.env.OPENAI_API_KEY) ||
276
+ (llmProvider === 'anthropic' && process.env.ANTHROPIC_API_KEY) ||
277
+ (llmProvider === 'gemini' && process.env.GOOGLE_API_KEY) ||
278
+ (llmProvider === 'openrouter' && process.env.OPENROUTER_API_KEY);
279
+ if (hasLlmApiKey) {
280
+ response += `✅ LLM API key available for ${llmProvider}\n`;
281
+ }
282
+ else {
283
+ response += `❌ LLM API key missing for ${llmProvider}. Either set in config or define environment variable.\n`;
284
+ }
285
+ }
286
+ // Verificar token do Telegram se ativado
287
+ if (config.channels?.telegram?.enabled) {
288
+ const hasTelegramToken = config.channels.telegram?.token || process.env.TELEGRAM_BOT_TOKEN;
289
+ if (hasTelegramToken) {
290
+ response += '✅ Telegram bot token available\n';
291
+ }
292
+ else {
293
+ response += '❌ Telegram bot token missing. Either set in config or define TELEGRAM_BOT_TOKEN environment variable.\n';
294
+ }
295
+ }
296
+ }
297
+ else {
298
+ response += '⚠️ Configuration: Missing\n';
299
+ }
300
+ await ctx.reply(response, { parse_mode: 'Markdown' });
301
+ }
302
+ async handleStatsCommand(ctx, user) {
303
+ try {
304
+ // Criar instância temporária do histórico para obter estatísticas
305
+ const history = new SQLiteChatMessageHistory({
306
+ sessionId: "default",
307
+ databasePath: undefined, // Usará o caminho padrão
308
+ limit: 100, // Limite arbitrário para esta operação
309
+ });
310
+ const stats = await history.getGlobalUsageStats();
311
+ const groupedStats = await history.getUsageStatsByProviderAndModel();
312
+ let response = '*Token Usage Statistics*\n\n';
313
+ response += `Total Input Tokens: ${stats.totalInputTokens}\n`;
314
+ response += `Total Output Tokens: ${stats.totalOutputTokens}\n`;
315
+ response += `Total Tokens: ${stats.totalInputTokens + stats.totalOutputTokens}\n\n`;
316
+ if (groupedStats.length > 0) {
317
+ response += '*Breakdown by Provider and Model:*\n';
318
+ for (const stat of groupedStats) {
319
+ response += `- ${stat.provider}/${stat.model}: ${stat.totalTokens} tokens (${stat.messageCount} messages)\n`;
320
+ }
321
+ }
322
+ else {
323
+ response += 'No detailed usage statistics available.';
324
+ }
325
+ await ctx.reply(response, { parse_mode: 'Markdown' });
326
+ // Fechar conexão com o banco de dados
327
+ history.close();
328
+ }
329
+ catch (error) {
330
+ await ctx.reply(`Failed to retrieve statistics: ${error.message}`);
331
+ }
332
+ }
333
+ async handleDefaultCommand(ctx, user, command) {
334
+ const prompt = `O usuário envio o comando: ${command},
335
+ Não entendemos o comando
336
+ temos os seguintes comandos disponíveis: /start, /status, /doctor, /stats, /help, /zaion, /sati <qnt>, /restart
337
+ Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
338
+ Só faça isso agora.`;
339
+ let response = await this.oracle.chat(prompt);
340
+ if (response) {
341
+ await ctx.reply(response, { parse_mode: 'Markdown' });
342
+ }
343
+ // await ctx.reply(`Command not recognized. Type /help to see available commands.`);
344
+ }
345
+ async handleHelpCommand(ctx, user) {
346
+ const helpMessage = `
347
+ *Available Commands:*
348
+
349
+ /start - Show welcome message and available commands
350
+ /status - Check the status of the Morpheus agent
351
+ /doctor - Diagnose environment and configuration issues
352
+ /stats - Show token usage statistics
353
+ /help - Show this help message
354
+ /zaion - Show system configurations
355
+ /sati <qnt> - Show specific memories
356
+ /restart - Restart the Morpheus agent
357
+
358
+ How can I assist you today?`;
359
+ await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
360
+ }
361
+ async handleZaionCommand(ctx, user) {
362
+ const config = this.config.get();
363
+ let response = '*System Configuration*\n\n';
364
+ response += `*Agent:*\n`;
365
+ response += `- Name: ${config.agent.name}\n`;
366
+ response += `- Personality: ${config.agent.personality}\n\n`;
367
+ response += `*LLM:*\n`;
368
+ response += `- Provider: ${config.llm.provider}\n`;
369
+ response += `- Model: ${config.llm.model}\n`;
370
+ response += `- Temperature: ${config.llm.temperature}\n`;
371
+ response += `- Context Window: ${config.llm.context_window || 100}\n\n`;
372
+ response += `*Channels:*\n`;
373
+ response += `- Telegram Enabled: ${config.channels.telegram.enabled}\n`;
374
+ response += `- Discord Enabled: ${config.channels.discord.enabled}\n\n`;
375
+ response += `*UI:*\n`;
376
+ response += `- Enabled: ${config.ui.enabled}\n`;
377
+ response += `- Port: ${config.ui.port}\n\n`;
378
+ response += `*Audio:*\n`;
379
+ response += `- Enabled: ${config.audio.enabled}\n`;
380
+ response += `- Max Duration: ${config.audio.maxDurationSeconds}s\n`;
381
+ await ctx.reply(response, { parse_mode: 'Markdown' });
382
+ }
383
+ async handleSatiCommand(ctx, user, args) {
384
+ let limit = null;
385
+ if (args.length > 0) {
386
+ limit = parseInt(args[0], 10);
387
+ if (isNaN(limit) || limit <= 0) {
388
+ await ctx.reply('Invalid quantity. Please specify a positive number. Usage: /sati <qnt>');
389
+ return;
390
+ }
391
+ }
392
+ try {
393
+ // Usar o repositório SATI para obter memórias de longo prazo
394
+ const repository = SatiRepository.getInstance();
395
+ const memories = repository.getAllMemories();
396
+ if (memories.length === 0) {
397
+ await ctx.reply(`No memories found.`);
398
+ return;
399
+ }
400
+ // Se nenhum limite for especificado, usar todas as memórias
401
+ let selectedMemories = memories;
402
+ if (limit !== null) {
403
+ selectedMemories = memories.slice(0, Math.min(limit, memories.length));
404
+ }
405
+ let response = `*${selectedMemories.length} SATI Memories${limit !== null ? ` (Showing first ${selectedMemories.length})` : ''}:*\n\n`;
406
+ for (const memory of selectedMemories) {
407
+ // Limitar o tamanho do resumo para evitar mensagens muito longas
408
+ const truncatedSummary = memory.summary.length > 200 ? memory.summary.substring(0, 200) + '...' : memory.summary;
409
+ response += `*${memory.category} (${memory.importance}):* ${truncatedSummary}\n\n`;
410
+ }
411
+ await ctx.reply(response, { parse_mode: 'Markdown' });
412
+ }
413
+ catch (error) {
414
+ await ctx.reply(`Failed to retrieve memories: ${error.message}`);
415
+ }
416
+ }
417
+ async handleRestartCommand(ctx, user) {
418
+ // Store the user ID who requested the restart
419
+ const userId = ctx.from.id;
420
+ // Save the user ID to a temporary file so the restarted process can notify them
421
+ const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
422
+ try {
423
+ await fs.writeJson(restartNotificationFile, { userId: userId, username: user }, { encoding: 'utf8' });
424
+ }
425
+ catch (error) {
426
+ this.display.log(`Failed to save restart notification info: ${error.message}`, { source: 'Telegram', level: 'error' });
427
+ }
428
+ // Respond to the user first
429
+ await ctx.reply('🔄 Restart initiated. The Morpheus agent will restart shortly.');
430
+ // Schedule the restart after a short delay to ensure the response is sent
431
+ setTimeout(() => {
432
+ // Stop the bot to prevent processing more messages
433
+ if (this.bot) {
434
+ try {
435
+ this.bot.stop();
436
+ }
437
+ catch (e) {
438
+ // Ignore stop errors
439
+ }
440
+ }
441
+ // Execute the restart command using the CLI
442
+ const restartProcess = spawn(process.execPath, [process.argv[1], 'restart'], {
443
+ detached: true,
444
+ stdio: 'ignore'
445
+ });
446
+ restartProcess.unref();
447
+ // Exit the current process
448
+ process.exit(0);
449
+ }, 500); // Shorter delay to minimize chance of processing more messages
450
+ }
451
+ async checkAndSendRestartNotification() {
452
+ const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
453
+ try {
454
+ // Check if the notification file exists
455
+ if (await fs.pathExists(restartNotificationFile)) {
456
+ const notificationData = await fs.readJson(restartNotificationFile);
457
+ // Send a message to the user who requested the restart
458
+ if (this.bot && notificationData.userId) {
459
+ try {
460
+ await this.bot.telegram.sendMessage(notificationData.userId, '✅ Morpheus agent has been successfully restarted!');
461
+ // Optionally, also send to the display
462
+ this.display.log(`Restart notification sent to user ${notificationData.username} (ID: ${notificationData.userId})`, { source: 'Telegram', level: 'info' });
463
+ }
464
+ catch (error) {
465
+ this.display.log(`Failed to send restart notification to user ${notificationData.username}: ${error.message}`, { source: 'Telegram', level: 'error' });
466
+ }
467
+ }
468
+ // Remove the notification file after sending the message
469
+ await fs.remove(restartNotificationFile);
470
+ }
471
+ }
472
+ catch (error) {
473
+ this.display.log(`Error checking restart notification: ${error.message}`, { source: 'Telegram', level: 'error' });
474
+ }
475
+ }
177
476
  }
@@ -47,6 +47,68 @@ export const doctorCommand = new Command('doctor')
47
47
  else if (deprecatedLimit !== undefined && contextWindow !== undefined) {
48
48
  console.log(chalk.yellow('⚠') + ' Found both \'memory.limit\' and \'llm.context_window\'. Remove \'memory.limit\' from config.');
49
49
  }
50
+ // Check API keys availability for active providers
51
+ const llmProvider = config.llm?.provider;
52
+ const santiProvider = config.santi?.provider;
53
+ // Check LLM provider API key
54
+ if (llmProvider && llmProvider !== 'ollama') {
55
+ const hasLlmApiKey = config.llm?.api_key ||
56
+ (llmProvider === 'openai' && process.env.OPENAI_API_KEY) ||
57
+ (llmProvider === 'anthropic' && process.env.ANTHROPIC_API_KEY) ||
58
+ (llmProvider === 'gemini' && process.env.GOOGLE_API_KEY) ||
59
+ (llmProvider === 'openrouter' && process.env.OPENROUTER_API_KEY);
60
+ if (hasLlmApiKey) {
61
+ console.log(chalk.green('✓') + ` LLM API key available for ${llmProvider}`);
62
+ }
63
+ else {
64
+ console.log(chalk.red('✗') + ` LLM API key missing for ${llmProvider}. Either set in config or define environment variable.`);
65
+ allPassed = false;
66
+ }
67
+ }
68
+ // Check Santi provider API key
69
+ if (santiProvider && santiProvider !== 'ollama') {
70
+ const hasSantiApiKey = config.santi?.api_key ||
71
+ (santiProvider === 'openai' && process.env.OPENAI_API_KEY) ||
72
+ (santiProvider === 'anthropic' && process.env.ANTHROPIC_API_KEY) ||
73
+ (santiProvider === 'gemini' && process.env.GOOGLE_API_KEY) ||
74
+ (santiProvider === 'openrouter' && process.env.OPENROUTER_API_KEY);
75
+ if (hasSantiApiKey) {
76
+ console.log(chalk.green('✓') + ` Santi API key available for ${santiProvider}`);
77
+ }
78
+ else {
79
+ console.log(chalk.red('✗') + ` Santi API key missing for ${santiProvider}. Either set in config or define environment variable.`);
80
+ allPassed = false;
81
+ }
82
+ }
83
+ // Check audio API key if enabled
84
+ if (config.audio?.enabled && config.llm?.provider !== 'gemini') {
85
+ const hasAudioApiKey = config.audio?.apiKey || process.env.GOOGLE_API_KEY;
86
+ if (hasAudioApiKey) {
87
+ console.log(chalk.green('✓') + ' Audio API key available for transcription');
88
+ }
89
+ else {
90
+ console.log(chalk.red('✗') + ' Audio API key missing. Either set in config or define GOOGLE_API_KEY environment variable.');
91
+ allPassed = false;
92
+ }
93
+ }
94
+ // Check Telegram token if enabled
95
+ if (config.channels?.telegram?.enabled) {
96
+ const hasTelegramToken = config.channels.telegram?.token || process.env.TELEGRAM_BOT_TOKEN;
97
+ if (hasTelegramToken) {
98
+ console.log(chalk.green('✓') + ' Telegram bot token available');
99
+ }
100
+ else {
101
+ console.log(chalk.red('✗') + ' Telegram bot token missing. Either set in config or define TELEGRAM_BOT_TOKEN environment variable.');
102
+ allPassed = false;
103
+ }
104
+ }
105
+ // Check if default password is being used for dashboard
106
+ if (!process.env.THE_ARCHITECT_PASS) {
107
+ console.log(chalk.yellow('⚠') + ' Using default password for dashboard (iamthearchitect). For security, set THE_ARCHITECT_PASS environment variable.');
108
+ }
109
+ else {
110
+ console.log(chalk.green('✓') + ' Custom dashboard password set');
111
+ }
50
112
  }
51
113
  else {
52
114
  console.log(chalk.yellow('!') + ' Configuration: Missing (will be created on start)');