UNPKG

create-eliza

Version:

Initialize an Eliza project

875 lines (866 loc) 27.2 kB
import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { copyTemplate, execa, fetch, getConfig, getPluginRepository, getRegistryIndex, installPlugin, runBunCommand } from "./chunk-WUGAPG42.js"; import { handleError } from "./chunk-S7QJ5TCR.js"; import { require_prompts } from "./chunk-OGSHIQ3J.js"; import { require_main } from "./chunk-ZMJ3QLUC.js"; import { source_default } from "./chunk-BY3DNMXE.js"; import { logger } from "./chunk-UWEMLI2Z.js"; import { Command } from "./chunk-CKY7YPIS.js"; import { __toESM } from "./chunk-WCMDOJQK.js"; // src/commands/plugins.ts import { promises as fs3, existsSync as existsSync2 } from "node:fs"; import path3 from "node:path"; // src/utils/github.ts import { promises as fs } from "node:fs"; import os from "node:os"; import path from "node:path"; var GITHUB_API_URL = "https://api.github.com"; async function validateGitHubToken(token) { try { const response = await fetch(`${GITHUB_API_URL}/user`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } }); if (response.status === 200) { const userData = await response.json(); logger.success(`Authenticated as ${userData.login}`); return true; } return false; } catch (error) { logger.error(`Failed to validate GitHub token: ${error.message}`); return false; } } async function forkExists(token, owner, repo, username) { try { const response = await fetch( `${GITHUB_API_URL}/repos/${username}/${repo}`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); return response.status === 200; } catch (error) { return false; } } async function forkRepository(token, owner, repo) { try { const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/forks`, { method: "POST", headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); if (response.status === 202) { const forkData = await response.json(); logger.success(`Forked ${owner}/${repo} to ${forkData.full_name}`); return forkData.full_name; } logger.error(`Failed to fork repository: ${response.statusText}`); return null; } catch (error) { logger.error(`Failed to fork repository: ${error.message}`); return null; } } async function branchExists(token, owner, repo, branch) { try { const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/branches/${branch}`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); return response.status === 200; } catch (error) { return false; } } async function createBranch(token, owner, repo, branch, baseBranch = "main") { try { const baseResponse = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/git/ref/heads/${baseBranch}`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); if (baseResponse.status !== 200) { logger.error( `Failed to get base branch ${baseBranch}: ${baseResponse.statusText}` ); return false; } const baseData = await baseResponse.json(); const sha = baseData.object.sha; const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/git/refs`, { method: "POST", headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", "Content-Type": "application/json" }, body: JSON.stringify({ ref: `refs/heads/${branch}`, sha }) } ); if (response.status === 201) { logger.success(`Created branch ${branch} in ${owner}/${repo}`); return true; } logger.error(`Failed to create branch: ${response.statusText}`); return false; } catch (error) { logger.error(`Failed to create branch: ${error.message}`); return false; } } async function getFileContent(token, owner, repo, path4, branch = "main") { try { const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/contents/${path4}?ref=${branch}`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); if (response.status === 200) { const data = await response.json(); return Buffer.from(data.content, "base64").toString("utf-8"); } return null; } catch (error) { return null; } } async function updateFile(token, owner, repo, path4, content, message, branch = "main") { try { const existingContent = await getFileContent( token, owner, repo, path4, branch ); const method = existingContent !== null ? "PUT" : "POST"; let sha; if (existingContent !== null) { const response2 = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/contents/${path4}?ref=${branch}`, { headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json" } } ); if (response2.status === 200) { const data = await response2.json(); sha = data.sha; } } const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/contents/${path4}`, { method, headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", "Content-Type": "application/json" }, body: JSON.stringify({ message, content: Buffer.from(content).toString("base64"), branch, sha }) } ); if (response.status === 200 || response.status === 201) { logger.success( `${existingContent !== null ? "Updated" : "Created"} file ${path4} in ${owner}/${repo}` ); return true; } logger.error( `Failed to ${existingContent !== null ? "update" : "create"} file: ${response.statusText}` ); return false; } catch (error) { logger.error(`Failed to update file: ${error.message}`); return false; } } async function createPullRequest(token, owner, repo, title, body, head, base = "main") { try { const response = await fetch( `${GITHUB_API_URL}/repos/${owner}/${repo}/pulls`, { method: "POST", headers: { Authorization: `token ${token}`, Accept: "application/vnd.github.v3+json", "Content-Type": "application/json" }, body: JSON.stringify({ title, body, head, base }) } ); if (response.status === 201) { const data = await response.json(); logger.success(`Created pull request: ${data.html_url}`); return data.html_url; } logger.error(`Failed to create pull request: ${response.statusText}`); return null; } catch (error) { logger.error(`Failed to create pull request: ${error.message}`); return null; } } async function getGitHubCredentials() { if (process.env.GITHUB_USERNAME && process.env.GITHUB_TOKEN) { const isValid2 = await validateGitHubToken(process.env.GITHUB_TOKEN); if (isValid2) { return { username: process.env.GITHUB_USERNAME, token: process.env.GITHUB_TOKEN }; } } const prompt = await import("./prompts-AYEGBXQG.js"); const { username } = await prompt.default({ type: "text", name: "username", message: "Enter your GitHub username:", validate: (value) => value ? true : "Username is required" }); if (!username) { return null; } const { token } = await prompt.default({ type: "password", name: "token", message: "Enter your GitHub Personal Access Token (with repo scope):", validate: (value) => value ? true : "Token is required" }); if (!token) { return null; } const isValid = await validateGitHubToken(token); if (!isValid) { logger.error("Invalid GitHub token"); return null; } const envFile = path.join(os.homedir(), ".eliza", ".env"); const envDir = path.dirname(envFile); await fs.mkdir(envDir, { recursive: true }); try { let envContent = ""; try { envContent = await fs.readFile(envFile, "utf-8"); if (!envContent.endsWith("\n")) { envContent += "\n"; } } catch (error) { envContent = "# Environment variables for Eliza\n\n"; } envContent += `GITHUB_USERNAME=${username} `; envContent += `GITHUB_TOKEN=${token} `; await fs.writeFile(envFile, envContent); logger.success(`GitHub credentials saved to ${envFile}`); } catch (error) { logger.warn(`Failed to save GitHub credentials: ${error.message}`); } return { username, token }; } // src/utils/plugin-env.ts import { promises as fs2, existsSync } from "node:fs"; import os2 from "node:os"; import path2 from "node:path"; var import_dotenv = __toESM(require_main(), 1); var PLUGIN_ENV_REQUIREMENTS = { "@elizaos/plugin-openai": [ { name: "OPENAI_API_KEY", description: "OpenAI API key", required: true }, { name: "OPENAI_ORG_ID", description: "OpenAI organization ID", required: false } ], "@elizaos/plugin-anthropic": [ { name: "ANTHROPIC_API_KEY", description: "Anthropic API key", required: true }, { name: "ANTHROPIC_SMALL_MODEL", description: "Anthropic small model name", required: false, default: "claude-3-haiku-20240307" }, { name: "ANTHROPIC_LARGE_MODEL", description: "Anthropic large model name", required: false, default: "claude-3-opus-20240229" } ], "@elizaos/plugin-telegram": [ { name: "TELEGRAM_BOT_TOKEN", description: "Telegram bot token", required: true } ], "@elizaos/plugin-twitter": [ { name: "TWITTER_USERNAME", description: "Twitter username", required: true }, { name: "TWITTER_PASSWORD", description: "Twitter password", required: true }, { name: "TWITTER_EMAIL", description: "Twitter email", required: false }, { name: "TWITTER_2FA_SECRET", description: "Twitter 2FA secret", required: false } ] }; var ENV_LOCATIONS = [ ".env", // Current directory path2.join(os2.homedir(), ".env"), // Home directory path2.join(os2.homedir(), ".eliza", ".env") // Global Eliza config ]; function isEnvSet(name) { return process.env[name] !== void 0; } async function loadAllEnvFiles() { for (const location of ENV_LOCATIONS) { try { await fs2.stat(location); import_dotenv.default.config({ path: location }); logger.info(`Loaded environment variables from ${location}`); } catch (error) { } } } function getPluginEnvRequirements(pluginName) { return PLUGIN_ENV_REQUIREMENTS[pluginName] || []; } function checkPluginEnvRequirements(pluginName) { const requirements = getPluginEnvRequirements(pluginName); const missing = requirements.filter( (req) => req.required && !isEnvSet(req.name) ); return { missing, all: requirements }; } async function ensurePluginEnvRequirements(pluginName, interactive = true, envFile = path2.join(os2.homedir(), ".eliza", ".env")) { await loadAllEnvFiles(); const { missing, all } = checkPluginEnvRequirements(pluginName); all.forEach((req) => { if (req.default && !isEnvSet(req.name)) { process.env[req.name] = req.default; logger.info(`Using default value for ${req.name}: ${req.default}`); } }); const { missing: stillMissing } = checkPluginEnvRequirements(pluginName); if (stillMissing.length === 0) { return true; } if (!interactive) { logger.warn(`Missing required environment variables for ${pluginName}:`); stillMissing.forEach((req) => { logger.warn(` - ${req.name}: ${req.description}`); }); return false; } const envDir = path2.dirname(envFile); await fs2.mkdir(envDir, { recursive: true }); let envContent = ""; try { envContent = await fs2.readFile(envFile, "utf-8"); if (!envContent.endsWith("\n")) { envContent += "\n"; } } catch (error) { envContent = "# Environment variables for Eliza\n\n"; } const prompt = await import("./prompts-AYEGBXQG.js"); logger.info(`Setting up environment variables for ${pluginName}...`); for (const req of stillMissing) { const { value } = await prompt.default({ type: "text", name: "value", message: `Enter ${req.name} (${req.description})`, validate: (value2) => req.required && !value2 ? "This field is required" : true }); if (value) { process.env[req.name] = value; envContent += `${req.name}=${value} `; } } await fs2.writeFile(envFile, envContent); logger.success(`Environment variables for ${pluginName} saved to ${envFile}`); return true; } // src/commands/plugins.ts var import_prompts = __toESM(require_prompts(), 1); var plugins = new Command().name("plugins").description("manage ElizaOS plugins"); plugins.command("list").description("list available plugins").option("-t, --type <type>", "filter by type (adapter, client, plugin)").action(async (opts) => { try { const registry = await getRegistryIndex(); const plugins2 = Object.keys(registry).filter((name) => !opts.type || name.includes(opts.type)).sort(); logger.info("\nAvailable plugins:"); for (const plugin of plugins2) { logger.info(` ${plugin}`); } logger.info(""); } catch (error) { handleError(error); } }); plugins.command("add").description("add a plugin").argument("<plugin>", "plugin name").option("--no-env-prompt", "Skip prompting for environment variables").action(async (plugin, opts) => { try { const cwd = process.cwd(); const config = await getConfig(cwd); if (!config) { logger.error("No project.json found. Please run init first."); process.exit(1); } const repo = await getPluginRepository(plugin); if (!repo) { logger.error(`Plugin ${plugin} not found in registry`); process.exit(1); } if (!config.plugins.installed.includes(plugin)) { config.plugins.installed.push(plugin); } logger.info(`Installing ${plugin}...`); await installPlugin(repo, cwd); if (opts.envPrompt !== false) { await ensurePluginEnvRequirements(plugin, true); } logger.success(`Successfully installed ${plugin}`); } catch (error) { handleError(error); } }); plugins.command("remove").description("remove a plugin").argument("<plugin>", "plugin name").action(async (plugin, _opts) => { try { const cwd = process.cwd(); const config = await getConfig(cwd); if (!config) { logger.error("No project.json found. Please run init first."); process.exit(1); } config.plugins.installed = config.plugins.installed.filter( (p) => p !== plugin ); logger.info(`Removing ${plugin}...`); await execa("bun", ["remove", plugin], { cwd, stdio: "inherit" }); logger.success(`Successfully removed ${plugin}`); } catch (error) { handleError(error); } }); plugins.command("update").description("update plugins").option("-p, --plugin <plugin>", "specific plugin to update").action(async (opts) => { try { const cwd = process.cwd(); const config = await getConfig(cwd); if (!config) { logger.error("No project.json found. Please run init first."); process.exit(1); } const _registry = await getRegistryIndex(); const plugins2 = opts.plugin ? [opts.plugin] : config.plugins.installed; for (const plugin of plugins2) { const repo = await getPluginRepository(plugin); if (!repo) { logger.warn(`Plugin ${plugin} not found in registry, skipping`); continue; } logger.info(`Updating ${plugin}...`); await execa("bun", ["update", plugin], { cwd, stdio: "inherit" }); } logger.success("Plugins updated successfully"); } catch (error) { handleError(error); } }); plugins.command("create").description("create a new plugin").option("-d, --dir <dir>", "installation directory", ".").action(async (opts) => { try { const { name } = await (0, import_prompts.default)({ type: "text", name: "name", message: "What would you like to name your plugin?", validate: (value) => value.length > 0 || "Plugin name is required" }); if (!name) { process.exit(0); } const targetDir = opts.dir === "." ? path3.resolve(name) : path3.resolve(opts.dir); if (!existsSync2(targetDir)) { await fs3.mkdir(targetDir, { recursive: true }); } else { const files = await fs3.readdir(targetDir); const isEmpty = files.length === 0 || files.every((f) => f.startsWith(".")); if (!isEmpty) { const { proceed } = await (0, import_prompts.default)({ type: "confirm", name: "proceed", message: "Directory is not empty. Continue anyway?", initial: false }); if (!proceed) { process.exit(0); } } } const pluginName = name.startsWith("@elizaos/plugin-") ? name : `@elizaos/plugin-${name}`; await copyTemplate("plugin", targetDir, pluginName); logger.info("Installing dependencies..."); try { await runBunCommand(["install"], targetDir); logger.success("Dependencies installed successfully!"); } catch (_error) { logger.warn( "Failed to install dependencies automatically. Please run 'bun install' manually." ); } logger.success("Plugin created successfully!"); logger.info(` Next steps: 1. ${source_default.cyan(`cd ${name}`)} to navigate to your plugin directory 2. Update the plugin code in ${source_default.cyan("src/index.ts")} 3. Run ${source_default.cyan("bun dev")} to start development 4. Run ${source_default.cyan("bun build")} to build your plugin`); } catch (error) { handleError(error); } }); plugins.command("deploy").description("deploy a plugin to GitHub").option( "-r, --registry <registry>", "target registry", "elizaos-plugins/registry" ).option("-n, --npm", "publish to npm instead of GitHub", false).action(async (opts) => { try { const cwd = process.cwd(); const packageJsonPath = path3.join(cwd, "package.json"); if (!existsSync2(packageJsonPath)) { logger.error("No package.json found in current directory."); process.exit(1); } const packageJsonContent = await fs3.readFile(packageJsonPath, "utf-8"); const packageJson = JSON.parse(packageJsonContent); if (!packageJson.name || !packageJson.version) { logger.error("Invalid package.json: missing name or version."); process.exit(1); } if (!packageJson.name.includes("plugin-")) { logger.warn( "This doesn't appear to be an ElizaOS plugin. Package name should include 'plugin-'." ); const { proceed } = await (0, import_prompts.default)({ type: "confirm", name: "proceed", message: "Proceed anyway?", initial: false }); if (!proceed) { process.exit(0); } } const cliPackageJsonPath = path3.resolve( __dirname, "../../../package.json" ); const cliPackageJsonContent = await fs3.readFile( cliPackageJsonPath, "utf-8" ); const cliPackageJson = JSON.parse(cliPackageJsonContent); const cliVersion = cliPackageJson.version || "0.0.0"; if (opts.npm) { logger.info("Publishing to npm..."); try { await execa("npm", ["whoami"], { stdio: "inherit" }); } catch (error) { logger.error("Not logged in to npm. Please run 'npm login' first."); process.exit(1); } logger.info("Building package..."); await execa("npm", ["run", "build"], { cwd, stdio: "inherit" }); logger.info("Publishing to npm..."); await execa("npm", ["publish"], { cwd, stdio: "inherit" }); logger.success( `Successfully published ${packageJson.name}@${packageJson.version} to npm` ); return; } logger.info("Deploying to GitHub..."); const credentials = await getGitHubCredentials(); if (!credentials) { logger.error("Failed to get GitHub credentials."); process.exit(1); } const [registryOwner, registryRepo] = opts.registry.split("/"); if (!registryOwner || !registryRepo) { logger.error("Invalid registry format. Expected 'owner/repo'."); process.exit(1); } const registryFullName = `${registryOwner}/${registryRepo}`; logger.info(`Checking for fork of ${registryFullName}...`); const hasFork = await forkExists( credentials.token, registryOwner, registryRepo, credentials.username ); let forkFullName; if (!hasFork) { logger.info(`Creating fork of ${registryFullName}...`); const fork = await forkRepository( credentials.token, registryOwner, registryRepo ); if (!fork) { logger.error("Failed to fork registry repository."); process.exit(1); } forkFullName = fork; } else { forkFullName = `${credentials.username}/${registryRepo}`; logger.info(`Using existing fork: ${forkFullName}`); } const branchName = `plugin-${packageJson.name.replace(/^@elizaos\//, "")}-${packageJson.version}`; const branchAlreadyExists = await branchExists( credentials.token, credentials.username, registryRepo, branchName ); if (branchAlreadyExists) { logger.warn(`Branch ${branchName} already exists.`); const { proceed } = await (0, import_prompts.default)({ type: "confirm", name: "proceed", message: "Use existing branch? This might overwrite previous changes.", initial: false }); if (!proceed) { process.exit(0); } } else { logger.info(`Creating branch ${branchName}...`); const branchCreated = await createBranch( credentials.token, credentials.username, registryRepo, branchName ); if (!branchCreated) { logger.error("Failed to create branch."); process.exit(1); } } const packageIndexPath = `packages/${packageJson.name.replace(/^@elizaos\//, "")}.json`; const existingPackageContent = await getFileContent( credentials.token, registryOwner, registryRepo, packageIndexPath ); let packageData; if (existingPackageContent) { packageData = JSON.parse(existingPackageContent); logger.info(`Found existing package data: ${packageJson.name}`); if (packageData.versions?.includes(packageJson.version)) { logger.error( `Version ${packageJson.version} already exists in registry.` ); logger.error( "Please increment your package version before deploying." ); process.exit(1); } packageData.versions = packageData.versions || []; packageData.versions.push(packageJson.version); packageData.latestVersion = packageJson.version; packageData.runtimeVersion = cliVersion; } else { logger.info(`Creating new package entry for ${packageJson.name}`); packageData = { name: packageJson.name, description: packageJson.description || "", author: packageJson.author || "", repository: packageJson.repository?.url || "", versions: [packageJson.version], latestVersion: packageJson.version, runtimeVersion: cliVersion, maintainer: credentials.username }; } logger.info(`Updating package index for ${packageJson.name}...`); const packageIndexUpdated = await updateFile( credentials.token, credentials.username, registryRepo, packageIndexPath, JSON.stringify(packageData, null, 2), `Update ${packageJson.name} to version ${packageJson.version}`, branchName ); if (!packageIndexUpdated) { logger.error("Failed to update package index."); process.exit(1); } const registryIndexPath = "index.json"; const existingRegistryContent = await getFileContent( credentials.token, credentials.username, registryRepo, registryIndexPath, branchName ) || await getFileContent( credentials.token, registryOwner, registryRepo, registryIndexPath ); let registryData = {}; if (existingRegistryContent) { registryData = JSON.parse(existingRegistryContent); } let repoUrl = packageJson.repository?.url || ""; if (repoUrl.startsWith("git+")) { repoUrl = repoUrl.substring(4); } if (repoUrl.endsWith(".git")) { repoUrl = repoUrl.slice(0, -4); } if (!repoUrl) { repoUrl = `https://github.com/${registryOwner}/${packageJson.name.replace(/^@elizaos\//, "")}`; } registryData[packageJson.name] = repoUrl; const sortedRegistryData = {}; Object.keys(registryData).sort().forEach((key) => { sortedRegistryData[key] = registryData[key]; }); logger.info("Updating registry index..."); const registryIndexUpdated = await updateFile( credentials.token, credentials.username, registryRepo, registryIndexPath, JSON.stringify(sortedRegistryData, null, 2), `Add ${packageJson.name}@${packageJson.version} to registry`, branchName ); if (!registryIndexUpdated) { logger.error("Failed to update registry index."); process.exit(1); } logger.info("Creating pull request..."); const pullRequestCreated = await createPullRequest( credentials.token, registryOwner, registryRepo, `Add ${packageJson.name}@${packageJson.version} to registry`, `This PR adds ${packageJson.name} version ${packageJson.version} to the registry. - Package name: ${packageJson.name} - Version: ${packageJson.version} - Runtime version: ${cliVersion} - Description: ${packageJson.description || "No description provided"} - Repository: ${repoUrl} Submitted by: @${credentials.username}`, `${credentials.username}:${branchName}`, "main" ); if (!pullRequestCreated) { logger.error("Failed to create pull request."); process.exit(1); } logger.success( `Successfully created pull request for ${packageJson.name}@${packageJson.version}` ); logger.info( `Your plugin will be available in the registry after the PR is merged: ${pullRequestCreated}` ); } catch (error) { handleError(error); } }); export { plugins }; //# sourceMappingURL=chunk-HO7BRZ6V.js.map