Skip to content

Commit e154ec1

Browse files
committed
feat(plugin): use exact Claude Code stdin telemetry in status bar (#1325)
Prefer exact telemetry from Claude Code stdin over estimates: - cost: stdin cost.total_cost_usd > estimate_cost() (show $ vs ~$) - duration: stdin cost.total_duration_ms > hud-state timestamp - agent: stdin agent.name > CODINGBUDDY_ACTIVE_AGENT env - model: show display_name when available - rate_limits: show 5h/7d usage when present - worktree: show WT name when present Closes #1325
1 parent 4d02cd2 commit e154ec1

2 files changed

Lines changed: 339 additions & 22 deletions

File tree

packages/claude-code-plugin/hooks/codingbuddy-hud.py

Lines changed: 119 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
#!/usr/bin/env python3
2-
"""CodingBuddy statusLine script (#1088).
2+
"""CodingBuddy statusLine script (#1088, #1325).
33
44
Claude Code invokes this via settings.json statusLine.command.
55
Reads 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
"""
713
import json
814
import 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+
106122
def 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+
141218
def 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

178277
def 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

Comments
 (0)