From d812c7f49a267d731895bce2456389a21c499202 Mon Sep 17 00:00:00 2001 From: Bryan Ramos Date: Thu, 2 Apr 2026 08:28:29 -0400 Subject: [PATCH] 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 --- .gitignore | 3 + README.md | 29 ++++++++ flake.lock | 27 +++++++ flake.nix | 14 ++++ generate-codex.sh | 184 ++++++++++++++++++++++++++++++++++++++++++++++ install.sh | 30 ++++++++ 6 files changed, 287 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix create mode 100755 generate-codex.sh diff --git a/.gitignore b/.gitignore index a3cadf6..0da7350 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ settings.local.json # OS noise .DS_Store Thumbs.db + +# Generated Codex CLI config (derived from Claude source files via generate-codex.sh) +codex/ diff --git a/README.md b/README.md index 6ca7fb7..c8ab5c0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,35 @@ For simple tasks, agents can be invoked directly: /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 Each project repo can extend the team with local config in `.claude/`: diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..6b92e8a --- /dev/null +++ b/flake.lock @@ -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 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..d07f69e --- /dev/null +++ b/flake.nix @@ -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 ]; + }; + }); + }; +} diff --git a/generate-codex.sh b/generate-codex.sh new file mode 100755 index 0000000..8bf99a2 --- /dev/null +++ b/generate-codex.sh @@ -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" < "$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" <