#!/bin/sh # UberLLM balance for the Claude Code status line — https://uberllm.dev/docs/claude-code # # Install (adds "statusLine" to ~/.claude/settings.json, or to # ./.claude/settings.local.json with --project): # # curl -fsSL https://uberllm.dev/install/statusline.sh | sh -s -- --install # # Claude Code pipes a JSON description of the session to this script and shows # the first line it prints. We print the UberLLM balance and what the last 24 # hours saved against the official list price, e.g. # UberLLM $12.40 · $0.35 held · saved $3.10 (24h) · Opus # The key is read from $UBERLLM_API_KEY, else the key file the Claude Code # installer saved for this project, else ~/.uberllm/key. No key = no output. ULL_API_BASE="https://api.uberllm.dev" ULL_SITE="https://uberllm.dev" CACHE_SECONDS=60 # ------------------------------------------------------------------ helpers -- # field — one number or string field, without a JSON parser. field() { printf '%s' "$1" | tr -d '\n' | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\{0,1\}\([^\",}]*\).*/\1/p" } # money — $12.40 money() { awk -v v="$1" 'BEGIN { if (v == "") exit; printf "$%.2f", v }' } # key_for — the key file the installer wrote for or a parent. key_for() { _d=$1 while [ -n "$_d" ] && [ "$_d" != "/" ] && [ "$_d" != "$HOME" ]; do _sum=$(printf '%s' "$_d" | cksum | cut -d' ' -f1) _f="$HOME/.uberllm/key-$(basename "$_d" | tr -c 'A-Za-z0-9._-\n' '_')-$_sum" [ -f "$_f" ] && { printf '%s' "$_f"; return; } _d=$(dirname "$_d") done [ -f "$HOME/.uberllm/key" ] && printf '%s' "$HOME/.uberllm/key" } # ---------------------------------------------------------------- install -- install() { _scope=user [ "${1:-}" = "--project" ] && _scope=project _dest="$HOME/.uberllm/statusline.sh" (umask 077; mkdir -p "$HOME/.uberllm") curl -fsSL "$ULL_SITE/install/statusline.sh" -o "$_dest.tmp" && mv "$_dest.tmp" "$_dest" \ || { echo "Could not download the status line script." >&2; exit 1; } chmod 700 "$_dest" if [ "$_scope" = project ]; then _settings="$(pwd)/.claude/settings.local.json" else _settings="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/settings.json" fi _cmd="sh \"$_dest\"" if command -v python3 >/dev/null 2>&1; then python3 -c 'import json, os, sys path, cmd = sys.argv[1], sys.argv[2] try: cfg = json.load(open(path)) except Exception: cfg = {} if not isinstance(cfg, dict): cfg = {} cfg["statusLine"] = {"type": "command", "command": cmd, "padding": 0} os.makedirs(os.path.dirname(path), exist_ok=True) open(path + ".tmp", "w").write(json.dumps(cfg, indent=2) + "\n") os.replace(path + ".tmp", path)' "$_settings" "$_cmd" elif command -v node >/dev/null 2>&1; then node -e ' const fs = require("fs"), path = require("path"); const [file, cmd] = process.argv.slice(1); let cfg = {}; try { cfg = JSON.parse(fs.readFileSync(file, "utf8")); } catch {} if (cfg === null || typeof cfg !== "object" || Array.isArray(cfg)) cfg = {}; cfg.statusLine = { type: "command", command: cmd, padding: 0 }; fs.mkdirSync(path.dirname(file), { recursive: true }); fs.writeFileSync(file + ".tmp", JSON.stringify(cfg, null, 2) + "\n"); fs.renameSync(file + ".tmp", file);' "$_settings" "$_cmd" else echo "Saved $_dest. Add this to $_settings by hand:" echo " \"statusLine\": {\"type\": \"command\", \"command\": \"sh $_dest\", \"padding\": 0}" exit 0 fi echo "UberLLM balance added to the Claude Code status line ($_settings)." echo "Restart Claude Code to see it." } # ------------------------------------------------------------------- show -- show() { _in=$(cat 2>/dev/null) _dir=$(field "$_in" project_dir) [ -n "$_dir" ] || _dir=$(field "$_in" current_dir) [ -n "$_dir" ] || _dir=$(pwd) _model=$(field "$_in" display_name) _key=${UBERLLM_API_KEY:-} _id=env if [ -z "$_key" ]; then _kf=$(key_for "$_dir") [ -n "$_kf" ] || exit 0 _key=$(head -n 1 "$_kf" 2>/dev/null) _id=$(printf '%s' "$_kf" | cksum | cut -d' ' -f1) fi [ -n "$_key" ] || exit 0 _cache="${TMPDIR:-/tmp}/uberllm-balance-$(id -u)-$_id" _now=$(date +%s) _body='' if [ -f "$_cache" ]; then _at=$(head -n 1 "$_cache") if [ $((_now - ${_at:-0})) -lt "$CACHE_SECONDS" ]; then _body=$(sed -n '2,$p' "$_cache"); fi fi if [ -z "$_body" ]; then _body=$(curl -fsS -m 3 -H "Authorization: Bearer $_key" "$ULL_API_BASE/v1/balance" 2>/dev/null) if [ -n "$_body" ]; then (umask 077; printf '%s\n%s\n' "$_now" "$_body" > "$_cache") elif [ -f "$_cache" ]; then _body=$(sed -n '2,$p' "$_cache") # API unreachable: keep the last answer fi fi _avail=$(money "$(field "$_body" available_usd)") [ -n "$_avail" ] || _avail=$(money "$(field "$_body" balance_usd)") _held=$(field "$_body" held_usd) _saved=$(field "$_body" saved_24h_usd) _line="UberLLM" [ -n "$_avail" ] && _line="$_line $_avail" if [ -n "$_held" ] && awk -v v="$_held" 'BEGIN { exit !(v >= 0.005) }'; then _line="$_line · $(money "$_held") held" fi if [ -n "$_saved" ] && awk -v v="$_saved" 'BEGIN { exit !(v >= 0.01) }'; then _line="$_line · saved $(money "$_saved") (24h)" fi [ -n "$_model" ] && _line="$_line · $_model" printf '%s\n' "$_line" } case "${1:-}" in --install) shift; install "$@" ;; *) show ;; esac