Skip Navigation
9 comments
  • 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).

  • Waybar with Claude Code & Codex Usage Display

    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.

    📸 Example

    Waybar displays two modules:

    • CC (Claude Code): Shows 5h-block status with progress bar
    • COD (Codex): Shows rate-limit status with progress bar

    Example output:

     
        
    CC 🕐2h45m  67% ████████░  │  COD 🕐4h12m  23% ██░░░░░░
    
      

    🔧 Prerequisites

    System

    • Waybar (for Wayland/Hyprland)
    • Linux (Arch Linux, Ubuntu, etc.)
    • Bash Shell
    • jq (JSON Processor)

    Node.js Tools

    One of the following package managers:

    • npx (comes with Node.js)
    • bunx (Bun)
    • pnpm

    Claude Code & Codex Tools

    • ccusage: npm install -g ccusage or via npx ccusage
    • @ccusage/codex: npm install -g @ccusage/codex or via npx @ccusage/codex

    📁 File Structure

     
        
    ~/.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
    
      

    📝 Step 1: Waybar Configuration

    Open ~/.config/waybar/config.jsonc and add the two modules:

    1.1 Add modules to modules-center

     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 data
    • interval: Update interval in seconds (300s = 5min)
    • format: Display format (here directly the script output)
    • on-click: Clicking triggers a manual script update

    📝 Step 2: Waybar Styling

    Add 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 size

    📝 Step 3: Create Scripts

    3.1 Create script directory

     bash
        
    mkdir -p ~/.config/waybar/scripts
    chmod +x ~/.config/waybar/scripts/*.sh
    
      

    3.2 Claude Code Script

    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"
    
      

    3.3 Codex Script

    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
      
9 comments