codex

package
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 6, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package codex implements the Codex CLI adapter for agentsync.

Codex stores user-level configuration under $CODEX_HOME (defaulting to ~/.codex). Unlike Claude (JSON) and OpenCode (JSONC), Codex's primary config file is TOML (config.toml), so MCP servers are projected as TOML tables and merged via the merge-toml-keys strategy (settings.go). Memory, skills, subagents, commands, and hooks land in their own files. See docs/capability-matrix.md for the per-component coverage and documented loss.

Package codex implements the Codex CLI adapter for agentsync.

settings.go: TOML-aware per-key merge for ~/.codex/config.toml. agentsync owns the `[mcp_servers.*]` section; the user's other keys (model, approval_policy, sandbox_mode, [plugins.*], …) are FOREIGN and must survive a write. The merge currency is a map[string]any (same as the JSON key-merge): the rendered op carries JSON (`{"mcp_servers": {...}}`) so the render pipeline's pointer/ownership machinery is format-agnostic, and only the on-disk file is TOML — parsed and re-emitted here.

v1 trade-off: TOML comments and key ordering in the original file are NOT preserved (the file is decoded to a map and re-marshalled). Foreign keys and values are preserved; only formatting/comments are lost. Comment-preserving mutation is deferred to v1.x (matches the README "Known limits", same as opencode.json).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func IngestMCPSpec

func IngestMCPSpec(raw map[string]any) source.MCPServerSpec

IngestMCPSpec translates one Codex-native MCP server table — the value under config.toml `[mcp_servers.<id>]` — into the canonical MCPServerSpec. It is the inverse of codexMCPSpec (Render). A server carrying a URL is canonicalised to the "http" transport; otherwise "stdio". Codex's `http_headers` map back onto canonical `headers`.

func MergeTOML

func MergeTOML(existing []byte, ours map[string]any, ownedPointers []string) ([]byte, error)

MergeTOML merges ours (a JSON-decoded owned subtree, e.g. {"mcp_servers":…}) into existing TOML content, removing ownedPointers no longer present in ours. Foreign top-level keys and tables are preserved. The result is re-emitted as TOML.

Types

type Adapter

type Adapter struct {
	// contains filtered or unexported fields
}

Adapter implements adapter.Adapter (and adapter.PluginIngester) for Codex CLI.

func New

func New(opts Options) *Adapter

New constructs a Codex adapter.

func (*Adapter) Apply

func (a *Adapter) Apply(ops []adapter.FileOp, w adapter.DestWriter) error

Apply routes every destination write through the supplied DestWriter rather than calling iox.AtomicWrite directly. This is the contract that keeps the foreign-collision backup guarantee honest — see the doc on adapter.DestWriter.

func (*Adapter) Capabilities

func (a *Adapter) Capabilities() adapter.Capability

func (*Adapter) Detect

func (a *Adapter) Detect() (bool, error)

func (*Adapter) Ingest

func (a *Adapter) Ingest(scope adapter.Scope, project string) (source.Canonical, error)

Ingest reads Codex's native config files and returns a partial source.Canonical. It is the inverse of Render.

Round-trip note: subagents lose the Claude-side `tools`/`color` frontmatter (Codex agents have no equivalent), so Ingest reconstructs only the `description` + `model` that were written to the agent TOML, plus the body from `developer_instructions`. Project-scope slash commands are never written (global-only), so they don't ingest at project scope either.

func (*Adapter) IngestPlugins

func (a *Adapter) IngestPlugins(scope adapter.Scope, project string) ([]adapter.NativeMarketplace, []adapter.NativePlugin, error)

IngestPlugins discovers the plugins Codex records in ~/.codex/config.toml. The read side of plugin `import`: each enabled plugin is mapped onto an agentsync marketplace source and `marketplace add` + `plugin install` are replayed.

Codex records per-plugin enable-state under quoted `[plugins."<name>@<source>"]` tables (an `enabled` bool; default-on when the field is absent) — the same `name@source` shape as Claude's `enabledPlugins`. Unlike Claude, Codex does NOT record a marketplace's fetch source in a documented config location (marketplaces are added with `codex plugin marketplace add <source>` and read from fixed paths like ~/.agents/plugins/marketplace.json), so this returns NO NativeMarketplaces. `import` then resolves each plugin's marketplace from agentsync's own registered marketplaces (run `agentsync marketplace add <source>` first), warning + skipping any it cannot resolve — exactly how Claude's auto-available built-in marketplace is handled.

Parsing is lenient: a missing config.toml yields no plugins, and a malformed one is treated as "no plugins discovered" rather than failing the whole import. Only a genuine read error (e.g. a permission problem) is surfaced.

func (*Adapter) KeyMergeStrategy

func (a *Adapter) KeyMergeStrategy() string

KeyMergeStrategy is codex's single key-merge strategy: TOML (config.toml), merged via go-toml (not strict JSON). config.toml is codex's ONLY key-merge destination — both [mcp_servers.*] and [hooks.*] live there — so the single strategy the render pipeline uses for orphan-cleanup synthesis is correct for every key-merge path this adapter owns. (Hooks are written as inline config.toml tables rather than a separate hooks.json precisely so there is one strategy; see renderHooks.)

func (*Adapter) Name

func (a *Adapter) Name() string

func (*Adapter) Render

func (a *Adapter) Render(r secrets.Resolved, scope adapter.Scope, project string) ([]adapter.FileOp, []adapter.Skip, error)

Render converts the resolved canonical into FileOps for Codex CLI.

Render projects each plugin's COMPONENTS (MCP, hooks, memory, skills, subagents, commands) to Codex's native paths and intentionally does NOT re-emit `[plugins."<name>@<source>"]` enable-state into ~/.codex/config.toml — `IngestPlugins` reads those tables on `import` for discovery, but they are NEVER written back on apply. This is the same cross-adapter invariant the Claude adapter follows (`PluginIngester` is read-only by design); see `internal/adapter/adapter.go` and `docs/architecture.md` § "PluginIngester (read-only)" for the full rationale. Foreign `[plugins.*]` entries the user set in Codex's UI are preserved by config.toml's merge-toml-keys writer because this render claims no keys under that section.

func (*Adapter) SetStderr

func (a *Adapter) SetStderr(w io.Writer)

SetStderr replaces the warning sink the adapter writes Ingest warnings to, so a CLI command can route adapter warnings through the same styled writer it uses for its own output. See claude.Adapter.SetStderr for the contract.

type Options

type Options struct {
	TargetRoot string // honors AGENTSYNC_TARGET_ROOT (real "/Users/x" in production)
	// LookPath overrides exec.LookPath for testing. nil means use exec.LookPath.
	LookPath func(file string) (string, error)
	// Stderr receives Ingest warnings (lenient-YAML notices, dropped components).
	// nil means os.Stderr.
	Stderr io.Writer
}

Options configure the adapter at construction.

type Paths

type Paths struct {
	ConfigDir  string // ~/.codex
	Config     string // ~/.codex/config.toml (mcp_servers + [hooks.*] + plugin enables live here)
	Memory     string // AGENTS.md path
	SkillsDir  string // ~/.agents/skills (shared cross-agent skills dir)
	AgentsDir  string // ~/.codex/agents (subagent TOML)
	PromptsDir string // ~/.codex/prompts (custom prompts → slash commands)
}

Paths resolves the destination paths for a given (scope, project, target-root).

Note SkillsDir lives under ~/.agents/skills (the cross-agent AGENTS skills directory Codex scans), NOT under ~/.codex — Codex reads personal skills from $HOME/.agents/skills. Everything else is rooted at $CODEX_HOME (~/.codex).

func ResolvePaths

func ResolvePaths(targetRoot, project string, projectScope bool) Paths

ResolvePaths returns the Paths for the given target root and optional project. projectScope=true + non-empty project uses project-local .codex/ dirs and the repo's AGENTS.md / .agents/skills.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL