UNPKG

@pompeii-labs/cli

Version:

Magma CLI

194 lines (193 loc) 6.51 kB
import chalk from "chalk"; import path from "path"; import fs from "fs"; import { getTemplate, listTemplates } from "../api.js"; import { exec } from "child_process"; import { promisify } from "util"; import boxen from "boxen"; import { UI } from "../ui.js"; import { getOrg, isAuthenticated } from "../auth.js"; import { Bundler } from "../bundler.js"; import { select } from "@inquirer/prompts"; const execAsync = promisify(exec); const providerEnvMap = { openai: "OPENAI_API_KEY", anthropic: "ANTHROPIC_API_KEY", groq: "GROQ_API_KEY", google: "GOOGLE_API_KEY" }; const providerDefaultModelMap = { openai: "gpt-4o", anthropic: "claude-3-5-sonnet-20240620", groq: "llama3-8b-8192", google: "gemini-1.5-flash" }; function createProjectDirectory(name) { const projectDir = path.join(process.cwd(), name); if (fs.existsSync(projectDir)) { throw new Error(`Directory ${projectDir} already exists`); } fs.mkdirSync(projectDir, { recursive: true }); return projectDir; } async function implementTemplate(name, options, projectDir) { const template = await getTemplate(options.templateId); const templateSpinner = UI.spinner( `Implementing template ${chalk.cyan(template.name)}...` ).start(); for (const file of template.version.files) { const dir = path.dirname(file.path); if (!fs.existsSync(path.join(projectDir, dir))) { fs.mkdirSync(path.join(projectDir, dir), { recursive: true }); } fs.writeFileSync(path.join(projectDir, file.path), file.content); } if (!fs.existsSync(path.join(projectDir, ".env"))) { fs.writeFileSync(path.join(projectDir, ".env"), ""); } if (options.provider in providerEnvMap) { fs.appendFileSync( path.join(projectDir, ".env"), ` ${providerEnvMap[options.provider]}=` ); } const indexPath = path.join(projectDir, "index.ts"); if (fs.existsSync(indexPath)) { const index = fs.readFileSync(indexPath, "utf8"); let updatedIndex = index.replace( /provider:\s*['"].*?['"]/, `provider: '${options.provider}'` ); updatedIndex = updatedIndex.replace( /model:\s*['"].*?['"]/, `model: '${providerDefaultModelMap[options.provider]}'` ); fs.writeFileSync(indexPath, updatedIndex); } const packageJsonPath = path.join(projectDir, "package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")); packageJson.name = name; fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2)); templateSpinner.succeed(); } async function init(name, options) { console.log(chalk.cyan(`Creating agent ${chalk.bold(name)}...`)); const projectDir = createProjectDirectory(name); try { await implementTemplate(name, options, projectDir); const depsSpinner2 = UI.spinner("Installing dependencies").start(); await execAsync("npm install", { cwd: projectDir }); depsSpinner2.succeed(); } catch (error) { fs.rmSync(projectDir, { recursive: true, force: true }); throw error; } const depsSpinner = UI.spinner("Installing dependencies").start(); await execAsync("npm install", { cwd: projectDir }); depsSpinner.succeed(); let agentId; if (options.save) { const org = await getOrg(); if (!org) { console.error( chalk.red( "No organization found. Please run `magma switch` to select an organization." ) ); process.exit(1); } const bundler = new Bundler(projectDir, org); const agent = await bundler.run(false); agentId = agent.id; } console.log(chalk.green("\n\u{1F30B} Successfully created agent:", chalk.bold(name))); console.log(chalk.dim(` Created in ${projectDir}`)); console.log(chalk.dim("\nNext steps:\n")); console.log(chalk.white(` 1. cd ${chalk.bold(name + "/")}`)); console.log( chalk.white( ` 2. Add your ${chalk.bold(providerEnvMap[options.provider])} key to ${chalk.bold(".env")}` ) ); console.log(chalk.white(` 3. Try it out with ${chalk.cyan(chalk.bold("magma run"))}`)); console.log(chalk.white(` 4. ${chalk.cyan(chalk.bold("magma deploy"))} when you're ready! `)); if (agentId) { console.log(chalk.dim(` Agent ID: ${chalk.bold(agentId)}`)); } } async function interactiveSetup() { const templates = await listTemplates(); console.clear(); console.log( boxen(chalk.bold("Create a new Magma agent"), { padding: 1, margin: 1, borderStyle: "round", borderColor: "cyan" }) ); process.stdout.write(chalk.green("\u2714 ") + chalk.bold("What do you want to name your agent? ")); const name = await new Promise((resolve) => { const stdin = process.stdin; stdin.resume(); stdin.setEncoding("utf-8"); stdin.once("data", (data) => { const input = data.toString().trim(); if (!input) { console.error(chalk.red("Name cannot be empty")); process.exit(1); } resolve(input); }); }); const templateId = await select({ message: "Which template would you like to use?", choices: templates.map((t) => ({ name: t.name, value: t.id })), default: "hello-world" }); const providers = ["openai", "anthropic", "groq", "google"]; const provider = await select({ message: "Which provider would you like to use?", choices: providers.map((p) => ({ name: p, value: p })) }); const saveString = await select({ message: "Would you like to save this agent to your Magma account?", choices: ["Yes", "No"] }); const save = saveString === "Yes"; console.log(); return { name, options: { templateId, save, provider } }; } function initCommand(program) { program.command("init").description("Initialize a new Magma agent").argument("[name]", "Name of the agent").option("-t, --template <name>", "Template to use", "hello-world").option("--no-save", "Do not save the agent to your account").option("-p, --provider <name>", "LLM provider to use", "openai").action(async (name, options) => { if (!isAuthenticated()) { console.log(chalk.red("Not authenticated with Magma. Please run `magma login`.")); process.exit(1); } try { if (!name) { const setup = await interactiveSetup(); name = setup.name; options = { ...options, ...setup.options }; } await init(name, options); } catch (error) { console.error(chalk.red("\n\u2716 Failed to create agent:"), error.message); process.exit(1); } }); } export { initCommand };