55import os
66import shutil
77import subprocess
8- import time
8+ import sys
99from datetime import datetime , timedelta
1010from pathlib import Path
1111
1515from rich .table import Table
1616
1717from . import __version__
18+ from .banner import (
19+ BRAND_CYAN ,
20+ BRAND_DIM ,
21+ BRAND_GREEN ,
22+ BRAND_MAGENTA ,
23+ COMPACT_LOGO ,
24+ print_banner ,
25+ print_divider ,
26+ status_icon ,
27+ )
1828from .constants import (
1929 BOT_COMMAND ,
2030 DATA_DIR ,
2131 DB_FILE ,
2232 ENV_FILE ,
33+ IS_LINUX ,
2334 IS_MACOS ,
35+ IS_WINDOWS ,
2436 LOG_DIR ,
2537 MEMORY_DIR ,
2638 find_site_packages ,
4860# ── version ────────────────────────────────────────────
4961def _version_callback (value : bool ) -> None :
5062 if value :
51- console . print ( f"Mr.Stack [bold]v { __version__ } [/]" )
63+ print_banner ( console , compact = True )
5264 raise typer .Exit ()
5365
5466
@@ -77,8 +89,10 @@ def start(
7789 background : bool = typer .Option (False , "--bg" , "-b" , help = "Run in background." ),
7890) -> None :
7991 """Start the bot."""
92+ print_banner (console , compact = True )
8093 if background :
8194 start_background ()
95+ _print_quick_status ()
8296 else :
8397 start_foreground ()
8498
@@ -99,21 +113,28 @@ def daemon(
99113 if uninstall :
100114 daemon_uninstall ()
101115 else :
116+ print_banner (console , compact = True )
102117 daemon_install ()
103118
104119
105120# ── status ─────────────────────────────────────────────
106121@app .command ()
107122def status () -> None :
108123 """Show current status."""
124+ print_banner (console , compact = True )
125+ print_divider (console )
126+
109127 pid = find_bot_pid ()
110128 running = pid is not None
111129
112- # Collect info
113- version_str = f"v{ __version__ } "
114- status_str = f"[green]Running (PID { pid } )[/]" if running else "[red]Stopped[/]"
130+ # Status
131+ if running :
132+ console .print (f" { status_icon (True )} Status [green bold]Running[/] [dim](PID { pid } )[/]" )
133+ else :
134+ console .print (f" { status_icon (False )} Status [red bold]Stopped[/]" )
115135
116- uptime_str = "—"
136+ # Uptime
137+ uptime_str = ""
117138 if running and pid :
118139 try :
119140 import psutil
@@ -131,54 +152,41 @@ def status() -> None:
131152 parts .append (f"{ mins } m" )
132153 uptime_str = " " .join (parts )
133154 except Exception :
134- uptime_str = "unknown"
155+ uptime_str = "?"
156+ console .print (f" { status_icon (True )} Uptime [bold]{ uptime_str } [/]" )
135157
158+ # Memory
136159 memory_count = 0
137160 if MEMORY_DIR .is_dir ():
138161 memory_count = sum (1 for _ in MEMORY_DIR .rglob ("*.md" ))
162+ console .print (f" { status_icon (memory_count > 0 )} Memory [bold]{ memory_count } [/] entries" )
163+
164+ # Last message
165+ last_msg = _get_last_message_time ()
166+ console .print (f" { status_icon (last_msg != '—' )} Last msg { last_msg } " )
139167
140- jarvis_str = "[dim]OFF[/]"
168+ # Jarvis
141169 jarvis_enabled = resolve_env_value ("ENABLE_JARVIS" , "false" ).lower () == "true"
142170 if jarvis_enabled :
143171 if IS_MACOS :
144- jarvis_str = " [green]ON[/]"
172+ console . print ( f" { status_icon ( True ) } Jarvis [green bold ]ON[/]")
145173 else :
146- jarvis_str = "[yellow]ON (limited — not macOS)[/]"
174+ console .print (f" { status_icon (True )} Jarvis [yellow bold]ON[/] [dim](limited)[/]" )
175+ else :
176+ console .print (f" { status_icon (False )} Jarvis [dim]OFF[/]" )
147177
148- last_msg = "—"
149- if DB_FILE .is_file ():
150- try :
151- import sqlite3
178+ # Platform
179+ platform_label = "macOS" if IS_MACOS else ("Linux" if IS_LINUX else "Windows" )
180+ console .print (f" { status_icon (True )} Platform { platform_label } " )
152181
153- with sqlite3 .connect (str (DB_FILE )) as conn :
154- row = conn .execute (
155- "SELECT MAX(created_at) FROM messages"
156- ).fetchone ()
157- if row and row [0 ]:
158- ts = datetime .fromisoformat (row [0 ])
159- delta = datetime .now () - ts
160- if delta < timedelta (minutes = 1 ):
161- last_msg = "just now"
162- elif delta < timedelta (hours = 1 ):
163- last_msg = f"{ int (delta .total_seconds () // 60 )} m ago"
164- elif delta < timedelta (days = 1 ):
165- last_msg = f"{ int (delta .total_seconds () // 3600 )} h ago"
166- else :
167- last_msg = ts .strftime ("%Y-%m-%d %H:%M" )
168- except Exception :
169- pass
182+ print_divider (console )
170183
171- panel = Panel (
172- f" Status: { status_str } \n "
173- f" Uptime: { uptime_str } \n "
174- f" Memory: { memory_count } entries\n "
175- f" Last message: { last_msg } \n "
176- f" Jarvis: { jarvis_str } \n "
177- f" Data: { DATA_DIR } " ,
178- title = f"[bold]Mr.Stack { version_str } [/]" ,
179- border_style = "cyan" ,
180- )
181- console .print (panel )
184+ # Quick hints
185+ if not running :
186+ console .print (f" [dim]Start: [/][bold]mrstack start[/]" )
187+ else :
188+ console .print (f" [dim]Logs: [/][bold]mrstack logs -f[/] [dim]| Stop: [/][bold]mrstack stop[/]" )
189+ console .print ()
182190
183191
184192# ── logs ───────────────────────────────────────────────
@@ -205,7 +213,6 @@ def logs(
205213 pass
206214 except FileNotFoundError :
207215 console .print ("[red]'tail' command not found.[/]" )
208- # Fallback: read with Python
209216 content = log_file .read_text ()
210217 for line in content .splitlines ()[- lines :]:
211218 console .print (line )
@@ -243,7 +250,8 @@ def jarvis(
243250 enable = state == "on"
244251 if enable and not IS_MACOS :
245252 console .print (
246- "[yellow]Jarvis mode has limited functionality on non-macOS platforms.[/]"
253+ "[yellow]Jarvis has limited functionality on this platform.[/]\n "
254+ "[dim] Active app detection and Chrome tab reading are macOS-only.[/]"
247255 )
248256
249257 text = ENV_FILE .read_text ()
@@ -257,11 +265,14 @@ def jarvis(
257265 text += f"\n ENABLE_JARVIS={ new_val } \n "
258266
259267 ENV_FILE .write_text (text )
260- icon = "[green]ON[/]" if enable else "[red]OFF[/]"
261- console .print (f"Jarvis mode: { icon } " )
268+
269+ if enable :
270+ console .print (f" { status_icon (True )} Jarvis [green bold]ON[/]" )
271+ else :
272+ console .print (f" { status_icon (False )} Jarvis [dim]OFF[/]" )
262273
263274 if is_running ():
264- console .print ("[dim]Restart the bot for changes to take effect.[/]" )
275+ console .print (" [dim]Restart for changes to take effect.[/]" )
265276
266277
267278# ── patch ──────────────────────────────────────────────
@@ -279,52 +290,93 @@ def patch(
279290@app .command ()
280291def update () -> None :
281292 """Update Mr.Stack to the latest version."""
282- console .print (" Updating Mr.Stack..." )
293+ console .print (f" { status_icon ( True ) } Updating Mr.Stack..." )
283294 if shutil .which ("uv" ):
284295 subprocess .run (["uv" , "tool" , "upgrade" , "mrstack" ], check = True )
285296 elif shutil .which ("pipx" ):
286297 subprocess .run (["pipx" , "upgrade" , "mrstack" ], check = True )
287298 else :
288299 subprocess .run (["pip" , "install" , "--upgrade" , "mrstack" ], check = True )
289300
290- # Re-patch after update
291- console .print ("Re-applying patches..." )
301+ console .print (f" { status_icon (True )} Re-applying patches..." )
292302 from .patcher import patch_install
293303
294304 patch_install (force = True )
295- console .print (" [green]Update complete.[/]" )
305+ console .print (f" [green bold ]Update complete.[/]" )
296306
297307
298308# ── version (explicit command) ─────────────────────────
299309@app .command (name = "version" )
300310def version_cmd () -> None :
301311 """Show version information."""
302- table = Table (show_header = False , box = None , padding = (0 , 2 ))
303- table .add_row ("Mr.Stack" , f"v{ __version__ } " )
312+ from .banner import LOGO
313+
314+ console .print (LOGO )
315+
316+ print_divider (console )
317+
318+ console .print (f" { status_icon (True )} Mr.Stack [bold]v{ __version__ } [/]" )
304319
305320 # claude-code-telegram version
306321 try :
307322 from importlib .metadata import version as pkg_version
308323
309324 cct_ver = pkg_version ("claude-code-telegram" )
310- table . add_row ( " claude-code-telegram" , f" v{ cct_ver } " )
325+ console . print ( f" { status_icon ( True ) } claude-code-telegram [bold] v{ cct_ver } [/] " )
311326 except Exception :
312- table . add_row ( " claude-code-telegram" , " [dim]not installed[/]" )
327+ console . print ( f" { status_icon ( False ) } claude-code-telegram [dim]not installed[/]" )
313328
314329 # Claude Code version
315330 try :
316331 result = subprocess .run (
317332 ["claude" , "--version" ], capture_output = True , text = True , timeout = 5
318333 )
319334 if result .returncode == 0 :
320- table . add_row ( " Claude Code" , result .stdout .strip ())
335+ console . print ( f" { status_icon ( True ) } Claude Code [bold] { result .stdout .strip ()} [/]" )
321336 except Exception :
322- table . add_row ( " Claude Code" , " [dim]not found[/]" )
337+ console . print ( f" { status_icon ( False ) } Claude Code [dim]not found[/]" )
323338
324339 # Platform
325340 import platform
326341
327- table .add_row ("Platform" , f"{ platform .system ()} { platform .machine ()} " )
328- table .add_row ("Python" , platform .python_version ())
342+ console .print (f" { status_icon (True )} Platform { platform .system ()} { platform .machine ()} " )
343+ console .print (f" { status_icon (True )} Python { platform .python_version ()} " )
344+
345+ print_divider (console )
346+ console .print ()
329347
330- console .print (table )
348+
349+ # ── Helpers ────────────────────────────────────────────
350+ def _get_last_message_time () -> str :
351+ if DB_FILE .is_file ():
352+ try :
353+ import sqlite3
354+
355+ with sqlite3 .connect (str (DB_FILE )) as conn :
356+ row = conn .execute (
357+ "SELECT MAX(created_at) FROM messages"
358+ ).fetchone ()
359+ if row and row [0 ]:
360+ ts = datetime .fromisoformat (row [0 ])
361+ delta = datetime .now () - ts
362+ if delta < timedelta (minutes = 1 ):
363+ return "just now"
364+ elif delta < timedelta (hours = 1 ):
365+ return f"{ int (delta .total_seconds () // 60 )} m ago"
366+ elif delta < timedelta (days = 1 ):
367+ return f"{ int (delta .total_seconds () // 3600 )} h ago"
368+ else :
369+ return ts .strftime ("%Y-%m-%d %H:%M" )
370+ except Exception :
371+ pass
372+ return "[dim]—[/]"
373+
374+
375+ def _print_quick_status () -> None :
376+ """Print a quick 2-line status after start."""
377+ pid = find_bot_pid ()
378+ if pid :
379+ jarvis = resolve_env_value ("ENABLE_JARVIS" , "false" ).lower () == "true"
380+ j_str = "[green]ON[/]" if jarvis else "[dim]OFF[/]"
381+ console .print (f" { status_icon (True )} Jarvis: { j_str } { status_icon (True )} Logs: [bold]mrstack logs -f[/]" )
382+ console .print ()
0 commit comments