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