11#!/usr/bin/env python3
2- """CodingBuddy statusLine script (#1088).
2+ """CodingBuddy statusLine script (#1088, #1325 ).
33
44Claude Code invokes this via settings.json statusLine.command.
55Reads session data from stdin JSON, outputs formatted status to stdout.
6+
7+ Telemetry fallback order per field:
8+ cost → stdin cost.total_cost_usd > estimate_cost()
9+ duration → stdin cost.total_duration_ms > hud-state sessionStartTimestamp
10+ agent → stdin agent.name > CODINGBUDDY_ACTIVE_AGENT env
11+ model → stdin model.display_name > model.id
612"""
713import json
814import os
@@ -103,6 +109,16 @@ def get_health(ctx_pct: float) -> str:
103109 return "\U0001f7e2 " # 🟢
104110
105111
112+ def format_duration_ms (ms ) -> str :
113+ """Format milliseconds to duration like '12m' or '1h23m'."""
114+ total_minutes = int (ms / 60_000 )
115+ if total_minutes < 60 :
116+ return f"{ total_minutes } m"
117+ hours = total_minutes // 60
118+ minutes = total_minutes % 60
119+ return f"{ hours } h{ minutes :02d} m"
120+
121+
106122def format_duration (start_timestamp : str ) -> str :
107123 """Format ISO timestamp to duration like '12m' or '1h23m'."""
108124 try :
@@ -138,41 +154,124 @@ def read_state(state_file: str = DEFAULT_STATE_FILE) -> dict:
138154 return {}
139155
140156
157+ def resolve_cost (stdin_data : dict , model_id : str , ctx_window : dict ) -> tuple :
158+ """Resolve cost: stdin exact > estimate. Returns (cost, is_exact)."""
159+ exact = (stdin_data .get ("cost" ) or {}).get ("total_cost_usd" )
160+ if exact is not None :
161+ return (float (exact ), True )
162+ return (estimate_cost (model_id , ctx_window ), False )
163+
164+
165+ def resolve_duration (stdin_data : dict , hud_state : dict ) -> str :
166+ """Resolve duration: stdin exact > hud-state timestamp > '0m'."""
167+ exact_ms = (stdin_data .get ("cost" ) or {}).get ("total_duration_ms" )
168+ if exact_ms is not None :
169+ return format_duration_ms (exact_ms )
170+ start_ts = hud_state .get ("sessionStartTimestamp" , "" )
171+ if start_ts :
172+ return format_duration (start_ts )
173+ return "0m"
174+
175+
176+ def resolve_agent (stdin_data : dict , env_agent : str = "" ) -> str :
177+ """Resolve agent: stdin > env var."""
178+ stdin_agent = (stdin_data .get ("agent" ) or {}).get ("name" , "" )
179+ return stdin_agent or env_agent
180+
181+
182+ def resolve_model_label (stdin_data : dict ) -> tuple :
183+ """Resolve model info. Returns (model_id, display_label)."""
184+ model_info = stdin_data .get ("model" ) or {}
185+ model_id = model_info .get ("id" , "" )
186+ display_name = model_info .get ("display_name" , "" )
187+ return (model_id , display_name )
188+
189+
190+ def format_rate_limits (stdin_data : dict ) -> str :
191+ """Format rate-limit info if present. Returns '' when absent."""
192+ rl = stdin_data .get ("rate_limits" )
193+ if not rl :
194+ return ""
195+ parts = []
196+ five = rl .get ("five_hour" )
197+ if five :
198+ pct = five .get ("used_percentage" , 0 )
199+ parts .append (f"5h:{ pct :.0f} %" )
200+ seven = rl .get ("seven_day" )
201+ if seven :
202+ pct = seven .get ("used_percentage" , 0 )
203+ parts .append (f"7d:{ pct :.0f} %" )
204+ if not parts :
205+ return ""
206+ return "RL:" + "," .join (parts )
207+
208+
209+ def format_worktree (stdin_data : dict ) -> str :
210+ """Format worktree name if present. Returns '' when absent."""
211+ wt = stdin_data .get ("worktree" )
212+ if not wt :
213+ return ""
214+ name = wt .get ("name" , "" )
215+ return f"WT:{ name } " if name else ""
216+
217+
141218def format_status_line (
142219 stdin_data : dict ,
143220 hud_state : dict ,
144221 active_agent : str = "" ,
145222) -> str :
146- """Format the statusLine output."""
223+ """Format the statusLine output.
224+
225+ Fallback order per field:
226+ cost → stdin cost.total_cost_usd > estimate_cost()
227+ duration → stdin cost.total_duration_ms > hud-state sessionStartTimestamp
228+ agent → stdin agent.name > active_agent param
229+ model → stdin model.display_name > model.id
230+ """
147231 version = hud_state .get ("version" , "" )
148232 mode = hud_state .get ("currentMode" )
149233 mode_label = mode if mode else "Ready"
150234
151- ctx_window = stdin_data .get ("context_window" , {})
235+ ctx_window = stdin_data .get ("context_window" ) or {}
152236 ctx_pct = ctx_window .get ("used_percentage" , 0 ) or 0
153237 health = get_health (ctx_pct )
154238
155- start_ts = hud_state .get ("sessionStartTimestamp" , "" )
156- duration = format_duration (start_ts ) if start_ts else "0m"
157-
158- model_id = ""
159- model_info = stdin_data .get ("model" , {})
160- if model_info :
161- model_id = model_info .get ("id" , "" )
162-
163- cost = estimate_cost (model_id , ctx_window )
239+ model_id , display_name = resolve_model_label (stdin_data )
240+ cost , is_exact = resolve_cost (stdin_data , model_id , ctx_window )
241+ duration = resolve_duration (stdin_data , hud_state )
164242 cache = compute_cache_hit_rate (ctx_window )
243+ agent = resolve_agent (stdin_data , active_agent )
244+
245+ cost_prefix = "$" if is_exact else "~$"
165246
166247 ver_str = f" v{ version } " if version else ""
167- line1 = (
168- f"{ BUDDY_FACE } CB{ ver_str } | { mode_label } { health } | "
169- f"{ duration } | ~${ cost :.2f} | Cache:{ cache :.0f} % | Ctx:{ ctx_pct :.0f} %"
170- )
171248
172- if not active_agent :
249+ segments = [
250+ f"{ BUDDY_FACE } CB{ ver_str } " ,
251+ f"{ mode_label } { health } " ,
252+ duration ,
253+ f"{ cost_prefix } { cost :.2f} " ,
254+ f"Cache:{ cache :.0f} %" ,
255+ f"Ctx:{ ctx_pct :.0f} %" ,
256+ ]
257+
258+ rl = format_rate_limits (stdin_data )
259+ if rl :
260+ segments .append (rl )
261+
262+ wt = format_worktree (stdin_data )
263+ if wt :
264+ segments .append (wt )
265+
266+ if display_name :
267+ segments .append (display_name )
268+
269+ line1 = " | " .join (segments )
270+
271+ if not agent :
173272 return line1
174273
175- return f"{ line1 } \n \U0001f916 { active_agent } "
274+ return f"{ line1 } \n \U0001f916 { agent } "
176275
177276
178277def main ():
@@ -183,9 +282,9 @@ def main():
183282 state_file = os .environ .get ("CODINGBUDDY_HUD_STATE_FILE" , DEFAULT_STATE_FILE )
184283 hud_state = read_state (state_file )
185284
186- active_agent = os .environ .get ("CODINGBUDDY_ACTIVE_AGENT" , "" )
285+ env_agent = os .environ .get ("CODINGBUDDY_ACTIVE_AGENT" , "" )
187286
188- output = format_status_line (stdin_data , hud_state , active_agent )
287+ output = format_status_line (stdin_data , hud_state , env_agent )
189288 print (output )
190289 except Exception :
191290 print (f"{ BUDDY_FACE } CodingBuddy" )
0 commit comments