diff --git a/README.md b/README.md index 9ece15e..dfb684f 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,11 @@ Run the audit script periodically. If session storage balloons again, archive th - `balanced`: everyday profile - `deep`: higher-effort work when architecture or debugging really needs it -These profiles are defined in `templates/global/config.toml`. The installer preserves machine-specific plugin and trust settings while merging the managed cost and memory block into your live `~/.codex/config.toml`. +Each profile is defined in its own `templates/global/.config.toml` and is installed to `~/.codex/.config.toml`. Select one with `codex --profile `; plain `codex` uses the `config.toml` defaults, which match `balanced`. + +Codex 0.144+ rejects the legacy top-level `profile = "..."` key and `[profiles.]` tables, so the kit no longer emits them and strips them from a live config on upgrade. + +The installer merges the managed cost and memory block into your live `~/.codex/config.toml`, preserving machine-specific settings that appear *before* the managed block. ## New Machine Rollout diff --git a/scripts/install_codex_kit.sh b/scripts/install_codex_kit.sh index 14654c2..53d0408 100755 --- a/scripts/install_codex_kit.sh +++ b/scripts/install_codex_kit.sh @@ -28,6 +28,9 @@ 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", @@ -42,46 +45,70 @@ managed_sections = { "profiles.deep", } -text = config_path.read_text() if config_path.exists() else "" -managed = managed_path.read_text().rstrip() +# 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() -if managed_start in text and managed_end in text: - before = text.split(managed_start, 1)[0].rstrip() -else: - before = text.rstrip() -filtered_lines = [] -current_section = None +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 -for line in before.splitlines(): - stripped = line.strip() - if stripped.startswith("[") and stripped.endswith("]"): - current_section = stripped[1:-1].strip() 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) - continue - if current_section in managed_sections: - continue + return "\n".join(filtered_lines).strip() - 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) +# 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 = "\n".join(filtered_lines).strip() +before = strip_managed(before) +after = strip_managed(after) block = f"{managed_start}\n{managed}\n{managed_end}\n" -if before: - config_path.write_text(before + "\n\n" + block) -else: - config_path.write_text(block) +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/" @@ -89,6 +116,6 @@ echo "Installed Codex kit into: $CODEX_HOME" echo "Backup created at: $BACKUP_DIR" echo echo "Next steps:" -echo "- Start Codex and use the balanced profile by default." -echo "- Switch to economy for lighter work and deep for hard tasks." +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." diff --git a/templates/global/balanced.config.toml b/templates/global/balanced.config.toml new file mode 100644 index 0000000..f47d563 --- /dev/null +++ b/templates/global/balanced.config.toml @@ -0,0 +1,4 @@ +model = "gpt-5.4" +model_reasoning_effort = "medium" +plan_mode_reasoning_effort = "medium" +web_search = "cached" diff --git a/templates/global/config-managed.toml b/templates/global/config-managed.toml index 83b0dde..489a094 100644 --- a/templates/global/config-managed.toml +++ b/templates/global/config-managed.toml @@ -1,27 +1,8 @@ model = "gpt-5.4" model_reasoning_effort = "medium" -profile = "balanced" project_doc_max_bytes = 16384 web_search = "cached" [history] max_bytes = 10485760 persistence = "save-all" - -[profiles.economy] -model = "gpt-5.4-mini" -model_reasoning_effort = "low" -plan_mode_reasoning_effort = "low" -web_search = "cached" - -[profiles.balanced] -model = "gpt-5.4" -model_reasoning_effort = "medium" -plan_mode_reasoning_effort = "medium" -web_search = "cached" - -[profiles.deep] -model = "gpt-5.4" -model_reasoning_effort = "high" -plan_mode_reasoning_effort = "high" -web_search = "live" diff --git a/templates/global/config.toml b/templates/global/config.toml index 83b0dde..6b4fcf4 100644 --- a/templates/global/config.toml +++ b/templates/global/config.toml @@ -1,27 +1,12 @@ +# Defaults match the balanced profile. Per-profile settings live in sibling +# .config.toml files (economy/balanced/deep) and are selected with +# `codex --profile `. Codex 0.144+ rejects the legacy top-level +# `profile = "..."` key and `[profiles.*]` tables. model = "gpt-5.4" model_reasoning_effort = "medium" -profile = "balanced" project_doc_max_bytes = 16384 web_search = "cached" [history] max_bytes = 10485760 persistence = "save-all" - -[profiles.economy] -model = "gpt-5.4-mini" -model_reasoning_effort = "low" -plan_mode_reasoning_effort = "low" -web_search = "cached" - -[profiles.balanced] -model = "gpt-5.4" -model_reasoning_effort = "medium" -plan_mode_reasoning_effort = "medium" -web_search = "cached" - -[profiles.deep] -model = "gpt-5.4" -model_reasoning_effort = "high" -plan_mode_reasoning_effort = "high" -web_search = "live" diff --git a/templates/global/deep.config.toml b/templates/global/deep.config.toml new file mode 100644 index 0000000..daa0d88 --- /dev/null +++ b/templates/global/deep.config.toml @@ -0,0 +1,4 @@ +model = "gpt-5.4" +model_reasoning_effort = "high" +plan_mode_reasoning_effort = "high" +web_search = "live" diff --git a/templates/global/economy.config.toml b/templates/global/economy.config.toml new file mode 100644 index 0000000..195ddf9 --- /dev/null +++ b/templates/global/economy.config.toml @@ -0,0 +1,4 @@ +model = "gpt-5.4-mini" +model_reasoning_effort = "low" +plan_mode_reasoning_effort = "low" +web_search = "cached"