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 +203 -0
- package/dist/channels/telegram.js +301 -2
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/init.js +66 -11
- package/dist/cli/commands/restart.js +167 -0
- package/dist/cli/index.js +2 -0
- package/dist/config/manager.js +84 -5
- package/dist/config/mcp-manager.js +140 -0
- package/dist/config/precedence.js +138 -0
- package/dist/config/schemas.js +3 -1
- package/dist/http/__tests__/status_api.test.js +55 -0
- package/dist/http/__tests__/status_with_server_api.test.js +60 -0
- package/dist/http/api.js +85 -0
- package/dist/http/middleware/auth.js +7 -5
- package/dist/http/server.js +21 -0
- package/dist/runtime/__tests__/manual_start_verify.js +1 -0
- package/dist/runtime/oracle.js +3 -3
- package/dist/runtime/providers/factory.js +14 -4
- package/dist/runtime/tools/config-tools.js +25 -31
- package/dist/types/config.js +1 -0
- package/dist/ui/assets/index-BiXkm8Yr.css +1 -0
- package/dist/ui/assets/index-BrbyUtJ5.js +96 -0
- package/dist/ui/index.html +16 -14
- package/package.json +1 -1
- package/dist/ui/assets/index-3USYAgWN.css +0 -1
- package/dist/ui/assets/index-Pd9zlYEP.js +0 -58
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)');
|