diff --git a/ccstatusline/settings.json b/ccstatusline/settings.json index c6bf7d2..f19159b 100644 --- a/ccstatusline/settings.json +++ b/ccstatusline/settings.json @@ -1,14 +1,29 @@ { "version": 3, "lines": [ + [ + { + "id": "95e108c1-bf22-4a23-94c8-be096ccaf8c8", + "type": "model" + }, + { + "id": "222b40b7-1291-4090-a0ae-bc43637241b3", + "type": "custom-command", + "commandPath": "uv run ~/.config/claude/scripts/context-status.py" + }, + { + "id": "64a1187d-65c6-4490-9f30-fa618da8b8ce", + "type": "output-style" + } + ], [ { "id": "9fc4ae79-f172-4d4b-b7f8-c2c3e923e7aa", "type": "current-working-dir", + "color": "white", "commandPath": "pwd | xargs basename", "preserveColors": true, - "timeout": 5000, - "color": "white" + "timeout": 5000 }, { "id": "4f12f8ba-4728-4590-ac5d-8c2b96ec91e8", @@ -23,12 +38,33 @@ "type": "version" } ], - [], - [] + [ + { + "id": "032c2103-52cd-42fd-9e03-325ee4dfc6c7", + "type": "session-cost" + }, + { + "id": "e7177c91-ecb8-422d-9496-b380d05d6e6b", + "type": "session-clock" + }, + { + "id": "5aef38b5-a24f-40a7-b201-afd3a5870f2d", + "type": "custom-command", + "commandPath": "echo \"$(ccusage daily --json | jq -r 'if type == \\\"array\\\" then .[-1] else . end | if .totalCostUSD then \\\"$\\\" + (.totalCostUSD | tostring) else \\\"$0.00\\\" end') 📅\"", + "timeout": 10000 + }, + { + "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "type": "custom-command", + "commandPath": "ccusage blocks --active --json | jq -r '.blocks[0] | \"$\" + (.costUSD | . * 100 | round / 100 | tostring) + \" block (\" + (.projection.remainingMinutes / 60 | floor | tostring) + \"h \" + (.projection.remainingMinutes % 60 | tostring) + \"m left)\"'", + "timeout": 10000 + } + ] ], "flexMode": "full", "compactThreshold": 60, "colorLevel": 2, + "defaultPadding": " ", "inheritSeparatorColors": false, "globalBold": false, "powerline": { @@ -41,8 +77,7 @@ ], "startCaps": [], "endCaps": [], - "autoAlign": false, - "theme": "nord-aurora" - }, - "defaultPadding": " " + "theme": "nord-aurora", + "autoAlign": false + } } \ No newline at end of file diff --git a/claude/scripts/context-status.py b/claude/scripts/context-status.py new file mode 100755 index 0000000..234b225 --- /dev/null +++ b/claude/scripts/context-status.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +import json +import sys + +# Constant +CONTEXT_LIMIT = int(168000) # CC triggers /compact at ~78% context utilization + +# Read JSON from stdin +data = json.load(sys.stdin) + +transcript_path = data["transcript_path"] + +# Parse transcript file to calculate context usage +context_used_token = 0 + +try: + with open(transcript_path, "r") as f: + lines = f.readlines() + + # Iterate from last line to first line + for line in reversed(lines): + line = line.strip() + if not line: + continue + + try: + obj = json.loads(line) + # Check if this line contains the required token usage fields + if ( + obj.get("type") == "assistant" + and "message" in obj + and "usage" in obj["message"] + and all( + key in obj["message"]["usage"] + for key in [ + "input_tokens", + "cache_creation_input_tokens", + "cache_read_input_tokens", + "output_tokens", + ] + ) + ): + usage = obj["message"]["usage"] + input_tokens = usage["input_tokens"] + cache_creation_input_tokens = usage["cache_creation_input_tokens"] + cache_read_input_tokens = usage["cache_read_input_tokens"] + output_tokens = usage["output_tokens"] + + context_used_token = ( + input_tokens + + cache_creation_input_tokens + + cache_read_input_tokens + + output_tokens + ) + break # Break after finding the first occurrence + + except json.JSONDecodeError: + # Skip malformed JSON lines + continue + +except FileNotFoundError: + # If transcript file doesn't exist, keep context_used_token as 0 + pass + +context_used_rate = (context_used_token / CONTEXT_LIMIT) * 100 + +# Create progress bar +bar_length = 20 +filled_length = int(bar_length * context_used_token // CONTEXT_LIMIT) +bar = "█" * filled_length + "░" * (bar_length - filled_length) + +print(f"[{bar}] {context_used_rate:.1f}% ({context_used_token:,})") diff --git a/claude/scripts/usage-line.sh b/claude/scripts/usage-line.sh new file mode 100755 index 0000000..4b456e2 --- /dev/null +++ b/claude/scripts/usage-line.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +# Get current session cost (assuming current working directory as session) +session_data=$(ccusage session --json | jq -r '.sessions[] | select(.sessionId == "'"$(pwd | sed 's|/|-|g')"'") | .totalCost') +session_cost=${session_data:-0} + +# Get today's cost +today_cost=$(ccusage daily --json | jq -r '.daily[0].totalCost // 0') + +# Get current active block info +block_info=$(ccusage blocks --json | jq -r ' + .blocks[] | + select(.isActive == true) | + "\(.costUSD // 0)|\(.startTime)|\(.endTime)" +') + +if [[ -n "$block_info" ]]; then + block_cost=$(echo "$block_info" | cut -d'|' -f1) + start_time=$(echo "$block_info" | cut -d'|' -f2) + end_time=$(echo "$block_info" | cut -d'|' -f3) + + # Calculate time left in block (5 hours from start) + start_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%S.%fZ" "$start_time" +%s 2>/dev/null || date -d "$start_time" +%s 2>/dev/null || echo "0") + current_epoch=$(date +%s) + elapsed_seconds=$((current_epoch - start_epoch)) + remaining_seconds=$((18000 - elapsed_seconds)) # 5 hours = 18000 seconds + + if [[ $remaining_seconds -gt 0 ]]; then + hours=$((remaining_seconds / 3600)) + minutes=$(((remaining_seconds % 3600) / 60)) + time_left="${hours}h ${minutes}m left" + else + time_left="expired" + fi + + # Calculate hourly rate + if [[ $elapsed_seconds -gt 0 ]]; then + hourly_rate=$(echo "scale=2; $block_cost * 3600 / $elapsed_seconds" | bc -l 2>/dev/null || echo "0.00") + else + hourly_rate="0.00" + fi +else + block_cost="0.00" + time_left="no active block" + hourly_rate="0.00" +fi + +# Format output +printf "💰 \$%.2f session / \$%.2f today / \$%.2f block (%s) | 🔥 \$%.2f/hr\n" \ + "$session_cost" "$today_cost" "$block_cost" "$time_left" "$hourly_rate" \ No newline at end of file diff --git a/claude/settings.json b/claude/settings.json index 0508a6e..7f6d947 100644 --- a/claude/settings.json +++ b/claude/settings.json @@ -1,11 +1,6 @@ { - "model": "opusplan", "includeCoAuthoredBy": false, - "statusLine": { - "type": "command", - "command": "bash ~/.config/claude/scripts/statusline.sh", - "padding": 0 - }, + "model": "opusplan", "hooks": { "Stop": [ { @@ -18,5 +13,10 @@ ] } ] + "statusLine": { + "type": "command", + "command": "bunx -y ccstatusline@latest", + "padding": 0 + }, } }