morpheus-cli 0.3.4 → 0.3.7

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/bin/morpheus.js CHANGED
@@ -1,49 +1,49 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'fs';
3
- import path from 'path';
4
-
5
- // Load .env file if it exists (Simple shim to avoid 'dotenv' dependency issues)
6
- const envPath = path.join(process.cwd(), '.env');
7
- if (fs.existsSync(envPath)) {
8
- try {
9
- const envConfig = fs.readFileSync(envPath, 'utf-8');
10
- envConfig.split('\n').forEach(line => {
11
- const trimmed = line.trim();
12
- if (!trimmed || trimmed.startsWith('#')) return;
13
-
14
- const match = trimmed.match(/^([^=]+)=(.*)$/);
15
- if (match) {
16
- const key = match[1].trim();
17
- let value = match[2].trim();
18
- // Remove quotes if present
19
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
20
- value = value.slice(1, -1);
21
- }
22
- // Don't overwrite existing env vars
23
- if (!process.env[key]) {
24
- process.env[key] = value;
25
- }
26
- }
27
- });
28
- } catch (err) {
29
- // Ignore .env errors
30
- }
31
- }
32
-
33
- // Suppress experimental warnings for JSON modules
34
- const originalEmit = process.emit;
35
- process.emit = function (name, data, ...args) {
36
- if (
37
- name === 'warning' &&
38
- typeof data === 'object' &&
39
- data.name === 'ExperimentalWarning' &&
40
- data.message.includes('Importing JSON modules')
41
- ) {
42
- return false;
43
- }
44
- return originalEmit.apply(process, [name, data, ...args]);
45
- };
46
-
47
- // Use dynamic import to ensure the warning suppression is active before the module graph loads
48
- const { cli } = await import('../dist/cli/index.js');
49
- cli();
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+
5
+ // Load .env file if it exists (Simple shim to avoid 'dotenv' dependency issues)
6
+ const envPath = path.join(process.cwd(), '.env');
7
+ if (fs.existsSync(envPath)) {
8
+ try {
9
+ const envConfig = fs.readFileSync(envPath, 'utf-8');
10
+ envConfig.split('\n').forEach(line => {
11
+ const trimmed = line.trim();
12
+ if (!trimmed || trimmed.startsWith('#')) return;
13
+
14
+ const match = trimmed.match(/^([^=]+)=(.*)$/);
15
+ if (match) {
16
+ const key = match[1].trim();
17
+ let value = match[2].trim();
18
+ // Remove quotes if present
19
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
20
+ value = value.slice(1, -1);
21
+ }
22
+ // Don't overwrite existing env vars
23
+ if (!process.env[key]) {
24
+ process.env[key] = value;
25
+ }
26
+ }
27
+ });
28
+ } catch (err) {
29
+ // Ignore .env errors
30
+ }
31
+ }
32
+
33
+ // Suppress experimental warnings for JSON modules
34
+ const originalEmit = process.emit;
35
+ process.emit = function (name, data, ...args) {
36
+ if (
37
+ name === 'warning' &&
38
+ typeof data === 'object' &&
39
+ data.name === 'ExperimentalWarning' &&
40
+ data.message.includes('Importing JSON modules')
41
+ ) {
42
+ return false;
43
+ }
44
+ return originalEmit.apply(process, [name, data, ...args]);
45
+ };
46
+
47
+ // Use dynamic import to ensure the warning suppression is active before the module graph loads
48
+ const { cli } = await import('../dist/cli/index.js');
49
+ cli();
@@ -21,18 +21,19 @@ export class TelegramAdapter {
21
21
  oracle;
22
22
  telephonist = null;
23
23
  telephonistProvider = null;
24
+ telephonistModel = null;
24
25
  history = new SQLiteChatMessageHistory({ sessionId: '' });
25
- HELP_MESSAGE = `/start - Show this welcome message and available commands
26
- /status - Check the status of the Morpheus agent
27
- /doctor - Diagnose environment and configuration issues
28
- /stats - Show token usage statistics
29
- /help - Show available commands
30
- /zaion - Show system configurations
31
- /sati <qnt> - Show specific memories
32
- /newsession - Archive current session and start fresh
33
- /sessions - List all sessions with titles and switch between them
34
- /restart - Restart the Morpheus agent
35
- /mcpreload - Reload MCP servers without restarting
26
+ HELP_MESSAGE = `/start - Show this welcome message and available commands
27
+ /status - Check the status of the Morpheus agent
28
+ /doctor - Diagnose environment and configuration issues
29
+ /stats - Show token usage statistics
30
+ /help - Show available commands
31
+ /zaion - Show system configurations
32
+ /sati <qnt> - Show specific memories
33
+ /newsession - Archive current session and start fresh
34
+ /sessions - List all sessions with titles and switch between them
35
+ /restart - Restart the Morpheus agent
36
+ /mcpreload - Reload MCP servers without restarting
36
37
  /mcp or /mcps - List registered MCP servers`;
37
38
  constructor(oracle) {
38
39
  this.oracle = oracle;
@@ -106,9 +107,10 @@ export class TelegramAdapter {
106
107
  await ctx.reply(`Audio transcription requires an API key for provider '${config.audio.provider}'. Please configure \`audio.apiKey\` or use the same provider as your LLM.`);
107
108
  return;
108
109
  }
109
- if (!this.telephonist || this.telephonistProvider !== config.audio.provider) {
110
+ if (!this.telephonist || this.telephonistProvider !== config.audio.provider || this.telephonistModel !== config.audio.model) {
110
111
  this.telephonist = createTelephonist(config.audio);
111
112
  this.telephonistProvider = config.audio.provider;
113
+ this.telephonistModel = config.audio.model;
112
114
  }
113
115
  const duration = ctx.message.voice.duration;
114
116
  if (duration > config.audio.maxDurationSeconds) {
@@ -474,13 +476,13 @@ export class TelegramAdapter {
474
476
  }
475
477
  }
476
478
  async handleStartCommand(ctx, user) {
477
- const welcomeMessage = `
478
- Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
479
-
480
- I am your local AI operator/agent. Here are the commands you can use:
481
-
482
- ${this.HELP_MESSAGE}
483
-
479
+ const welcomeMessage = `
480
+ Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
481
+
482
+ I am your local AI operator/agent. Here are the commands you can use:
483
+
484
+ ${this.HELP_MESSAGE}
485
+
484
486
  How can I assist you today?`;
485
487
  await ctx.reply(welcomeMessage);
486
488
  }
@@ -578,24 +580,29 @@ How can I assist you today?`;
578
580
  }
579
581
  }
580
582
  async handleDefaultCommand(ctx, user, command) {
581
- const prompt = `O usuário enviou o comando: ${command},
582
- Não entendemos o comando
583
- temos os seguintes comandos disponíveis:
584
- ${this.HELP_MESSAGE}
585
- Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
583
+ const prompt = `O usuário enviou o comando: ${command},
584
+ Não entendemos o comando
585
+ temos os seguintes comandos disponíveis:
586
+ ${this.HELP_MESSAGE}
587
+ Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
586
588
  Só faça isso agora.`;
587
589
  let response = await this.oracle.chat(prompt);
588
590
  if (response) {
589
- await ctx.reply(response, { parse_mode: 'Markdown' });
591
+ try {
592
+ await ctx.reply(response, { parse_mode: 'Markdown' });
593
+ }
594
+ catch {
595
+ await ctx.reply(response);
596
+ }
590
597
  }
591
598
  // await ctx.reply(`Command not recognized. Type /help to see available commands.`);
592
599
  }
593
600
  async handleHelpCommand(ctx, user) {
594
- const helpMessage = `
595
- *Available Commands:*
596
-
597
- ${this.HELP_MESSAGE}
598
-
601
+ const helpMessage = `
602
+ *Available Commands:*
603
+
604
+ ${this.HELP_MESSAGE}
605
+
599
606
  How can I assist you today?`;
600
607
  await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
601
608
  }
@@ -1,9 +1,10 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import fs from 'fs-extra';
4
+ import { confirm } from '@inquirer/prompts';
4
5
  import { scaffold } from '../../runtime/scaffold.js';
5
6
  import { DisplayManager } from '../../runtime/display.js';
6
- import { writePid, readPid, isProcessRunning, clearPid, checkStalePid } from '../../runtime/lifecycle.js';
7
+ import { writePid, readPid, isProcessRunning, clearPid, checkStalePid, killProcess } from '../../runtime/lifecycle.js';
7
8
  import { ConfigManager } from '../../config/manager.js';
8
9
  import { renderBanner } from '../utils/render.js';
9
10
  import { TelegramAdapter } from '../../channels/telegram.js';
@@ -18,6 +19,7 @@ export const startCommand = new Command('start')
18
19
  .option('--ui', 'Enable web UI', true)
19
20
  .option('--no-ui', 'Disable web UI')
20
21
  .option('-p, --port <number>', 'Port for web UI', '3333')
22
+ .option('-y, --yes', 'Automatically answer yes to prompts')
21
23
  .action(async (options) => {
22
24
  const display = DisplayManager.getInstance();
23
25
  try {
@@ -27,8 +29,40 @@ export const startCommand = new Command('start')
27
29
  await checkStalePid();
28
30
  const existingPid = await readPid();
29
31
  if (existingPid !== null && isProcessRunning(existingPid)) {
30
- display.log(chalk.red(`Morpheus is already running (PID: ${existingPid})`));
31
- process.exit(1);
32
+ display.log(chalk.yellow(`Morpheus is already running (PID: ${existingPid})`));
33
+ let shouldKill = options.yes;
34
+ if (!shouldKill) {
35
+ try {
36
+ shouldKill = await confirm({
37
+ message: 'Do you want to stop the running instance and start a new one?',
38
+ default: false,
39
+ });
40
+ }
41
+ catch (error) {
42
+ // User cancelled (Ctrl+C)
43
+ display.log(chalk.gray('\nCancelled'));
44
+ process.exit(1);
45
+ }
46
+ }
47
+ if (shouldKill) {
48
+ display.log(chalk.cyan(`Stopping existing process (PID: ${existingPid})...`));
49
+ const killed = killProcess(existingPid);
50
+ if (killed) {
51
+ display.log(chalk.green('✓ Process stopped successfully'));
52
+ await clearPid();
53
+ // Give a moment for the process to fully terminate
54
+ await new Promise(resolve => setTimeout(resolve, 1000));
55
+ }
56
+ else {
57
+ display.log(chalk.red('Failed to stop the process'));
58
+ await clearPid();
59
+ process.exit(1);
60
+ }
61
+ }
62
+ else {
63
+ display.log(chalk.gray('Use a different port or stop the running instance manually'));
64
+ process.exit(0);
65
+ }
32
66
  }
33
67
  // Always remove any leftover PID file before writing the new one.
34
68
  // Guards against PID reuse on Linux where a stale PID may coincidentally
@@ -70,11 +70,14 @@ export class ConfigManager {
70
70
  };
71
71
  }
72
72
  // Apply precedence to audio config
73
+ const audioProvider = config.audio.provider;
74
+ // AudioProvider uses 'google' but resolveApiKey expects LLMProvider which uses 'gemini'
75
+ const audioProviderForKey = (audioProvider === 'google' ? 'gemini' : audioProvider);
73
76
  const audioConfig = {
74
- provider: config.audio.provider, // Audio provider is fixed as 'google'
77
+ provider: audioProvider,
75
78
  model: resolveString('MORPHEUS_AUDIO_MODEL', config.audio.model, DEFAULT_CONFIG.audio.model),
76
79
  enabled: resolveBoolean('MORPHEUS_AUDIO_ENABLED', config.audio.enabled, DEFAULT_CONFIG.audio.enabled),
77
- apiKey: resolveApiKey('gemini', 'MORPHEUS_AUDIO_API_KEY', config.audio.apiKey),
80
+ apiKey: resolveApiKey(audioProviderForKey, 'MORPHEUS_AUDIO_API_KEY', config.audio.apiKey),
78
81
  maxDurationSeconds: resolveNumeric('MORPHEUS_AUDIO_MAX_DURATION', config.audio.maxDurationSeconds, DEFAULT_CONFIG.audio.maxDurationSeconds),
79
82
  supportedMimeTypes: config.audio.supportedMimeTypes
80
83
  };
@@ -39,3 +39,16 @@ export async function checkStalePid() {
39
39
  }
40
40
  }
41
41
  }
42
+ export function killProcess(pid) {
43
+ try {
44
+ process.kill(pid, 'SIGTERM');
45
+ return true;
46
+ }
47
+ catch (e) {
48
+ if (e.code === 'ESRCH') {
49
+ // Process doesn't exist
50
+ return false;
51
+ }
52
+ throw e;
53
+ }
54
+ }
@@ -12,12 +12,12 @@ const BATCH_SIZE = 50;
12
12
  async function run() {
13
13
  console.log('🔎 Buscando memórias sem embedding vetorial...');
14
14
  while (true) {
15
- const rows = db.prepare(`
16
- SELECT m.rowid, m.summary, m.details
17
- FROM long_term_memory m
18
- LEFT JOIN memory_vec v ON m.rowid = v.rowid
19
- WHERE v.rowid IS NULL
20
- LIMIT ?
15
+ const rows = db.prepare(`
16
+ SELECT m.rowid, m.summary, m.details
17
+ FROM long_term_memory m
18
+ LEFT JOIN memory_vec v ON m.rowid = v.rowid
19
+ WHERE v.rowid IS NULL
20
+ LIMIT ?
21
21
  `).all(BATCH_SIZE);
22
22
  if (rows.length === 0) {
23
23
  console.log('✅ Todas as memórias já possuem embedding.');
@@ -30,13 +30,13 @@ async function run() {
30
30
  const vector = await embeddingService.generate(text);
31
31
  vectors.push({ rowid: row.rowid, vector });
32
32
  }
33
- const insertVec = db.prepare(`
34
- INSERT INTO memory_vec (embedding)
35
- VALUES (?)
33
+ const insertVec = db.prepare(`
34
+ INSERT INTO memory_vec (embedding)
35
+ VALUES (?)
36
36
  `);
37
- const insertMap = db.prepare(`
38
- INSERT INTO memory_embedding_map (memory_id, vec_rowid)
39
- VALUES (?, ?)
37
+ const insertMap = db.prepare(`
38
+ INSERT INTO memory_embedding_map (memory_id, vec_rowid)
39
+ VALUES (?, ?)
40
40
  `);
41
41
  const transaction = db.transaction((items) => {
42
42
  for (const item of items) {
@@ -28,11 +28,11 @@ export class SatiMemoryMiddleware {
28
28
  .map(m => `- [${m.category.toUpperCase()}] ${m.summary}`)
29
29
  .join('\n');
30
30
  display.log(`Retrieved ${result.relevant_memories.length} memories.`, { source: 'Sati' });
31
- return new AIMessage(`
32
- ### LONG-TERM MEMORY (SATI)
33
- The following information was retrieved from previous sessions. Use it if relevant:
34
-
35
- ${memoryContext}
31
+ return new AIMessage(`
32
+ ### LONG-TERM MEMORY (SATI)
33
+ The following information was retrieved from previous sessions. Use it if relevant:
34
+
35
+ ${memoryContext}
36
36
  `);
37
37
  }
38
38
  catch (error) {