magnus-one / magnus.js
heavylildude's picture
Upload 2 files
6c7818b verified
#!/usr/bin/env node
import readline from "readline";
import { spawn, execSync } from "child_process";
import { initializeDatabase, db } from "./modules/database.js";
import { bootUp, getPrompt, log, C, setReadline } from "./modules/ui.js";
import { commandHandlers } from "./modules/commands.js";
import { route } from "./modules/router.js";
import { GGUF } from "./config.js";
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
setReadline(rl); // Give the UI module access to the readline instance
let multiline = false;
let multiBuf = [];
function refreshPrompt() {
const newPrompt = getPrompt();
rl.setPrompt(newPrompt);
rl.prompt(true);
}
function handleMultiline(line) {
if (line.trim() === "```") {
if (!multiline) {
multiline = true;
multiBuf = [];
log.gray("(multiline ON — type ``` to send)");
rl.setPrompt(''); // Use a blank prompt for multiline input
rl.prompt();
} else {
multiline = false;
return multiBuf.join("\n");
}
return null;
}
if (multiline) {
multiBuf.push(line);
return null;
}
return line.trim();
}
async function processInput(input, source = 'terminal') {
if (!input) return false;
try {
const lowerInput = input.trim().toLowerCase();
if (lowerInput === 'exit' || lowerInput === 'quit' || lowerInput === 'bye') {
await commandHandlers.quit();
// Return a special value to signal that we are quitting and should not refresh the prompt.
return { isQuitting: true };
} else if (input.trim().startsWith('/')) {
const [command] = input.substring(1).split(/\s+/);
const handler = commandHandlers[command];
if (handler) {
await handler(input);
} else {
log.warn(`Unknown command, brah. Sending to router anyway...`);
await route(input);
}
} else {
await route(input);
}
// Return a default object if not quitting.
return { isQuitting: false };
} catch (err) {
log.error(`Magnus error: An unhandled exception occurred.`);
console.error(err);
return { isQuitting: false };
}
}
let serverProcess = null; // Keep the server process in a higher scope
function startServer() { // No longer returns the process
log.info('[SERVER] Firing up the llama-server engine...');
serverProcess = spawn('llama-server', ['-m', GGUF, '--jinja'], {
detached: true,
stdio: 'ignore'
});
serverProcess.unref(); // Let the main process exit independently
log.gray(`[SERVER] Llama-server is shredding on PID: ${serverProcess.pid}`);
}
function shutdown(exitCode = 0) {
log.warn("\n[CLEANUP] Taking down the server... Catch ya later, legend!");
if (serverProcess && serverProcess.pid) {
try {
if (process.platform === "win32") {
// Bitchin' Fix: Use execSync to block until the command finishes. No more zombies!
execSync(`taskkill /pid ${serverProcess.pid} /f /t`);
} else {
// Kill the entire process group. The `-` is the magic here.
process.kill(-serverProcess.pid, 'SIGTERM');
}
log.success("[CLEANUP] Server process terminated.");
} catch (e) {
log.error(`[CLEANUP] Bogus! Couldn't kill server process ${serverProcess.pid}: ${e.message}`);
}
}
process.exit(exitCode);
}
async function main() {
startServer();
log.gray('[SYSTEM] Giving the server 5 seconds to warm up...');
await new Promise(resolve => setTimeout(resolve, 5000)); // Simple 5-second delay
log.success('[SERVER] Aight, server should be ready. Let\'s rock!');
initializeDatabase();
await bootUp();
rl.setPrompt(getPrompt());
rl.prompt();
rl.on("line", async (line) => {
const processedLine = handleMultiline(line);
if (processedLine === null) return; // Multiline is active, just wait.
rl.pause(); // Pause the prompt while processing and streaming
const { isQuitting } = await processInput(processedLine, 'terminal');
if (isQuitting) {
shutdown(0); // Gracefully shutdown if command handler signals a quit
return;
}
if (!multiline) refreshPrompt();
rl.resume();
});
// Catch Ctrl+C and other termination signals for a graceful shutdown
process.on('SIGINT', () => shutdown(0));
process.on('SIGTERM', () => shutdown(0));
}
main().catch(err => { console.error(err); shutdown(1); });