Best-effort OS appearance detection for ``auto``. Mirrors the behaviour of ``watchSystemTheme`` in ``typescript/src/utils/theme.js`` but does not attempt to install any system-level watcher — we snapshot once at boot. Detection order: 1. ``CLAWCODEX_THEME`` env var — explicit o
(*, env: dict[str, str] | None = None)
| 136 | |
| 137 | |
| 138 | def resolve_auto_theme(*, env: dict[str, str] | None = None) -> str: |
| 139 | """Best-effort OS appearance detection for ``auto``. |
| 140 | |
| 141 | Mirrors the behaviour of ``watchSystemTheme`` in |
| 142 | ``typescript/src/utils/theme.js`` but does not attempt to install |
| 143 | any system-level watcher — we snapshot once at boot. Detection |
| 144 | order: |
| 145 | |
| 146 | 1. ``CLAWCODEX_THEME`` env var — explicit override, returned verbatim |
| 147 | if it names a known palette. |
| 148 | 2. ``COLORFGBG`` (VTE / iTerm2) — trailing digit; ``0``/``dark`` |
| 149 | means dark surface. |
| 150 | 3. macOS ``defaults read -g AppleInterfaceStyle`` via subprocess — |
| 151 | returns ``"dark"`` when Dark Mode is on. |
| 152 | 4. Fallback: ``"dark"``. |
| 153 | """ |
| 154 | |
| 155 | import os |
| 156 | import subprocess |
| 157 | |
| 158 | environment = env if env is not None else os.environ |
| 159 | |
| 160 | forced = environment.get("CLAWCODEX_THEME", "").strip().lower() |
| 161 | if forced and forced != "auto" and forced in _PALETTES: |
| 162 | return forced |
| 163 | |
| 164 | cfgbg = environment.get("COLORFGBG", "").strip() |
| 165 | if cfgbg: |
| 166 | try: |
| 167 | trailing = cfgbg.split(";")[-1].strip() |
| 168 | bg = int(trailing) |
| 169 | # Low numbers (0-6) are generally dark; 7-15 bright. |
| 170 | return "dark" if bg < 7 else "light" |
| 171 | except (ValueError, IndexError): |
| 172 | pass |
| 173 | |
| 174 | if environment.get("__CFBundleIdentifier") or environment.get("TERM_PROGRAM"): |
| 175 | try: |
| 176 | out = subprocess.run( |
| 177 | ["defaults", "read", "-g", "AppleInterfaceStyle"], |
| 178 | capture_output=True, |
| 179 | text=True, |
| 180 | timeout=0.5, |
| 181 | ) |
| 182 | if out.returncode == 0 and "dark" in out.stdout.lower(): |
| 183 | return "dark" |
| 184 | if out.returncode != 0: |
| 185 | # The key is absent when Light Mode is active. |
| 186 | return "light" |
| 187 | except Exception: |
| 188 | pass |
| 189 | |
| 190 | return "dark" |
| 191 | |
| 192 | |
| 193 | def get_palette(name: str | None, *, env: dict[str, str] | None = None) -> Palette: |