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 -1
View File
@@ -8,7 +8,7 @@ import type { CodexInstallResult } from "./install-codex"
import type { InstallArgs } from "./types"
const codexResult: CodexInstallResult = {
marketplaceName: "code-yeongyu-codex-plugins",
marketplaceName: "sisyphuslabs",
installed: [],
configPath: "/tmp/codex-config.toml",
codexHome: "/tmp/codex-home",
+1 -1
View File
@@ -138,7 +138,7 @@ describe("runCliInstaller", () => {
installed: [],
configPath: "/tmp/codex-config.toml",
codexHome: "/tmp/codex-home",
marketplaceName: "code-yeongyu-codex-plugins",
marketplaceName: "sisyphuslabs",
})
const args: InstallArgs = {
@@ -14,16 +14,26 @@ describe("codex-config-toml", () => {
await updateCodexConfig({
configPath,
repoRoot: "/repo/packages/omo-codex",
marketplaceName: "code-yeongyu-codex-plugins",
marketplaceName: "sisyphuslabs",
marketplaceSource: {
sourceType: "git",
source: "https://github.com/code-yeongyu/lazycodex.git",
ref: "main",
},
pluginNames: ["omo"],
trustedHookStates: [{ key: "omo@code-yeongyu-codex-plugins:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:abc" }],
trustedHookStates: [{ key: "omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:abc" }],
})
await updateCodexConfig({
configPath,
repoRoot: "/repo/packages/omo-codex",
marketplaceName: "code-yeongyu-codex-plugins",
marketplaceName: "sisyphuslabs",
marketplaceSource: {
sourceType: "git",
source: "https://github.com/code-yeongyu/lazycodex.git",
ref: "main",
},
pluginNames: ["omo"],
trustedHookStates: [{ key: "omo@code-yeongyu-codex-plugins:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:abc" }],
trustedHookStates: [{ key: "omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0", trustedHash: "sha256:abc" }],
})
// then
@@ -31,8 +41,14 @@ describe("codex-config-toml", () => {
expect(content).toContain("[features]")
expect(content).toContain("plugins = true")
expect(content).toContain("plugin_hooks = true")
expect(content).toContain("[marketplaces.code-yeongyu-codex-plugins]")
expect(content).toContain("[plugins.\"omo@code-yeongyu-codex-plugins\"]")
expect(content).toContain("[hooks.state.\"omo@code-yeongyu-codex-plugins:hooks/hooks.json:post_tool_use:0:0\"]")
expect(content).toContain("[marketplaces.sisyphuslabs]")
expect(content).toContain('source_type = "git"')
expect(content).toContain('source = "https://github.com/code-yeongyu/lazycodex.git"')
expect(content).toContain('ref = "main"')
expect(content).toContain("[plugins.\"omo@sisyphuslabs\"]")
expect(content).toContain("[hooks.state.\"omo@sisyphuslabs:hooks/hooks.json:post_tool_use:0:0\"]")
expect(content).not.toContain("[marketplaces.lazycodex]")
expect(content).not.toContain("code-yeongyu-codex-plugins")
expect(content).not.toContain('source_type = "local"')
})
})
+15 -11
View File
@@ -1,11 +1,12 @@
import { mkdir, readFile, writeFile } from "node:fs/promises"
import { dirname } from "node:path"
import type { TrustedHookState } from "./types"
import type { CodexMarketplaceSource, TrustedHookState } from "./types"
export async function updateCodexConfig(input: {
readonly configPath: string
readonly repoRoot: string
readonly marketplaceName: string
readonly marketplaceSource: CodexMarketplaceSource
readonly pluginNames: readonly string[]
readonly trustedHookStates?: readonly TrustedHookState[]
}): Promise<void> {
@@ -18,7 +19,7 @@ export async function updateCodexConfig(input: {
config = removeStaleMarketplaceHookStateBlocks(config, input.marketplaceName, pluginSet)
config = ensureFeatureEnabled(config, "plugins")
config = ensureFeatureEnabled(config, "plugin_hooks")
config = ensureMarketplaceBlock(config, input.marketplaceName, input.repoRoot)
config = ensureMarketplaceBlock(config, input.marketplaceName, input.marketplaceSource)
for (const pluginName of input.pluginNames) {
config = ensurePluginEnabled(config, `${pluginName}@${input.marketplaceName}`)
}
@@ -60,18 +61,21 @@ function ensureFeatureEnabled(config: string, featureName: string): string {
return replaceOrInsertSetting(config, section, featureName, "true")
}
function ensureMarketplaceBlock(config: string, marketplaceName: string, repoRoot: string): string {
function ensureMarketplaceBlock(config: string, marketplaceName: string, source: CodexMarketplaceSource): string {
const header = `marketplaces.${marketplaceName}`
if (findTomlSection(config, header)) return config
const block = [
`[${header}]`,
`last_updated = "${new Date().toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
`source_type = ${JSON.stringify(source.sourceType)}`,
`source = ${JSON.stringify(source.source)}`,
`ref = ${JSON.stringify(source.ref)}`,
"",
].join("\n")
const section = findTomlSection(config, header)
if (section) return config.slice(0, section.start) + block + config.slice(section.end)
return appendBlock(
config,
[
`[${header}]`,
`last_updated = "${new Date().toISOString().replace(/\.\d{3}Z$/, "Z")}"`,
'source_type = "local"',
`source = ${JSON.stringify(repoRoot)}`,
"",
].join("\n"),
block,
)
}
@@ -14,7 +14,7 @@ describe("codex-hook-trust", () => {
// when
const states = await trustedHookStatesForPlugin({
marketplaceName: "code-yeongyu-codex-plugins",
marketplaceName: "sisyphuslabs",
pluginName: "omo",
pluginRoot,
})
@@ -11,7 +11,7 @@ describe("codex-marketplace", () => {
const pkgRoot = join(root, "packages", "omo-codex")
const pluginRoot = join(pkgRoot, "plugin", ".codex-plugin")
await mkdir(pluginRoot, { recursive: true })
await writeFile(join(pkgRoot, "marketplace.json"), JSON.stringify({ name: "code-yeongyu-codex-plugins", plugins: [{ name: "omo", source: "./plugins/omo" }] }))
await writeFile(join(pkgRoot, "marketplace.json"), JSON.stringify({ name: "sisyphuslabs", plugins: [{ name: "omo", source: "./plugins/omo" }] }))
await writeFile(join(pluginRoot, "plugin.json"), JSON.stringify({ name: "omo", version: "0.1.0" }))
// when
@@ -20,7 +20,7 @@ describe("codex-marketplace", () => {
const manifest = await readPluginManifest(sourcePath)
// then
expect(marketplace.name).toBe("code-yeongyu-codex-plugins")
expect(marketplace.name).toBe("sisyphuslabs")
expect(sourcePath).toBe(join(pkgRoot, "plugin"))
expect(manifest.name).toBe("omo")
})
@@ -30,7 +30,7 @@ describe("codex-marketplace", () => {
const root = await mkdtemp(join(tmpdir(), "omo-codex-marketplace-"))
const pkgRoot = join(root, "packages", "omo-codex")
await mkdir(pkgRoot, { recursive: true })
await writeFile(join(pkgRoot, "marketplace.json"), JSON.stringify({ name: "code-yeongyu-codex-plugins", plugins: [{ name: "omo", source: "./../escape" }] }))
await writeFile(join(pkgRoot, "marketplace.json"), JSON.stringify({ name: "sisyphuslabs", plugins: [{ name: "omo", source: "./../escape" }] }))
// when
const action = readMarketplace(root)
+9 -3
View File
@@ -19,16 +19,22 @@ describe("install-codex", () => {
const second = await runCodexInstaller({ codexHome, binDir, repoRoot, runCommand: async () => undefined })
// then
expect(first.marketplaceName).toBe("code-yeongyu-codex-plugins")
expect(first.marketplaceName).toBe("sisyphuslabs")
expect(second.installed.length).toBe(1)
const configContent = await readFile(join(codexHome, "config.toml"), "utf8")
expect(configContent).toContain("[features]")
expect(configContent).toContain("[marketplaces.code-yeongyu-codex-plugins]")
expect(configContent).toContain("[plugins.\"omo@code-yeongyu-codex-plugins\"]")
expect(configContent).toContain("[marketplaces.sisyphuslabs]")
expect(configContent).toContain('source_type = "git"')
expect(configContent).toContain('source = "https://github.com/code-yeongyu/lazycodex.git"')
expect(configContent).toContain('ref = "main"')
expect(configContent).toContain("[plugins.\"omo@sisyphuslabs\"]")
expect(configContent).toContain("[hooks.state.")
expect(configContent).not.toContain("code-yeongyu-codex-plugins")
expect(configContent).not.toContain("[marketplaces.lazycodex]")
const pluginPath = first.installed[0]?.path
expect(pluginPath).toBeDefined()
expect(pluginPath).toContain(join("plugins", "cache", "sisyphuslabs", "omo"))
const stats = await stat(pluginPath ?? "")
expect(stats.isDirectory()).toBe(true)
})
+7
View File
@@ -9,6 +9,12 @@ import { readMarketplace, readPluginManifest, resolvePluginSource, validatePathS
import { defaultRunCommand } from "./codex-process"
import type { CodexInstallOptions, CodexInstallResult, InstalledPlugin } from "./types"
const LAZYCODEX_MARKETPLACE_SOURCE = {
sourceType: "git",
source: "https://github.com/code-yeongyu/lazycodex.git",
ref: "main",
} as const
export async function runCodexInstaller(options: CodexInstallOptions = {}): Promise<CodexInstallResult> {
const repoRoot = resolve(options.repoRoot ?? findRepoRootFromImporter(import.meta.dir))
const codexHome = resolve(options.codexHome ?? process.env.CODEX_HOME ?? join(homedir(), ".codex"))
@@ -78,6 +84,7 @@ export async function runCodexInstaller(options: CodexInstallOptions = {}): Prom
configPath,
repoRoot: codexPackageRoot,
marketplaceName: marketplace.name,
marketplaceSource: LAZYCODEX_MARKETPLACE_SOURCE,
pluginNames: marketplace.plugins.map((plugin) => plugin.name),
trustedHookStates,
})
+6
View File
@@ -30,6 +30,12 @@ export interface TrustedHookState {
readonly trustedHash: string
}
export interface CodexMarketplaceSource {
readonly sourceType: "git"
readonly source: string
readonly ref: string
}
export interface CommandRunOptions {
readonly cwd: string
}
+1 -3
View File
@@ -1,8 +1,6 @@
/// <reference types="bun-types" />
import { describe, expect, test } from "bun:test"
import { readFile } from "node:fs/promises"
import path from "node:path"
import { resolveInstallArgs } from "./cli-program"
describe("install platform resolution", () => {
@@ -85,7 +83,7 @@ describe("install platform resolution", () => {
test("defines Commander choices so invalid --platform values are rejected", async () => {
// given
const cliProgramSource = await readFile(path.resolve(import.meta.dir, "cli-program.ts"), "utf-8")
const cliProgramSource = await Bun.file(new URL("./cli-program.ts", import.meta.url)).text()
// when
const installBlock = cliProgramSource.match(/program\s*\n\s*\.command\("install"\)([\s\S]*?)\.action\(/)