feat: add Codex CLI compatibility layer

- flake.nix: devShell with yq-go for config generation
- generate-codex.sh: generates Codex agent TOML, AGENTS.md, and
  config.toml from Claude source files (idempotent, yq-powered)
- install.sh: optional Codex symlinks when ~/.codex exists (skills
  shared via ~/.agents/skills/, agents/config/AGENTS.md to ~/.codex/)
- .gitignore: exclude generated codex/ directory
- README.md: document Codex compatibility setup and model mapping
This commit is contained in:
Bryan Ramos 2026-04-02 08:28:29 -04:00
parent 8d08f9650c
commit d812c7f49a
6 changed files with 287 additions and 0 deletions

3
.gitignore vendored
View file

@ -8,3 +8,6 @@ settings.local.json
# OS noise # OS noise
.DS_Store .DS_Store
Thumbs.db Thumbs.db
# Generated Codex CLI config (derived from Claude source files via generate-codex.sh)
codex/

View file

@ -59,6 +59,35 @@ For simple tasks, agents can be invoked directly:
/agent worker Fix the broken pagination in the user list endpoint /agent worker Fix the broken pagination in the user list endpoint
``` ```
## Codex CLI compatibility
This project also generates configuration for [OpenAI Codex CLI](https://github.com/openai/codex). Claude Code config is the source of truth; Codex config is derived from it.
### Setup
```bash
nix develop # enter devShell with yq
./generate-codex.sh # generate Codex config from Claude source files
./install.sh # installs both Claude and Codex (if ~/.codex exists)
```
### What gets generated
| Source | Generated | Codex location |
|---|---|---|
| `agents/*.md` | `codex/agents/*.toml` | `~/.codex/agents/` |
| `CLAUDE.md` + `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` |
| `settings.json` | `codex/config.toml` | `~/.codex/config.toml` |
| `skills/` | (shared as-is) | `~/.agents/skills/` |
### Model mapping
| Claude Code | Codex CLI |
|---|---|
| `opus` | `o3` |
| `sonnet` | `o4-mini` |
| `haiku` | `o4-mini` |
## Project-specific config ## Project-specific config
Each project repo can extend the team with local config in `.claude/`: Each project repo can extend the team with local config in `.claude/`:

27
flake.lock generated Normal file
View file

@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1775095191,
"narHash": "sha256-CsqRiYbgQyv01LS0NlC7shwzhDhjNDQSrhBX8VuD3nM=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "106eb93cbb9d4e4726bf6bc367a3114f7ed6b32f",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

14
flake.nix Normal file
View file

@ -0,0 +1,14 @@
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { nixpkgs, ... }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = [ pkgs.yq-go ];
};
});
};
}

184
generate-codex.sh Executable file
View file

@ -0,0 +1,184 @@
#!/usr/bin/env bash
set -euo pipefail
# generate-codex.sh — generates Codex CLI config from Claude source files.
# Claude source files are the source of truth; this script derives Codex equivalents.
# Idempotent: safe to run multiple times.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CODEX_DIR="$SCRIPT_DIR/codex"
CODEX_AGENTS_DIR="$CODEX_DIR/agents"
AGENTS_SRC="$SCRIPT_DIR/agents"
RULES_DIR="$SCRIPT_DIR/rules"
CLAUDE_MD="$SCRIPT_DIR/CLAUDE.md"
SETTINGS_JSON="$SCRIPT_DIR/settings.json"
# Create output directories
mkdir -p "$CODEX_DIR"
mkdir -p "$CODEX_AGENTS_DIR"
# Clean existing generated agent TOMLs
rm -f "$CODEX_AGENTS_DIR"/*.toml
# ---------------------------------------------------------------------------
# map_model — maps Claude model name to Codex model name
# ---------------------------------------------------------------------------
map_model() {
local model="$1"
case "$model" in
opus) echo "o3" ;;
sonnet) echo "o4-mini" ;;
haiku) echo "o4-mini" ;;
*) echo "o4-mini" ;;
esac
}
# ---------------------------------------------------------------------------
# map_effort — maps Claude effort level to Codex model_reasoning_effort
# ---------------------------------------------------------------------------
map_effort() {
local effort="$1"
case "$effort" in
low) echo "low" ;;
medium) echo "medium" ;;
high) echo "high" ;;
max) echo "xhigh" ;;
*) echo "medium" ;;
esac
}
# ---------------------------------------------------------------------------
# map_sandbox_mode — determines Codex sandbox_mode from agent frontmatter
# $1 = permissionMode value (plan / acceptEdits / "")
# $2 = tools list (comma-separated)
# ---------------------------------------------------------------------------
map_sandbox_mode() {
local permission_mode="$1"
local tools="$2"
# plan mode is read-only
if [ "$permission_mode" = "plan" ]; then
echo "read-only"
return
fi
# acceptEdits with Write or Edit tool → workspace-write
if [ "$permission_mode" = "acceptEdits" ]; then
if echo "$tools" | grep -qE '\b(Write|Edit)\b'; then
echo "workspace-write"
return
fi
fi
# Default: read-only
echo "read-only"
}
# ---------------------------------------------------------------------------
# generate_agent_toml — converts a single agent .md file to Codex .toml
# ---------------------------------------------------------------------------
generate_agent_toml() {
local src_file="$1"
local agent_basename
agent_basename="$(basename "$src_file" .md)"
local dst_file="$CODEX_AGENTS_DIR/${agent_basename}.toml"
# Extract YAML frontmatter using yq
local frontmatter
frontmatter="$(yq --front-matter=extract '.' "$src_file")"
# Extract individual fields from frontmatter
local name description model effort permission_mode tools disallowed_tools
name="$(echo "$frontmatter" | yq '.name // ""')"
description="$(echo "$frontmatter" | yq '.description // ""')"
model="$(echo "$frontmatter" | yq '.model // ""')"
effort="$(echo "$frontmatter" | yq '.effort // ""')"
permission_mode="$(echo "$frontmatter" | yq '.permissionMode // ""')"
tools="$(echo "$frontmatter" | yq '.tools // ""')"
disallowed_tools="$(echo "$frontmatter" | yq '.disallowedTools // ""')"
# Map to Codex equivalents
local codex_model codex_effort codex_sandbox
codex_model="$(map_model "$model")"
codex_effort="$(map_effort "${effort:-medium}")"
codex_sandbox="$(map_sandbox_mode "$permission_mode" "$tools")"
# Extract markdown body (everything after the closing frontmatter ---)
# The frontmatter block starts at line 1 with --- and ends at the second ---
local body
body="$(awk 'BEGIN{fm=0} /^---$/{if(fm==0){fm=1;next} if(fm==1){fm=2;next}} fm==2{print}' "$src_file")"
# Build developer_instructions: append disallowedTools note if present
local developer_instructions
developer_instructions="$body"
if [ -n "$disallowed_tools" ] && [ "$disallowed_tools" != "null" ]; then
developer_instructions="${developer_instructions}
You do NOT have access to these tools: ${disallowed_tools}"
fi
# Write TOML output
cat > "$dst_file" <<TOML
name = "${name}"
description = "${description}"
model = "${codex_model}"
model_reasoning_effort = "${codex_effort}"
sandbox_mode = "${codex_sandbox}"
developer_instructions = """
${developer_instructions}
"""
TOML
echo "Generated: $dst_file"
}
# ---------------------------------------------------------------------------
# Generate agents
# ---------------------------------------------------------------------------
echo "Generating Codex agent definitions..."
for agent_file in "$AGENTS_SRC"/*.md; do
[ -f "$agent_file" ] || continue
generate_agent_toml "$agent_file"
done
# ---------------------------------------------------------------------------
# Generate AGENTS.md — concatenate CLAUDE.md and rules/*.md (sorted)
# ---------------------------------------------------------------------------
echo ""
echo "Generating codex/AGENTS.md..."
{
echo "# Agent Team Instructions"
echo ""
echo "Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project)."
for rules_file in $(ls "$RULES_DIR"/*.md | sort); do
echo ""
cat "$rules_file"
done
} > "$CODEX_DIR/AGENTS.md"
echo "Generated: $CODEX_DIR/AGENTS.md"
# ---------------------------------------------------------------------------
# Generate config.toml — derive sandbox_mode from settings.json defaultMode
# ---------------------------------------------------------------------------
echo ""
echo "Generating codex/config.toml..."
default_mode="$(yq -r '.permissions.defaultMode // "acceptEdits"' "$SETTINGS_JSON")"
# Map Claude defaultMode to Codex sandbox_mode
case "$default_mode" in
plan) config_sandbox="read-only" ;;
acceptEdits) config_sandbox="workspace-write" ;;
*) config_sandbox="workspace-write" ;;
esac
cat > "$CODEX_DIR/config.toml" <<TOML
model = "o4-mini"
model_reasoning_effort = "medium"
sandbox_mode = "${config_sandbox}"
approval_policy = "on-request"
TOML
echo "Generated: $CODEX_DIR/config.toml"
echo ""
echo "Done. Run ./install.sh to link generated files into ~/.codex/"

View file

@ -122,5 +122,35 @@ create_symlink "$RULES_SRC" "$RULES_DST" "rules"
create_file_symlink "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST" "CLAUDE.md" create_file_symlink "$CLAUDE_MD_SRC" "$CLAUDE_MD_DST" "CLAUDE.md"
create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json" create_file_symlink "$SETTINGS_SRC" "$SETTINGS_DST" "settings.json"
# Codex CLI integration (optional — only if ~/.codex exists)
CODEX_DIR="$HOME/.codex"
CODEX_AGENTS_DIR="$HOME/.agents"
if [ -d "$CODEX_DIR" ]; then
echo ""
echo "Codex CLI detected at $CODEX_DIR"
# Skills shared via ~/.agents/skills/ (Codex discovery path)
mkdir -p "$CODEX_AGENTS_DIR"
create_symlink "$SKILLS_SRC" "$CODEX_AGENTS_DIR/skills" "codex skills"
# Generated agents
if [ -d "$SCRIPT_DIR/codex/agents" ]; then
create_symlink "$SCRIPT_DIR/codex/agents" "$CODEX_DIR/agents" "codex agents"
else
echo "Run ./generate-codex.sh first to generate Codex agent definitions"
fi
# Generated AGENTS.md (symlink to project root for Codex discovery)
if [ -f "$SCRIPT_DIR/codex/AGENTS.md" ]; then
create_file_symlink "$SCRIPT_DIR/codex/AGENTS.md" "$CODEX_DIR/AGENTS.md" "codex AGENTS.md"
fi
# Generated config.toml
if [ -f "$SCRIPT_DIR/codex/config.toml" ]; then
create_file_symlink "$SCRIPT_DIR/codex/config.toml" "$CODEX_DIR/config.toml" "codex config.toml"
fi
fi
echo "" echo ""
echo "Done. Open Claude Code and load the orchestrate skill to begin." echo "Done. Open Claude Code and load the orchestrate skill to begin."