#!/usr/bin/env bash set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CODEX_HOME="${CODEX_HOME:-$HOME/.codex}" STAMP="$(date +%Y%m%d-%H%M%S)" BACKUP_DIR="$CODEX_HOME/backups/$STAMP" MANAGED_CONFIG="$REPO_ROOT/templates/global/config-managed.toml" mkdir -p "$CODEX_HOME" "$BACKUP_DIR" "$CODEX_HOME/memories" if [[ -f "$CODEX_HOME/config.toml" ]]; then cp "$CODEX_HOME/config.toml" "$BACKUP_DIR/config.toml.bak" fi if [[ -f "$CODEX_HOME/AGENTS.md" ]]; then cp "$CODEX_HOME/AGENTS.md" "$BACKUP_DIR/AGENTS.md.bak" fi cp "$REPO_ROOT/templates/global/AGENTS.md" "$CODEX_HOME/AGENTS.md" python3 - "$CODEX_HOME/config.toml" "$MANAGED_CONFIG" <<'PY' from pathlib import Path import sys config_path = Path(sys.argv[1]) managed_path = Path(sys.argv[2]) managed_start = "# >>> codex-ops-kit managed block >>>" managed_end = "# <<< codex-ops-kit managed block <<<" # "profile" and the "profiles.*" tables are legacy: Codex 0.144+ refuses to load # a config containing them. They stay in these strip lists (despite no longer # being emitted) so an upgrade from an older kit removes them from a live config. managed_root_keys = { "model", "model_reasoning_effort", "profile", "project_doc_max_bytes", "web_search", } managed_sections = { "history", "profiles.economy", "profiles.balanced", "profiles.deep", } # utf-8-sig drops a leading BOM if one is present. Preserving it risks stranding # the BOM mid-file once content is merged around the block, which is invalid TOML. text = config_path.read_text(encoding="utf-8-sig") if config_path.exists() else "" managed = managed_path.read_text(encoding="utf-8-sig").rstrip() def strip_managed(chunk): """Drop kit-owned keys/tables so only machine-specific settings remain.""" filtered_lines = [] current_section = None for line in chunk.splitlines(): stripped = line.strip() if stripped.startswith("[") and stripped.endswith("]"): current_section = stripped[1:-1].strip() if current_section in managed_sections: continue filtered_lines.append(line) continue if current_section in managed_sections: continue if "=" in line and not line.lstrip().startswith("#"): key = line.split("=", 1)[0].strip() if current_section is None and key in managed_root_keys: continue filtered_lines.append(line) return "\n".join(filtered_lines).strip() # Settings may sit on either side of the managed block -- notably `codex mcp add` # appends [mcp_servers.*] to the end of the file. Keep both sides; rewriting only # what precedes the block silently discards everything after it. if managed_start in text and managed_end in text: before = text.split(managed_start, 1)[0] after = text.split(managed_end, 1)[1] else: before = text after = "" before = strip_managed(before) after = strip_managed(after) block = f"{managed_start}\n{managed}\n{managed_end}\n" parts = [part for part in (before, block.rstrip(), after) if part] config_path.write_text("\n\n".join(parts) + "\n", encoding="utf-8") PY # Profile files. Codex 0.144+ reads per-profile settings from .config.toml # next to config.toml rather than from [profiles.] tables. These files are # wholly kit-managed, so they are replaced outright (after backup). for profile in economy balanced deep; do profile_src="$REPO_ROOT/templates/global/$profile.config.toml" profile_dest="$CODEX_HOME/$profile.config.toml" if [[ -f "$profile_dest" ]]; then cp "$profile_dest" "$BACKUP_DIR/$profile.config.toml.bak" fi cp "$profile_src" "$profile_dest" done mkdir -p "$CODEX_HOME/memories/portable-kit" cp "$REPO_ROOT"/memory/*.md "$CODEX_HOME/memories/portable-kit/" echo "Installed Codex kit into: $CODEX_HOME" echo "Backup created at: $BACKUP_DIR" echo echo "Next steps:" echo "- Defaults match the balanced profile; plain 'codex' needs no flag." echo "- Use 'codex --profile economy' for lighter work and '--profile deep' for hard tasks." echo "- Run bash scripts/audit_codex_home.sh periodically."