Waybar with claude code and Codex usage
Waybar with claude code and Codex usage
Hello,
i am a new noob to hyprland and i used claude code to edit my dotfiles. For that i used ccusage.
Greetings!
Waybar with claude code and Codex usage
Hello,
i am a new noob to hyprland and i used claude code to edit my dotfiles. For that i used ccusage.
Greetings!
This guide shows how to configure Waybar (for Wayland/Hyprland) with live status displays for Claude Code and Codex. The modules show current usage and rate limits in real-time in the status bar.
Waybar displays two modules:
Example output:
CC 🕐2h45m 67% ████████░ │ COD 🕐4h12m 23% ██░░░░░░
jq (JSON Processor)One of the following package managers:
npx (comes with Node.js)bunx (Bun)pnpmnpm install -g ccusage or via npx ccusagenpm install -g @ccusage/codex or via npx @ccusage/codex
~/.config/waybar/
├── config.jsonc # Waybar main configuration
├── style.css # Waybar styling
└── scripts/
├── ccusage-statusline.sh # Claude Code status script
├── codex-statusline.sh # Codex status script
└── api-config.sh (optional) # API configuration
Open ~/.config/waybar/config.jsonc and add the two modules:
jsonc
{
"modules-center": [
// ... other modules ...
"custom/ccusage",
"custom/codex-usage",
// ... other modules ...
],
// ... rest of config ...
"custom/ccusage": {
"exec": "~/.config/waybar/scripts/ccusage-statusline.sh",
"interval": 300,
"format": "{}",
"tooltip-format": "ccusage daily + monthly totals (Claude Code)\nClick to refresh",
"on-click": "~/.config/waybar/scripts/ccusage-statusline.sh"
},
"custom/codex-usage": {
"exec": "~/.config/waybar/scripts/codex-statusline.sh",
"interval": 300,
"format": "{}",
"tooltip-format": "ccusage codex daily + monthly totals\nClick to refresh",
"on-click": "~/.config/waybar/scripts/codex-statusline.sh"
}
}
Explanation:
exec: Path to the script that provides the datainterval: Update interval in seconds (300s = 5min)format: Display format (here directly the script output)on-click: Clicking triggers a manual script updateAdd the styling for the modules in ~/.config/waybar/style.css:
css
#custom-ccusage {
background-color: rgba(40, 42, 54, 0.8);
padding: 4px 10px;
margin: 2px 4px;
border-radius: 8px;
font-family: 'JetBrainsMono Nerd Font Mono';
font-size: 11px;
color: #ff6ac1; /* Pink for Claude Code */
}
#custom-codex-usage {
background-color: rgba(40, 42, 54, 0.8);
padding: 4px 10px;
margin: 2px 4px;
border-radius: 8px;
font-family: 'JetBrainsMono Nerd Font Mono';
font-size: 11px;
color: #6ef2d0; /* Turquoise for Codex */
}
Customizations:
color: Text color (can be changed as desired)font-family: Nerd Font for icons (install JetBrainsMono Nerd Font)font-size: Adjust font sizebash
mkdir -p ~/.config/waybar/scripts
chmod +x ~/.config/waybar/scripts/*.sh
Create ~/.config/waybar/scripts/ccusage-statusline.sh:
bash
#!/bin/bash
# Compact ccusage summary for Waybar (daily totals).
CONFIG_FILE="$HOME/.config/waybar/scripts/api-config.sh"
[ -f "$CONFIG_FILE" ] && source "$CONFIG_FILE"
# Prefer explicitly set binary, then cached npx installs, then package runners.
choose_runner() {
if [ -n "$CCUSAGE_BIN" ] && command -v "$CCUSAGE_BIN" >/dev/null 2>&1; then
echo "$CCUSAGE_BIN"
return
fi
# Reuse first cached npx install if present to avoid network lookups.
CACHE_BIN=$(find "$HOME/.npm/_npx" -path '*/node_modules/.bin/ccusage' -maxdepth 5 -print -quit 2>/dev/null)
if [ -n "$CACHE_BIN" ]; then
echo "$CACHE_BIN"
return
fi
if command -v ccusage >/dev/null 2>&1; then
echo "ccusage"
return
fi
if command -v bunx >/dev/null 2>&1; then
echo "bunx ccusage@latest"
return
fi
if command -v pnpm >/dev/null 2>&1; then
echo "pnpm dlx ccusage"
return
fi
if command -v npx >/dev/null 2>&1; then
echo "npx --yes ccusage@latest"
return
fi
}
RUNNER=$(choose_runner)
[ -z "$RUNNER" ] && { echo "CC ?"; exit 0; }
# Run command with a timeout to avoid hanging on network fetches.
run_cmd() {
local cmd=$1
if command -v timeout >/dev/null 2>&1; then
timeout 20s bash -lc "$cmd"
else
bash -lc "$cmd"
fi
}
# Helper: call ccusage command and emit cost/tokens TSV.
get_totals() {
local cmd=$1
local json
json=$(NO_COLOR=1 run_cmd "$RUNNER $cmd --json --offline" 2>/dev/null)
if [ -z "$json" ]; then
return 1
fi
printf '%s' "$json" | jq -r '(.totals.totalCost // .totals.costUSD // 0) as $c | (.totals.totalTokens // 0) as $t | [$c, $t] | @tsv' 2>/dev/null
}
format_tokens_short() {
local tokens=$1
if awk -v t="$tokens" 'BEGIN { exit (t >= 1000000000) ? 0 : 1 }'; then
awk -v t="$tokens" 'BEGIN { printf("%.1fB", t/1000000000) }'
elif awk -v t="$tokens" 'BEGIN { exit (t >= 1000000) ? 0 : 1 }'; then
awk -v t="$tokens" 'BEGIN { printf("%.1fM", t/1000000) }'
elif awk -v t="$tokens" 'BEGIN { exit (t >= 1000) ? 0 : 1 }'; then
awk -v t="$tokens" 'BEGIN { printf("%.0fk", t/1000) }'
else
printf "%s" "$tokens"
fi
}
build_progress_bar() {
local percent=$1
local segments=8
local filled=$((percent * segments / 100))
local empty=$((segments - filled))
[ $filled -gt $segments ] && filled=$segments
[ $empty -lt 0 ] && empty=0
local bar=""
for ((i = 0; i < filled; i++)); do bar+="█"; done
for ((i = 0; i < empty; i++)); do bar+="░"; done
printf "%s" "$bar"
}
block_progress() {
# Show current active 5h billing block status
local json
json=$(NO_COLOR=1 run_cmd "$RUNNER blocks --json --offline" 2>/dev/null)
if [ -z "$json" ]; then
return 1
fi
# Extract active block info: cost, end time
local block_info
block_info=$(printf '%s' "$json" | jq -r '
.blocks[]
| select(.isActive == true and .isGap == false)
| [.costUSD, .endTime, .tokenCounts.inputTokens + .tokenCounts.outputTokens] | @tsv
' 2>/dev/null) || return 1
if [ -z "$block_info" ]; then
# No active block, show "idle"
printf "⏸ idle"
return 0
fi
local cost end_time tokens
read -r cost end_time tokens <<<"$block_info"
# Estimate block limit: Claude Code typically allows ~$15-20 per 5h block
# We'll use $15 as baseline (adjustable via CLAUDE_BLOCK_LIMIT_USD)
local block_limit="${CLAUDE_BLOCK_LIMIT_USD:-15}"
local percent
percent=$(awk -v c="$cost" -v l="$block_limit" 'BEGIN { if (l <= 0) { print 0 } else { printf("%.0f", (c / l) * 100) } }') || percent=0
[ -z "$percent" ] && percent=0
[ "$percent" -gt 100 ] && percent=100
[ "$percent" -lt 0 ] && percent=0
# Calculate time remaining until block ends
local now_epoch end_epoch time_remaining
now_epoch=$(date +%s)
end_epoch=$(date -d "$end_time" +%s 2>/dev/null)
if [ -n "$end_epoch" ] && [ "$end_epoch" -gt "$now_epoch" ]; then
time_remaining=$((end_epoch - now_epoch))
local hours=$((time_remaining / 3600))
local mins=$(( (time_remaining % 3600) / 60 ))
local time_str="${hours}h${mins}m"
else
local time_str="ending"
fi
local bar
bar=$(build_progress_bar "$percent")
printf "🕐%s %3d%% %s" "$time_str" "$percent" "$bar"
}
format_line() {
local label=$1
local cost=$2
printf "%s $%.2f" "$label" "$cost"
}
BLOCK_STATUS=$(block_progress)
if [ -z "$BLOCK_STATUS" ]; then
echo "CC …"
exit 0
fi
echo "CC $BLOCK_STATUS"
Create ~/.config/waybar/scripts/codex-statusline.sh:
bash
#!/bin/bash
# Compact @ccusage/codex summary for Waybar (daily + monthly).
CONFIG_FILE="$HOME/.config/waybar/scripts/api-config.sh"
[ -f "$CONFIG_FILE" ] && source "$CONFIG_FILE"
choose_runner() {
# Explicit override.
if [ -n "$CCUSAGE_CODEX_BIN" ] && command -v "$CCUSAGE_CODEX_BIN" >/dev/null 2>&1; then
echo "$CCUSAGE_CODEX_BIN"
return
fi
# Local install via npm prefix.
LOCAL_BIN="$HOME/.local/share/ccusage-codex/node_modules/.bin/ccusage-codex"
if [ -x "$LOCAL_BIN" ]; then
echo "$LOCAL_BIN"
return
fi
# Cached npx install.
CACHE_BIN=$(find "$HOME/.npm/_npx" -path '*@ccusage/codex*/node_modules/.bin/codex' -maxdepth 5 -print -quit 2>/dev/null)
[ -z "$CACHE_BIN" ] && CACHE_BIN=$(find "$HOME/.npm/_npx" -path '*@ccusage/codex*/node_modules/.bin/ccusage-codex' -maxdepth 5 -print -quit 2>/dev/null)
if [ -n "$CACHE_BIN" ]; then
echo "$CACHE_BIN"
return
fi
if command -v bunx >/dev/null 2>&1; then
echo "bunx @ccusage/codex@latest"
return
fi
if command -v pnpm >/dev/null 2>&1; then
echo "pnpm dlx @ccusage/codex"
return
fi
if command -v npx >/dev/null 2>&1; then
echo "npm_config_offline=true npx --yes @ccusage/codex@latest"
return
fi
# Last resort: plain codex in PATH (may collide with other tools).
if command -v codex >/dev/null 2>&1; then
echo "codex"
return
fi
}
RUNNER=$(choose_runner)
[ -z "$RUNNER" ] && { echo "codex ?"; exit 0; }
run_cmd() {
local cmd=$1
if command -v timeout >/dev/null 2>&1; then
timeout 20s bash -lc "$cmd"
else
bash -lc "$cmd"
fi
}
get_totals() {
local cmd=$1
local json
json=$(NO_COLOR=1 run_cmd "$RUNNER $cmd --json --offline" 2>/dev/null)
if [ -z "$json" ]; then
return 1
fi
printf '%s' "$json" | jq -r '(.totals.totalCost // .totals.costUSD // 0) as $c | (.totals.totalTokens // 0) as $t | [$c, $t] | @tsv' 2>/dev/null
}
format_tokens_short() {
local tokens=$1
if awk -v t="$tokens" 'BEGIN { exit (t >= 1000000000) ? 0 : 1 }'; then
awk -v t="$tokens" 'BEGIN { printf("%.1fB", t/1000000000) }'
elif awk -v t="$tokens" 'BEGIN { exit (t >= 1
Random aside, but ZAI is having a sale on GLM 4.6 API. Its dirt cheap, and open weights (unlike claude), and awesome.
Even better, you can host it locally fairly easily with a decent gaming PC, and get a fairly close approximation to the remote API (for free, privately).
I have a Month sub but im Not that happy. But for Kovalainen usage it would be really nice.