@pompeii-labs/cli
Version:
Magma CLI
147 lines (145 loc) • 5.03 kB
JavaScript
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
};