feat(omo-codex): install through sisyphuslabs marketplace
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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"')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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,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\(/)
|
||||
|
||||
Reference in New Issue
Block a user