morpheus-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +129 -0
- package/bin/morpheus.js +19 -0
- package/dist/channels/__tests__/telegram.test.js +63 -0
- package/dist/channels/telegram.js +89 -0
- package/dist/cli/commands/config.js +81 -0
- package/dist/cli/commands/doctor.js +69 -0
- package/dist/cli/commands/init.js +108 -0
- package/dist/cli/commands/start.js +145 -0
- package/dist/cli/commands/status.js +21 -0
- package/dist/cli/commands/stop.js +28 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/utils/render.js +11 -0
- package/dist/config/__tests__/manager.test.js +11 -0
- package/dist/config/manager.js +55 -0
- package/dist/config/paths.js +15 -0
- package/dist/config/schemas.js +35 -0
- package/dist/config/utils.js +21 -0
- package/dist/http/__tests__/config_api.test.js +79 -0
- package/dist/http/api.js +134 -0
- package/dist/http/server.js +52 -0
- package/dist/runtime/__tests__/agent.test.js +91 -0
- package/dist/runtime/__tests__/agent_persistence.test.js +148 -0
- package/dist/runtime/__tests__/display.test.js +135 -0
- package/dist/runtime/__tests__/manual_start_verify.js +42 -0
- package/dist/runtime/__tests__/manual_us1.js +33 -0
- package/dist/runtime/agent.js +84 -0
- package/dist/runtime/display.js +146 -0
- package/dist/runtime/errors.js +16 -0
- package/dist/runtime/lifecycle.js +41 -0
- package/dist/runtime/memory/__tests__/sqlite.test.js +179 -0
- package/dist/runtime/memory/sqlite.js +192 -0
- package/dist/runtime/providers/factory.js +62 -0
- package/dist/runtime/scaffold.js +31 -0
- package/dist/runtime/types.js +1 -0
- package/dist/types/config.js +24 -0
- package/dist/types/display.js +1 -0
- package/dist/ui/assets/index-nNle8n-Z.css +1 -0
- package/dist/ui/assets/index-ySbKLOXZ.js +50 -0
- package/dist/ui/index.html +14 -0
- package/dist/ui/vite.svg +31 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="./assets/logo.png" alt="Morpheus Logo" width="220" />
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
# Morpheus
|
|
6
|
+
|
|
7
|
+
> **Morpheus is a local-first AI operator that bridges developers and machines.**
|
|
8
|
+
|
|
9
|
+
Morpheus is a local AI agent for developers, running as a CLI daemon that connects to **LLMs**, **local tools**, and **MCPs**, enabling interaction via **Terminal, Telegram, and Discord**. Inspired by the character Morpheus from *The Matrix*, the project acts as an **intelligent orchestrator**, bridging the gap between the developer and complex systems.
|
|
10
|
+
|
|
11
|
+
## Technical Overview
|
|
12
|
+
|
|
13
|
+
Morpheus is built with **Node.js** and **TypeScript**, using **LangChain** as the orchestration engine. It runs as a background daemon process, managing connections to LLM providers (OpenAI, Anthropic, Ollama) and external channels (Telegram, Discord).
|
|
14
|
+
|
|
15
|
+
### Core Components
|
|
16
|
+
|
|
17
|
+
- **Runtime (`src/runtime/`)**: The heart of the application. Manages the agent lifecycle, provider instantiation, and command execution.
|
|
18
|
+
- **CLI (`src/cli/`)**: Built with `commander`, handles user interaction, configuration, and daemon control (`start`, `stop`, `status`).
|
|
19
|
+
- **Configuration (`src/config/`)**: Singleton-based configuration manager using `zod` for validation and `js-yaml` for persistence (`~/.morpheus/config.yaml`).
|
|
20
|
+
- **Channels (`src/channels/`)**: Adapters for external communication. Currently supports Telegram (`telegraf`) with strict user whitelisting.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- **Node.js**: >= 18.x
|
|
25
|
+
- **npm**: >= 9.x
|
|
26
|
+
- **TypeScript**: >= 5.x
|
|
27
|
+
|
|
28
|
+
## Getting Started (Development)
|
|
29
|
+
|
|
30
|
+
This guide is for developers contributing to the Morpheus codebase.
|
|
31
|
+
|
|
32
|
+
### 1. Clone & Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/your-org/morpheus.git
|
|
36
|
+
cd morpheus
|
|
37
|
+
npm install
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### 2. Build
|
|
41
|
+
|
|
42
|
+
Compile TypeScript source to `dist/`.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Run the CLI
|
|
49
|
+
|
|
50
|
+
You can run the CLI directly from the source using `npm start`.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Initialize configuration (creates ~/.morpheus)
|
|
54
|
+
npm start -- init
|
|
55
|
+
|
|
56
|
+
# Start the daemon
|
|
57
|
+
npm start -- start
|
|
58
|
+
|
|
59
|
+
# Check status
|
|
60
|
+
npm start -- status
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 4. Configuration
|
|
64
|
+
|
|
65
|
+
The configuration file is located at `~/.morpheus/config.yaml`. You can edit it manually or use the CLI.
|
|
66
|
+
|
|
67
|
+
```yaml
|
|
68
|
+
agent:
|
|
69
|
+
name: "Morpheus"
|
|
70
|
+
personality: "stoic, wise, and helpful"
|
|
71
|
+
llm:
|
|
72
|
+
provider: "openai" # options: openai, anthropic, ollama
|
|
73
|
+
model: "gpt-4-turbo"
|
|
74
|
+
temperature: 0.7
|
|
75
|
+
api_key: "sk-..."
|
|
76
|
+
channels:
|
|
77
|
+
telegram:
|
|
78
|
+
enabled: true
|
|
79
|
+
token: "YOUR_TELEGRAM_BOT_TOKEN"
|
|
80
|
+
allowedUsers: ["123456789"] # Your Telegram User ID
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Testing
|
|
84
|
+
|
|
85
|
+
We use **Vitest** for testing.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Run unit tests
|
|
89
|
+
npm test
|
|
90
|
+
|
|
91
|
+
# Run tests in watch mode
|
|
92
|
+
npm run test:watch
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Project Structure
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
.
|
|
99
|
+
├── assets/ # Static assets
|
|
100
|
+
├── bin/ # CLI entry point (morpheus.js)
|
|
101
|
+
├── specs/ # Technical specifications & documentation
|
|
102
|
+
├── src/
|
|
103
|
+
│ ├── channels/ # Communication adapters (Telegram, etc.)
|
|
104
|
+
│ ├── cli/ # CLI commands and logic
|
|
105
|
+
│ ├── config/ # Configuration management
|
|
106
|
+
│ ├── runtime/ # Core agent logic, lifecycle, and providers
|
|
107
|
+
│ ├── types/ # Shared TypeScript definitions
|
|
108
|
+
│ └── index.ts
|
|
109
|
+
└── package.json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Roadmap
|
|
113
|
+
|
|
114
|
+
- [ ] **MCP Support**: Full integration with Model Context Protocol.
|
|
115
|
+
- [ ] **Discord Adapter**: Support for Discord interactions.
|
|
116
|
+
- [ ] **Web Dashboard**: Local UI for management and logs.
|
|
117
|
+
- [ ] **Plugin System**: Extend functionality via external modules.
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
|
|
121
|
+
1. Fork the repository.
|
|
122
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`).
|
|
123
|
+
3. Commit your changes (`git commit -m 'feat: Add amazing feature'`).
|
|
124
|
+
4. Push to the branch (`git push origin feature/amazing-feature`).
|
|
125
|
+
5. Open a Pull Request.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT
|
package/bin/morpheus.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Suppress experimental warnings for JSON modules
|
|
4
|
+
const originalEmit = process.emit;
|
|
5
|
+
process.emit = function (name, data, ...args) {
|
|
6
|
+
if (
|
|
7
|
+
name === 'warning' &&
|
|
8
|
+
typeof data === 'object' &&
|
|
9
|
+
data.name === 'ExperimentalWarning' &&
|
|
10
|
+
data.message.includes('Importing JSON modules')
|
|
11
|
+
) {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
return originalEmit.apply(process, [name, data, ...args]);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Use dynamic import to ensure the warning suppression is active before the module graph loads
|
|
18
|
+
const { cli } = await import('../dist/cli/index.js');
|
|
19
|
+
cli();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { TelegramAdapter } from '../telegram.js';
|
|
3
|
+
// Mock dependencies
|
|
4
|
+
vi.mock('telegraf', () => {
|
|
5
|
+
return {
|
|
6
|
+
Telegraf: class {
|
|
7
|
+
telegram;
|
|
8
|
+
on;
|
|
9
|
+
launch;
|
|
10
|
+
stop;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.telegram = {
|
|
13
|
+
getMe: vi.fn().mockResolvedValue({ username: 'test_bot' }),
|
|
14
|
+
};
|
|
15
|
+
this.on = vi.fn();
|
|
16
|
+
this.launch = vi.fn().mockResolvedValue(undefined);
|
|
17
|
+
this.stop = vi.fn();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
vi.mock('../../runtime/display.js', () => ({
|
|
23
|
+
DisplayManager: {
|
|
24
|
+
getInstance: () => ({
|
|
25
|
+
log: vi.fn(),
|
|
26
|
+
}),
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
describe('TelegramAdapter', () => {
|
|
30
|
+
let adapter;
|
|
31
|
+
let mockAgent;
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
mockAgent = {
|
|
34
|
+
chat: vi.fn(),
|
|
35
|
+
};
|
|
36
|
+
adapter = new TelegramAdapter(mockAgent);
|
|
37
|
+
});
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
vi.clearAllMocks();
|
|
40
|
+
});
|
|
41
|
+
describe('Authorization', () => {
|
|
42
|
+
it('should be able to authorize trusted users', () => {
|
|
43
|
+
// Accessing private method for unit testing
|
|
44
|
+
const isAuthorized = adapter.isAuthorized.bind(adapter);
|
|
45
|
+
const allowed = ['123', '456'];
|
|
46
|
+
expect(isAuthorized('123', allowed)).toBe(true);
|
|
47
|
+
expect(isAuthorized('456', allowed)).toBe(true);
|
|
48
|
+
expect(isAuthorized('789', allowed)).toBe(false);
|
|
49
|
+
});
|
|
50
|
+
it('should handle numeric inputs converted to strings', () => {
|
|
51
|
+
const isAuthorized = adapter.isAuthorized.bind(adapter);
|
|
52
|
+
const allowed = ['123'];
|
|
53
|
+
// Telegram ID comes as string from our logic, but let's verify string comparison
|
|
54
|
+
expect(isAuthorized('123', allowed)).toBe(true);
|
|
55
|
+
expect(isAuthorized('1234', allowed)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
describe('Connection', () => {
|
|
59
|
+
it('should connect successfully with token', async () => {
|
|
60
|
+
await expect(adapter.connect('fake_token', ['123'])).resolves.not.toThrow();
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Telegraf } from 'telegraf';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { DisplayManager } from '../runtime/display.js';
|
|
4
|
+
export class TelegramAdapter {
|
|
5
|
+
bot = null;
|
|
6
|
+
isConnected = false;
|
|
7
|
+
display = DisplayManager.getInstance();
|
|
8
|
+
agent;
|
|
9
|
+
constructor(agent) {
|
|
10
|
+
this.agent = agent;
|
|
11
|
+
}
|
|
12
|
+
async connect(token, allowedUsers) {
|
|
13
|
+
if (this.isConnected) {
|
|
14
|
+
this.display.log('Telegram adapter already connected.', { source: 'Telegram', level: 'warning' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
this.display.log('Connecting to Telegram...', { source: 'Telegram' });
|
|
19
|
+
this.bot = new Telegraf(token);
|
|
20
|
+
// Verify token/connection
|
|
21
|
+
const me = await this.bot.telegram.getMe();
|
|
22
|
+
this.display.log(`✓ Telegram Connected: @${me.username}`, { source: 'Telegram', level: 'success' });
|
|
23
|
+
this.display.log(`Allowed Users: ${allowedUsers.join(', ')}`, { source: 'Telegram', level: 'info' });
|
|
24
|
+
// Listen for messages
|
|
25
|
+
this.bot.on('text', async (ctx) => {
|
|
26
|
+
const user = ctx.from.username || ctx.from.first_name;
|
|
27
|
+
const userId = ctx.from.id.toString();
|
|
28
|
+
const text = ctx.message.text;
|
|
29
|
+
// AUTH GUARD
|
|
30
|
+
if (!this.isAuthorized(userId, allowedUsers)) {
|
|
31
|
+
this.display.log(`Unauthorized access attempt by @${user} (ID: ${userId})`, { source: 'Telegram', level: 'warning' });
|
|
32
|
+
return; // Silent fail for security
|
|
33
|
+
}
|
|
34
|
+
this.display.log(`@${user}: ${text}`, { source: 'Telegram' });
|
|
35
|
+
try {
|
|
36
|
+
// Send "typing" status
|
|
37
|
+
await ctx.sendChatAction('typing');
|
|
38
|
+
// Process with Agent
|
|
39
|
+
const response = await this.agent.chat(text);
|
|
40
|
+
if (response) {
|
|
41
|
+
await ctx.reply(response);
|
|
42
|
+
this.display.log(`Responded to @${user}`, { source: 'Telegram' });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
this.display.log(`Error processing message for @${user}: ${error.message}`, { source: 'Telegram', level: 'error' });
|
|
47
|
+
try {
|
|
48
|
+
await ctx.reply("Sorry, I encountered an error while processing your request.");
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
// Ignore reply error
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.bot.launch().catch((err) => {
|
|
56
|
+
if (this.isConnected) {
|
|
57
|
+
this.display.log(`Telegram bot error: ${err}`, { source: 'Telegram', level: 'error' });
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
this.isConnected = true;
|
|
61
|
+
process.once('SIGINT', () => this.disconnect());
|
|
62
|
+
process.once('SIGTERM', () => this.disconnect());
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
this.display.log(`Failed to connect to Telegram: ${error.message}`, { source: 'Telegram', level: 'error' });
|
|
66
|
+
this.isConnected = false;
|
|
67
|
+
this.bot = null;
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
isAuthorized(userId, allowedUsers) {
|
|
72
|
+
return allowedUsers.includes(userId);
|
|
73
|
+
}
|
|
74
|
+
async disconnect() {
|
|
75
|
+
if (!this.isConnected || !this.bot) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this.display.log('Disconnecting Telegram...', { source: 'Telegram', level: 'warning' });
|
|
79
|
+
try {
|
|
80
|
+
this.bot.stop();
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
// Ignore stop errors
|
|
84
|
+
}
|
|
85
|
+
this.isConnected = false;
|
|
86
|
+
this.bot = null;
|
|
87
|
+
this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import fs from 'fs-extra';
|
|
5
|
+
import { PATHS } from '../../config/paths.js';
|
|
6
|
+
import { scaffold } from '../../runtime/scaffold.js';
|
|
7
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
8
|
+
function parseValue(value) {
|
|
9
|
+
if (value.toLowerCase() === 'true')
|
|
10
|
+
return true;
|
|
11
|
+
if (value.toLowerCase() === 'false')
|
|
12
|
+
return false;
|
|
13
|
+
if (!Number.isNaN(Number(value)) && value.trim() !== '') {
|
|
14
|
+
return Number(value);
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
export const configCommand = new Command('config')
|
|
19
|
+
.description('View or edit configuration')
|
|
20
|
+
.option('-e, --edit', 'Open config file in default editor')
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
try {
|
|
23
|
+
await scaffold(); // Ensure config exits
|
|
24
|
+
if (options.edit) {
|
|
25
|
+
console.log(chalk.cyan(`Opening config file: ${PATHS.config}`));
|
|
26
|
+
await open(PATHS.config);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
console.log(chalk.bold('Configuration File:'), chalk.cyan(PATHS.config));
|
|
30
|
+
console.log(chalk.gray('---'));
|
|
31
|
+
if (await fs.pathExists(PATHS.config)) {
|
|
32
|
+
const content = await fs.readFile(PATHS.config, 'utf8');
|
|
33
|
+
console.log(content);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.log(chalk.yellow('Config file not found (scaffold should have created it).'));
|
|
37
|
+
}
|
|
38
|
+
console.log(chalk.gray('---'));
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
console.error(chalk.red('Failed to handle config command:'), error.message);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
configCommand.command('set')
|
|
47
|
+
.description('Set a configuration value')
|
|
48
|
+
.argument('<key>', 'Configuration key (e.g. channels.telegram.enabled)')
|
|
49
|
+
.argument('<value>', 'Value to set')
|
|
50
|
+
.action(async (key, value) => {
|
|
51
|
+
try {
|
|
52
|
+
await scaffold(); // Ensure config loads
|
|
53
|
+
const manager = ConfigManager.getInstance();
|
|
54
|
+
await manager.load();
|
|
55
|
+
const parsedValue = parseValue(value);
|
|
56
|
+
try {
|
|
57
|
+
await manager.set(key, parsedValue);
|
|
58
|
+
console.log(chalk.green(`✓ Set ${key} = ${parsedValue}`));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
// Fallback: If Zod fails, maybe it was a string that looked like a number?
|
|
62
|
+
if (typeof parsedValue === 'number') {
|
|
63
|
+
try {
|
|
64
|
+
await manager.set(key, value); // Try original string
|
|
65
|
+
console.log(chalk.green(`✓ Set ${key} = "${value}" (treated as string)`));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
catch (ignored) { }
|
|
69
|
+
}
|
|
70
|
+
throw e;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error(chalk.red('Failed to set config:'), error.message || error);
|
|
75
|
+
if (error.issues) {
|
|
76
|
+
// Zod error
|
|
77
|
+
console.error(chalk.red('Validation issues:'), JSON.stringify(error.issues, null, 2));
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { PATHS } from '../../config/paths.js';
|
|
6
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
7
|
+
export const doctorCommand = new Command('doctor')
|
|
8
|
+
.description('Diagnose environment and configuration issues')
|
|
9
|
+
.action(async () => {
|
|
10
|
+
console.log(chalk.bold('Morpheus Doctor'));
|
|
11
|
+
console.log(chalk.gray('================'));
|
|
12
|
+
let allPassed = true;
|
|
13
|
+
// 1. Check Node.js Version
|
|
14
|
+
const nodeVersion = process.version;
|
|
15
|
+
const majorVersion = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
|
|
16
|
+
if (majorVersion >= 18) {
|
|
17
|
+
console.log(chalk.green('✓') + ` Node.js Version: ${nodeVersion} (Satisfied)`);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.log(chalk.red('✗') + ` Node.js Version: ${nodeVersion} (Required: >=18)`);
|
|
21
|
+
allPassed = false;
|
|
22
|
+
}
|
|
23
|
+
// 2. Check Configuration
|
|
24
|
+
try {
|
|
25
|
+
if (await fs.pathExists(PATHS.config)) {
|
|
26
|
+
await ConfigManager.getInstance().load();
|
|
27
|
+
console.log(chalk.green('✓') + ' Configuration: Valid');
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(chalk.yellow('!') + ' Configuration: Missing (will be created on start)');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.log(chalk.red('✗') + ` Configuration: Invalid (${error.message})`);
|
|
35
|
+
allPassed = false;
|
|
36
|
+
}
|
|
37
|
+
// 3. Check Permissions
|
|
38
|
+
try {
|
|
39
|
+
await fs.ensureDir(PATHS.root);
|
|
40
|
+
const testFile = path.join(PATHS.root, '.perm-test');
|
|
41
|
+
await fs.writeFile(testFile, 'test');
|
|
42
|
+
await fs.remove(testFile);
|
|
43
|
+
console.log(chalk.green('✓') + ` Permissions: Write access to ${PATHS.root}`);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
console.log(chalk.red('✗') + ` Permissions: Cannot write to ${PATHS.root}`);
|
|
47
|
+
allPassed = false;
|
|
48
|
+
}
|
|
49
|
+
// 4. Check Logs Permissions
|
|
50
|
+
try {
|
|
51
|
+
await fs.ensureDir(PATHS.logs);
|
|
52
|
+
const testLogFile = path.join(PATHS.logs, '.perm-test');
|
|
53
|
+
await fs.writeFile(testLogFile, 'test');
|
|
54
|
+
await fs.remove(testLogFile);
|
|
55
|
+
console.log(chalk.green('✓') + ` Logs: Write access to ${PATHS.logs}`);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.log(chalk.red('✗') + ` Logs: Cannot write to ${PATHS.logs}`);
|
|
59
|
+
allPassed = false;
|
|
60
|
+
}
|
|
61
|
+
console.log(chalk.gray('================'));
|
|
62
|
+
if (allPassed) {
|
|
63
|
+
console.log(chalk.green('Diagnostics Passed. You are ready to run Morpheus!'));
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(chalk.red('Issues detected. Please fix them before running Morpheus.'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { input, select, password, confirm, checkbox } from '@inquirer/prompts';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { ConfigManager } from '../../config/manager.js';
|
|
5
|
+
import { renderBanner } from '../utils/render.js';
|
|
6
|
+
import { DisplayManager } from '../../runtime/display.js';
|
|
7
|
+
import { scaffold } from '../../runtime/scaffold.js';
|
|
8
|
+
export const initCommand = new Command('init')
|
|
9
|
+
.description('Initialize Morpheus configuration')
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const display = DisplayManager.getInstance();
|
|
12
|
+
renderBanner();
|
|
13
|
+
// Ensure directory exists
|
|
14
|
+
await scaffold();
|
|
15
|
+
display.log(chalk.blue('Let\'s set up your Morpheus agent!'));
|
|
16
|
+
try {
|
|
17
|
+
const name = await input({
|
|
18
|
+
message: 'Name your agent:',
|
|
19
|
+
default: 'morpheus',
|
|
20
|
+
});
|
|
21
|
+
const personality = await input({
|
|
22
|
+
message: 'Describe its personality:',
|
|
23
|
+
default: 'helpful and concise',
|
|
24
|
+
});
|
|
25
|
+
const provider = await select({
|
|
26
|
+
message: 'Select LLM Provider:',
|
|
27
|
+
choices: [
|
|
28
|
+
{ name: 'OpenAI', value: 'openai' },
|
|
29
|
+
{ name: 'Anthropic', value: 'anthropic' },
|
|
30
|
+
{ name: 'Ollama', value: 'ollama' },
|
|
31
|
+
{ name: 'Google Gemini', value: 'gemini' },
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
let defaultModel = 'gpt-3.5-turbo';
|
|
35
|
+
switch (provider) {
|
|
36
|
+
case 'openai':
|
|
37
|
+
defaultModel = 'gpt-4o';
|
|
38
|
+
break;
|
|
39
|
+
case 'anthropic':
|
|
40
|
+
defaultModel = 'claude-3-5-sonnet-20240620';
|
|
41
|
+
break;
|
|
42
|
+
case 'ollama':
|
|
43
|
+
defaultModel = 'llama3';
|
|
44
|
+
break;
|
|
45
|
+
case 'gemini':
|
|
46
|
+
defaultModel = 'gemini-pro';
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
const model = await input({
|
|
50
|
+
message: 'Enter Model Name:',
|
|
51
|
+
default: defaultModel,
|
|
52
|
+
});
|
|
53
|
+
let apiKey;
|
|
54
|
+
if (provider !== 'ollama') {
|
|
55
|
+
apiKey = await password({
|
|
56
|
+
message: 'Enter API Key (leave empty if using env vars):',
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const configManager = ConfigManager.getInstance();
|
|
60
|
+
// Update config
|
|
61
|
+
await configManager.set('agent.name', name);
|
|
62
|
+
await configManager.set('agent.personality', personality);
|
|
63
|
+
await configManager.set('llm.provider', provider);
|
|
64
|
+
await configManager.set('llm.model', model);
|
|
65
|
+
if (apiKey) {
|
|
66
|
+
await configManager.set('llm.api_key', apiKey);
|
|
67
|
+
}
|
|
68
|
+
// External Channels Configuration
|
|
69
|
+
const configureChannels = await confirm({
|
|
70
|
+
message: 'Do you want to configure external channels?',
|
|
71
|
+
default: false,
|
|
72
|
+
});
|
|
73
|
+
if (configureChannels) {
|
|
74
|
+
const channels = await checkbox({
|
|
75
|
+
message: 'Select channels to enable:',
|
|
76
|
+
choices: [
|
|
77
|
+
{ name: 'Telegram', value: 'telegram' },
|
|
78
|
+
],
|
|
79
|
+
});
|
|
80
|
+
if (channels.includes('telegram')) {
|
|
81
|
+
display.log(chalk.yellow('\n--- Telegram Configuration ---'));
|
|
82
|
+
display.log(chalk.gray('1. Create a bot via @BotFather to get your token.'));
|
|
83
|
+
display.log(chalk.gray('2. Get your User ID via @userinfobot.\n'));
|
|
84
|
+
const token = await password({
|
|
85
|
+
message: 'Enter Telegram Bot Token:',
|
|
86
|
+
validate: (value) => value.length > 0 || 'Token is required.'
|
|
87
|
+
});
|
|
88
|
+
const allowedUsersInput = await input({
|
|
89
|
+
message: 'Enter Allowed User IDs (comma separated):',
|
|
90
|
+
validate: (value) => value.length > 0 || 'At least one user ID is required for security.'
|
|
91
|
+
});
|
|
92
|
+
const allowedUsers = allowedUsersInput.split(',').map(id => id.trim()).filter(id => id.length > 0);
|
|
93
|
+
await configManager.set('channels.telegram.enabled', true);
|
|
94
|
+
await configManager.set('channels.telegram.token', token);
|
|
95
|
+
await configManager.set('channels.telegram.allowedUsers', allowedUsers);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
display.log(chalk.green('\nConfiguration saved successfully!'));
|
|
99
|
+
display.log(chalk.cyan(`Run 'morpheus start' to launch ${name}.`));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
if (error instanceof Error && error.message.includes('force closed')) {
|
|
103
|
+
display.log(chalk.yellow('\nSetup cancelled.'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
display.log(chalk.red('\nFailed to save configuration: ' + error.message));
|
|
107
|
+
}
|
|
108
|
+
});
|