66import json
77import logging
88import os
9+ import re
910import shutil
1011import subprocess
1112import threading
1213import time
1314from pathlib import Path
1415from typing import Dict , List , Any , Optional , Tuple
1516
17+ from rich import box as rich_box
1618from rich .console import Console
1719from rich .panel import Panel
1820from rich .table import Table
1921
2022from prompt_toolkit import print_formatted_text as _pt_print
2123from prompt_toolkit .formatted_text import ANSI as _PT_ANSI
24+ from gauss_cli .colors import render_terminal_text , supports_ansi , supports_unicode
2225
2326logger = logging .getLogger (__name__ )
2427
3538
3639def cprint (text : str ):
3740 """Print ANSI-colored text through prompt_toolkit's renderer."""
38- _pt_print (_PT_ANSI (text ))
41+ ansi_enabled = supports_ansi ()
42+ rendered = render_terminal_text (
43+ text ,
44+ allow_ansi = ansi_enabled ,
45+ allow_unicode = supports_unicode (),
46+ )
47+ if ansi_enabled :
48+ _pt_print (_PT_ANSI (rendered ))
49+ else :
50+ _pt_print (rendered )
3951
4052
4153# =========================================================================
@@ -73,6 +85,12 @@ def _skin_branding(key: str, fallback: str) -> str:
7385[#CD7F32]╚██████╔╝██║ ██║╚██████╔╝███████║███████║[/]
7486[#CD7F32] ╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝[/]"""
7587
88+ GAUSS_AGENT_LOGO_ASCII = """[bold #FFD700] ____ _ _ _ ____ ____[/]
89+ [bold #FFD700] / ___| / \\ | | | / ___/ ___|[/]
90+ [#FFBF00]| | _ / _ \\ | | | \\ ___ \\ ___ \\ [/]
91+ [#FFBF00]| |_| |/ ___ \\ | |_| |___) |__) |[/]
92+ [#CD7F32] \\ ____/_/ \\ _\\ \\ ___/|____/____/[/]"""
93+
7694GAUSS_CADUCEUS = """[#CD7F32]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡀⠀⣀⣀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]
7795[#CD7F32]⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣇⠸⣿⣿⠇⣸⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀[/]
7896[#FFBF00]⠀⢀⣠⣴⣶⠿⠋⣩⡿⣿⡿⠻⣿⡇⢠⡄⢸⣿⠟⢿⣿⢿⣍⠙⠿⣶⣦⣄⡀⠀[/]
@@ -89,28 +107,74 @@ def _skin_branding(key: str, fallback: str) -> str:
89107[#B8860B]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠳⠈⣡⠞⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]
90108[#B8860B]⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀[/]"""
91109
110+ GAUSS_CADUCEUS_ASCII = """[dim #B8860B]Lean workspace and managed proof workflows[/]"""
111+
92112COMPACT_BANNER = """[bold #FFD700]GAUSS[/] [dim #B8860B]· Lean Autoformalization[/]"""
113+ COMPACT_BANNER_ASCII = """[bold #FFD700]GAUSS[/] [dim #B8860B]- Lean Autoformalization[/]"""
93114
94115FULL_BANNER_MIN_WIDTH = 124
95116FULL_STACK_BANNER_MIN_WIDTH = 88
96117MINI_BANNER_MIN_WIDTH = 34
97118SIMPLIFIED_BANNER_WIDTH = 72
98119
99120
100- def _select_banner_art (term_width : int , skin : Optional [Any ]) -> Tuple [str , str , str ]:
121+ _MARKUP_TAG_RE = re .compile (r"\[[^\]]+\]" )
122+
123+
124+ def _markup_is_ascii (markup : str ) -> bool :
125+ plain = _MARKUP_TAG_RE .sub ("" , markup or "" )
126+ try :
127+ plain .encode ("ascii" )
128+ except UnicodeEncodeError :
129+ return False
130+ return True
131+
132+
133+ def _select_banner_art (
134+ term_width : int ,
135+ skin : Optional [Any ],
136+ unicode_enabled : Optional [bool ] = None ,
137+ ) -> Tuple [str , str , str ]:
101138 """Return ``(layout_mode, logo_markup, hero_markup)`` for the current width."""
102- full_logo = skin .banner_logo if skin and getattr (skin , "banner_logo" , "" ) else GAUSS_AGENT_LOGO
103- compact_logo = (
104- skin .banner_logo_compact
105- if skin and getattr (skin , "banner_logo_compact" , "" )
106- else COMPACT_BANNER
107- )
108- full_hero = skin .banner_hero if skin and getattr (skin , "banner_hero" , "" ) else GAUSS_CADUCEUS
109- compact_hero = (
110- skin .banner_hero_compact
111- if skin and getattr (skin , "banner_hero_compact" , "" )
112- else ""
113- )
139+ unicode_enabled = supports_unicode () if unicode_enabled is None else unicode_enabled
140+ if unicode_enabled :
141+ full_logo = skin .banner_logo if skin and getattr (skin , "banner_logo" , "" ) else GAUSS_AGENT_LOGO
142+ compact_logo = (
143+ skin .banner_logo_compact
144+ if skin and getattr (skin , "banner_logo_compact" , "" )
145+ else COMPACT_BANNER
146+ )
147+ full_hero = skin .banner_hero if skin and getattr (skin , "banner_hero" , "" ) else GAUSS_CADUCEUS
148+ compact_hero = (
149+ skin .banner_hero_compact
150+ if skin and getattr (skin , "banner_hero_compact" , "" )
151+ else ""
152+ )
153+ else :
154+ full_logo = (
155+ skin .banner_logo
156+ if skin and getattr (skin , "banner_logo" , "" ) and _markup_is_ascii (skin .banner_logo )
157+ else GAUSS_AGENT_LOGO_ASCII
158+ )
159+ compact_logo = (
160+ skin .banner_logo_compact
161+ if skin
162+ and getattr (skin , "banner_logo_compact" , "" )
163+ and _markup_is_ascii (skin .banner_logo_compact )
164+ else COMPACT_BANNER_ASCII
165+ )
166+ full_hero = (
167+ skin .banner_hero
168+ if skin and getattr (skin , "banner_hero" , "" ) and _markup_is_ascii (skin .banner_hero )
169+ else GAUSS_CADUCEUS_ASCII
170+ )
171+ compact_hero = (
172+ skin .banner_hero_compact
173+ if skin
174+ and getattr (skin , "banner_hero_compact" , "" )
175+ and _markup_is_ascii (skin .banner_hero_compact )
176+ else ""
177+ )
114178
115179 if term_width >= FULL_BANNER_MIN_WIDTH :
116180 return "split" , full_logo , full_hero
@@ -318,6 +382,10 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
318382 """
319383 term_width = shutil .get_terminal_size ().columns
320384 simplified = term_width < SIMPLIFIED_BANNER_WIDTH
385+ unicode_enabled = supports_unicode ()
386+ separator = "·" if unicode_enabled else "|"
387+ long_dash = "—" if unicode_enabled else "-"
388+ warn_mark = "⚠" if unicode_enabled else "!"
321389
322390 # Resolve skin colors once for the entire banner
323391 accent = _skin_color ("banner_accent" , "#FFBF00" )
@@ -332,7 +400,7 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
332400 except Exception :
333401 _bskin = None
334402
335- layout_mode , _logo , _hero = _select_banner_art (term_width , _bskin )
403+ layout_mode , _logo , _hero = _select_banner_art (term_width , _bskin , unicode_enabled = unicode_enabled )
336404
337405 layout_table = Table .grid (padding = (0 , 1 if layout_mode == "stack" else 2 ))
338406 if layout_mode == "split" :
@@ -350,12 +418,12 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
350418 model_short = model_short [: model_max - 3 ] + "..."
351419 ctx_str = ""
352420 if context_length and not simplified :
353- ctx_str = f" [dim { dim } ]· [/] [dim { dim } ]{ _format_context_length (context_length )} context[/]"
421+ ctx_str = f" [dim { dim } ]{ separator } [/] [dim { dim } ]{ _format_context_length (context_length )} context[/]"
354422 if simplified :
355- left_lines .append (f"[{ accent } ]{ model_short } [/] [dim { dim } ]· [/] [dim { dim } ]Math Inc.[/]" )
423+ left_lines .append (f"[{ accent } ]{ model_short } [/] [dim { dim } ]{ separator } [/] [dim { dim } ]Math Inc.[/]" )
356424 left_lines .append (f"[dim { dim } ]{ _shorten_middle (cwd , max (18 , term_width - 10 ))} [/]" )
357425 else :
358- left_lines .append (f"[{ accent } ]{ model_short } [/]{ ctx_str } [dim { dim } ]· [/] [dim { dim } ]Math Inc.[/]" )
426+ left_lines .append (f"[{ accent } ]{ model_short } [/]{ ctx_str } [dim { dim } ]{ separator } [/] [dim { dim } ]Math Inc.[/]" )
359427 left_lines .append (f"[dim { dim } ]{ cwd } [/]" )
360428 if project_label :
361429 left_lines .append (f"[dim { dim } ]Project: { project_label } [/]" )
@@ -376,13 +444,13 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
376444 right_lines .append (f"[{ text } ]`/help`[/] [dim { dim } ]commands and diagnostics[/]" )
377445 else :
378446 right_lines .append (f"[bold { accent } ]Primary Workflow[/]" )
379- right_lines .append (f"[{ text } ]`/project`[/] [dim { dim } ]— create, convert, inspect, or switch the active project[/]" )
380- right_lines .append (f"[{ text } ]`/prove`[/] [dim { dim } ]— spawn a guided managed proving agent[/]" )
381- right_lines .append (f"[{ text } ]`/draft`[/] [dim { dim } ]— draft Lean declaration skeletons[/]" )
382- right_lines .append (f"[{ text } ]`/autoprove`[/] [dim { dim } ]— spawn an autonomous managed proving agent[/]" )
383- right_lines .append (f"[{ text } ]`/formalize`[/] [dim { dim } ]— spawn an interactive managed formalization agent[/]" )
384- right_lines .append (f"[{ text } ]`/autoformalize`[/] [dim { dim } ]— spawn an autonomous managed formalization agent[/]" )
385- right_lines .append (f"[{ text } ]`/help`[/] [dim { dim } ]— session and diagnostics commands[/]" )
447+ right_lines .append (f"[{ text } ]`/project`[/] [dim { dim } ]{ long_dash } create, convert, inspect, or switch the active project[/]" )
448+ right_lines .append (f"[{ text } ]`/prove`[/] [dim { dim } ]{ long_dash } spawn a guided managed proving agent[/]" )
449+ right_lines .append (f"[{ text } ]`/draft`[/] [dim { dim } ]{ long_dash } draft Lean declaration skeletons[/]" )
450+ right_lines .append (f"[{ text } ]`/autoprove`[/] [dim { dim } ]{ long_dash } spawn an autonomous managed proving agent[/]" )
451+ right_lines .append (f"[{ text } ]`/formalize`[/] [dim { dim } ]{ long_dash } spawn an interactive managed formalization agent[/]" )
452+ right_lines .append (f"[{ text } ]`/autoformalize`[/] [dim { dim } ]{ long_dash } spawn an autonomous managed formalization agent[/]" )
453+ right_lines .append (f"[{ text } ]`/help`[/] [dim { dim } ]{ long_dash } session and diagnostics commands[/]" )
386454 right_lines .append (f"[dim { dim } ]Bundled skills and user-managed MCP are off by default.[/]" )
387455
388456 try :
@@ -405,16 +473,16 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
405473 "/help" ,
406474 ]
407475 summary_parts .append ("/help for commands" )
408- right_lines .append (f"[dim { dim } ]{ ' · ' .join (summary_parts )} [/]" )
476+ right_lines .append (f"[dim { dim } ]{ f' { separator } ' .join (summary_parts )} [/]" )
409477
410478 # Update check — use prefetched result if available
411479 try :
412480 behind = get_update_result (timeout = 0.5 )
413481 if behind and behind > 0 :
414482 commits_word = "commit" if behind == 1 else "commits"
415483 right_lines .append (
416- f"[bold yellow]⚠ { behind } { commits_word } behind[/]"
417- f"[dim yellow] — run [bold]gauss update[/bold] to update[/]"
484+ f"[bold yellow]{ warn_mark } { behind } { commits_word } behind[/]"
485+ f"[dim yellow] { long_dash } run [bold]gauss update[/bold] to update[/]"
418486 )
419487 except Exception :
420488 pass # Never break the banner over an update check
@@ -435,6 +503,7 @@ def build_welcome_banner(console: Console, model: str, cwd: str,
435503 title = f"[bold { title_color } ]{ agent_name } v{ VERSION } ({ RELEASE_DATE } )[/]" ,
436504 border_style = border_color ,
437505 padding = (0 , 2 ),
506+ box = rich_box .ROUNDED if unicode_enabled else rich_box .ASCII ,
438507 )
439508
440509 console .print ()
0 commit comments