Skip to content

Commit 5a67c0e

Browse files
rldyourmndclaude
andcommitted
feat(installer): manage owner [tui] status line in system configs
install_system_codex.sh now manages status_line (model with reasoning, context remainder, five-hour/weekly rate-limit remainder, git branch, working directory) and status_line_use_colors in config.toml and both rldyour-yolo/rldyour-safe profile layers while preserving other user [tui] keys, including inline and root-dotted forms. Covered by new unit tests and recorded in the surface adoption matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 72f89e2 commit 5a67c0e

4 files changed

Lines changed: 178 additions & 1 deletion

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ Run the full bootstrap smoke flow on a new or resynced machine:
134134
scripts/bootstrap_check.sh --apply
135135
```
136136

137-
The installer writes `~/.codex/AGENTS.md`, managed `~/.codex/agents/*.toml` subagent role configs, installs managed Codex execpolicy rules from `system/rules/*.rules`, registers this marketplace, enables the approved plugins, configures the approved MCP servers, enables Codex hooks and multi-agent support, writes the official Codex config schema hint, applies the owner-standard full-auto permission defaults unless `--safe-mode` is supplied, writes `~/.codex/rldyour-yolo.config.toml` and `~/.codex/rldyour-safe.config.toml` profile layers for current Codex `--profile` semantics, sets the maintainer-selected parent and subagent model defaults, writes approved MCP tool overrides, and synchronizes the versioned local plugin cache at `~/.codex/plugins/cache/rldyour-codex/<plugin>/<version>`. Existing `~/.codex/AGENTS.md`, managed subagent configs, managed rule files, `~/.codex/config.toml`, and managed profile files are backed up before write operations. Credentials and OAuth tokens are never written by this repository.
137+
The installer writes `~/.codex/AGENTS.md`, managed `~/.codex/agents/*.toml` subagent role configs, installs managed Codex execpolicy rules from `system/rules/*.rules`, registers this marketplace, enables the approved plugins, configures the approved MCP servers, enables Codex hooks and multi-agent support, writes the official Codex config schema hint, applies the owner-standard full-auto permission defaults unless `--safe-mode` is supplied, writes `~/.codex/rldyour-yolo.config.toml` and `~/.codex/rldyour-safe.config.toml` profile layers for current Codex `--profile` semantics, sets the maintainer-selected parent and subagent model defaults, writes approved MCP tool overrides, manages the owner `[tui]` status line (`status_line` with model, context remainder, five-hour/weekly rate-limit remainder, git branch, and working directory plus `status_line_use_colors`) in the main config and both profile layers while preserving other user `[tui]` keys, and synchronizes the versioned local plugin cache at `~/.codex/plugins/cache/rldyour-codex/<plugin>/<version>`. Existing `~/.codex/AGENTS.md`, managed subagent configs, managed rule files, `~/.codex/config.toml`, and managed profile files are backed up before write operations. Credentials and OAuth tokens are never written by this repository.
138138

139139
The Codex adapter contract lives in `config/rldyour-contract.json` and is documented in `docs/contract-matrix.md`. It records the intended Codex surface: 9 plugins, 41 skills, no slash commands by design, 8 managed subagents, command-only plugin hook lifecycle mappings, versioned plugin cache layout, and the owner-standard full-auto profile boundary. Validate it with `python3 scripts/validate_contract.py`.
140140

references/codex-surface-adoption.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Source of truth:
1212
| Surface | Introduced | Decision | Implementation | Validator |
1313
| --- | --- | --- | --- | --- |
1414
| Codex CLI runtime baseline | 0.138.0 | Adopted | Root contract and adapter runtime pins require `@openai/codex` / `codex-cli` `0.138.0`; local installed runtime must report `codex-cli 0.138.0`. | `python3 scripts/check_mcp_runtime_versions.py --fail-on-outdated` |
15+
| `[tui].status_line` owner status line | 0.119.0 | Adopted | `scripts/install_system_codex.sh` manages `status_line = ["model-with-reasoning", "context-remaining", "five-hour-limit", "weekly-limit", "git-branch", "current-dir"]` and `status_line_use_colors = true` in `config.toml` and both `rldyour-yolo`/`rldyour-safe` profile configs so every session footer shows model, context remainder, and five-hour/weekly rate-limit remainder. Only these two keys are managed; other user `[tui]` keys are preserved. | `tests/unit/test_install_system_codex_tui_status_line.py` |
1516
| `/app` desktop handoff and Windows workspace launch | 0.138.0 | Operational | Treat as runtime capability. Repository config does not hard-code Desktop handoff state, but installed-runtime smoke may rely on the `codex` binary being at the 0.138.0 baseline before diagnosing app/server integration behavior. | `scripts/doctor_system_codex.sh --quick --strict-runtime` |
1617
| Local image file paths exposed to the model | 0.138.0 | Capability-dependent | Local image attachment and generated-image path exposure is runtime behavior. Do not add repository claims about availability unless installed-runtime checks prove the active account/session supports the capability. | n/a |
1718
| Plugin command JSON and richer plugin metadata | 0.138.0 | Operational | 0.138.0 documents richer plugin JSON surfaces. The adapter already treats JSON plugin inventory as installed-runtime evidence and keeps static validators from inventing runtime plugin state. | root `scripts/ry_repair_sync.py --plan --apply-system --json` |

scripts/install_system_codex.sh

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,10 +445,14 @@ out: list[str] = [
445445
skip_managed = False
446446
in_features = False
447447
in_memories = False
448+
in_tui = False
448449
features_table_seen = False
450+
tui_table_seen = False
449451
feature_dotted_lines: list[str] = []
452+
tui_preserved_lines: list[str] = []
450453
hooks_written = False
451454
multi_agent_written = False
455+
tui_managed_written = False
452456
memories_external_context_written = "disable_on_external_context" in existing_memories
453457
current_header: str | None = None
454458
managed_feature_keys = {
@@ -461,6 +465,15 @@ managed_feature_keys = {
461465
"web_search_cached",
462466
"web_search_request",
463467
}
468+
# Owner-standard TUI status line (Codex CLI 0.119.0+ [tui].status_line):
469+
# model + reasoning, context left, five-hour/weekly rate-limit remainder,
470+
# git branch, and working directory. Only these two keys are managed; any
471+
# other user [tui] keys (notifications, animations, terminal_title) survive.
472+
managed_tui_keys = {"status_line", "status_line_use_colors"}
473+
managed_tui_lines = [
474+
'status_line = ["model-with-reasoning", "context-remaining", "five-hour-limit", "weekly-limit", "git-branch", "current-dir"]',
475+
"status_line_use_colors = true",
476+
]
464477
legacy_hook_feature_keys = {"codex_hooks"}
465478
deprecated_root_keys = {
466479
"background_terminal_timeout",
@@ -581,6 +594,20 @@ def append_features_block() -> None:
581594
append_managed_features()
582595
583596
597+
def append_managed_tui() -> None:
598+
global tui_managed_written
599+
if not tui_managed_written:
600+
out.extend(managed_tui_lines)
601+
tui_managed_written = True
602+
603+
604+
def append_tui_block() -> None:
605+
add_blank()
606+
out.append("[tui]")
607+
out.extend(tui_preserved_lines)
608+
append_managed_tui()
609+
610+
584611
def is_rldyour_plugin_header(header_path: list[str]) -> bool:
585612
return (
586613
len(header_path) == 2
@@ -632,18 +659,24 @@ for raw_line in existing.splitlines():
632659
if match:
633660
if in_features:
634661
append_managed_features()
662+
if in_tui:
663+
append_managed_tui()
635664
header = match.group(1)
636665
header_path = split_toml_key(header)
637666
if is_managed_header(header, header_path) or is_rldyour_plugin_header(header_path):
638667
skip_managed = True
639668
in_features = False
640669
in_memories = False
670+
in_tui = False
641671
continue
642672
skip_managed = False
643673
in_features = header_path == ["features"]
644674
in_memories = header_path == ["memories"]
675+
in_tui = header_path == ["tui"]
645676
if in_features:
646677
features_table_seen = True
678+
if in_tui:
679+
tui_table_seen = True
647680
current_header = header
648681
out.append(raw_line)
649682
continue
@@ -703,6 +736,24 @@ for raw_line in existing.splitlines():
703736
continue
704737
feature_dotted_lines.append(f"{toml_key(feature_key)} = {key_info[1]}")
705738
continue
739+
if len(key_path) == 1 and key_path[0] == "tui":
740+
try:
741+
inline_tui = tomllib.loads(raw_line).get("tui") or {}
742+
except Exception:
743+
inline_tui = {}
744+
if not isinstance(inline_tui, dict):
745+
continue
746+
for tui_key, tui_value in inline_tui.items():
747+
if tui_key in managed_tui_keys:
748+
continue
749+
tui_preserved_lines.append(f"{toml_key(str(tui_key))} = {toml_value(tui_value)}")
750+
continue
751+
if len(key_path) >= 2 and key_path[0] == "tui":
752+
if key_path[1] in managed_tui_keys:
753+
continue
754+
tui_subkey = ".".join(toml_key(segment) for segment in key_path[1:])
755+
tui_preserved_lines.append(f"{tui_subkey} = {key_info[1]}")
756+
continue
706757
if (
707758
len(key_path) == 2
708759
and key_path[0] == "memories"
@@ -740,6 +791,12 @@ for raw_line in existing.splitlines():
740791
multi_agent_written = True
741792
continue
742793
794+
if in_tui:
795+
key_info = assignment_key_path(raw_line)
796+
key_path = key_info[0] if key_info else []
797+
if key_path and key_path[0] in managed_tui_keys:
798+
continue
799+
743800
if in_memories:
744801
key_info = assignment_key_path(raw_line)
745802
key_path = key_info[0] if key_info else []
@@ -756,13 +813,18 @@ for raw_line in existing.splitlines():
756813
757814
if in_features:
758815
append_managed_features()
816+
if in_tui:
817+
append_managed_tui()
759818
760819
while out and out[-1] == "":
761820
out.pop()
762821
763822
if not features_table_seen:
764823
append_features_block()
765824
825+
if not tui_table_seen:
826+
append_tui_block()
827+
766828
add_blank()
767829
out.extend([
768830
"[marketplaces.rldyour-codex]",
@@ -837,6 +899,9 @@ def profile_config_text(*, yolo: bool) -> str:
837899
f"model_reasoning_effort = {json.dumps(managed_reasoning_effort)}",
838900
"suppress_unstable_features_warning = true",
839901
"",
902+
"[tui]",
903+
*managed_tui_lines,
904+
"",
840905
])
841906
return "\n".join(lines)
842907
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
from __future__ import annotations
2+
3+
import subprocess
4+
import tomllib
5+
from pathlib import Path
6+
7+
8+
ROOT = Path(__file__).resolve().parents[2]
9+
DEFAULT_TIMEOUT = 60
10+
11+
MANAGED_STATUS_LINE = [
12+
"model-with-reasoning",
13+
"context-remaining",
14+
"five-hour-limit",
15+
"weekly-limit",
16+
"git-branch",
17+
"current-dir",
18+
]
19+
20+
21+
def run_installer(codex_home: Path) -> None:
22+
subprocess.run(
23+
["bash", "scripts/install_system_codex.sh", "--apply", "--codex-home", str(codex_home)],
24+
cwd=ROOT,
25+
check=True,
26+
text=True,
27+
capture_output=True,
28+
timeout=DEFAULT_TIMEOUT,
29+
)
30+
31+
32+
def load_toml(path: Path) -> dict:
33+
return tomllib.loads(path.read_text(encoding="utf-8"))
34+
35+
36+
def assert_managed_tui(data: dict) -> None:
37+
tui = data.get("tui")
38+
assert isinstance(tui, dict), data
39+
assert tui.get("status_line") == MANAGED_STATUS_LINE, tui
40+
assert tui.get("status_line_use_colors") is True, tui
41+
42+
43+
def test_fresh_install_writes_managed_tui_status_line(tmp_path: Path) -> None:
44+
codex_home = tmp_path / "fresh"
45+
codex_home.mkdir()
46+
47+
run_installer(codex_home)
48+
49+
assert_managed_tui(load_toml(codex_home / "config.toml"))
50+
assert_managed_tui(load_toml(codex_home / "rldyour-yolo.config.toml"))
51+
assert_managed_tui(load_toml(codex_home / "rldyour-safe.config.toml"))
52+
53+
54+
def test_existing_tui_keys_preserved_and_status_line_managed(tmp_path: Path) -> None:
55+
codex_home = tmp_path / "keep"
56+
codex_home.mkdir()
57+
(codex_home / "config.toml").write_text(
58+
"\n".join(
59+
[
60+
"[tui]",
61+
"notifications = true",
62+
'status_line = ["model"]',
63+
"animations = false",
64+
"",
65+
"[tui.extra]",
66+
"custom = 1",
67+
"",
68+
]
69+
),
70+
encoding="utf-8",
71+
)
72+
73+
run_installer(codex_home)
74+
75+
data = load_toml(codex_home / "config.toml")
76+
assert_managed_tui(data)
77+
assert data["tui"]["notifications"] is True
78+
assert data["tui"]["animations"] is False
79+
assert data["tui"]["extra"] == {"custom": 1}
80+
81+
82+
def test_root_dotted_tui_keys_migrate_into_managed_table(tmp_path: Path) -> None:
83+
codex_home = tmp_path / "dotted"
84+
codex_home.mkdir()
85+
(codex_home / "config.toml").write_text(
86+
'tui.notifications = true\ntui.status_line = ["model"]\n',
87+
encoding="utf-8",
88+
)
89+
90+
run_installer(codex_home)
91+
92+
data = load_toml(codex_home / "config.toml")
93+
assert_managed_tui(data)
94+
assert data["tui"]["notifications"] is True
95+
96+
97+
def test_reinstall_is_idempotent_for_tui(tmp_path: Path) -> None:
98+
# Full-file equality is not asserted because the post-install hook trust
99+
# refresh may append [hooks.state] through a live codex app-server.
100+
codex_home = tmp_path / "idem"
101+
codex_home.mkdir()
102+
103+
run_installer(codex_home)
104+
first = load_toml(codex_home / "config.toml")
105+
run_installer(codex_home)
106+
second_text = (codex_home / "config.toml").read_text(encoding="utf-8")
107+
second = tomllib.loads(second_text)
108+
109+
assert first["tui"] == second["tui"]
110+
assert_managed_tui(second)
111+
assert second_text.count("status_line =") == 1

0 commit comments

Comments
 (0)