UNPKG

@pompeii-labs/cli

Version:

Magma CLI

147 lines (145 loc) 5.03 kB
import chalk from "chalk"; import path from "path"; import fs from "fs"; import boxen from "boxen"; import { createInterface } from "readline"; import { ThinkingSpinner, UI } from "../ui.js"; import { exec } from "child_process"; import { promisify } from "util"; import { ContainerServer } from "../container-server/server.js"; const execAsync = promisify(exec); async function validateProject(projectDir = process.cwd()) { const packagePath = path.join(projectDir, "package.json"); if (!fs.existsSync(packagePath)) { throw new Error("Not in a Magma project directory (package.json not found)"); } const pkg = JSON.parse(fs.readFileSync(packagePath, "utf8")); if (!pkg) { throw new Error("Not a Magma project (package.json is invalid)"); } const envPath = path.join(projectDir, ".env"); if (!fs.existsSync(envPath)) { throw new Error("Not a Magma project (env file not found)"); } const env = fs.readFileSync(envPath, "utf8").split("\n").filter((line) => line.trim()).map((line) => line.split("=")).reduce((acc, [key, value]) => { acc[key] = value; return acc; }, {}); for (const key in env) { if (key.charAt(0) === "#") { continue; } if (["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "GOOGLE_API_KEY", "GROQ_API_KEY"].includes(key)) { if (!env[key] || env[key] === "") { throw new Error(`${key} is missing from your .env file!`); } } } return pkg.main; } async function loadAgent(main) { try { const fileUrl = new URL(`file://${main.replace(/\\/g, "/")}`); let AgentClass = await import(fileUrl.toString()); while (AgentClass.default) { AgentClass = AgentClass.default; } return AgentClass; } catch (error) { throw new Error(`Failed to load agent: ${error.message}`); } } async function startInteractiveSession(AgentClass, options) { const agent = new AgentClass(); await agent.setup(); const server = new ContainerServer({ ...options, agent }); await server.start(); let runMessage = `\u{1F30B} ${chalk.bold(`Running ${agent.constructor.name ?? "Agent"}`)}... ${chalk.dim( `Server: ${chalk.bold(`http://${server.host}:${server.port}`)}` )}`; if (server.jobs.length > 0) { runMessage += ` ${chalk.dim(`Jobs: ${chalk.bold(server.jobs.length)} scheduled`)}`; } console.log( boxen(runMessage, { padding: 1, margin: 1, borderStyle: "round", borderColor: "cyan" }) ); console.log(chalk.dim('Type your messages and press Enter. Type "exit" to quit.\n')); const thinking = new ThinkingSpinner(); const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: chalk.cyan("You > ") }); rl.prompt(); rl.on("line", async (line) => { if (["exit", "quit", "q"].includes(line.toLowerCase().trim())) { console.log(chalk.yellow("\nEnding session...")); await agent.cleanup(); server.stop(); rl.close(); return; } try { thinking.start(); agent.addMessage({ role: "user", content: line }); const reply = await agent.main(); if (!reply) { return; } thinking.stop(); process.stdout.write("\r\x1B[K"); console.log(chalk.hex("#FF7500")("Agent > ") + reply.content); } catch (error) { console.error(chalk.red("Error: ") + error.message); process.exit(1); } rl.prompt(); }); rl.on("close", () => { console.log(chalk.yellow("\nGoodbye! \u{1F44B}\n")); process.exit(0); }); } function runCommand(program) { program.command("run").description("Run your Magma agent locally").option("-p, --port <port>", "Port to run the server on").option("-h, --host <host>", "Host to run the server on").option("-dir, --directory <path>", "Path to the project directory", ".").action(async (options) => { try { const projectDir = path.resolve(options.directory); const main = await validateProject(projectDir); const packageJson = JSON.parse( fs.readFileSync(path.join(projectDir, "package.json"), "utf8") ); const agentPath = path.join(projectDir, main || "src/index.js"); if (packageJson.scripts?.build) { const buildSpinner = UI.spinner("Building agent").start(); try { await execAsync(`npm run build`, { cwd: projectDir }); } catch (error) { const errorOutput = error.stdout || error.stderr || error.message; buildSpinner.fail(); console.log(errorOutput); console.log(chalk.red("\nFailed to build agent")); process.exit(1); } buildSpinner.succeed(); if (!fs.existsSync(agentPath)) { throw new Error(`Agent entry point not found: ${agentPath}`); } } const AgentClass = await loadAgent(agentPath); await startInteractiveSession(AgentClass, options); } catch (error) { console.error(chalk.red("\n\u2716 Failed to start agent:"), error.message); process.exit(1); } }); } export { runCommand };