OverviewFilesCommitsIssuesDocsReleasesPeers

ollama_semantic_function_calling / ollamatools-new / second-device-keet / keet_cmd.js

792 lines · javascript ref:
#!/usr/bin/env node
/**
 * Keet Command-line Agent
 * 
 * This script provides a minimal CLI agent for Keet that can:
 * - Join a room
 * - Set its name
 * - Respond to user messages using Ollama
 */

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { parseArgs } from 'node:util';
import fetch from 'node-fetch';

// Get the directory name of the current module
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

// Configure logging
const logLevels = {
  INFO: 'INFO',
  ERROR: 'ERROR',
  WARNING: 'WARNING',
  DEBUG: 'DEBUG'
};

function log(level, message) {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] [${level}] ${message}`);
}

// More detailed logging for verbose mode
function verboseLog(message, args) {
  if (args && args.verbose) {
    const timestamp = new Date().toISOString();
    console.log(`[${timestamp}] [VERBOSE] ${message}`);
  }
}

// Import Keet dependencies or mock implementations
let KeetBackend, KeetCoreApi;
let KEET_AVAILABLE = false;
let useMock = false;

async function importDependencies() {
  try {
    // Try to import the real Keet dependencies
    const backendModule = await import('@holepunchto/keet-backend');
    const coreApiModule = await import('@holepunchto/keet-core-api');
    
    // Try to get the exports
    KeetBackend = backendModule.default || backendModule;
    KeetCoreApi = coreApiModule.default || coreApiModule;
    
    KEET_AVAILABLE = true;
    
    log(logLevels.INFO, 'Successfully imported Keet dependencies');
  } catch (error) {
    log(logLevels.WARNING, `Failed to import Keet dependencies: ${error.message}`);
    log(logLevels.WARNING, 'Using mock implementation');
    
    // Create a simple mock implementation
    class MockKeetBackend {
      constructor(storagePath, options) {
        this.storagePath = storagePath;
        this.options = options;
        this.api = {
          core: {
            getIdentity: async () => {
              try {
                const identityPath = path.join(storagePath, 'identity.json');
                if (fs.existsSync(identityPath)) {
                  return JSON.parse(fs.readFileSync(identityPath, 'utf8'));
                }
                return null;
              } catch (error) {
                return null;
              }
            },
            generateIdentity: async () => {
              const identity = {
                publicKey: `mock-${Date.now()}`,
                displayName: 'Mock User'
              };
              const identityPath = path.join(storagePath, 'identity.json');
              fs.writeFileSync(identityPath, JSON.stringify(identity));
              return identity;
            },
            getRecentRooms: async () => {
              try {
                const roomsPath = path.join(storagePath, 'rooms.json');
                if (fs.existsSync(roomsPath)) {
                  return JSON.parse(fs.readFileSync(roomsPath, 'utf8'));
                }
                return [];
              } catch (error) {
                return [];
              }
            },
            pairRoom: async (roomKey) => {
              const rooms = await this.api.core.getRecentRooms();
              const newRoom = {
                roomId: roomKey,
                title: `Room ${roomKey.substring(0, 8)}`,
                lastMessage: null,
                timestamp: Date.now()
              };
              rooms.push(newRoom);
              const roomsPath = path.join(storagePath, 'rooms.json');
              fs.writeFileSync(roomsPath, JSON.stringify(rooms));
              return true;
            },
            getChatMessages: async (roomId) => {
              try {
                const messagesPath = path.join(storagePath, `messages_${roomId}.json`);
                if (fs.existsSync(messagesPath)) {
                  return JSON.parse(fs.readFileSync(messagesPath, 'utf8'));
                }
                return [];
              } catch (error) {
                return [];
              }
            },
            addChatMessage: async (roomId, text) => {
              const messages = await this.api.core.getChatMessages(roomId);
              const identity = await this.api.core.getIdentity();
              const newMessage = {
                id: `msg-${Date.now()}`,
                timestamp: Date.now(),
                author: identity.publicKey,
                chat: {
                  text: text
                }
              };
              messages.push(newMessage);
              const messagesPath = path.join(storagePath, `messages_${roomId}.json`);
              fs.writeFileSync(messagesPath, JSON.stringify(messages));
              return true;
            },
            setDisplayName: async (name) => {
              const identity = await this.api.core.getIdentity();
              if (identity) {
                identity.displayName = name;
                const identityPath = path.join(storagePath, 'identity.json');
                fs.writeFileSync(identityPath, JSON.stringify(identity));
              }
              return true;
            }
          }
        };
      }
      
      async ready() {
        // Create necessary directories
        if (!fs.existsSync(this.storagePath)) {
          fs.mkdirSync(this.storagePath, { recursive: true });
        }
        
        // Initialize rooms file if it doesn't exist
        const roomsPath = path.join(this.storagePath, 'rooms.json');
        if (!fs.existsSync(roomsPath)) {
          fs.writeFileSync(roomsPath, JSON.stringify([]));
        }
        
        return true;
      }
      
      async close() {
        return true;
      }
    }
    
    KeetBackend = MockKeetBackend;
    KEET_AVAILABLE = true;
    useMock = true;
  }
}

/**
 * Parse command-line arguments.
 */
function parseArguments() {
  const options = {
    storage: { type: 'string' },
    'room-key': { type: 'string' },
    name: { type: 'string', default: 'KeetBot' },
    model: { type: 'string', default: 'llama3.1:latest' },
    'ollama-host': { type: 'string', default: 'localhost:11434' },
    prompt: { type: 'string' },
    mock: { type: 'boolean', default: false },
    verbose: { type: 'boolean', default: false },
    'link-device': { type: 'string' }, // Name of the device to link
    'confirm-device': { type: 'string' }, // Device ID to confirm
    'mobile-device': { type: 'boolean', default: false } // Whether the linked device is mobile
  };
  
  const { values } = parseArgs({ options });
  
  // Force mock mode if specified
  if (values.mock) {
    useMock = true;
  }
  
  return values;
}

/**
 * Ollama client for interacting with the Ollama API.
 */
class OllamaClient {
  constructor(host = 'localhost:11434', model = 'llama3.1:latest', systemPrompt = null) {
    this.host = host;
    this.model = model;
    this.systemPrompt = systemPrompt;
    this.baseUrl = `http://${host}`;
    
    // User context tracking
    this.userContexts = new Map();
    this.maxContextLength = 10;  // Maximum number of messages to keep in context
  }
  
  /**
   * Call Ollama chat API to generate a response.
   */
  async chat(message, userId, roomId) {
    try {
      const contextKey = `${userId}:${roomId}`;
      const messages = [];
      
      // Add system prompt if provided
      if (this.systemPrompt) {
        messages.push({
          role: 'system',
          content: this.systemPrompt
        });
      }
      
      // Add conversation context if available
      if (this.userContexts.has(contextKey)) {
        for (const ctxMsg of this.userContexts.get(contextKey)) {
          messages.push(ctxMsg);
        }
      }
      
      // Add user message
      const userMessage = {
        role: 'user',
        content: message
      };
      messages.push(userMessage);
      
      // Store user message in context
      if (!this.userContexts.has(contextKey)) {
        this.userContexts.set(contextKey, []);
      }
      this.userContexts.get(contextKey).push(userMessage);
      
      // Trim context if it gets too long
      if (this.userContexts.get(contextKey).length > this.maxContextLength) {
        this.userContexts.set(
          contextKey,
          this.userContexts.get(contextKey).slice(-this.maxContextLength)
        );
      }
      
      // Make API request to Ollama
      const response = await fetch(`${this.baseUrl}/api/chat`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          model: this.model,
          messages: messages,
          stream: false
        })
      });
      
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Ollama API error (${response.status}): ${errorText}`);
      }
      
      const data = await response.json();
      const messageContent = data.message.content;
      
      // Store assistant response in context
      this.userContexts.get(contextKey).push({
        role: 'assistant',
        content: messageContent
      });
      
      // Trim context if it gets too long
      if (this.userContexts.get(contextKey).length > this.maxContextLength) {
        this.userContexts.set(
          contextKey,
          this.userContexts.get(contextKey).slice(-this.maxContextLength)
        );
      }
      
      return messageContent;
    } catch (error) {
      log(logLevels.ERROR, `Error calling Ollama API: ${error.message}`);
      throw error;
    }
  }
  
  /**
   * List available models from Ollama.
   */
  async listModels() {
    try {
      const response = await fetch(`${this.baseUrl}/api/tags`);
      
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Ollama API error (${response.status}): ${errorText}`);
      }
      
      const data = await response.json();
      return data.models.map(model => model.name);
    } catch (error) {
      log(logLevels.ERROR, `Error listing Ollama models: ${error.message}`);
      throw error;
    }
  }
  
  /**
   * Change the model being used.
   */
  changeModel(model) {
    this.model = model;
    log(logLevels.INFO, `Changed Ollama model to: ${model}`);
  }
  
  /**
   * Clear context for a specific user/room.
   */
  clearContext(userId, roomId) {
    const contextKey = `${userId}:${roomId}`;
    this.userContexts.delete(contextKey);
    log(logLevels.INFO, `Cleared context for ${userId} in room ${roomId}`);
  }
}

/**
 * Process new messages and respond using Ollama.
 */
async function processMessages(backend, roomId, lastProcessedTimestamp, ollamaClient, verbose) {
  try {
    // Get messages from the room
    verboseLog(`Fetching messages from room ${roomId}...`, { verbose });
    const messages = await backend.api.core.getChatMessages(roomId);
    verboseLog(`Retrieved ${messages.length} total messages from room`, { verbose });
    
    // Get identity for filtering
    const identity = await backend.api.core.getIdentity();
    verboseLog(`Current identity: ${identity ? identity.publicKey : 'undefined'}`, { verbose });
    
    // Filter messages that are newer than the last processed timestamp
    const newMessages = messages.filter(msg => {
      // Skip our own messages
      if (identity && identity.publicKey && msg.author === identity.publicKey) {
        verboseLog(`Skipping own message with ID: ${typeof msg.id === 'object' ? JSON.stringify(msg.id) : msg.id}`, { verbose });
        return false;
      }
      
      // Check if the message is newer than the last processed timestamp
      const isNewer = msg.timestamp > lastProcessedTimestamp;
      if (isNewer) {
        verboseLog(`Found new message with ID: ${typeof msg.id === 'object' ? JSON.stringify(msg.id) : msg.id}, timestamp: ${new Date(msg.timestamp).toISOString()}`, { verbose });
      }
      return isNewer;
    });
    
    verboseLog(`Found ${newMessages.length} new messages to process`, { verbose });
    
    if (newMessages.length > 0) {
      // Update the last processed timestamp
      const latestTimestamp = Math.max(...newMessages.map(msg => msg.timestamp));
      verboseLog(`Updating last processed timestamp to: ${new Date(latestTimestamp).toISOString()}`, { verbose });
      
      // Process each new message
      for (const message of newMessages) {
        if (message.chat && message.chat.text) {
          const authorId = message.author ? message.author.substring(0, 8) : 'unknown';
          log(logLevels.INFO, `New message from ${authorId}: ${message.chat.text}`);
          
          try {
            // Generate a response using Ollama
            log(logLevels.INFO, `Generating response using Ollama (model: ${ollamaClient.model})...`);
            const response = await ollamaClient.chat(message.chat.text, message.author || 'unknown-user', roomId);
            
            // Send the response back to the room
            log(logLevels.INFO, `Sending response to room ${roomId}: ${response}`);
            verboseLog(`Attempting to send message to room ${roomId}...`, { verbose });
            await backend.api.core.addChatMessage(roomId, response);
            log(logLevels.INFO, `✓ Message successfully sent to room`);
          } catch (error) {
            log(logLevels.ERROR, `Failed to generate or send response: ${error.message}`);
          }
        }
      }
      
      return latestTimestamp;
    } else {
      verboseLog(`No new messages to process`, { verbose });
    }
    
    return lastProcessedTimestamp;
  } catch (error) {
    log(logLevels.ERROR, `Error processing messages: ${error.message}`);
    return lastProcessedTimestamp;
  }
}

/**
 * Main function.
 */
async function main() {
  // Parse command-line arguments
  const args = parseArguments();
  
  // Set verbose logging if requested
  if (args.verbose) {
    console.log('Verbose logging enabled');
  }
  
  // Import dependencies
  await importDependencies();
  
  if (!KEET_AVAILABLE) {
    log(logLevels.ERROR, 'Keet dependencies not available');
    return 1;
  }
  
  // Create a storage directory if it doesn't exist
  const storagePath = args.storage || path.join(__dirname, '..', 'test-keet-storage');
  if (!fs.existsSync(storagePath)) {
    fs.mkdirSync(storagePath, { recursive: true });
  }
  
  // Initialize Keet backend
  log(logLevels.INFO, 'Initializing Keet backend...');
  const backend = new KeetBackend(storagePath, {
    experimental: false,
    devMirrors: false
  });
  
  // Wait for backend to be ready
  await backend.ready();
  
  // Check if we have an identity
  log(logLevels.INFO, 'Checking identity...');
  let identity = await backend.api.core.getIdentity();
  
  // Ensure the identity has anonymous set to false
  if (identity && identity.anonymous !== false) {
    log(logLevels.INFO, 'Setting anonymous to false in identity...');
    identity.anonymous = false;
    
    // If we're using a mock implementation, manually update the identity file
    if (useMock) {
      const identityPath = path.join(storagePath, 'identity.json');
      fs.writeFileSync(identityPath, JSON.stringify(identity));
      log(logLevels.INFO, 'Updated identity file with anonymous=false');
    }
  }
  
// If we don't have an identity, create one
if (!identity) {
  log(logLevels.INFO, 'No identity found, generating a new one...');
  await backend.api.core.generateIdentity();
  identity = await backend.api.core.getIdentity();
  
  // If we're using a mock implementation, we need to manually set up the identity
  if (useMock) {
    log(logLevels.INFO, 'Using mock implementation, manually setting up identity...');
    
    // Create a proper identity structure
    const identityPath = path.join(storagePath, 'identity.json');
    const mockIdentity = {
      anonymous: false, // Set to false for a real identity
      memberId: `real-${Date.now()}`,
      timestamp: Date.now(),
      displayName: args.name || 'KeetBot',
      avatar: null,
      devices: [{
        deviceId: `device-${Date.now()}`,
        isMobile: false,
        name: 'Primary Device',
        confirmed: true
      }],
      local: {
        deviceId: `device-${Date.now()}`
      }
    };
    
    // Make sure the local deviceId matches the first device in the devices array
    mockIdentity.local.deviceId = mockIdentity.devices[0].deviceId;
    
    // Write the identity to the file
    fs.writeFileSync(identityPath, JSON.stringify(mockIdentity));
    
    // Update our identity reference
    identity = mockIdentity;
    log(logLevels.INFO, `Created new identity with ID: ${identity.memberId}`);
  }
}

// Set the display name if provided
if (args.name && identity) {
  log(logLevels.INFO, `Setting display name to: ${args.name}`);
  if (backend.api.core.setDisplayName) {
    await backend.api.core.setDisplayName(args.name);
  } else {
    log(logLevels.WARNING, 'setDisplayName method not available');
    
    // If we're using a mock implementation, manually update the display name
    if (useMock) {
      const identityPath = path.join(storagePath, 'identity.json');
      identity.displayName = args.name;
      fs.writeFileSync(identityPath, JSON.stringify(identity));
      log(logLevels.INFO, `Manually updated display name to: ${args.name}`);
    }
  }
}

// Add a function to link a new device to the identity
async function linkDevice(deviceName = 'New Device', isMobile = false) {
  try {
    // Get the current identity
    const identity = await backend.api.core.getIdentity();
    if (!identity) {
      log(logLevels.ERROR, 'No identity found, cannot link device');
      return null;
    }
    
    // If we're using the real Keet implementation
    if (!useMock) {
      // Check if there's a linkDevice method available
      if (backend.api.core.linkDevice) {
        log(logLevels.INFO, `Linking new device: ${deviceName}`);
        const result = await backend.api.core.linkDevice(deviceName, isMobile);
        log(logLevels.INFO, `Device linked successfully: ${JSON.stringify(result)}`);
        return result;
      } else {
        log(logLevels.WARNING, 'linkDevice method not available');
        return null;
      }
    } else {
      // For mock implementation, manually add a device to the identity
      const identityPath = path.join(storagePath, 'identity.json');
      
      // Create a new device
      const newDevice = {
        deviceId: `device-${Date.now()}`,
        isMobile: isMobile,
        name: deviceName,
        confirmed: false // New devices start as unconfirmed
      };
      
      // Add the device to the identity
      identity.devices.push(newDevice);
      
      // Write the updated identity to the file
      fs.writeFileSync(identityPath, JSON.stringify(identity));
      
      log(logLevels.INFO, `Manually added new device: ${newDevice.deviceId}`);
      return newDevice;
    }
  } catch (error) {
    log(logLevels.ERROR, `Error linking device: ${error.message}`);
    return null;
  }
}

// Add a function to confirm a linked device
async function confirmDevice(deviceId) {
  try {
    // Get the current identity
    const identity = await backend.api.core.getIdentity();
    if (!identity) {
      log(logLevels.ERROR, 'No identity found, cannot confirm device');
      return false;
    }
    
    // If we're using the real Keet implementation
    if (!useMock) {
      // Check if there's a confirmDevice method available
      if (backend.api.core.confirmDevice) {
        log(logLevels.INFO, `Confirming device: ${deviceId}`);
        const result = await backend.api.core.confirmDevice(deviceId);
        log(logLevels.INFO, `Device confirmed successfully`);
        return result;
      } else {
        log(logLevels.WARNING, 'confirmDevice method not available');
        return false;
      }
    } else {
      // For mock implementation, manually confirm the device
      const identityPath = path.join(storagePath, 'identity.json');
      
      // Find the device in the identity
      const deviceIndex = identity.devices.findIndex(device => device.deviceId === deviceId);
      if (deviceIndex === -1) {
        log(logLevels.ERROR, `Device not found: ${deviceId}`);
        return false;
      }
      
      // Confirm the device
      identity.devices[deviceIndex].confirmed = true;
      
      // Write the updated identity to the file
      fs.writeFileSync(identityPath, JSON.stringify(identity));
      
      log(logLevels.INFO, `Manually confirmed device: ${deviceId}`);
      return true;
    }
  } catch (error) {
    log(logLevels.ERROR, `Error confirming device: ${error.message}`);
    return false;
  }
}
  
  log(logLevels.INFO, `Current identity: ${JSON.stringify(identity)}`);
  
  // Handle device linking if requested
  if (args['link-device']) {
    log(logLevels.INFO, `Linking new device: ${args['link-device']}`);
    const newDevice = await linkDevice(args['link-device'], args['mobile-device']);
    if (newDevice) {
      log(logLevels.INFO, `Successfully linked new device: ${JSON.stringify(newDevice)}`);
      log(logLevels.INFO, `Device ID: ${newDevice.deviceId}`);
      log(logLevels.INFO, `To confirm this device, run with --confirm-device=${newDevice.deviceId}`);
      
      // Exit after linking the device
      return 0;
    } else {
      log(logLevels.ERROR, 'Failed to link device');
      return 1;
    }
  }
  
  // Handle device confirmation if requested
  if (args['confirm-device']) {
    log(logLevels.INFO, `Confirming device: ${args['confirm-device']}`);
    const confirmed = await confirmDevice(args['confirm-device']);
    if (confirmed) {
      log(logLevels.INFO, `Successfully confirmed device: ${args['confirm-device']}`);
      
      // Exit after confirming the device
      return 0;
    } else {
      log(logLevels.ERROR, `Failed to confirm device: ${args['confirm-device']}`);
      return 1;
    }
  }
  
  // Initialize Ollama client
  log(logLevels.INFO, `Initializing Ollama client with host: ${args['ollama-host']}, model: ${args.model}`);
  const ollamaClient = new OllamaClient(args['ollama-host'], args.model, args.prompt);
  
  // Join the room if a room key is provided
  let roomId;
  if (args['room-key']) {
    const roomKey = args['room-key'];
    log(logLevels.INFO, `Joining room with key: ${roomKey}`);
    
    // Extract the room key from the invite link if needed
    let normalizedRoomKey = roomKey;
    if (roomKey.startsWith('pear://keet/')) {
      normalizedRoomKey = roomKey.replace('pear://keet/', '');
      verboseLog(`Extracted room key from pear link: ${normalizedRoomKey}`, args);
    }
    
    // Check if there's a pairRoom method available
    if (backend.api.core.pairRoom) {
      log(logLevels.INFO, 'Using pairRoom method to join the room...');
      verboseLog(`Attempting to pair with room using key: ${normalizedRoomKey.substring(0, 16)}...`, args);
      
      try {
        verboseLog(`Starting room pairing process...`, args);
        await backend.api.core.pairRoom(normalizedRoomKey);
        log(logLevels.INFO, '✓ Successfully paired with the room');
        roomId = normalizedRoomKey;
        
        // Verify the room is in recent rooms after pairing
        const recentRooms = await backend.api.core.getRecentRooms();
        const roomFound = recentRooms.some(room => room.roomId === normalizedRoomKey);
        
        if (roomFound) {
          verboseLog(`Room verified in recent rooms list`, args);
        } else {
          log(logLevels.WARNING, `Room paired but not found in recent rooms list - this may cause issues`);
        }
      } catch (error) {
        log(logLevels.ERROR, `Failed to pair with room: ${error.message}`);
        verboseLog(`Detailed error: ${error.stack || 'No stack trace available'}`, args);
        return 1;
      }
    } else {
      log(logLevels.ERROR, 'pairRoom method not available');
      verboseLog(`Available API methods: ${Object.keys(backend.api.core).join(', ')}`, args);
      return 1;
    }
  } else {
    // Get recent rooms
    log(logLevels.INFO, 'No room key provided, checking recent rooms...');
    const recentRooms = await backend.api.core.getRecentRooms();
    verboseLog(`Found ${recentRooms.length} recent rooms`, args);
    
    if (recentRooms.length > 0) {
      roomId = recentRooms[0].roomId;
      log(logLevels.INFO, `Using the most recent room: ${roomId.substring(0, 16)}...`);
      verboseLog(`Room details: ${JSON.stringify(recentRooms[0])}`, args);
    } else {
      log(logLevels.ERROR, 'No room key provided and no recent rooms found');
      return 1;
    }
  }
  
  // Main loop to process messages
  log(logLevels.INFO, `Starting message processing loop for room: ${roomId}`);
  log(logLevels.INFO, `Using Ollama model: ${args.model}`);
  
  // Get the current timestamp to avoid processing old messages
  let lastProcessedTimestamp = Date.now();
  
  try {
    // Send an initial message to the room
    const initialMessage = `Hello! I'm ${args.name}, a Keet bot powered by Ollama. I'm here to assist you.`;
    log(logLevels.INFO, `Sending initial greeting message to room ${roomId.substring(0, 16)}...`);
    verboseLog(`Full message: "${initialMessage}"`, args);
    
    try {
      verboseLog(`Attempting to send initial message...`, args);
      await backend.api.core.addChatMessage(roomId, initialMessage);
      log(logLevels.INFO, '✓ Initial greeting message sent successfully');
    } catch (sendError) {
      log(logLevels.ERROR, `Failed to send initial message: ${sendError.message}`);
      verboseLog(`Detailed error: ${sendError.stack || 'No stack trace available'}`, args);
    }
    
    // Main loop
    log(logLevels.INFO, `Starting main message processing loop`);
    verboseLog(`Message polling interval: 2000ms`, args);
    
    while (true) {
      verboseLog(`Checking for new messages in room ${roomId.substring(0, 16)}...`, args);
      
      // Process new messages
      lastProcessedTimestamp = await processMessages(
        backend, 
        roomId, 
        lastProcessedTimestamp, 
        ollamaClient,
        args.verbose
      );
      
      // Wait a bit before checking for new messages
      verboseLog(`Waiting 2000ms before next message check...`, args);
      await new Promise(resolve => setTimeout(resolve, 2000));
    }
  } catch (error) {
    log(logLevels.ERROR, `Error in main loop: ${error.message}`);
    verboseLog(`Detailed error: ${error.stack || 'No stack trace available'}`, args);
    
    // Close the backend
    log(logLevels.INFO, 'Closing backend...');
    await backend.close();
    
    return 1;
  }
}

// Run the main function if this file is executed directly
if (import.meta.url === `file://${process.argv[1]}`) {
  try {
    const exitCode = await main();
    process.exit(exitCode);
  } catch (error) {
    log(logLevels.ERROR, `Unhandled error: ${error.message}`);
    log(logLevels.ERROR, error.stack);
    process.exit(1);
  }
}

export { main };