Files
oh-my-opencode/packages/omo-codex/scripts/install/agents.mjs
T
YeonGyu-Kim a49acecc3a feat(omo-codex): link bundled component agents into codex home
Discover component .toml agent files and symlink (or copy on Windows)
them into the Codex agents dir during install, recording an installed
agent manifest and wiring agent config_file entries into config.toml.
Add CodexAgentConfig type and support local marketplace source.
2026-05-29 11:18:01 +09:00

85 lines
2.7 KiB
JavaScript

import { basename, join } from "node:path";
import { copyFile, lstat, mkdir, readdir, rm, symlink, writeFile } from "node:fs/promises";
import { exists } from "./utils.mjs";
const MANIFEST_FILE = ".installed-agents.json";
export async function linkCachedPluginAgents({ codexHome, pluginRoot, platform = process.platform }) {
const bundledAgents = await discoverBundledAgents(pluginRoot);
if (bundledAgents.length === 0) {
await writeManifest(pluginRoot, []);
return [];
}
const agentsDir = join(codexHome, "agents");
await mkdir(agentsDir, { recursive: true });
const linked = [];
for (const agentPath of bundledAgents) {
const linkPath = join(agentsDir, basename(agentPath));
if (platform === "win32") {
await replaceWithCopy(linkPath, agentPath);
} else {
await replaceWithSymlink(linkPath, agentPath);
}
linked.push({ name: basename(agentPath), path: linkPath, target: agentPath });
}
await writeManifest(pluginRoot, linked.map((entry) => entry.path));
return linked;
}
async function discoverBundledAgents(pluginRoot) {
const componentsRoot = join(pluginRoot, "components");
if (!(await exists(componentsRoot))) return [];
const componentEntries = await readdir(componentsRoot, { withFileTypes: true });
const agents = [];
for (const entry of componentEntries) {
if (!entry.isDirectory()) continue;
const agentsRoot = join(componentsRoot, entry.name, "agents");
if (!(await exists(agentsRoot))) continue;
const agentEntries = await readdir(agentsRoot, { withFileTypes: true });
for (const file of agentEntries) {
if (!file.isFile() || !file.name.endsWith(".toml")) continue;
agents.push(join(agentsRoot, file.name));
}
}
agents.sort();
return agents;
}
async function replaceWithSymlink(linkPath, target) {
await prepareReplacement(linkPath);
await symlink(target, linkPath);
}
async function replaceWithCopy(linkPath, target) {
await prepareReplacement(linkPath);
await copyFile(target, linkPath);
}
async function prepareReplacement(linkPath) {
if (!(await lstatExists(linkPath))) return;
const entryStat = await lstat(linkPath);
if (entryStat.isDirectory() && !entryStat.isSymbolicLink()) {
throw new Error(`${linkPath} already exists and is a directory; refusing to replace`);
}
await rm(linkPath, { force: true });
}
async function writeManifest(pluginRoot, agentPaths) {
const manifestPath = join(pluginRoot, MANIFEST_FILE);
const payload = { agents: [...agentPaths].sort() };
await writeFile(manifestPath, `${JSON.stringify(payload, null, "\t")}\n`);
}
async function lstatExists(path) {
try {
await lstat(path);
return true;
} catch (error) {
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
throw error;
}
}