feat(omo-codex): install through sisyphuslabs marketplace

This commit is contained in:
YeonGyu-Kim
2026-05-27 18:48:33 +09:00
parent a3567056c0
commit 56f39d60fc
35 changed files with 620 additions and 305 deletions
@@ -1,60 +1,11 @@
import assert from "node:assert/strict";
import { mkdir, readFile, readlink, stat, writeFile } from "node:fs/promises";
import { dirname } from "node:path";
import { join } from "node:path";
import test from "node:test";
import { tmpdir } from "node:os";
import { mkdtemp } from "node:fs/promises";
import { installMarketplaceLocally } from "./install-local.mjs";
import { linkCachedPluginBins } from "./install/cache.mjs";
async function makeTempDir() {
return mkdtemp(join(tmpdir(), "codex-plugins-install-"));
}
async function writeJson(path, value) {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
}
async function writePlugin(root, name, version) {
const pluginRoot = join(root, "plugins", name);
await mkdir(join(pluginRoot, ".codex-plugin"), { recursive: true });
await mkdir(join(pluginRoot, "dist"), { recursive: true });
await mkdir(join(pluginRoot, "hooks"), { recursive: true });
await mkdir(join(pluginRoot, "skills", name), { recursive: true });
await writeJson(join(pluginRoot, ".codex-plugin", "plugin.json"), {
name,
version,
description: `${name} test plugin`,
mcpServers: "./.mcp.json",
hooks: "./hooks/hooks.json",
skills: "./skills/",
});
await writeJson(join(pluginRoot, ".mcp.json"), {
mcpServers: {
[name]: {
command: "node",
args: ["./dist/cli.js", "mcp"],
cwd: ".",
},
},
});
await writeJson(join(pluginRoot, "hooks", "hooks.json"), { hooks: {} });
await writeFile(join(pluginRoot, "skills", name, "SKILL.md"), "---\nname: test\n---\n");
await writeJson(join(pluginRoot, "package.json"), {
name: `@example/${name}`,
version,
bin: {
[name]: "./dist/cli.js",
},
scripts: {
build: "node -e \"require('fs').writeFileSync('dist/cli.js', 'console.log(1)')\"",
},
dependencies: {},
});
}
import { makeTempDir, writeJson, writePlugin } from "./install-test-fixtures.mjs";
test("#given local marketplace #when installing #then copies versioned plugins and enables config", async () => {
const repoRoot = await makeTempDir();
@@ -151,6 +102,35 @@ test("#given local marketplace #when installing #then copies versioned plugins a
assert.doesNotMatch(config, /stale@debug-marketplace/);
});
test("#given sisyphuslabs marketplace #when installing #then registers lazycodex git source", async () => {
const repoRoot = await makeTempDir();
const codexHome = await makeTempDir();
await mkdir(join(repoRoot, ".agents", "plugins"), { recursive: true });
await writeJson(join(repoRoot, ".agents", "plugins", "marketplace.json"), {
name: "sisyphuslabs",
plugins: [{ name: "omo", source: "./plugins/omo" }],
});
await writePlugin(repoRoot, "omo", "0.1.0");
await installMarketplaceLocally({
repoRoot,
codexHome,
runCommand: async () => {},
log: () => {},
});
const config = await readFile(join(codexHome, "config.toml"), "utf8");
assert.match(config, /\[marketplaces\.sisyphuslabs\]/);
assert.match(config, /source_type = "git"/);
assert.match(config, /source = "https:\/\/github\.com\/code-yeongyu\/lazycodex\.git"/);
assert.match(config, /ref = "main"/);
assert.match(config, /\[plugins\."omo@sisyphuslabs"\]\nenabled = true/);
assert.doesNotMatch(config, /\[marketplaces\.lazycodex\]/);
assert.doesNotMatch(config, /code-yeongyu-codex-plugins/);
assert.doesNotMatch(config, /source_type = "local"/);
});
test("#given plugin hooks #when installing #then records trusted hook hashes", async () => {
const repoRoot = await makeTempDir();
const codexHome = await makeTempDir();
@@ -0,0 +1,50 @@
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
export async function makeTempDir() {
return mkdtemp(join(tmpdir(), "omo-codex-install-"));
}
export async function writeJson(path, value) {
await mkdir(dirname(path), { recursive: true });
await writeFile(path, `${JSON.stringify(value, null, 2)}\n`);
}
export async function writePlugin(root, name, version) {
const pluginRoot = join(root, "plugins", name);
await mkdir(join(pluginRoot, ".codex-plugin"), { recursive: true });
await mkdir(join(pluginRoot, "dist"), { recursive: true });
await mkdir(join(pluginRoot, "hooks"), { recursive: true });
await mkdir(join(pluginRoot, "skills", name), { recursive: true });
await writeJson(join(pluginRoot, ".codex-plugin", "plugin.json"), {
name,
version,
description: `${name} test plugin`,
mcpServers: "./.mcp.json",
hooks: "./hooks/hooks.json",
skills: "./skills/",
});
await writeJson(join(pluginRoot, ".mcp.json"), {
mcpServers: {
[name]: {
command: "node",
args: ["./dist/cli.js", "mcp"],
cwd: ".",
},
},
});
await writeJson(join(pluginRoot, "hooks", "hooks.json"), { hooks: {} });
await writeFile(join(pluginRoot, "skills", name, "SKILL.md"), "---\nname: test\n---\n");
await writeJson(join(pluginRoot, "package.json"), {
name: `@example/${name}`,
version,
bin: {
[name]: "./dist/cli.js",
},
scripts: {
build: "node -e \"require('fs').writeFileSync('dist/cli.js', 'console.log(1)')\"",
},
dependencies: {},
});
}
+35 -14
View File
@@ -3,7 +3,20 @@ import { dirname } from "node:path";
import { exists } from "./utils.mjs";
export async function updateCodexConfig({ configPath, repoRoot, marketplaceName, pluginNames, trustedHookStates = [] }) {
const LAZYCODEX_MARKETPLACE_SOURCE = {
sourceType: "git",
source: "https://github.com/code-yeongyu/lazycodex.git",
ref: "main",
};
export async function updateCodexConfig({
configPath,
repoRoot,
marketplaceName,
marketplaceSource = defaultMarketplaceSource(marketplaceName, repoRoot),
pluginNames,
trustedHookStates = [],
}) {
await mkdir(dirname(configPath), { recursive: true });
let config = "";
if (await exists(configPath)) config = await readFile(configPath, "utf8");
@@ -12,7 +25,7 @@ export async function updateCodexConfig({ configPath, repoRoot, marketplaceName,
config = removeStaleMarketplaceHookStateBlocks(config, marketplaceName, new Set(pluginNames));
config = ensureFeatureEnabled(config, "plugins");
config = ensureFeatureEnabled(config, "plugin_hooks");
config = ensureMarketplaceBlock(config, marketplaceName, repoRoot);
config = ensureMarketplaceBlock(config, marketplaceName, marketplaceSource);
for (const pluginName of pluginNames) {
config = ensurePluginEnabled(config, `${pluginName}@${marketplaceName}`);
}
@@ -23,6 +36,14 @@ export async function updateCodexConfig({ configPath, repoRoot, marketplaceName,
await writeFile(configPath, config.trimEnd() + "\n");
}
function defaultMarketplaceSource(marketplaceName, repoRoot) {
if (marketplaceName === "sisyphuslabs") return LAZYCODEX_MARKETPLACE_SOURCE;
return {
sourceType: "local",
source: repoRoot,
};
}
function removeStaleMarketplacePluginBlocks(config, marketplaceName, keepPluginNames) {
return removeTomlSections(config, (header) => {
const pluginKey = parseQuotedPluginHeader(header);
@@ -54,19 +75,19 @@ function ensureFeatureEnabled(config, featureName) {
return replaceOrInsertSetting(config, section, featureName, "true");
}
function ensureMarketplaceBlock(config, marketplaceName, repoRoot) {
function ensureMarketplaceBlock(config, marketplaceName, source) {
const header = `marketplaces.${marketplaceName}`;
if (findTomlSection(config, header)) return config;
return appendBlock(
config,
[
`[${header}]`,
`last_updated = "${new Date().toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
"source_type = \"local\"",
`source = ${JSON.stringify(repoRoot)}`,
"",
].join("\n"),
);
const block = [
`[${header}]`,
`last_updated = "${new Date().toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
`source_type = ${JSON.stringify(source.sourceType)}`,
`source = ${JSON.stringify(source.source)}`,
source.ref === undefined ? null : `ref = ${JSON.stringify(source.ref)}`,
"",
].filter((line) => line !== null).join("\n");
const section = findTomlSection(config, header);
if (section) return config.slice(0, section.start) + block + config.slice(section.end);
return appendBlock(config, block);
}
function ensurePluginEnabled(config, pluginKey) {