|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import os |
| 4 | +from typing import Literal |
| 5 | + |
1 | 6 | from rich.theme import Theme |
2 | 7 | from rich.console import Console |
3 | 8 |
|
4 | | -custom_theme = Theme( |
5 | | - { |
6 | | - # Text styles |
7 | | - "primary": "#caaef5", # Purple 300 ⭐ (lighter when bold) |
8 | | - "secondary": "dim #caaef5", # Purple 500 ⭐ (mid-tone without bold) |
9 | | - "accent": "#ff68d4", # Pink 500 ⭐ |
10 | | - "muted": "#98a0b3", # Grey 400 ⭐ |
11 | | - # Semantic styles |
12 | | - "success": "bold #0dce74", # Green 400 ⭐ |
13 | | - "info": "#64afff", # Blue 500 ⭐ |
14 | | - "warning": "bold #ff815d", # Red 500 ⭐ |
15 | | - "error": "bold #c63800", # Red 700 ⭐ |
16 | | - # UI elements |
17 | | - "prompt": "#ba92ff", # Purple 500 ⭐ (no bold) |
18 | | - "prompt.choices": "#caaef5", # Purple 300 ⭐ |
19 | | - "prompt.default": "dim #98a0b3", # Grey 400 ⭐ |
20 | | - # Table styles |
21 | | - "table.header": "#414858", # Purple 300 ⭐ (lighter when bold) |
22 | | - "table.border": "#626b84", # Grey 600 ⭐ |
23 | | - "table.row": "#c4c9d4", # Grey 300 ⭐ |
24 | | - # Progress/Loading |
25 | | - "progress.description": "#caaef5", # Purple 300 ⭐ |
26 | | - "progress.percentage": "bold #caaef5", # Purple 300 ⭐ (lighter when bold) |
27 | | - "bar.complete": "#ba92ff", # Purple 500 ⭐ (no bold) |
28 | | - "bar.finished": "#0dce74", # Green 400 ⭐ |
29 | | - "bar.pulse": "#ff68d4", # Pink 500 ⭐ |
30 | | - } |
31 | | -) |
32 | | - |
33 | | -console = Console(theme=custom_theme, highlight=False) |
| 9 | +CliThemeName = Literal["light", "dark"] |
| 10 | + |
| 11 | +# Dark theme: tuned for dark terminal backgrounds (original Together CLI palette). |
| 12 | +_DARK_STYLES = { |
| 13 | + # Text styles |
| 14 | + "primary": "#caaef5", # Purple 300 |
| 15 | + "secondary": "#caaef5", # solid (no dim) so help body text stays readable |
| 16 | + "accent": "#ff68d4", # Pink 500 |
| 17 | + "muted": "#98a0b3", # Grey 400 |
| 18 | + # Semantic styles |
| 19 | + "success": "bold #0dce74", # Green 400 |
| 20 | + "info": "#64afff", # Blue 500 |
| 21 | + "warning": "bold #ff815d", # Red 500 |
| 22 | + "error": "bold #c63800", # Red 700 |
| 23 | + # UI elements |
| 24 | + "prompt": "#ba92ff", # Purple 500 |
| 25 | + "prompt.choices": "#caaef5", # Purple 300 |
| 26 | + "prompt.default": "#98a0b3", # Grey 400 |
| 27 | + # Table styles |
| 28 | + "table.header": "#414858", |
| 29 | + "table.border": "#626b84", # Grey 600 |
| 30 | + "table.row": "#c4c9d4", # Grey 300 |
| 31 | + # Progress/Loading |
| 32 | + "progress.description": "#caaef5", # Purple 300 |
| 33 | + "progress.percentage": "bold #caaef5", |
| 34 | + "bar.complete": "#ba92ff", # Purple 500 |
| 35 | + "bar.finished": "#0dce74", # Green 400 |
| 36 | + "bar.pulse": "#ff68d4", # Pink 500 |
| 37 | +} |
| 38 | + |
| 39 | +# Light theme: darker brand tones + solid dim/white overrides for white backgrounds. |
| 40 | +# `[dim]` / `[white]` markup resolves through the theme, so overrides fix widespread help |
| 41 | +# and pagination output without touching every call site. |
| 42 | +_LIGHT_STYLES = { |
| 43 | + # Text styles |
| 44 | + "primary": "#6d28d9", # Violet 700 |
| 45 | + "secondary": "#5b21b6", # Violet 800 |
| 46 | + "accent": "#db2777", # Pink 600 |
| 47 | + "muted": "#4b5563", # Grey 600 |
| 48 | + # Replace ANSI dim / bright-white (both fail on light backgrounds) |
| 49 | + "dim": "#4b5563", # Grey 600 |
| 50 | + "white": "#111827", # Grey 900 |
| 51 | + # Semantic styles |
| 52 | + "success": "bold #047857", # Green 700 |
| 53 | + "info": "#1d4ed8", # Blue 700 |
| 54 | + "warning": "bold #c2410c", # Orange 700 |
| 55 | + "error": "bold #b91c1c", # Red 700 |
| 56 | + # UI elements |
| 57 | + "prompt": "#6d28d9", # Violet 700 |
| 58 | + "prompt.choices": "#6d28d9", |
| 59 | + "prompt.default": "#4b5563", |
| 60 | + # Table styles |
| 61 | + "table.header": "#111827", # Grey 900 |
| 62 | + "table.border": "#9ca3af", # Grey 400 |
| 63 | + "table.row": "#1f2937", # Grey 800 |
| 64 | + # Progress/Loading |
| 65 | + "progress.description": "#6d28d9", |
| 66 | + "progress.percentage": "bold #6d28d9", |
| 67 | + "bar.complete": "#7c3aed", # Violet 600 |
| 68 | + "bar.finished": "#047857", |
| 69 | + "bar.pulse": "#db2777", |
| 70 | +} |
| 71 | + |
| 72 | + |
| 73 | +def resolve_cli_theme( |
| 74 | + env: dict[str, str] | None = None, |
| 75 | +) -> CliThemeName: |
| 76 | + """Pick light/dark CLI theme from env. |
| 77 | +
|
| 78 | + Precedence: |
| 79 | + 1. ``TG_THEME`` or ``TOGETHER_CLI_THEME`` = ``light`` | ``dark`` |
| 80 | + 2. ``COLORFGBG`` background index (``>= 7`` → light) |
| 81 | + 3. Default ``dark`` (historical CLI default) |
| 82 | + """ |
| 83 | + environ = os.environ if env is None else env |
| 84 | + |
| 85 | + for key in ("TG_THEME", "TOGETHER_CLI_THEME"): |
| 86 | + explicit = environ.get(key, "").strip().lower() |
| 87 | + if explicit in ("light", "dark"): |
| 88 | + return explicit # type: ignore[return-value] |
| 89 | + |
| 90 | + colorfgbg = environ.get("COLORFGBG", "").strip() |
| 91 | + if colorfgbg: |
| 92 | + bg_part = colorfgbg.split(";")[-1].strip() |
| 93 | + try: |
| 94 | + bg = int(bg_part) |
| 95 | + except ValueError: |
| 96 | + pass |
| 97 | + else: |
| 98 | + # ANSI: 0 black … 7 white, 8–15 bright. Light terminals commonly use 7 or 15. |
| 99 | + return "light" if bg >= 7 else "dark" |
| 100 | + |
| 101 | + return "dark" |
| 102 | + |
| 103 | + |
| 104 | +def build_theme(theme_name: CliThemeName | None = None) -> Theme: |
| 105 | + name = resolve_cli_theme() if theme_name is None else theme_name |
| 106 | + styles = _LIGHT_STYLES if name == "light" else _DARK_STYLES |
| 107 | + return Theme(styles) |
| 108 | + |
| 109 | + |
| 110 | +def create_console(theme_name: CliThemeName | None = None) -> Console: |
| 111 | + return Console(theme=build_theme(theme_name), highlight=False) |
| 112 | + |
| 113 | + |
| 114 | +cli_theme_name: CliThemeName = resolve_cli_theme() |
| 115 | +custom_theme = build_theme(cli_theme_name) |
| 116 | +console = create_console(cli_theme_name) |
0 commit comments