@@ -886,11 +886,13 @@ def _conversation_insights_html(conversations):
886886 return ""
887887
888888 # Estimate relative cost from model and recency (more recent = more active = higher cost)
889+ import re as _re
889890 ranked = []
890891 now = datetime .now (timezone .utc )
891892 for c in conversations [:20 ]:
892893 model_id , model_label = _get_conversation_model (c )
893- weight = MODEL_COST_WEIGHT .get (model_id , 1.0 )
894+ base_model_id = _re .sub (r"-\d{8}$" , "" , model_id )
895+ weight = MODEL_COST_WEIGHT .get (model_id , MODEL_COST_WEIGHT .get (base_model_id , 1.0 ))
894896 name = c .get ("name" ) or "Untitled"
895897 uuid = c .get ("uuid" , "" )
896898 updated = c .get ("updated_at" , "" )
@@ -1334,7 +1336,7 @@ def generate_dashboard(usage, stats, conversations=None):
13341336<head>
13351337<meta charset="UTF-8">
13361338<meta name="viewport" content="width=device-width,initial-scale=1">
1337- <title>ClaudeWatch Dashboard</title>
1339+ <title>ClaudeMonitor Dashboard</title>
13381340<script src="https://cdn.jsdelivr.net/npm/chart.js@4/dist/chart.umd.min.js"></script>
13391341<style>
13401342 *{{box-sizing:border-box;margin:0;padding:0}}
@@ -1396,7 +1398,7 @@ def generate_dashboard(usage, stats, conversations=None):
13961398 <circle cx="50" cy="50" r="48" fill="#0D9488"/>
13971399 <text x="50" y="67" text-anchor="middle" font-size="52" fill="white" font-family="-apple-system">◈</text>
13981400 </svg>
1399- ClaudeWatch
1401+ ClaudeMonitor
14001402</h1>
14011403<p class="sub">Updated { now_str } </p>
14021404
@@ -1447,7 +1449,7 @@ def generate_dashboard(usage, stats, conversations=None):
14471449{ conversations_html }
14481450{ _model_guide_html (usage , stats )}
14491451
1450- <div class="footer">ClaudeWatch · data from Claude desktop app · <a href="{ CLAUDE_USAGE_URL } " style="color:var(--accent)">open claude.ai</a></div>
1452+ <div class="footer">ClaudeMonitor · data from Claude desktop app · <a href="{ CLAUDE_USAGE_URL } " style="color:var(--accent)">open claude.ai</a></div>
14511453</body>
14521454</html>"""
14531455
@@ -1535,7 +1537,7 @@ def _build_conversations_html(conversations):
15351537 </tr>\n """
15361538
15371539 return f"""<!DOCTYPE html>
1538- <html><head><meta charset="utf-8"><title>ClaudeWatch — Recent Conversations</title>
1540+ <html><head><meta charset="utf-8"><title>ClaudeMonitor — Recent Conversations</title>
15391541<style>
15401542body {{ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
15411543 margin: 2rem; background: #1a1a2e; color: #e0e0e0; }}
@@ -1616,15 +1618,33 @@ def _build_title(usage, config, velocity=None, recent_session_pcts=None):
16161618 return title
16171619
16181620
1621+ def _apply_colored_title (app , title , session_pct , weekly_pct ):
1622+ """Set the menu bar title with color using NSAttributedString."""
1623+ try :
1624+ import AppKit
1625+ pct = max (session_pct or 0 , weekly_pct or 0 )
1626+ if pct >= 80 :
1627+ color = AppKit .NSColor .systemRedColor ()
1628+ elif pct >= 60 :
1629+ color = AppKit .NSColor .systemOrangeColor ()
1630+ else :
1631+ color = AppKit .NSColor .labelColor ()
1632+ attrs = {AppKit .NSForegroundColorAttributeName : color }
1633+ attr_str = AppKit .NSAttributedString .alloc ().initWithString_attributes_ (title , attrs )
1634+ app ._nsapp .nsstatusitem .setAttributedTitle_ (attr_str )
1635+ except Exception :
1636+ pass # plain title already set as fallback
1637+
1638+
16191639def _build_tooltip (usage , velocity = None , runway = None , msg_est = None ):
16201640 if usage is None :
1621- return "ClaudeWatch — no data"
1641+ return "ClaudeMonitor — no data"
16221642 spct = usage .get ("session_pct" , 0 )
16231643 wpct = usage .get ("weekly_pct" , 0 )
16241644 sr = _fmt_reset (usage .get ("session_resets_at" , "" ))
16251645 wr = _fmt_reset (usage .get ("weekly_resets_at" , "" ))
16261646 lines = [
1627- "ClaudeWatch " ,
1647+ "ClaudeMonitor " ,
16281648 f"Session (5h): { spct :.0f} %" + (f" — resets in { sr } " if sr else "" ),
16291649 f"Weekly (7d): { wpct :.0f} %" + (f" — resets in { wr } " if wr else "" ),
16301650 ]
@@ -1798,30 +1818,30 @@ def _info(label):
17981818
17991819 # First-run: offer login item when running as bundled .app
18001820 if getattr (_sys , 'frozen' , False ):
1801- _plist = Path .home () / "Library/LaunchAgents/com.katespurr.claudewatch .plist"
1821+ _plist = Path .home () / "Library/LaunchAgents/com.katespurr.claudemonitor .plist"
18021822 if not _plist .exists ():
18031823 self ._first_run_timer = rumps .Timer (self ._prompt_login_item , 1.5 )
18041824 self ._first_run_timer .start ()
18051825
18061826 def _prompt_login_item (self , timer ):
18071827 timer .stop ()
18081828 response = rumps .alert (
1809- title = "Welcome to ClaudeWatch " ,
1810- message = "Start ClaudeWatch automatically when you log in?" ,
1829+ title = "Welcome to ClaudeMonitor " ,
1830+ message = "Start ClaudeMonitor automatically when you log in?" ,
18111831 ok = "Yes, start at login" ,
18121832 cancel = "Not now" ,
18131833 )
18141834 if response == 1 :
1815- executable = Path (_sys .executable ).parent / "ClaudeWatch "
1816- plist_file = Path .home () / "Library/LaunchAgents/com.katespurr.claudewatch .plist"
1835+ executable = Path (_sys .executable ).parent / "ClaudeMonitor "
1836+ plist_file = Path .home () / "Library/LaunchAgents/com.katespurr.claudemonitor .plist"
18171837 plist_file .parent .mkdir (parents = True , exist_ok = True )
18181838 plist_file .write_text (f"""<?xml version="1.0" encoding="UTF-8"?>
18191839<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
18201840 "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
18211841<plist version="1.0">
18221842<dict>
18231843 <key>Label</key>
1824- <string>com.katespurr.claudewatch </string>
1844+ <string>com.katespurr.claudemonitor </string>
18251845 <key>ProgramArguments</key>
18261846 <array>
18271847 <string>{ executable } </string>
@@ -1837,8 +1857,8 @@ def _prompt_login_item(self, timer):
18371857</dict>
18381858</plist>""" )
18391859 subprocess .run (["launchctl" , "load" , str (plist_file )], check = False )
1840- rumps .notification ("ClaudeWatch " , "Login item installed" ,
1841- "ClaudeWatch will start automatically at login." )
1860+ rumps .notification ("ClaudeMonitor " , "Login item installed" ,
1861+ "ClaudeMonitor will start automatically at login." )
18421862
18431863 # ── Settings ──
18441864
@@ -1909,7 +1929,7 @@ def open_dashboard(self, _):
19091929 path = generate_dashboard (self ._usage , self ._stats , getattr (self , "_conversations" , None ))
19101930 webbrowser .open (f"file://{ path } " )
19111931 else :
1912- rumps .notification ("ClaudeWatch " , "No data yet" , "Fetching usage now…" )
1932+ rumps .notification ("ClaudeMonitor " , "No data yet" , "Fetching usage now…" )
19131933 threading .Thread (target = self ._refresh , daemon = True ).start ()
19141934
19151935 @rumps .clicked ("Recent Conversations" )
@@ -1925,7 +1945,7 @@ def _fetch_conversations(self, sender):
19251945 path .write_text (html )
19261946 webbrowser .open (f"file://{ path } " )
19271947 except Exception as e :
1928- rumps .notification ("ClaudeWatch " , "Error loading conversations" , str (e )[:100 ])
1948+ rumps .notification ("ClaudeMonitor " , "Error loading conversations" , str (e )[:100 ])
19291949 finally :
19301950 sender .title = "Recent Conversations"
19311951
@@ -1938,7 +1958,7 @@ def quit_app(self, _):
19381958 rumps .quit_application ()
19391959
19401960 def restart_app (self , _ ):
1941- rumps .notification ("ClaudeWatch " , "Restarting…" , "" )
1961+ rumps .notification ("ClaudeMonitor " , "Restarting…" , "" )
19421962 rumps .quit_application ()
19431963
19441964 # ── Refresh interval ──
@@ -2169,7 +2189,7 @@ def _check_limits(self, usage):
21692189 # Notify on new session (but not on first launch)
21702190 if old_reset is not None and sreset :
21712191 rumps .notification (
2172- "ClaudeWatch " ,
2192+ "ClaudeMonitor " ,
21732193 "New session started" ,
21742194 "Fresh 5-hour window — slate is clean." ,
21752195 )
@@ -2181,14 +2201,14 @@ def _check_limits(self, usage):
21812201 key = f"session_{ t } "
21822202 if spct >= t and key not in self ._notified :
21832203 self ._notified .add (key )
2184- rumps .notification ("ClaudeWatch " , f"Session usage at { t } %" ,
2204+ rumps .notification ("ClaudeMonitor " , f"Session usage at { t } %" ,
21852205 f"Resets in { sr } ." if sr else "" )
21862206
21872207 for t in WEEKLY_THRESHOLDS :
21882208 key = f"weekly_{ t } "
21892209 if wpct >= t and key not in self ._notified :
21902210 self ._notified .add (key )
2191- rumps .notification ("ClaudeWatch " , f"Weekly usage at { t } %" ,
2211+ rumps .notification ("ClaudeMonitor " , f"Weekly usage at { t } %" ,
21922212 f"Resets in { wr } ." if wr else "" )
21932213
21942214 extra_pct = usage .get ("extra_pct" )
@@ -2198,7 +2218,7 @@ def _check_limits(self, usage):
21982218 key = f"extra_{ t } "
21992219 if extra_pct >= t and key not in self ._notified :
22002220 self ._notified .add (key )
2201- rumps .notification ("ClaudeWatch " , f"Extra credits at { t } %" ,
2221+ rumps .notification ("ClaudeMonitor " , f"Extra credits at { t } %" ,
22022222 f"{ eu :.0f} credits used" )
22032223
22042224 def _apply_ui (self , usage , error = None ):
@@ -2210,7 +2230,7 @@ def _apply_ui(self, usage, error=None):
22102230 if error :
22112231 self .title = "!"
22122232 self .menu [session_detail ].title = f" { error [:60 ]} "
2213- self ._set_tooltip (f"ClaudeWatch — error\n { error [:120 ]} " )
2233+ self ._set_tooltip (f"ClaudeMonitor — error\n { error [:120 ]} " )
22142234 return
22152235
22162236 velocity = getattr (self , "_velocity" , None )
@@ -2220,6 +2240,7 @@ def _apply_ui(self, usage, error=None):
22202240 recent_pcts = getattr (self , "_recent_session_pcts" , None )
22212241
22222242 self .title = _build_title (usage , self .config , velocity , recent_pcts )
2243+ _apply_colored_title (self , self .title , usage .get ("session_pct" , 0 ), usage .get ("weekly_pct" , 0 ))
22232244
22242245 if self .config .get ("show_hover_tooltip" , True ):
22252246 self ._set_tooltip (_build_tooltip (usage , velocity , runway , msg_est ))
0 commit comments