morpheus-cli 0.2.5 → 0.2.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.
@@ -4,9 +4,14 @@ 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';
14
+ import { MCPManager } from '../config/mcp-manager.js';
10
15
  export class TelegramAdapter {
11
16
  bot = null;
12
17
  isConnected = false;
@@ -40,13 +45,18 @@ export class TelegramAdapter {
40
45
  return; // Silent fail for security
41
46
  }
42
47
  this.display.log(`@${user}: ${text}`, { source: 'Telegram' });
48
+ // Handle system commands
49
+ if (text.startsWith('/')) {
50
+ await this.handleSystemCommand(ctx, text, user);
51
+ return;
52
+ }
43
53
  try {
44
54
  // Send "typing" status
45
55
  await ctx.sendChatAction('typing');
46
56
  // Process with Agent
47
57
  const response = await this.oracle.chat(text);
48
58
  if (response) {
49
- await ctx.reply(response);
59
+ await ctx.reply(response, { parse_mode: 'Markdown' });
50
60
  this.display.log(`Responded to @${user}`, { source: 'Telegram' });
51
61
  }
52
62
  }
@@ -135,6 +145,10 @@ export class TelegramAdapter {
135
145
  }
136
146
  });
137
147
  this.isConnected = true;
148
+ // Check if there's a restart notification to send
149
+ this.checkAndSendRestartNotification().catch((err) => {
150
+ this.display.log(`Failed to send restart notification: ${err.message}`, { source: 'Telegram', level: 'error' });
151
+ });
138
152
  process.once('SIGINT', () => this.disconnect());
139
153
  process.once('SIGTERM', () => this.disconnect());
140
154
  }
@@ -174,4 +188,327 @@ export class TelegramAdapter {
174
188
  this.bot = null;
175
189
  this.display.log(chalk.gray('Telegram disconnected.'), { source: 'Telegram' });
176
190
  }
191
+ async handleSystemCommand(ctx, text, user) {
192
+ const command = text.split(' ')[0];
193
+ const args = text.split(' ').slice(1);
194
+ switch (command) {
195
+ case '/start':
196
+ await this.handleStartCommand(ctx, user);
197
+ break;
198
+ case '/status':
199
+ await this.handleStatusCommand(ctx, user);
200
+ break;
201
+ case '/doctor':
202
+ await this.handleDoctorCommand(ctx, user);
203
+ break;
204
+ case '/stats':
205
+ await this.handleStatsCommand(ctx, user);
206
+ break;
207
+ case '/help':
208
+ await this.handleHelpCommand(ctx, user);
209
+ break;
210
+ case '/zaion':
211
+ await this.handleZaionCommand(ctx, user);
212
+ break;
213
+ case '/sati':
214
+ await this.handleSatiCommand(ctx, user, args);
215
+ break;
216
+ case '/restart':
217
+ await this.handleRestartCommand(ctx, user);
218
+ break;
219
+ case '/mcp':
220
+ case '/mcps':
221
+ await this.handleMcpListCommand(ctx, user);
222
+ break;
223
+ default:
224
+ await this.handleDefaultCommand(ctx, user, command);
225
+ }
226
+ }
227
+ async handleStartCommand(ctx, user) {
228
+ const welcomeMessage = `
229
+ Hello, @${user}! I am ${this.config.get().agent.name}, ${this.config.get().agent.personality}.
230
+
231
+ I am your local AI operator/agent. Here are the commands you can use:
232
+
233
+ /start - Show this welcome message and available commands
234
+ /status - Check the status of the Morpheus agent
235
+ /doctor - Diagnose environment and configuration issues
236
+ /stats - Show token usage statistics
237
+ /help - Show available commands
238
+ /zaion - Show system configurations
239
+ /sati <qnt> - Show specific memories
240
+ /restart - Restart the Morpheus agent
241
+
242
+ How can I assist you today?`;
243
+ await ctx.reply(welcomeMessage);
244
+ }
245
+ async handleStatusCommand(ctx, user) {
246
+ try {
247
+ await checkStalePid();
248
+ const pid = await readPid();
249
+ if (pid && isProcessRunning(pid)) {
250
+ await ctx.reply(`Morpheus is running (PID: ${pid})`);
251
+ }
252
+ else {
253
+ await ctx.reply('Morpheus is stopped.');
254
+ }
255
+ }
256
+ catch (error) {
257
+ await ctx.reply(`Failed to check status: ${error.message}`);
258
+ }
259
+ }
260
+ async handleDoctorCommand(ctx, user) {
261
+ // Implementação simplificada do diagnóstico
262
+ const config = this.config.get();
263
+ let response = '*Morpheus Doctor*\n\n';
264
+ // Verificar versão do Node.js
265
+ const nodeVersion = process.version;
266
+ const majorVersion = parseInt(nodeVersion.replace('v', '').split('.')[0], 10);
267
+ if (majorVersion >= 18) {
268
+ response += '✅ Node.js Version: ' + nodeVersion + ' (Satisfied)\n';
269
+ }
270
+ else {
271
+ response += '❌ Node.js Version: ' + nodeVersion + ' (Required: >=18)\n';
272
+ }
273
+ // Verificar configuração
274
+ if (config) {
275
+ response += '✅ Configuration: Valid\n';
276
+ // Verificar se há chave de API disponível para o provedor ativo
277
+ const llmProvider = config.llm?.provider;
278
+ if (llmProvider && llmProvider !== 'ollama') {
279
+ const hasLlmApiKey = config.llm?.api_key ||
280
+ (llmProvider === 'openai' && process.env.OPENAI_API_KEY) ||
281
+ (llmProvider === 'anthropic' && process.env.ANTHROPIC_API_KEY) ||
282
+ (llmProvider === 'gemini' && process.env.GOOGLE_API_KEY) ||
283
+ (llmProvider === 'openrouter' && process.env.OPENROUTER_API_KEY);
284
+ if (hasLlmApiKey) {
285
+ response += `✅ LLM API key available for ${llmProvider}\n`;
286
+ }
287
+ else {
288
+ response += `❌ LLM API key missing for ${llmProvider}. Either set in config or define environment variable.\n`;
289
+ }
290
+ }
291
+ // Verificar token do Telegram se ativado
292
+ if (config.channels?.telegram?.enabled) {
293
+ const hasTelegramToken = config.channels.telegram?.token || process.env.TELEGRAM_BOT_TOKEN;
294
+ if (hasTelegramToken) {
295
+ response += '✅ Telegram bot token available\n';
296
+ }
297
+ else {
298
+ response += '❌ Telegram bot token missing. Either set in config or define TELEGRAM_BOT_TOKEN environment variable.\n';
299
+ }
300
+ }
301
+ }
302
+ else {
303
+ response += '⚠️ Configuration: Missing\n';
304
+ }
305
+ await ctx.reply(response, { parse_mode: 'Markdown' });
306
+ }
307
+ async handleStatsCommand(ctx, user) {
308
+ try {
309
+ // Criar instância temporária do histórico para obter estatísticas
310
+ const history = new SQLiteChatMessageHistory({
311
+ sessionId: "default",
312
+ databasePath: undefined, // Usará o caminho padrão
313
+ limit: 100, // Limite arbitrário para esta operação
314
+ });
315
+ const stats = await history.getGlobalUsageStats();
316
+ const groupedStats = await history.getUsageStatsByProviderAndModel();
317
+ let response = '*Token Usage Statistics*\n\n';
318
+ response += `Total Input Tokens: ${stats.totalInputTokens}\n`;
319
+ response += `Total Output Tokens: ${stats.totalOutputTokens}\n`;
320
+ response += `Total Tokens: ${stats.totalInputTokens + stats.totalOutputTokens}\n\n`;
321
+ if (groupedStats.length > 0) {
322
+ response += '*Breakdown by Provider and Model:*\n';
323
+ for (const stat of groupedStats) {
324
+ response += `- ${stat.provider}/${stat.model}: ${stat.totalTokens} tokens (${stat.messageCount} messages)\n`;
325
+ }
326
+ }
327
+ else {
328
+ response += 'No detailed usage statistics available.';
329
+ }
330
+ await ctx.reply(response, { parse_mode: 'Markdown' });
331
+ // Fechar conexão com o banco de dados
332
+ history.close();
333
+ }
334
+ catch (error) {
335
+ await ctx.reply(`Failed to retrieve statistics: ${error.message}`);
336
+ }
337
+ }
338
+ async handleDefaultCommand(ctx, user, command) {
339
+ const prompt = `O usuário envio o comando: ${command},
340
+ Não entendemos o comando
341
+ temos os seguintes comandos disponíveis: /start, /status, /doctor, /stats, /help, /zaion, /sati <qnt>, /restart, /mcp, /mcps
342
+ Identifique se ele talvez tenha errado o comando e pergunte se ele não quis executar outro comando.
343
+ Só faça isso agora.`;
344
+ let response = await this.oracle.chat(prompt);
345
+ if (response) {
346
+ await ctx.reply(response, { parse_mode: 'Markdown' });
347
+ }
348
+ // await ctx.reply(`Command not recognized. Type /help to see available commands.`);
349
+ }
350
+ async handleHelpCommand(ctx, user) {
351
+ const helpMessage = `
352
+ *Available Commands:*
353
+
354
+ /start - Show welcome message and available commands
355
+ /status - Check the status of the Morpheus agent
356
+ /doctor - Diagnose environment and configuration issues
357
+ /stats - Show token usage statistics
358
+ /help - Show this help message
359
+ /zaion - Show system configurations
360
+ /sati <qnt> - Show specific memories
361
+ /restart - Restart the Morpheus agent
362
+ /mcp or /mcps - List registered MCP servers
363
+
364
+ How can I assist you today? `;
365
+ await ctx.reply(helpMessage, { parse_mode: 'Markdown' });
366
+ }
367
+ async handleZaionCommand(ctx, user) {
368
+ const config = this.config.get();
369
+ let response = '*System Configuration*\n\n';
370
+ response += `*Agent:*\n`;
371
+ response += `- Name: ${config.agent.name}\n`;
372
+ response += `- Personality: ${config.agent.personality}\n\n`;
373
+ response += `*LLM:*\n`;
374
+ response += `- Provider: ${config.llm.provider}\n`;
375
+ response += `- Model: ${config.llm.model}\n`;
376
+ response += `- Temperature: ${config.llm.temperature}\n`;
377
+ response += `- Context Window: ${config.llm.context_window || 100}\n\n`;
378
+ response += `*Channels:*\n`;
379
+ response += `- Telegram Enabled: ${config.channels.telegram.enabled}\n`;
380
+ response += `- Discord Enabled: ${config.channels.discord.enabled}\n\n`;
381
+ response += `*UI:*\n`;
382
+ response += `- Enabled: ${config.ui.enabled}\n`;
383
+ response += `- Port: ${config.ui.port}\n\n`;
384
+ response += `*Audio:*\n`;
385
+ response += `- Enabled: ${config.audio.enabled}\n`;
386
+ response += `- Max Duration: ${config.audio.maxDurationSeconds}s\n`;
387
+ await ctx.reply(response, { parse_mode: 'Markdown' });
388
+ }
389
+ async handleSatiCommand(ctx, user, args) {
390
+ let limit = null;
391
+ if (args.length > 0) {
392
+ limit = parseInt(args[0], 10);
393
+ if (isNaN(limit) || limit <= 0) {
394
+ await ctx.reply('Invalid quantity. Please specify a positive number. Usage: /sati <qnt>');
395
+ return;
396
+ }
397
+ }
398
+ try {
399
+ // Usar o repositório SATI para obter memórias de longo prazo
400
+ const repository = SatiRepository.getInstance();
401
+ const memories = repository.getAllMemories();
402
+ if (memories.length === 0) {
403
+ await ctx.reply(`No memories found.`);
404
+ return;
405
+ }
406
+ // Se nenhum limite for especificado, usar todas as memórias
407
+ let selectedMemories = memories;
408
+ if (limit !== null) {
409
+ selectedMemories = memories.slice(0, Math.min(limit, memories.length));
410
+ }
411
+ let response = `*${selectedMemories.length} SATI Memories${limit !== null ? ` (Showing first ${selectedMemories.length})` : ''}:*\n\n`;
412
+ for (const memory of selectedMemories) {
413
+ // Limitar o tamanho do resumo para evitar mensagens muito longas
414
+ const truncatedSummary = memory.summary.length > 200 ? memory.summary.substring(0, 200) + '...' : memory.summary;
415
+ response += `*${memory.category} (${memory.importance}):* ${truncatedSummary}\n\n`;
416
+ }
417
+ await ctx.reply(response, { parse_mode: 'Markdown' });
418
+ }
419
+ catch (error) {
420
+ await ctx.reply(`Failed to retrieve memories: ${error.message}`);
421
+ }
422
+ }
423
+ async handleRestartCommand(ctx, user) {
424
+ // Store the user ID who requested the restart
425
+ const userId = ctx.from.id;
426
+ // Save the user ID to a temporary file so the restarted process can notify them
427
+ const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
428
+ try {
429
+ await fs.writeJson(restartNotificationFile, { userId: userId, username: user }, { encoding: 'utf8' });
430
+ }
431
+ catch (error) {
432
+ this.display.log(`Failed to save restart notification info: ${error.message}`, { source: 'Telegram', level: 'error' });
433
+ }
434
+ // Respond to the user first
435
+ await ctx.reply('🔄 Restart initiated. The Morpheus agent will restart shortly.');
436
+ // Schedule the restart after a short delay to ensure the response is sent
437
+ setTimeout(() => {
438
+ // Stop the bot to prevent processing more messages
439
+ if (this.bot) {
440
+ try {
441
+ this.bot.stop();
442
+ }
443
+ catch (e) {
444
+ // Ignore stop errors
445
+ }
446
+ }
447
+ // Execute the restart command using the CLI
448
+ const restartProcess = spawn(process.execPath, [process.argv[1], 'restart'], {
449
+ detached: true,
450
+ stdio: 'ignore'
451
+ });
452
+ restartProcess.unref();
453
+ // Exit the current process
454
+ process.exit(0);
455
+ }, 500); // Shorter delay to minimize chance of processing more messages
456
+ }
457
+ async checkAndSendRestartNotification() {
458
+ const restartNotificationFile = path.join(os.tmpdir(), 'morpheus_restart_notification.json');
459
+ try {
460
+ // Check if the notification file exists
461
+ if (await fs.pathExists(restartNotificationFile)) {
462
+ const notificationData = await fs.readJson(restartNotificationFile);
463
+ // Send a message to the user who requested the restart
464
+ if (this.bot && notificationData.userId) {
465
+ try {
466
+ await this.bot.telegram.sendMessage(notificationData.userId, '✅ Morpheus agent has been successfully restarted!');
467
+ // Optionally, also send to the display
468
+ this.display.log(`Restart notification sent to user ${notificationData.username} (ID: ${notificationData.userId})`, { source: 'Telegram', level: 'info' });
469
+ }
470
+ catch (error) {
471
+ this.display.log(`Failed to send restart notification to user ${notificationData.username}: ${error.message}`, { source: 'Telegram', level: 'error' });
472
+ }
473
+ }
474
+ // Remove the notification file after sending the message
475
+ await fs.remove(restartNotificationFile);
476
+ }
477
+ }
478
+ catch (error) {
479
+ this.display.log(`Error checking restart notification: ${error.message}`, { source: 'Telegram', level: 'error' });
480
+ }
481
+ }
482
+ async handleMcpListCommand(ctx, user) {
483
+ try {
484
+ const servers = await MCPManager.listServers();
485
+ if (servers.length === 0) {
486
+ await ctx.reply('*No MCP Servers Configured*\n\nThere are currently no MCP servers configured in the system.', { parse_mode: 'Markdown' });
487
+ return;
488
+ }
489
+ let response = `*MCP Servers (${servers.length})*\n\n`;
490
+ servers.forEach((server, index) => {
491
+ const status = server.enabled ? '✅ Enabled' : '❌ Disabled';
492
+ const transport = server.config.transport.toUpperCase();
493
+ response += `*${index + 1}. ${server.name}*\n`;
494
+ response += `Status: ${status}\n`;
495
+ response += `Transport: ${transport}\n`;
496
+ if (server.config.transport === 'stdio') {
497
+ response += `Command: \`${server.config.command}\`\n`;
498
+ if (server.config.args && server.config.args.length > 0) {
499
+ response += `Args: \`${server.config.args.join(' ')}\`\n`;
500
+ }
501
+ }
502
+ else if (server.config.transport === 'http') {
503
+ response += `URL: \`${server.config.url}\`\n`;
504
+ }
505
+ response += '\n';
506
+ });
507
+ await ctx.reply(response, { parse_mode: 'Markdown' });
508
+ }
509
+ catch (error) {
510
+ this.display.log('Error listing MCP servers: ' + (error instanceof Error ? error.message : String(error)), { source: 'Telegram', level: 'error' });
511
+ await ctx.reply('An error occurred while retrieving the list of MCP servers. Please check the logs for more details.', { parse_mode: 'Markdown' });
512
+ }
513
+ }
177
514
  }
@@ -0,0 +1,167 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs-extra';
4
+ import { scaffold } from '../../runtime/scaffold.js';
5
+ import { DisplayManager } from '../../runtime/display.js';
6
+ import { writePid, readPid, isProcessRunning, clearPid, checkStalePid } from '../../runtime/lifecycle.js';
7
+ import { ConfigManager } from '../../config/manager.js';
8
+ import { renderBanner } from '../utils/render.js';
9
+ import { TelegramAdapter } from '../../channels/telegram.js';
10
+ import { PATHS } from '../../config/paths.js';
11
+ import { Oracle } from '../../runtime/oracle.js';
12
+ import { ProviderError } from '../../runtime/errors.js';
13
+ import { HttpServer } from '../../http/server.js';
14
+ import { getVersion } from '../utils/version.js';
15
+ export const restartCommand = new Command('restart')
16
+ .description('Restart the Morpheus agent')
17
+ .option('--ui', 'Enable web UI', true)
18
+ .option('--no-ui', 'Disable web UI')
19
+ .option('-p, --port <number>', 'Port for web UI', '3333')
20
+ .action(async (options) => {
21
+ const display = DisplayManager.getInstance();
22
+ try {
23
+ // First, try to stop the current process
24
+ display.log(chalk.blue('Stopping current Morpheus process...'));
25
+ await checkStalePid();
26
+ const pid = await readPid();
27
+ if (pid) {
28
+ if (isProcessRunning(pid)) {
29
+ process.kill(pid, 'SIGTERM');
30
+ display.log(chalk.green(`Sent stop signal to Morpheus (PID: ${pid}).`));
31
+ // Wait a bit for the process to terminate
32
+ await new Promise(resolve => setTimeout(resolve, 2000));
33
+ }
34
+ else {
35
+ display.log(chalk.yellow('Current process not running, continuing with restart...'));
36
+ await clearPid();
37
+ }
38
+ }
39
+ else {
40
+ display.log(chalk.yellow('No running process found, continuing with restart...'));
41
+ }
42
+ // Now start a new process
43
+ display.log(chalk.blue('Starting new Morpheus process...'));
44
+ renderBanner(getVersion());
45
+ await scaffold(); // Ensure env exists
46
+ // Cleanup stale PID first
47
+ await checkStalePid();
48
+ const existingPid = await readPid();
49
+ if (existingPid !== null && isProcessRunning(existingPid)) {
50
+ display.log(chalk.red(`Morpheus is already running (PID: ${existingPid})`));
51
+ process.exit(1);
52
+ }
53
+ // Check config existence
54
+ if (!await fs.pathExists(PATHS.config)) {
55
+ display.log(chalk.yellow("Configuration not found."));
56
+ display.log(chalk.cyan("Please run 'morpheus init' first to set up your agent."));
57
+ process.exit(1);
58
+ }
59
+ // Write current PID
60
+ await writePid(process.pid);
61
+ const configManager = ConfigManager.getInstance();
62
+ const config = await configManager.load();
63
+ // Initialize persistent logging
64
+ await display.initialize(config.logging);
65
+ display.log(chalk.green(`Morpheus Agent (${config.agent.name}) starting...`));
66
+ display.log(chalk.gray(`PID: ${process.pid}`));
67
+ if (options.ui) {
68
+ display.log(chalk.blue(`Web UI enabled to port ${options.port}`));
69
+ }
70
+ // Initialize Oracle
71
+ const oracle = new Oracle(config);
72
+ try {
73
+ display.startSpinner(`Initializing ${config.llm.provider} oracle...`);
74
+ await oracle.initialize();
75
+ display.stopSpinner();
76
+ display.log(chalk.green('✓ Oracle initialized'), { source: 'Oracle' });
77
+ }
78
+ catch (err) {
79
+ display.stopSpinner();
80
+ if (err instanceof ProviderError) {
81
+ display.log(chalk.red(`\nProvider Error (${err.provider}):`));
82
+ display.log(chalk.white(err.message));
83
+ if (err.suggestion) {
84
+ display.log(chalk.yellow(`Tip: ${err.suggestion}`));
85
+ }
86
+ }
87
+ else {
88
+ display.log(chalk.red('\nOracle initialization failed:'));
89
+ display.log(chalk.white(err.message));
90
+ if (err.message.includes('API Key')) {
91
+ display.log(chalk.yellow('Tip: Check your API key in configuration or environment variables.'));
92
+ }
93
+ }
94
+ await clearPid();
95
+ process.exit(1);
96
+ }
97
+ const adapters = [];
98
+ let httpServer;
99
+ // Initialize Web UI
100
+ if (options.ui && config.ui.enabled) {
101
+ try {
102
+ httpServer = new HttpServer();
103
+ // Use CLI port if provided and valid, otherwise fallback to config or default
104
+ const port = parseInt(options.port) || config.ui.port || 3333;
105
+ httpServer.start(port);
106
+ }
107
+ catch (e) {
108
+ display.log(chalk.red(`Failed to start Web UI: ${e.message}`));
109
+ }
110
+ }
111
+ // Initialize Telegram
112
+ if (config.channels.telegram.enabled) {
113
+ if (config.channels.telegram.token) {
114
+ const telegram = new TelegramAdapter(oracle);
115
+ try {
116
+ await telegram.connect(config.channels.telegram.token, config.channels.telegram.allowedUsers || []);
117
+ adapters.push(telegram);
118
+ }
119
+ catch (e) {
120
+ display.log(chalk.red('Failed to initialize Telegram adapter. Continuing...'));
121
+ }
122
+ }
123
+ else {
124
+ display.log(chalk.yellow('Telegram enabled but no token provided. Skipping.'));
125
+ }
126
+ }
127
+ // Handle graceful shutdown
128
+ const shutdown = async (signal) => {
129
+ display.stopSpinner();
130
+ display.log(`\n${signal} received. Shutting down...`);
131
+ if (httpServer) {
132
+ httpServer.stop();
133
+ }
134
+ for (const adapter of adapters) {
135
+ await adapter.disconnect();
136
+ }
137
+ await clearPid();
138
+ process.exit(0);
139
+ };
140
+ process.on('SIGINT', () => shutdown('SIGINT'));
141
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
142
+ // Allow ESC to exit
143
+ if (process.stdin.isTTY) {
144
+ process.stdin.setRawMode(true);
145
+ process.stdin.resume();
146
+ process.stdin.setEncoding('utf8');
147
+ process.stdin.on('data', (key) => {
148
+ // ESC or Ctrl+C
149
+ if (key === '\u001B' || key === '\u0003') {
150
+ shutdown('User Quit');
151
+ }
152
+ });
153
+ }
154
+ // Keep process alive (Mock Agent Loop)
155
+ display.startSpinner('Agent active and listening... (Press ctrl+c to stop)');
156
+ // Prevent node from exiting
157
+ setInterval(() => {
158
+ // Heartbeat or background tasks would go here
159
+ }, 5000);
160
+ }
161
+ catch (error) {
162
+ display.stopSpinner();
163
+ console.error(chalk.red('Failed to restart Morpheus:'), error.message);
164
+ await clearPid();
165
+ process.exit(1);
166
+ }
167
+ });
package/dist/cli/index.js CHANGED
@@ -5,6 +5,7 @@ import { statusCommand } from './commands/status.js';
5
5
  import { configCommand } from './commands/config.js';
6
6
  import { doctorCommand } from './commands/doctor.js';
7
7
  import { initCommand } from './commands/init.js';
8
+ import { restartCommand } from './commands/restart.js';
8
9
  import { scaffold } from '../runtime/scaffold.js';
9
10
  import { getVersion } from './utils/version.js';
10
11
  export async function cli() {
@@ -19,6 +20,7 @@ export async function cli() {
19
20
  program.addCommand(initCommand);
20
21
  program.addCommand(startCommand);
21
22
  program.addCommand(stopCommand);
23
+ program.addCommand(restartCommand);
22
24
  program.addCommand(statusCommand);
23
25
  program.addCommand(configCommand);
24
26
  program.addCommand(doctorCommand);