From 1b2bf8cb753e76ce580a356793abbdb1c10a3e35 Mon Sep 17 00:00:00 2001 From: David Chen Date: Wed, 1 Oct 2025 15:26:31 -0700 Subject: [PATCH] add ccusage to tmux status --- tmux/tmux-status/ccusage-today.sh | 91 +++++++++++++++++++++++++++++++ tmux/tmux-status/right.sh | 38 ++++++++++--- 2 files changed, 121 insertions(+), 8 deletions(-) create mode 100644 tmux/tmux-status/ccusage-today.sh diff --git a/tmux/tmux-status/ccusage-today.sh b/tmux/tmux-status/ccusage-today.sh new file mode 100644 index 0000000..b30f0ac --- /dev/null +++ b/tmux/tmux-status/ccusage-today.sh @@ -0,0 +1,91 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Config +TTL_SECONDS=${TMUX_CCUSAGE_TTL:-10} +LOCK_WAIT=${TMUX_CCUSAGE_LOCK_WAIT:-2} +TODAY=$(date +%F) + +# Cache location (default to XDG cache) +CACHE_DIR="${XDG_CACHE_HOME:-$HOME/.cache}/tmux-ccusage" +mkdir -p "$CACHE_DIR" +CACHE_FILE="$CACHE_DIR/today-$TODAY.txt" +LOCK_DIR="$CACHE_DIR/today-$TODAY.lock" + +get_mtime() { + local f="$1" + if command -v stat >/dev/null 2>&1; then + # macOS: -f %m, GNU: -c %Y + stat -f %m "$f" 2>/dev/null || stat -c %Y "$f" 2>/dev/null || echo 0 + else + echo 0 + fi +} + +now=$(date +%s) + +# Serve cached value if fresh +if [[ -f "$CACHE_FILE" ]]; then + mtime=$(get_mtime "$CACHE_FILE") + if [[ -n "${mtime:-}" && $(( now - mtime )) -lt $TTL_SECONDS ]]; then + cat "$CACHE_FILE" + exit 0 + fi +fi + +# Build command +cmd=(ccusage-codex daily -j -O -s "$TODAY" -u "$TODAY") +if [[ -n "${TMUX_CCUSAGE_TZ:-}" ]]; then + cmd+=(-z "$TMUX_CCUSAGE_TZ") +fi + +# Attempt to acquire lock; if locked, wait briefly and serve stale +if mkdir "$LOCK_DIR" 2>/dev/null; then + trap 'rmdir "$LOCK_DIR" 2>/dev/null || true' EXIT + + # Fetch today's totals, offline pricing if available, tolerate failures + json=$("${cmd[@]}" 2>/dev/null || true) + + cost=$(printf '%s' "${json:-}" | jq -r '.totals?.costUSD // 0' 2>/dev/null || printf '0') + + # Coerce to number and format to 2 decimals, with dollar sign only + if command -v python3 >/dev/null 2>&1; then + formatted=$(COST="$cost" python3 - << 'PY' +import os,sys +try: + v=float(os.environ.get('COST','0')) +except Exception: + v=0.0 +print(f"${v:.2f}") +PY + ) + else + # Fallback shell formatting + formatted="$(printf '$%.2f' "${cost:-0}")" + fi + + tmp_file="$CACHE_FILE.$$" + printf '%s\n' "$formatted" > "$tmp_file" + mv "$tmp_file" "$CACHE_FILE" + printf '%s\n' "$formatted" +else + # Another process is updating; wait briefly for refresh, then serve cache + start=$(date +%s) + initial_mtime="" + [[ -f "$CACHE_FILE" ]] && initial_mtime=$(get_mtime "$CACHE_FILE") + while :; do + sleep 0.1 + now=$(date +%s) + (( now - start >= LOCK_WAIT )) && break + new_mtime="" + [[ -f "$CACHE_FILE" ]] && new_mtime=$(get_mtime "$CACHE_FILE") + if [[ -n "$new_mtime" && "$new_mtime" != "$initial_mtime" ]]; then + break + fi + done + if [[ -f "$CACHE_FILE" ]]; then + cat "$CACHE_FILE" + else + printf '$0.00\n' + fi +fi diff --git a/tmux/tmux-status/right.sh b/tmux/tmux-status/right.sh index 822f823..a5440c4 100755 --- a/tmux/tmux-status/right.sh +++ b/tmux/tmux-status/right.sh @@ -23,12 +23,16 @@ fi segment_bg="#3b4252" segment_fg="#eceff4" +# Host (domain) colors to mirror left active style +host_bg="#b294bb" +host_fg="#1d1f21" separator="" right_cap="█" hostname=$(hostname -s 2>/dev/null || hostname 2>/dev/null || printf 'host') rainbarf_bg="#2e3440" rainbarf_segment="" rainbarf_toggle="${TMUX_RAINBARF:-1}" +ccusage_segment="" case "$rainbarf_toggle" in 0|false|FALSE|off|OFF|no|NO) @@ -49,16 +53,34 @@ if [[ "$rainbarf_toggle" == "1" ]] && command -v rainbarf >/dev/null 2>&1; then fi fi -host_prefix=$(printf '#[fg=%s,bg=%s]%s#[fg=%s,bg=%s] ' \ - "$segment_bg" "$status_bg" "$separator" \ - "$segment_fg" "$segment_bg") - -if [[ -n "$rainbarf_segment" ]]; then - host_prefix=$(printf '#[fg=%s,bg=%s] ' "$segment_fg" "$segment_bg") +# ccusage-codex today cost (cached) +ccusage_output=$(bash "$HOME/.config/tmux/tmux-status/ccusage-today.sh" 2>/dev/null || true) +if [[ -n "$ccusage_output" ]]; then + # Connect from previous segment if present, else from status-bg + connector_bg="$status_bg" + if [[ -n "$rainbarf_segment" ]]; then + connector_bg="$rainbarf_bg" + fi + ccusage_segment=$(printf '#[fg=%s,bg=%s]%s#[fg=%s,bg=%s] %s ' \ + "$segment_bg" "$connector_bg" "$separator" \ + "$segment_fg" "$segment_bg" "$ccusage_output") fi -printf '%s%s%s #[fg=%s,bg=%s]%s' \ +# Build a connector into the hostname segment using host colors +host_connector_bg="$status_bg" +if [[ -n "$rainbarf_segment" ]]; then + host_connector_bg="$rainbarf_bg" +fi +if [[ -n "$ccusage_segment" ]]; then + host_connector_bg="$segment_bg" +fi +host_prefix=$(printf '#[fg=%s,bg=%s]%s#[fg=%s,bg=%s] ' \ + "$host_bg" "$host_connector_bg" "$separator" \ + "$host_fg" "$host_bg") + +printf '%s%s%s%s #[fg=%s,bg=%s]%s' \ "$rainbarf_segment" \ + "$ccusage_segment" \ "$host_prefix" \ "$hostname" \ - "$segment_bg" "$status_bg" "$right_cap" + "$host_bg" "$status_bg" "$right_cap"