Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 45 additions & 12 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ def load_cli_config() -> Dict[str, Any]:
"compact": False,
"resume_display": "full",
"show_reasoning": False,
"show_cost": False,
"skin": "default",
},
"clarify": {
Expand Down Expand Up @@ -1023,6 +1024,8 @@ def __init__(
self.bell_on_complete = CLI_CONFIG["display"].get("bell_on_complete", False)
# show_reasoning: display model thinking/reasoning before the response
self.show_reasoning = CLI_CONFIG["display"].get("show_reasoning", False)
# show_cost: display $ cost in the status bar (off by default)
self.show_cost = CLI_CONFIG["display"].get("show_cost", False)
self.verbose = verbose if verbose is not None else (self.tool_progress_mode == "verbose")

# Configuration - priority: CLI args > env vars > config file
Expand Down Expand Up @@ -1276,13 +1279,22 @@ def _build_status_bar_text(self, width: Optional[int] = None) -> str:
width = width or shutil.get_terminal_size((80, 24)).columns
percent = snapshot["context_percent"]
percent_label = f"{percent}%" if percent is not None else "--"
cost_label = f"${snapshot['session_cost']:.2f}" if snapshot["pricing_known"] else "cost n/a"
duration_label = snapshot["duration"]
show_cost = getattr(self, "show_cost", False)

if show_cost:
cost_label = f"${snapshot['session_cost']:.2f}" if snapshot["pricing_known"] else "cost n/a"
else:
cost_label = None

if width < 52:
return f"⚕ {snapshot['model_short']} · {duration_label}"
if width < 76:
return f"⚕ {snapshot['model_short']} · {percent_label} · {cost_label} · {duration_label}"
parts = [f"⚕ {snapshot['model_short']}", percent_label]
if cost_label:
parts.append(cost_label)
parts.append(duration_label)
return " · ".join(parts)

if snapshot["context_length"]:
ctx_total = _format_context_length(snapshot["context_length"])
Expand All @@ -1291,16 +1303,25 @@ def _build_status_bar_text(self, width: Optional[int] = None) -> str:
else:
context_label = "ctx --"

return f"⚕ {snapshot['model_short']} │ {context_label} │ {percent_label} │ {cost_label} │ {duration_label}"
parts = [f"⚕ {snapshot['model_short']}", context_label, percent_label]
if cost_label:
parts.append(cost_label)
parts.append(duration_label)
return " │ ".join(parts)
except Exception:
return f"⚕ {self.model if getattr(self, 'model', None) else 'Hermes'}"

def _get_status_bar_fragments(self):
try:
snapshot = self._get_status_bar_snapshot()
width = shutil.get_terminal_size((80, 24)).columns
cost_label = f"${snapshot['session_cost']:.2f}" if snapshot["pricing_known"] else "cost n/a"
duration_label = snapshot["duration"]
show_cost = getattr(self, "show_cost", False)

if show_cost:
cost_label = f"${snapshot['session_cost']:.2f}" if snapshot["pricing_known"] else "cost n/a"
else:
cost_label = None

if width < 52:
return [
Expand All @@ -1314,17 +1335,23 @@ def _get_status_bar_fragments(self):
percent = snapshot["context_percent"]
percent_label = f"{percent}%" if percent is not None else "--"
if width < 76:
return [
frags = [
("class:status-bar", " ⚕ "),
("class:status-bar-strong", snapshot["model_short"]),
("class:status-bar-dim", " · "),
(self._status_bar_context_style(percent), percent_label),
("class:status-bar-dim", " · "),
("class:status-bar-dim", cost_label),
]
if cost_label:
frags.extend([
("class:status-bar-dim", " · "),
("class:status-bar-dim", cost_label),
])
frags.extend([
("class:status-bar-dim", " · "),
("class:status-bar-dim", duration_label),
("class:status-bar", " "),
]
])
return frags

if snapshot["context_length"]:
ctx_total = _format_context_length(snapshot["context_length"])
Expand All @@ -1334,7 +1361,7 @@ def _get_status_bar_fragments(self):
context_label = "ctx --"

bar_style = self._status_bar_context_style(percent)
return [
frags = [
("class:status-bar", " ⚕ "),
("class:status-bar-strong", snapshot["model_short"]),
("class:status-bar-dim", " │ "),
Expand All @@ -1343,12 +1370,18 @@ def _get_status_bar_fragments(self):
(bar_style, self._build_context_bar(percent)),
("class:status-bar-dim", " "),
(bar_style, percent_label),
("class:status-bar-dim", " │ "),
("class:status-bar-dim", cost_label),
]
if cost_label:
frags.extend([
("class:status-bar-dim", " │ "),
("class:status-bar-dim", cost_label),
])
frags.extend([
("class:status-bar-dim", " │ "),
("class:status-bar-dim", duration_label),
("class:status-bar", " "),
]
])
return frags
except Exception:
return [("class:status-bar", f" {self._build_status_bar_text()} ")]

Expand Down
1 change: 1 addition & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def ensure_hermes_home():
"resume_display": "full",
"bell_on_complete": False,
"show_reasoning": False,
"show_cost": False, # Show $ cost in the status bar (off by default)
"skin": "default",
},

Expand Down
27 changes: 21 additions & 6 deletions tests/test_cli_status_bar.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,24 +65,39 @@ def test_build_status_bar_text_for_wide_terminal(self):
assert "claude-sonnet-4-20250514" in text
assert "12.4K/200K" in text
assert "6%" in text
assert "$0.06" in text
assert "$0.06" not in text # cost hidden by default
assert "15m" in text

def test_build_status_bar_text_shows_cost_when_enabled(self):
cli_obj = _attach_agent(
_make_cli(),
prompt_tokens=10000,
completion_tokens=2400,
total_tokens=12400,
api_calls=7,
context_tokens=12400,
context_length=200_000,
)
cli_obj.show_cost = True

text = cli_obj._build_status_bar_text(width=120)
assert "$" in text # cost is shown when enabled

def test_build_status_bar_text_collapses_for_narrow_terminal(self):
cli_obj = _attach_agent(
_make_cli(),
prompt_tokens=10_230,
completion_tokens=2_220,
total_tokens=12_450,
prompt_tokens=10000,
completion_tokens=2400,
total_tokens=12400,
api_calls=7,
context_tokens=12_450,
context_tokens=12400,
context_length=200_000,
)

text = cli_obj._build_status_bar_text(width=60)

assert "⚕" in text
assert "$0.06" in text
assert "$0.06" not in text # cost hidden by default
assert "15m" in text
assert "200K" not in text

Expand Down
Loading