summaryrefslogtreecommitdiff
path: root/ar/.config/claude/statuslines/statusline.sh
diff options
context:
space:
mode:
Diffstat (limited to 'ar/.config/claude/statuslines/statusline.sh')
-rwxr-xr-xar/.config/claude/statuslines/statusline.sh236
1 files changed, 228 insertions, 8 deletions
diff --git a/ar/.config/claude/statuslines/statusline.sh b/ar/.config/claude/statuslines/statusline.sh
index 047590a..e000b2b 100755
--- a/ar/.config/claude/statuslines/statusline.sh
+++ b/ar/.config/claude/statuslines/statusline.sh
@@ -2,10 +2,10 @@
set -euo pipefail 2>/dev/null || set -eu
# ============================================================
-# STATUSLINE v2.0.0 - Claude Code Status Line
+# STATUSLINE v2.1.0 - Claude Code Status Line
# ============================================================
-readonly STATUSLINE_VERSION="2.0.0"
+readonly STATUSLINE_VERSION="2.1.0"
# ============================================================
# CONFIGURATION
@@ -36,9 +36,16 @@ readonly CONTEXT_ICON="🧠"
readonly DIR_ICON="📁"
readonly GIT_ICON="đŸŒŋ"
readonly COST_ICON="💰"
+readonly DAILY_COST_ICON="📅"
readonly TOKEN_ICON="📊"
+readonly CACHE_ICON="💾"
readonly TIME_ICON="🕒"
+readonly REMAINING_ICON="âŗ"
readonly VERSION_ICON="📟"
+readonly PYTHON_ICON="🐍"
+readonly GO_ICON="đŸĻĢ"
+readonly NODE_ICON="đŸ“Ļ"
+readonly RUST_ICON="đŸĻ€"
# Git state constants
readonly STATE_NOT_REPO="not_repo"
@@ -223,6 +230,86 @@ get_context_message() {
}
# ============================================================
+# LANGUAGE/RUNTIME DETECTION
+# ============================================================
+
+detect_language_info() {
+ local current_dir="$1"
+ local lang_info=""
+
+ # Use current_dir or fallback to PWD
+ local check_dir="${current_dir:-$PWD}"
+ [[ "$check_dir" == "$NULL_VALUE" || "$check_dir" == "unknown" ]] && check_dir="$PWD"
+
+ # Check for Python project
+ if [[ -n "${VIRTUAL_ENV:-}" ]]; then
+ local venv_name="${VIRTUAL_ENV##*/}"
+ local folder_name="${check_dir##*/}"
+ # Show venv name unless it's generic
+ if [[ "$venv_name" == ".venv" || "$venv_name" == "venv" ]]; then
+ venv_name="$folder_name"
+ fi
+ local pyver
+ pyver=$(python3 --version 2>/dev/null | cut -d' ' -f2 || echo '')
+ [[ -n "$pyver" ]] && lang_info="${PYTHON_ICON} ${GREEN}${pyver}${NC} ${GRAY}(${venv_name})${NC}"
+ elif [[ -f "$check_dir/requirements.txt" || -f "$check_dir/setup.py" || -f "$check_dir/pyproject.toml" || -f "$check_dir/Pipfile" ]]; then
+ local pyver
+ pyver=$(python3 --version 2>/dev/null | cut -d' ' -f2 || echo '')
+ [[ -n "$pyver" ]] && lang_info="${PYTHON_ICON} ${GREEN}${pyver}${NC}"
+ # Check for Go project
+ elif [[ -f "$check_dir/go.mod" || -f "$check_dir/go.sum" ]]; then
+ local gover
+ gover=$(go version 2>/dev/null | grep -oE 'go[0-9]+\.[0-9]+(\.[0-9]+)?' | sed 's/go//' || echo '')
+ [[ -n "$gover" ]] && lang_info="${GO_ICON} ${CYAN}${gover}${NC}"
+ # Check for Node.js project
+ elif [[ -f "$check_dir/package.json" ]]; then
+ local nodever
+ nodever=$(node --version 2>/dev/null | sed 's/v//' || echo '')
+ [[ -n "$nodever" ]] && lang_info="${NODE_ICON} ${GREEN}${nodever}${NC}"
+ # Check for Rust project
+ elif [[ -f "$check_dir/Cargo.toml" ]]; then
+ local rustver
+ rustver=$(rustc --version 2>/dev/null | cut -d' ' -f2 || echo '')
+ [[ -n "$rustver" ]] && lang_info="${RUST_ICON} ${ORANGE}${rustver}${NC}"
+ fi
+
+ echo "$lang_info"
+}
+
+# ============================================================
+# CCUSAGE DATA FETCHING
+# ============================================================
+
+get_ccusage_data() {
+ local session_id="$1"
+
+ # Check if npx is available
+ command -v npx >/dev/null 2>&1 || return 1
+
+ # Get daily cost
+ local daily_cost=""
+ local daily_json
+ daily_json=$(npx ccusage daily --json 2>/dev/null)
+ if [[ -n "$daily_json" ]]; then
+ daily_cost=$(echo "$daily_json" | jq -r '.totals.totalCost // 0' 2>/dev/null)
+ fi
+
+ # Get active block data (projections, remaining time)
+ local remaining_minutes="" projected_cost="" block_cost="" burn_rate=""
+ local blocks_json
+ blocks_json=$(npx ccusage blocks --active --json 2>/dev/null)
+ if [[ -n "$blocks_json" ]]; then
+ remaining_minutes=$(echo "$blocks_json" | jq -r '.blocks[0].projection.remainingMinutes // empty' 2>/dev/null)
+ projected_cost=$(echo "$blocks_json" | jq -r '.blocks[0].projection.totalCost // empty' 2>/dev/null)
+ block_cost=$(echo "$blocks_json" | jq -r '.blocks[0].costUSD // empty' 2>/dev/null)
+ burn_rate=$(echo "$blocks_json" | jq -r '.blocks[0].burnRate.costPerHour // empty' 2>/dev/null)
+ fi
+
+ # Output: daily_cost|remaining_minutes|projected_cost|block_cost|burn_rate
+ echo "${daily_cost:-0}|${remaining_minutes:-0}|${projected_cost:-0}|${block_cost:-0}|${burn_rate:-0}"
+}
+
+# ============================================================
# JSON PARSING
# ============================================================
@@ -243,7 +330,9 @@ parse_with_jq() {
(.cost.total_cost_usd // 0),
(.cost.total_duration_ms // 0),
.version // "",
- .session_id // ""
+ .session_id // "",
+ (.context_window.current_usage.cache_creation_input_tokens // 0),
+ (.context_window.current_usage.cache_read_input_tokens // 0)
' 2>/dev/null
}
@@ -272,6 +361,7 @@ parse_without_jq() {
local model_name current_dir context_size current_usage
local total_input total_output cost_usd duration_ms version session_id
+ local cache_creation cache_read
model_name=$(extract_json_string "$input" "display_name" "Claude")
current_dir=$(extract_json_string "$input" "current_dir" "")
@@ -285,9 +375,12 @@ parse_without_jq() {
duration_ms=$(extract_json_string "$input" "total_duration_ms" "0")
version=$(extract_json_string "$input" "version" "")
session_id=$(extract_json_string "$input" "session_id" "")
+ cache_creation=$(extract_json_string "$input" "cache_creation_input_tokens" "0")
+ cache_read=$(extract_json_string "$input" "cache_read_input_tokens" "0")
printf '%s\n' "$model_name" "$current_dir" "$context_size" "$current_usage" \
- "$total_input" "$total_output" "$cost_usd" "$duration_ms" "$version" "$session_id"
+ "$total_input" "$total_output" "$cost_usd" "$duration_ms" "$version" "$session_id" \
+ "$cache_creation" "$cache_read"
}
# ============================================================
@@ -517,6 +610,91 @@ build_version_component() {
echo -n "${VERSION_ICON} ${GRAY}v${version}${NC}"
}
+build_language_component() {
+ local lang_info="$1"
+ [[ -z "$lang_info" ]] && return 0
+ echo -n "$lang_info"
+}
+
+build_daily_cost_component() {
+ local daily_cost="$1"
+
+ [[ -z "$daily_cost" || "$daily_cost" == "0" || "$daily_cost" == "$NULL_VALUE" ]] && return 0
+
+ local cost_fmt
+ cost_fmt=$(format_cost "$daily_cost")
+ echo -n "${DAILY_COST_ICON} ${ORANGE}\$${cost_fmt}/day${NC}"
+}
+
+build_remaining_time_component() {
+ local remaining_minutes="$1"
+ local projected_cost="$2"
+
+ [[ -z "$remaining_minutes" || "$remaining_minutes" == "0" || "$remaining_minutes" == "$NULL_VALUE" ]] && return 0
+
+ local hours=$((remaining_minutes / 60))
+ local mins=$((remaining_minutes % 60))
+
+ local time_str
+ if [[ "$hours" -gt 0 ]]; then
+ time_str="${hours}h ${mins}m"
+ else
+ time_str="${mins}m"
+ fi
+
+ # Color based on remaining time
+ local time_color
+ if [[ "$remaining_minutes" -gt 120 ]]; then
+ time_color="$GREEN"
+ elif [[ "$remaining_minutes" -gt 60 ]]; then
+ time_color="$YELLOW"
+ elif [[ "$remaining_minutes" -gt 30 ]]; then
+ time_color="$ORANGE"
+ else
+ time_color="$RED"
+ fi
+
+ echo -n "${REMAINING_ICON} ${time_color}${time_str} left${NC}"
+
+ # Show projected cost if available
+ if [[ -n "$projected_cost" && "$projected_cost" != "0" && "$projected_cost" != "$NULL_VALUE" ]]; then
+ local proj_fmt
+ proj_fmt=$(format_cost "$projected_cost")
+ echo -n " ${GRAY}(đŸ’ĩ\$${proj_fmt})${NC}"
+ fi
+}
+
+build_cache_component() {
+ local cache_creation="$1"
+ local cache_read="$2"
+
+ # Skip if no significant cache usage
+ local total_cache=$((cache_creation + cache_read))
+ [[ "$total_cache" -lt 1000 ]] && return 0
+
+ local creation_fmt read_fmt
+ creation_fmt=$(format_number "$cache_creation")
+ read_fmt=$(format_number "$cache_read")
+
+ # Calculate cache hit ratio
+ local hit_ratio=0
+ if [[ "$total_cache" -gt 0 ]]; then
+ hit_ratio=$((cache_read * 100 / total_cache))
+ fi
+
+ # Color based on hit ratio (higher is better for cost)
+ local ratio_color
+ if [[ "$hit_ratio" -ge 80 ]]; then
+ ratio_color="$GREEN"
+ elif [[ "$hit_ratio" -ge 50 ]]; then
+ ratio_color="$YELLOW"
+ else
+ ratio_color="$ORANGE"
+ fi
+
+ echo -n "${CACHE_ICON} ${GRAY}+${creation_fmt}${NC}/${ratio_color}â†ē${read_fmt}${NC} ${GRAY}(${hit_ratio}% hit)${NC}"
+}
+
# ============================================================
# MAIN
# ============================================================
@@ -549,6 +727,7 @@ main() {
# Extract fields
local model_name current_dir context_size current_usage
local total_input total_output cost_usd duration_ms version session_id
+ local cache_creation cache_read
{
read -r model_name
read -r current_dir
@@ -560,6 +739,8 @@ main() {
read -r duration_ms
read -r version
read -r session_id
+ read -r cache_creation
+ read -r cache_read
} <<<"$parsed"
log_debug "Parsed: model=$model_name, dir=$current_dir, context=$current_usage/$context_size, cost=$cost_usd, duration=$duration_ms"
@@ -568,14 +749,34 @@ main() {
local git_data
git_data=$(get_git_info "$current_dir")
+ # Get language/runtime info
+ local lang_info
+ lang_info=$(detect_language_info "$current_dir")
+
+ # Get ccusage data (daily cost, remaining time, etc.)
+ local ccusage_data="" daily_cost="" remaining_minutes="" projected_cost="" block_cost="" ccusage_burn_rate=""
+ if check_jq; then
+ ccusage_data=$(get_ccusage_data "$session_id" 2>/dev/null || echo "")
+ if [[ -n "$ccusage_data" ]]; then
+ IFS='|' read -r daily_cost remaining_minutes projected_cost block_cost ccusage_burn_rate <<<"$ccusage_data"
+ fi
+ fi
+
# Build components
local output=""
- # Line 1: Model | Directory | Git | Version
+ # Line 1: Model | Directory | Language | Git | Version
output+=$(build_model_component "$model_name")
output+=$(sep)
output+=$(build_directory_component "$current_dir")
+ local lang_component
+ lang_component=$(build_language_component "$lang_info")
+ [[ -n "$lang_component" ]] && {
+ output+=$(sep)
+ output+="$lang_component"
+ }
+
local git_component
git_component=$(build_git_component "$git_data")
[[ -n "$git_component" ]] && {
@@ -594,19 +795,32 @@ main() {
output+=$'\n'
output+=$(build_context_component "$context_size" "$current_usage")
- # Line 3: Cost | Tokens | Time
- local cost_component token_component time_component
+ # Line 3: Cost | Daily Cost | Remaining Time | Tokens | Time
+ local cost_component daily_cost_component remaining_component token_component time_component cache_component
cost_component=$(build_cost_component "$cost_usd" "$duration_ms")
+ daily_cost_component=$(build_daily_cost_component "$daily_cost")
+ remaining_component=$(build_remaining_time_component "$remaining_minutes" "$projected_cost")
token_component=$(build_token_component "$total_input" "$total_output" "$duration_ms")
time_component=$(build_time_component "$duration_ms")
+ cache_component=$(build_cache_component "${cache_creation:-0}" "${cache_read:-0}")
- if [[ -n "$cost_component" || -n "$token_component" || -n "$time_component" ]]; then
+ if [[ -n "$cost_component" || -n "$daily_cost_component" || -n "$remaining_component" || -n "$token_component" || -n "$time_component" ]]; then
output+=$'\n'
local first=1
if [[ -n "$cost_component" ]]; then
output+="$cost_component"
first=0
fi
+ if [[ -n "$daily_cost_component" ]]; then
+ [[ "$first" -eq 0 ]] && output+=$(sep)
+ output+="$daily_cost_component"
+ first=0
+ fi
+ if [[ -n "$remaining_component" ]]; then
+ [[ "$first" -eq 0 ]] && output+=$(sep)
+ output+="$remaining_component"
+ first=0
+ fi
if [[ -n "$token_component" ]]; then
[[ "$first" -eq 0 ]] && output+=$(sep)
output+="$token_component"
@@ -618,6 +832,12 @@ main() {
fi
fi
+ # Line 4: Cache info (if significant)
+ if [[ -n "$cache_component" ]]; then
+ output+=$'\n'
+ output+="$cache_component"
+ fi
+
echo -e "$output"
}