create-eliza
Version:
Initialize an Eliza project
872 lines (863 loc) • 27.1 kB
JavaScript
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
import {
copyTemplate,
execa,
fetch,
getConfig,
getPluginRepository,
getRegistryIndex,
installPlugin,
runBunCommand
} from "./chunk-2ILPP27V.js";
import {
require_main
} from "./chunk-ZMJ3QLUC.js";
import {
source_default
} from "./chunk-BY3DNMXE.js";
import {
logger
} from "./chunk-7NLMLE5E.js";
import {
require_prompts
} from "./chunk-OGSHIQ3J.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) {
(void 0)(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) {
(void 0)(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) {
(void 0)(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) {
(void 0)(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) {
(void 0)(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) {
(void 0)(error);
}
});
export {
plugins
};
//# sourceMappingURL=chunk-LTKBIDQ7.js.map