#!/usr/bin/env bash
set -euo pipefail

APP_CANDIDATES=(
  "${TOKENBAR_APP:-}"
  "$HOME/Applications/TokenBar.app"
  "$HOME/Applications/CodexLimitBar.app"
  "/Applications/TokenBar.app"
  "/Applications/CodexLimitBar.app"
)

download_url="https://github.com/Arnie016/codex-goated-skills/releases/download/v0.1.0/CodexLimitBar-macOS-arm64-2026-05-20.zip"
install_url="https://raw.githubusercontent.com/Arnie016/TokenBar/main/install.sh"
site_url="${TOKENBAR_SITE_URL:-https://www.tokenbar.site}"
skills_url="https://github.com/Arnie016/codex-goated-skills"
install_command="curl -fsSL $install_url | bash"
usage_index="$HOME/Library/Application Support/CodexLimitBar/usage-index.json"
support_dir="$HOME/Library/Application Support/CodexLimitBar"
control_file="$support_dir/user-controls.json"
agent_model="${TOKENBAR_OPENAI_MODEL:-${OPENAI_MODEL:-gpt-5.4}}"
agent_budget_tokens="${TOKENBAR_BUDGET_TOKENS:-}"
agent_price_per_million="${TOKENBAR_PRICE_PER_MILLION:-}"

usage() {
  cat <<'USAGE'
TokenBar terminal launcher

Usage:
  tokenbar                  Open TokenBar
  tokenbar open             Open TokenBar
  tokenbar status           Show app, process, and local usage status
  tokenbar forecast         Summarize local forecast inputs
  tokenbar controls         Show tokens, cost, reset, budget, and hours dials
  tokenbar limits           Show or set limits: daily 100M, weekly 2B, daily-cost 25
  tokenbar guardrails       Alias for limits
  tokenbar power            Show Mac heat, battery draw, and estimated energy
  tokenbar folders          Show token usage by folder/project
  tokenbar tracking         Show, pause, resume, or clear local log tracking
  tokenbar activity         Show or change optional activity-aware forecasting
  tokenbar ask PROMPT       Ask the log-aware TokenBar agent
  tokenbar doctor           Run install/path/index diagnostics
  tokenbar explain TOPIC    Explain quota, forecast, cost, power, providers, antigravity
  tokenbar providers        Show connector status
  tokenbar site             Open the TokenBar website
  tokenbar skills           Open the Codex Goated Skills repository
  tokenbar copy-install     Copy the install command to the clipboard
  tokenbar install          Install or refresh TokenBar
  tokenbar path             Print the app path if found
  tokenbar prompt           Start the log-aware agent prompt

Options:
  --quiet                   Open without terminal output
  --no-animation            Open with one plain status line
  --movie                   Force the launch animation
  --sound                   Add quiet macOS sound cues
  --no-agent                Open and return to the shell
  --model MODEL             OpenAI model for agent answers (default: gpt-5.4)
  --budget TOKENS           Session budget for runout answers, like 20B or 500M
  --price USD_PER_M         Session dollars-per-million token estimate
  --activity                Use optional activity cadence in this forecast session
  --help                    Show this help

USAGE
}

find_app() {
  local candidate
  for candidate in "${APP_CANDIDATES[@]}"; do
    if [[ -n "$candidate" && -d "$candidate" ]]; then
      printf '%s\n' "$candidate"
      return 0
    fi
  done
  return 1
}

running_pid() {
  pgrep -f "TokenBar.app/Contents/MacOS/CodexLimitBar|CodexLimitBar.app/Contents/MacOS/CodexLimitBar" | head -n 1 || true
}

install_tokenbar() {
  if [[ "$(uname -s)" != "Darwin" ]]; then
    cat >&2 <<'EOF'
TokenBar's menu-bar app is macOS-only.
The CLI can still inspect exported/local usage files on other systems when available.
EOF
    return 1
  fi

  local install_dir app_path tmp_dir source_app
  install_dir="$HOME/Applications"
  app_path="$install_dir/TokenBar.app"
  tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/tokenbar-install.XXXXXX")"

  trap 'rm -rf "$tmp_dir"; trap - RETURN' RETURN

  printf 'Downloading TokenBar menu-bar app...\n'
  curl -fL "$download_url" -o "$tmp_dir/TokenBar.zip"

  printf 'Unpacking TokenBar...\n'
  ditto -x -k "$tmp_dir/TokenBar.zip" "$tmp_dir/unpacked"

  source_app="$(find "$tmp_dir/unpacked" -maxdepth 2 -name '*.app' -type d | head -n 1)"
  if [[ -z "$source_app" ]]; then
    echo "Could not find a .app bundle in the downloaded zip." >&2
    return 1
  fi

  mkdir -p "$install_dir"
  rm -rf "$app_path"
  ditto "$source_app" "$app_path"

  printf 'Installed TokenBar app:\n  %s\n\n' "$app_path"
  printf 'Open it with:\n  tokenbar\n'
}

supports_animation() {
  [[ -t 1 && "${TERM:-dumb}" != "dumb" && -z "${NO_COLOR:-}" ]]
}

supports_agent_interface() {
  [[ -t 0 && -t 1 ]]
}

copy_install_command() {
  if command -v pbcopy >/dev/null 2>&1; then
    printf '%s' "$install_command" | pbcopy
    printf 'Copied install command:\n  %s\n' "$install_command"
  else
    printf 'pbcopy is unavailable. Install command:\n  %s\n' "$install_command"
  fi
}

open_site() {
  local script_dir local_site
  script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  local candidates=(
    "${TOKENBAR_SITE_PATH:-}"
    "$PWD/docs/index.html"
    "$script_dir/../docs/index.html"
  )

  for local_site in "${candidates[@]}"; do
    if [[ -n "$local_site" && -f "$local_site" ]]; then
      open "$local_site"
      printf 'Opened local TokenBar site: %s\n' "$local_site"
      return 0
    fi
  done

  open "$site_url"
  printf 'Opened TokenBar site: %s\n' "$site_url"
}

open_skills_repo() {
  open "$skills_url"
  printf 'Opened Codex Goated Skills: %s\n' "$skills_url"
}

set_control_value() {
  local key="$1"
  local value="$2"
  mkdir -p "$support_dir"
  TOKENBAR_CONTROL_PATH="$control_file" TOKENBAR_CONTROL_KEY="$key" TOKENBAR_CONTROL_VALUE="$value" python3 - <<'PY'
import json
import os

path = os.environ["TOKENBAR_CONTROL_PATH"]
key = os.environ["TOKENBAR_CONTROL_KEY"]
raw = os.environ["TOKENBAR_CONTROL_VALUE"]

try:
    with open(path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
except Exception:
    data = {}

if raw.lower() in ("true", "false"):
    value = raw.lower() == "true"
elif raw.lower() in ("none", "null", "off"):
    value = None
else:
    value = raw

data[key] = value
with open(path, "w", encoding="utf-8") as handle:
    json.dump(data, handle, indent=2, sort_keys=True)
    handle.write("\n")
PY
}

delete_usage_index() {
  rm -f "$usage_index"
}

tracking_is_paused() {
  command -v python3 >/dev/null 2>&1 || return 1
  [[ "$(TOKENBAR_CONTROL_PATH="$control_file" python3 - <<'PY'
import json
import os

path = os.environ["TOKENBAR_CONTROL_PATH"]
try:
    with open(path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
except Exception:
    data = {}
print("false" if data.get("logTrackingEnabled", True) is False else "true")
PY
)" == "false" ]]
}

usage_summary() {
  if tracking_is_paused; then
    cat <<EOF
Usage index: paused by user
Path: $usage_index
Tracking can be resumed with: tokenbar tracking on
EOF
    return 0
  fi

  if [[ ! -f "$usage_index" ]]; then
    cat <<EOF
Usage index: not found
Path: $usage_index
EOF
    return 0
  fi

  if ! command -v python3 >/dev/null 2>&1; then
    cat <<EOF
Usage index: found
Path: $usage_index
Python 3 is unavailable, so token totals were not decoded.
EOF
    return 0
  fi

  INDEX_PATH="$usage_index" python3 - <<'PY'
import json
import os
import time
from datetime import datetime, timedelta

path = os.environ["INDEX_PATH"]
try:
    with open(path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
except Exception as exc:
    print(f"Usage index: unreadable ({exc})")
    print(f"Path: {path}")
    raise SystemExit(0)

day_tokens = data.get("dayTokens") or {}
model_tokens = data.get("modelTokens") or {}
today_key = datetime.now().strftime("%Y-%m-%d")
today = int(day_tokens.get(today_key, 0) or 0)
last30 = sum(int(value or 0) for value in day_tokens.values())
active_days = sum(1 for value in day_tokens.values() if int(value or 0) > 0)
sessions = len(data.get("sessionPaths") or [])
updated = data.get("updatedAt") or 0
updated_text = time.strftime("%Y-%m-%d %H:%M", time.localtime(updated)) if updated else "unknown"
top_model = max(model_tokens.items(), key=lambda item: item[1])[0] if model_tokens else "unknown"

def compact(value):
    value = int(max(0, value))
    if value >= 1_000_000_000:
        return f"{value / 1_000_000_000:.2f}B".replace(".00B", "B")
    if value >= 1_000_000:
        return f"{value / 1_000_000:.1f}M".replace(".0M", "M")
    if value >= 1_000:
        return f"{value / 1_000:.1f}K".replace(".0K", "K")
    return str(value)

print("Usage index: ready")
print(f"Today: {compact(today)}")
print(f"Last 30d: {compact(last30)}")
print(f"Active days: {active_days}/30")
print(f"Sessions indexed: {sessions}")
print(f"Top local model: {top_model}")
print(f"Updated: {updated_text}")
PY
}

usage_context_json() {
  INDEX_PATH="$usage_index" \
  TOKENBAR_CONTROL_PATH="$control_file" \
  TOKENBAR_BUDGET_TOKENS="$agent_budget_tokens" \
  TOKENBAR_PRICE_PER_MILLION="$agent_price_per_million" \
  python3 - <<'PY'
import json
import os
import time
from datetime import datetime, timedelta

path = os.environ["INDEX_PATH"]
control_path = os.environ.get("TOKENBAR_CONTROL_PATH") or ""
budget_raw = os.environ.get("TOKENBAR_BUDGET_TOKENS") or ""
price_raw = os.environ.get("TOKENBAR_PRICE_PER_MILLION") or ""

def compact(value):
    value = int(max(0, value))
    if value >= 1_000_000_000:
        return f"{value / 1_000_000_000:.2f}B".replace(".00B", "B")
    if value >= 1_000_000:
        return f"{value / 1_000_000:.1f}M".replace(".0M", "M")
    if value >= 1_000:
        return f"{value / 1_000:.1f}K".replace(".0K", "K")
    return str(value)

def parse_tokens(text):
    if not text:
        return None
    raw = str(text).strip().upper().replace(",", "")
    multiplier = 1
    if raw.endswith("B"):
        multiplier = 1_000_000_000
        raw = raw[:-1]
    elif raw.endswith("M"):
        multiplier = 1_000_000
        raw = raw[:-1]
    elif raw.endswith("K"):
        multiplier = 1_000
        raw = raw[:-1]
    try:
        return int(float(raw) * multiplier)
    except ValueError:
        return None

def parse_money(text):
    if text in (None, ""):
        return None
    try:
        return float(str(text).strip().replace("$", "").replace(",", ""))
    except ValueError:
        return None

def signed_64(value):
    value = int(value)
    if value >= 2 ** 63:
        value -= 2 ** 64
    return value

def mac_power_sample():
    import re
    import subprocess
    try:
        output = subprocess.check_output(
            ["/usr/sbin/ioreg", "-r", "-n", "AppleSmartBattery", "-d", "1"],
            text=True,
            stderr=subprocess.DEVNULL,
            timeout=2,
        )
    except Exception:
        return {}

    def number(key):
        match = re.search(rf'"{re.escape(key)}" = (-?\d+)', output)
        if not match:
            return None
        return signed_64(match.group(1))

    voltage = number("Voltage") or number("AppleRawBatteryVoltage")
    current = number("InstantAmperage") or number("Amperage")
    temperature = number("Temperature")
    external = '"ExternalConnected" = Yes' in output or '"AppleRawExternalConnected" = Yes' in output
    watts = None
    if voltage and current:
        watts = abs(voltage / 1000 * current / 1000)
    return {
        "mac_power_watts": round(watts, 2) if watts else None,
        "battery_celsius": round(temperature / 100, 1) if temperature else None,
        "power_source": "adapter/battery sample" if external else "battery discharge sample",
    }

def safe_int(value):
    try:
        return int(value or 0)
    except Exception:
        return 0

def load_controls():
    controls = {
        "logTrackingEnabled": True,
        "activitySignalsEnabled": False,
        "dailyGuardrailTokens": None,
        "weeklyGuardrailTokens": None,
        "thirtyDayGuardrailTokens": None,
        "dailyCostLimitUSD": None,
        "weeklyCostLimitUSD": None,
    }
    if control_path and os.path.exists(control_path):
        try:
            with open(control_path, "r", encoding="utf-8") as handle:
                loaded = json.load(handle)
            if isinstance(loaded, dict):
                controls.update({key: loaded.get(key, value) for key, value in controls.items()})
        except Exception:
            pass
    return controls

def mean(values):
    return sum(values) / max(1, len(values))

def winsorized(values, fallback=0):
    usable = [float(value) for value in values if float(value) >= 0]
    if not usable:
        return float(max(0, fallback))
    if len(usable) < 4:
        return mean(usable)
    sorted_values = sorted(usable)
    low = sorted_values[min(len(sorted_values) - 1, int(round((len(sorted_values) - 1) * 0.10)))]
    high = sorted_values[min(len(sorted_values) - 1, int(round((len(sorted_values) - 1) * 0.85)))]
    clamped = [min(max(value, low), high) for value in usable]
    return mean(clamped)

def volatility_percent(values):
    active = [float(value) for value in values if value > 0]
    if len(active) < 2:
        return 0
    avg = mean(active)
    if avg <= 0:
        return 0
    variance = mean([(value - avg) ** 2 for value in active])
    return int(min(250, max(0, (variance ** 0.5) / avg * 100)))

def current_streak(values, active=True):
    count = 0
    for value in reversed(values):
        if (value > 0) == active:
            count += 1
        else:
            break
    return count

def activity_multiplier(active_days, active_streak, quiet_streak):
    active_rate = active_days / 30
    cadence_lift = min(0.14, active_rate * 0.18)
    streak_lift = min(0.16, active_streak * 0.025)
    quiet_drag = min(0.22, quiet_streak * 0.055)
    return min(1.24, max(0.72, 0.94 + cadence_lift + streak_lift - quiet_drag))

def uncertainty_percent(active_days, volatility, activity_enabled):
    history_penalty = max(0, 18 - active_days) * 3
    volatility_penalty = min(52, int(round(volatility * 0.42)))
    activity_adjustment = -6 if activity_enabled else 6
    return min(95, max(8, 18 + history_penalty + volatility_penalty + activity_adjustment))

def uncertainty_label(percent):
    if percent < 25:
        return "Low"
    if percent < 55:
        return "Medium"
    return "High"

controls = load_controls()

if controls.get("logTrackingEnabled") is False:
    daily_guardrail = controls.get("dailyGuardrailTokens")
    weekly_guardrail = controls.get("weeklyGuardrailTokens")
    thirty_guardrail = controls.get("thirtyDayGuardrailTokens")
    print(json.dumps({
        "ready": False,
        "path": path,
        "reason": "local log tracking paused by user",
        "tracking_enabled": False,
        "activity_signals_enabled": bool(controls.get("activitySignalsEnabled")),
        "daily_guardrail_tokens": daily_guardrail,
        "daily_guardrail_compact": compact(parse_tokens(daily_guardrail) or 0) if daily_guardrail else None,
        "weekly_guardrail_tokens": weekly_guardrail,
        "weekly_guardrail_compact": compact(parse_tokens(weekly_guardrail) or 0) if weekly_guardrail else None,
        "thirty_day_guardrail_tokens": thirty_guardrail,
        "thirty_day_guardrail_compact": compact(parse_tokens(thirty_guardrail) or 0) if thirty_guardrail else None,
        "daily_cost_limit_usd": parse_money(controls.get("dailyCostLimitUSD")),
        "weekly_cost_limit_usd": parse_money(controls.get("weeklyCostLimitUSD")),
        "budget_tokens_raw": budget_raw or None,
        "price_per_million_raw": price_raw or None,
    }))
    raise SystemExit(0)

if not os.path.exists(path):
    daily_guardrail = controls.get("dailyGuardrailTokens")
    weekly_guardrail = controls.get("weeklyGuardrailTokens")
    thirty_guardrail = controls.get("thirtyDayGuardrailTokens")
    print(json.dumps({
        "ready": False,
        "path": path,
        "reason": "usage-index.json not found",
        "tracking_enabled": True,
        "activity_signals_enabled": bool(controls.get("activitySignalsEnabled")),
        "daily_guardrail_tokens": daily_guardrail,
        "daily_guardrail_compact": compact(parse_tokens(daily_guardrail) or 0) if daily_guardrail else None,
        "weekly_guardrail_tokens": weekly_guardrail,
        "weekly_guardrail_compact": compact(parse_tokens(weekly_guardrail) or 0) if weekly_guardrail else None,
        "thirty_day_guardrail_tokens": thirty_guardrail,
        "thirty_day_guardrail_compact": compact(parse_tokens(thirty_guardrail) or 0) if thirty_guardrail else None,
        "daily_cost_limit_usd": parse_money(controls.get("dailyCostLimitUSD")),
        "weekly_cost_limit_usd": parse_money(controls.get("weeklyCostLimitUSD")),
        "budget_tokens_raw": budget_raw or None,
        "price_per_million_raw": price_raw or None,
    }))
    raise SystemExit(0)

try:
    with open(path, "r", encoding="utf-8") as handle:
        data = json.load(handle)
except Exception as exc:
    print(json.dumps({
        "ready": False,
        "path": path,
        "reason": f"usage-index.json unreadable: {exc}",
        "budget_tokens_raw": budget_raw or None,
        "price_per_million_raw": price_raw or None,
    }))
    raise SystemExit(0)

day_tokens = data.get("dayTokens") or {}
model_tokens = data.get("modelTokens") or {}
folder_tokens = data.get("folderTokens") or {}
folder_session_paths = data.get("folderSessionPaths") or {}
active_folder_paths = set(data.get("activeFolderPaths") or [])
items = sorted((date, safe_int(tokens)) for date, tokens in day_tokens.items())
today_key = datetime.now().strftime("%Y-%m-%d")
today = safe_int(day_tokens.get(today_key, 0))
last30_items = items[-30:]
last14_items = items[-14:]
last7_items = items[-7:]
last30 = sum(value for _, value in last30_items)
last14 = sum(value for _, value in last14_items)
last7 = sum(value for _, value in last7_items)
active_days = sum(1 for _, value in last30_items if value > 0)
active_all = [value for _, value in items if value > 0]
avg_active = int(sum(active_all) / len(active_all)) if active_all else 0
peak_day, peak_tokens = max(items, key=lambda item: item[1]) if items else ("", 0)
sessions = len(data.get("sessionPaths") or [])
updated = data.get("updatedAt") or 0
updated_text = time.strftime("%Y-%m-%d %H:%M", time.localtime(updated)) if updated else "unknown"
top_model = max(model_tokens.items(), key=lambda item: safe_int(item[1]))[0] if model_tokens else "unknown"
top_folders = []
for folder_path, tokens in sorted(folder_tokens.items(), key=lambda item: safe_int(item[1]), reverse=True)[:8]:
    sessions_for_folder = folder_session_paths.get(folder_path) or []
    if not isinstance(sessions_for_folder, list):
        sessions_for_folder = []
    name = os.path.basename(folder_path.rstrip("/")) or folder_path
    top_folders.append({
        "path": folder_path,
        "name": name,
        "tokens": safe_int(tokens),
        "compact": compact(safe_int(tokens)),
        "sessions": len(sessions_for_folder),
        "active": folder_path in active_folder_paths,
    })
last30_values = [value for _, value in last30_items]
last7_values = [value for _, value in last7_items]
daily_avg_7 = int(last7 / max(1, len(last7_items)))
daily_avg_30 = int(last30 / max(1, len(last30_items)))
recent = winsorized(last7_values, fallback=daily_avg_30)
active_average = winsorized(active_all, fallback=avg_active)
cap_base = max(1, recent, active_average, daily_avg_30)
today_capped = min(today, int(cap_base * 1.35))
history_depth = min(1.0, active_days / 18)
sparse_damping = 0.78 + history_depth * 0.22
base_daily = (recent * 0.40 + active_average * 0.28 + daily_avg_30 * 0.17 + today_capped * 0.15) * sparse_damping
active_streak = current_streak(last30_values, active=True)
quiet_streak = current_streak(last30_values, active=False)
activity_enabled = bool(controls.get("activitySignalsEnabled"))
if activity_enabled:
    base_daily *= activity_multiplier(active_days, active_streak, quiet_streak)
projected_daily = int(max(1, round(base_daily)))
projected_next7 = projected_daily * 7
projected_next30 = projected_daily * 30
volatility = volatility_percent(last30_values)
uncertainty = uncertainty_percent(active_days, volatility, activity_enabled)
budget_tokens = parse_tokens(budget_raw)
remaining_budget = budget_tokens - last30 if budget_tokens is not None else None
price_per_million = None
try:
    price_per_million = float(price_raw) if price_raw else None
except ValueError:
    price_per_million = None
estimated_30d_cost = (last30 / 1_000_000) * price_per_million if price_per_million is not None else None
projected_30d_cost = (projected_next30 / 1_000_000) * price_per_million if price_per_million is not None else None
today_cost = (today / 1_000_000) * price_per_million if price_per_million is not None else None
projected_daily_cost = (projected_daily / 1_000_000) * price_per_million if price_per_million is not None else None
projected_weekly_cost = (projected_next7 / 1_000_000) * price_per_million if price_per_million is not None else None
daily_cost_limit = parse_money(controls.get("dailyCostLimitUSD"))
weekly_cost_limit = parse_money(controls.get("weeklyCostLimitUSD"))
weekly_guardrail = controls.get("weeklyGuardrailTokens")
power = mac_power_sample()
reset_estimate_date = (datetime.now() + timedelta(days=7)).strftime("%Y-%m-%d")
estimated_runout_days = None
estimated_runout_date = None
if budget_tokens is not None and remaining_budget is not None:
    if remaining_budget <= 0:
        estimated_runout_days = 0
        estimated_runout_date = today_key
    elif daily_avg_7 > 0:
        estimated_runout_days = max(1, int((remaining_budget + daily_avg_7 - 1) / daily_avg_7))
        estimated_runout_date = (datetime.now() + timedelta(days=estimated_runout_days)).strftime("%Y-%m-%d")

context = {
    "ready": True,
    "path": path,
    "tracking_enabled": True,
    "activity_signals_enabled": activity_enabled,
    "today_key": today_key,
    "today_tokens": today,
    "today_compact": compact(today),
    "last7_tokens": last7,
    "last7_compact": compact(last7),
    "last14_tokens": last14,
    "last14_compact": compact(last14),
    "last30_tokens": last30,
    "last30_compact": compact(last30),
    "active_days_last30": active_days,
    "sessions_indexed": sessions,
    "top_local_model": top_model,
    "top_folders": top_folders,
    "updated": updated_text,
    "peak_day": peak_day,
    "peak_tokens": peak_tokens,
    "peak_compact": compact(peak_tokens),
    "avg_active_day_tokens": avg_active,
    "avg_active_day_compact": compact(avg_active),
    "daily_avg_7_tokens": daily_avg_7,
    "daily_avg_7_compact": compact(daily_avg_7),
    "projected_daily_tokens": projected_daily,
    "projected_daily_compact": compact(projected_daily),
    "projected_next7_tokens": projected_next7,
    "projected_next7_compact": compact(projected_next7),
    "projected_next30_tokens": projected_next30,
    "projected_next30_compact": compact(projected_next30),
    "volatility_percent": volatility,
    "uncertainty_percent": uncertainty,
    "uncertainty_label": uncertainty_label(uncertainty),
    "prediction_basis": "history + activity cadence" if activity_enabled else "history only",
    "daily_guardrail_tokens": controls.get("dailyGuardrailTokens"),
    "daily_guardrail_compact": compact(parse_tokens(controls.get("dailyGuardrailTokens")) or 0) if controls.get("dailyGuardrailTokens") else None,
    "weekly_guardrail_tokens": weekly_guardrail,
    "weekly_guardrail_compact": compact(parse_tokens(weekly_guardrail) or 0) if weekly_guardrail else None,
    "thirty_day_guardrail_tokens": controls.get("thirtyDayGuardrailTokens"),
    "thirty_day_guardrail_compact": compact(parse_tokens(controls.get("thirtyDayGuardrailTokens")) or 0) if controls.get("thirtyDayGuardrailTokens") else None,
    "daily_cost_limit_usd": daily_cost_limit,
    "weekly_cost_limit_usd": weekly_cost_limit,
    "budget_tokens_raw": budget_raw or None,
    "budget_tokens": budget_tokens,
    "budget_compact": compact(budget_tokens) if budget_tokens is not None else None,
    "remaining_budget_tokens": remaining_budget,
    "remaining_budget_compact": compact(remaining_budget) if remaining_budget is not None and remaining_budget >= 0 else None,
    "estimated_runout_days": estimated_runout_days,
    "estimated_runout_date": estimated_runout_date,
    "hypothetical_reset_date": reset_estimate_date,
    "price_per_million": price_per_million,
    "today_cost": round(today_cost, 2) if today_cost is not None else None,
    "projected_daily_cost": round(projected_daily_cost, 2) if projected_daily_cost is not None else None,
    "projected_weekly_cost": round(projected_weekly_cost, 2) if projected_weekly_cost is not None else None,
    "estimated_30d_cost": round(estimated_30d_cost, 2) if estimated_30d_cost is not None else None,
    "projected_30d_cost": round(projected_30d_cost, 2) if projected_30d_cost is not None else None,
    "mac_power_watts": power.get("mac_power_watts"),
    "battery_celsius": power.get("battery_celsius"),
    "power_source": power.get("power_source"),
    "recent_days": [
        {"date": date, "tokens": value, "compact": compact(value)}
        for date, value in last14_items
    ],
}
print(json.dumps(context, separators=(",", ":")))
PY
}

agent_api_status() {
  if [[ -n "${OPENAI_API_KEY:-}" ]]; then
    printf 'OpenAI Responses API ready (%s)' "$agent_model"
  else
    printf 'local analyst mode; set OPENAI_API_KEY for OpenAI Responses'
  fi
}

local_agent_answer() {
  local prompt="$1"
  local context
  context="$(usage_context_json)"
  TOKENBAR_USER_PROMPT="$prompt" TOKENBAR_USAGE_CONTEXT="$context" python3 - <<'PY'
import json
import os

prompt = os.environ.get("TOKENBAR_USER_PROMPT", "").strip()
ctx = json.loads(os.environ.get("TOKENBAR_USAGE_CONTEXT", "{}") or "{}")
lower = prompt.lower()

if not ctx.get("ready"):
    print(f"I tried to read the local usage index, but it is not ready: {ctx.get('reason', 'unknown reason')}.")
    print(f"Expected path: {ctx.get('path')}")
    raise SystemExit(0)

recent = ctx.get("recent_days") or []
recent_text = ", ".join(f"{d['date']}: {d['compact']}" for d in recent[-7:])
facts = [
    f"Today is {ctx['today_compact']} tokens.",
    f"Last 30 days are {ctx['last30_compact']} across {ctx['active_days_last30']}/30 active days.",
    f"Peak day was {ctx['peak_day']} at {ctx['peak_compact']}.",
    f"Forecast is {ctx['prediction_basis']}, projecting {ctx['projected_next7_compact']} next week with ±{ctx['uncertainty_percent']}% uncertainty.",
    f"Top local model is {ctx['top_local_model']}; sessions indexed: {ctx['sessions_indexed']}.",
]

print("I read the local TokenBar logs before answering.")
if any(word in lower for word in ("spike", "peak", "why high", "highest")):
    print(f"The main spike is {ctx['peak_day']} at {ctx['peak_compact']}. Compare that against the active-day average of {ctx['avg_active_day_compact']}.")
elif any(word in lower for word in ("budget", "runout", "run out", "cap", "limit")):
    if ctx.get("budget_tokens") is not None:
        rem = ctx.get("remaining_budget_compact") or "over budget"
        print(f"Configured budget is {ctx['budget_compact']}; last 30d used {ctx['last30_compact']}; remaining estimate is {rem}.")
        if ctx.get("estimated_runout_date"):
            print(f"At the recent 7-day pace, estimated runout is {ctx['estimated_runout_date']} ({ctx['estimated_runout_days']} day(s) from today).")
            print(f"Hypothetical reset marker is {ctx['hypothetical_reset_date']}; verify the real reset in the provider dashboard.")
            if ctx["estimated_runout_date"] <= ctx["hypothetical_reset_date"]:
                print("This would run out before or on the hypothetical reset marker.")
            else:
                print("This clears the hypothetical reset marker at the current pace.")
        if ctx.get("price_per_million") is not None and any(word in lower for word in ("cost", "dollar", "spend", "price")):
            projected_cost = round((ctx["projected_next7_tokens"] / 1_000_000) * ctx["price_per_million"], 2)
            print(f"Using ${ctx['price_per_million']}/M, next 7d projects about ${projected_cost}; local 30d estimate is ${ctx['estimated_30d_cost']}.")
    else:
        print(f"No budget is configured yet. Set one with: config budget 20B. Current 30d usage is {ctx['last30_compact']} and next 7d projects {ctx['projected_next7_compact']} (±{ctx['uncertainty_percent']}%).")
elif any(word in lower for word in ("cost", "dollar", "spend", "price")):
    if ctx.get("estimated_30d_cost") is not None:
        print(f"Using ${ctx['price_per_million']}/M, the local 30d cost estimate is ${ctx['estimated_30d_cost']}.")
    else:
        print(f"Cost needs a configured price. Set one with: config price 0.30. Local 30d usage is {ctx['last30_compact']}.")
elif any(word in lower for word in ("energy", "watt", "joule", "power", "heat", "temperature", "gpu")):
    watts = ctx.get("mac_power_watts")
    temp = ctx.get("battery_celsius")
    if watts:
        temp_text = f", battery {temp}C" if temp is not None else ""
        print(f"Current Mac draw sample is about {watts}W{temp_text} ({ctx.get('power_source', 'battery sample')}).")
    else:
        print("I could not read a live Mac battery power sample from ioreg in this shell.")
    print(f"Token side: today {ctx['today_compact']}, 30d {ctx['last30_compact']}, recent pace {ctx['daily_avg_7_compact']}/day.")
    print("Energy per token is still an estimate until TokenBar stores a power time series over each session.")
elif any(word in lower for word in ("folder", "folders", "project", "projects", "workspace", "workspaces")):
    folders = ctx.get("top_folders") or []
    if not folders:
        print("No folder-level token totals are indexed yet.")
    else:
        print("Top folders by local tokens:")
        for item in folders[:6]:
            active = " active" if item.get("active") else ""
            print(f"- {item['name']}: {item['compact']} across {item.get('sessions', 0)} session(s){active}")
elif any(word in lower for word in ("provider", "connector", "antigravity", "anti-gravity", "claude", "gemini", "cursor")):
    print(f"The live source is the local Codex usage index at {ctx['path']}.")
    print("Other agent providers should stay explicit connectors until they expose a reliable API, export, or user-approved local file.")
    print(f"Current local baseline: {ctx['last30_compact']} in 30d, top model {ctx['top_local_model']}.")
elif any(word in lower for word in ("forecast", "pace", "next week", "next 7")):
    print(f"Forecast basis is {ctx['prediction_basis']}: projected pace is {ctx['projected_daily_compact']}/day, next 7d {ctx['projected_next7_compact']}, next 30d {ctx['projected_next30_compact']}.")
    print(f"Uncertainty is ±{ctx['uncertainty_percent']}% ({ctx['uncertainty_label']}) from active-day history and volatility.")
    print(f"The reset marker shown here is hypothetical: {ctx['hypothetical_reset_date']}. Official reset windows need provider confirmation.")
elif any(word in lower for word in ("uncertain", "uncertainty", "confidence", "guardrail", "tracking", "activity")):
    print(f"Prediction basis is {ctx['prediction_basis']}; uncertainty is ±{ctx['uncertainty_percent']}% ({ctx['uncertainty_label']}).")
    print(f"Activity-aware forecasting is {'on' if ctx.get('activity_signals_enabled') else 'off'}; tracking is {'on' if ctx.get('tracking_enabled', True) else 'paused'}.")
    print(f"Limits: daily {ctx.get('daily_guardrail_compact') or 'off'}, weekly {ctx.get('weekly_guardrail_compact') or 'off'}, 30d {ctx.get('thirty_day_guardrail_compact') or 'off'}.")
    if ctx.get("daily_cost_limit_usd") or ctx.get("weekly_cost_limit_usd"):
        print(f"Cost limits: daily ${ctx.get('daily_cost_limit_usd') or 'off'}, weekly ${ctx.get('weekly_cost_limit_usd') or 'off'}.")
else:
    for line in facts:
        print(line)

if recent_text:
    print(f"Recent daily trail: {recent_text}.")
print("OpenAI API is not active in this shell, so this answer used the local analyst path only.")
PY
}

openai_agent_answer() {
  local prompt="$1"
  local context
  context="$(usage_context_json)"

  TOKENBAR_USER_PROMPT="$prompt" \
  TOKENBAR_USAGE_CONTEXT="$context" \
  TOKENBAR_MODEL="$agent_model" \
  TOKENBAR_BUDGET_TOKENS="$agent_budget_tokens" \
  TOKENBAR_PRICE_PER_MILLION="$agent_price_per_million" \
  python3 - <<'PY'
import json
import os
import sys
import urllib.error
import urllib.request

api_key = os.environ.get("OPENAI_API_KEY")
if not api_key:
    raise SystemExit(2)

prompt = os.environ.get("TOKENBAR_USER_PROMPT", "")
context = os.environ.get("TOKENBAR_USAGE_CONTEXT", "{}")
model = os.environ.get("TOKENBAR_MODEL") or "gpt-5.4"
budget = os.environ.get("TOKENBAR_BUDGET_TOKENS") or "not configured"
price = os.environ.get("TOKENBAR_PRICE_PER_MILLION") or "not configured"

instructions = """You are TokenBar Agent, a CLI analyst for AI coding-agent usage.
Use the supplied local usage-index JSON as the source of truth before answering.
Respect user controls in the JSON: if tracking is paused say so; treat activity signals as opt-in; mention uncertainty and guardrails when relevant.
If the user asks about folders/projects/workspaces, use top_folders from the JSON and name the largest folders.
If the user asks about power or heat, use mac_power_watts and battery_celsius when present; do not pretend this is precise wall power.
Do not give generic metric definitions unless the user explicitly asks for definitions.
Answer with concrete observations, likely next action, and any uncertainty.
Clearly label forecasts as estimates and never claim official billing, purchased credits, or organization-level truth.
If data is missing, name the exact missing source.
Keep answers concise enough for a terminal prompt."""

payload = {
    "model": model,
    "instructions": instructions,
    "input": (
        f"User prompt: {prompt}\n\n"
        f"Session config: budget_tokens={budget}; price_per_million={price}\n\n"
        f"Local usage context JSON:\n{context}"
    ),
    "max_output_tokens": 650,
    "store": False,
}

request = urllib.request.Request(
    "https://api.openai.com/v1/responses",
    data=json.dumps(payload).encode("utf-8"),
    headers={
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
    },
    method="POST",
)

try:
    with urllib.request.urlopen(request, timeout=45) as response:
        data = json.loads(response.read().decode("utf-8"))
except urllib.error.HTTPError as exc:
    body = exc.read().decode("utf-8", errors="replace")
    print(f"OpenAI API request failed ({exc.code}). {body[:600]}", file=sys.stderr)
    raise SystemExit(2)
except Exception as exc:
    print(f"OpenAI API request failed: {exc}", file=sys.stderr)
    raise SystemExit(2)

parts = []
for item in data.get("output", []):
    if item.get("type") != "message":
        continue
    for content in item.get("content", []):
        if content.get("type") == "output_text":
            parts.append(content.get("text", ""))

text = "\n".join(part for part in parts if part).strip()
if not text:
    print("OpenAI returned no text output.", file=sys.stderr)
    raise SystemExit(2)

print(text)
usage = data.get("usage") or {}
total = usage.get("total_tokens")
if total is not None:
    print(f"\n[agent tokens used: {total}]")
PY
}

agent_answer() {
  local prompt="$1"
  local cyan=$'\033[36m'
  local amber=$'\033[33m'
  local reset=$'\033[0m'

  printf '%sTokenBar%s checking local logs...\n' "$cyan" "$reset"
  if [[ -n "${OPENAI_API_KEY:-}" ]]; then
    if openai_agent_answer "$prompt"; then
      return 0
    fi
    printf '%sOpenAI path failed; falling back to local log analyst.%s\n' "$amber" "$reset" >&2
  fi
  local_agent_answer "$prompt"
}

status_report() {
  local app_path pid
  app_path="$(find_app || true)"
  pid="$(running_pid)"

  if [[ -n "$app_path" ]]; then
    printf 'App: %s\n' "$app_path"
  else
    printf 'App: not installed\n'
  fi

  if [[ -n "$pid" ]]; then
    printf 'Process: running (pid %s)\n' "$pid"
  else
    printf 'Process: not running\n'
  fi

  usage_summary
}

forecast_report() {
  cat <<'EOF'
Forecast model:
  facts     local coding-agent session tokens by day
  live      5-hour and weekly quota windows when a provider exposes them
  estimate  future pace, cost, runout, budget pressure, and confidence

EOF
  usage_summary
  cat <<'EOF'

Interpretation:
  tokenbar forecast is useful for direction and runway.
  official billing, purchased credits, and org attribution still belong to the provider dashboard.
EOF
}

provider_report() {
  cat <<'EOF'
Provider status:
  OpenAI Codex       live local log reader + rate-limit window when available
  ChatGPT/Codex      verify billing and credits in the official dashboard
  Antigravity        future connector target
  Claude             future connector target
  Gemini             future connector target
  Cursor             future connector target

TokenBar does not read browser cookies, passwords, auth files, or provider secrets.
EOF
}

folder_report() {
  local context
  context="$(usage_context_json 2>/dev/null || printf '{}')"
  TOKENBAR_USAGE_CONTEXT="$context" python3 - <<'PY'
import json
import os

ctx = json.loads(os.environ.get("TOKENBAR_USAGE_CONTEXT") or "{}")
print("Folder usage")
print("------------")
if not ctx.get("ready"):
    print(ctx.get("reason", "usage index unavailable"))
    raise SystemExit(0)

folders = ctx.get("top_folders") or []
if not folders:
    print("No folder-level usage has been indexed yet.")
    raise SystemExit(0)

for item in folders[:8]:
    marker = " live" if item.get("active") else ""
    print(f"{item.get('compact', '0'):>8}  {item.get('sessions', 0):>3} sessions  {item.get('name', item.get('path', 'folder'))}{marker}")

print()
print("Ask: why is this folder high?  Or open the app dashboard for clickable folder cards.")
PY
}

power_report() {
  local context
  context="$(usage_context_json 2>/dev/null || printf '{}')"
  TOKENBAR_USAGE_CONTEXT="$context" python3 - <<'PY'
import json
import os
import subprocess

ctx = json.loads(os.environ.get("TOKENBAR_USAGE_CONTEXT") or "{}")

def thermal_summary():
    try:
        output = subprocess.check_output(["/usr/bin/pmset", "-g", "therm"], text=True, timeout=2)
    except Exception:
        return "unknown", "pmset thermal state unavailable"
    text = " ".join(line.strip() for line in output.splitlines() if line.strip())
    lower = text.lower()
    if "no thermal warning" in lower and "no performance warning" in lower:
        return "nominal", "no thermal or performance warning recorded"
    return "watch", text or "thermal output was empty"

print("Mac power + heat")
print("----------------")
thermal_title, thermal_detail = thermal_summary()
print(f"Thermal: {thermal_title} - {thermal_detail}")

watts = ctx.get("mac_power_watts")
temp = ctx.get("battery_celsius")
if watts:
    temp_text = f" | battery {temp}C" if temp is not None else ""
    print(f"Current draw: {watts}W{temp_text} | {ctx.get('power_source', 'battery sample')}")
else:
    print("Current draw: unavailable from ioreg")

if ctx.get("ready"):
    print(f"Tokens: today {ctx.get('today_compact', '0')} | 30d {ctx.get('last30_compact', '0')} | next 7d {ctx.get('projected_next7_compact', '0')}")
    print("Energy note: this is a live Mac sample, not precise wall power. TokenBar can estimate tokens/W and joules/token once it stores samples over time.")
else:
    print(ctx.get("reason", "usage index unavailable"))
PY
}

controls_report() {
  cat <<'EOF'
TokenBar controls
-----------------
Tokens   local token movement by day, model, and active session
Cost     estimated dollars from local tokens and configured price assumptions
Reset    expected 5-hour and weekly windows when a provider exposes them
Budget   runway against quota, credits, and user-set soft limits
Hours    active coding rhythm inferred from local session activity

Current public build reads Codex locally. Antigravity and other providers stay connector-ready until reliable usage exports or APIs are available.
EOF
  printf '\n'
  guardrail_report
}

guardrail_report() {
  local context
  context="$(usage_context_json 2>/dev/null || printf '{}')"
  TOKENBAR_USAGE_CONTEXT="$context" python3 - <<'PY'
import json
import os

ctx = json.loads(os.environ.get("TOKENBAR_USAGE_CONTEXT") or "{}")
print("Prediction controls")
print("-------------------")
print(f"Tracking: {'on' if ctx.get('tracking_enabled', True) else 'paused'}")
print(f"Activity-aware forecast: {'on' if ctx.get('activity_signals_enabled') else 'off'}")
print(f"Prediction basis: {ctx.get('prediction_basis', 'history only')}")
if ctx.get("ready"):
    print(f"Uncertainty: ±{ctx.get('uncertainty_percent', 0)}% ({ctx.get('uncertainty_label', 'Unknown')})")
    print(f"Next 7d: {ctx.get('projected_next7_compact', '0')}")
    print(f"Next 30d: {ctx.get('projected_next30_compact', '0')}")
else:
    print(f"State: {ctx.get('reason', 'usage index unavailable')}")
print(f"Daily token limit: {ctx.get('daily_guardrail_compact') or 'off'}")
print(f"Weekly token limit: {ctx.get('weekly_guardrail_compact') or 'off'}")
print(f"30d token limit: {ctx.get('thirty_day_guardrail_compact') or 'off'}")
print(f"Daily cost limit: ${ctx.get('daily_cost_limit_usd')}" if ctx.get("daily_cost_limit_usd") else "Daily cost limit: off")
print(f"Weekly cost limit: ${ctx.get('weekly_cost_limit_usd')}" if ctx.get("weekly_cost_limit_usd") else "Weekly cost limit: off")
PY
}

guardrail_command() {
  local scope="${1:-}"
  local value="${2:-}"
  case "$scope" in
    ""|show|status)
      guardrail_report
      ;;
    daily|day)
      if [[ -z "$value" || "$value" == "off" ]]; then
        set_control_value dailyGuardrailTokens none
        printf 'Daily token limit cleared.\n'
      else
        set_control_value dailyGuardrailTokens "$value"
        printf 'Daily token limit set to %s.\n' "$value"
      fi
      ;;
    weekly|week)
      if [[ -z "$value" || "$value" == "off" ]]; then
        set_control_value weeklyGuardrailTokens none
        printf 'Weekly token limit cleared.\n'
      else
        set_control_value weeklyGuardrailTokens "$value"
        printf 'Weekly token limit set to %s.\n' "$value"
      fi
      ;;
    30d|30|monthly|month)
      if [[ -z "$value" || "$value" == "off" ]]; then
        set_control_value thirtyDayGuardrailTokens none
        printf '30-day token limit cleared.\n'
      else
        set_control_value thirtyDayGuardrailTokens "$value"
        printf '30-day token limit set to %s.\n' "$value"
      fi
      ;;
    daily-cost|day-cost|cost-daily)
      if [[ -z "$value" || "$value" == "off" ]]; then
        set_control_value dailyCostLimitUSD none
        printf 'Daily cost limit cleared.\n'
      else
        set_control_value dailyCostLimitUSD "$value"
        printf 'Daily cost limit set to $%s.\n' "$value"
      fi
      ;;
    weekly-cost|week-cost|cost-weekly)
      if [[ -z "$value" || "$value" == "off" ]]; then
        set_control_value weeklyCostLimitUSD none
        printf 'Weekly cost limit cleared.\n'
      else
        set_control_value weeklyCostLimitUSD "$value"
        printf 'Weekly cost limit set to $%s.\n' "$value"
      fi
      ;;
    *)
      printf 'Usage: tokenbar limits [daily 100M|weekly 2B|30d 20B|daily-cost 25|weekly-cost 100|status]\n'
      return 1
      ;;
  esac
}

tracking_command() {
  local action="${1:-status}"
  case "$action" in
    status|show|"")
      guardrail_report
      ;;
    on|resume|start)
      set_control_value logTrackingEnabled true
      printf 'Local log tracking resumed. Run tokenbar forecast to refresh from the app index.\n'
      ;;
    off|pause|stop)
      set_control_value logTrackingEnabled false
      printf 'Local log tracking paused. TokenBar will not read usage-index.json while paused.\n'
      ;;
    clear|delete|erase)
      set_control_value logTrackingEnabled false
      delete_usage_index
      printf 'Local log tracking paused and usage-index.json removed.\n'
      ;;
    *)
      printf 'Usage: tokenbar tracking [status|on|off|clear]\n'
      return 1
      ;;
  esac
}

activity_command() {
  local action="${1:-status}"
  case "$action" in
    status|show|"")
      guardrail_report
      ;;
    on|allow|enable)
      set_control_value activitySignalsEnabled true
      printf 'Activity-aware forecasting enabled. It uses local active/quiet cadence only.\n'
      ;;
    off|deny|disable)
      set_control_value activitySignalsEnabled false
      printf 'Activity-aware forecasting disabled. Forecasts are history-only.\n'
      ;;
    *)
      printf 'Usage: tokenbar activity [status|on|off]\n'
      return 1
      ;;
  esac
}

doctor_report() {
  local app_path launcher pid
  app_path="$(find_app || true)"
  launcher="$(command -v tokenbar 2>/dev/null || true)"
  pid="$(running_pid)"

  printf 'TokenBar doctor\n'
  printf '%s\n' '---------------'
  printf 'Launcher: %s\n' "${launcher:-not on PATH}"
  printf 'App: %s\n' "${app_path:-not installed}"
  if [[ -n "$pid" ]]; then
    printf 'Running: yes (pid %s)\n' "$pid"
  else
    printf 'Running: no\n'
  fi
  printf 'Usage index: %s\n' "$([[ -f "$usage_index" ]] && printf found || printf missing)"
  printf 'Download: %s\n' "$download_url"
  printf 'Install command: %s\n' "$install_command"
  printf '\n'
  usage_summary
}

explain_topic() {
  local topic="${1:-overview}"
  case "$(printf '%s' "$topic" | tr '[:upper:]' '[:lower:]')" in
    quota|limit|pressure)
      cat <<'EOF'
Quota pressure means the tighter of the live Codex 5-hour and weekly windows.
If either bucket reaches 100 percent used, Codex can pause until that bucket resets.
EOF
      ;;
    forecast|pace|runout)
      cat <<'EOF'
Forecasts are local hypotheses from recent token pace, active-day average, and reset windows.
The app shows today as the boundary: solid history before it, dotted forecast after it.
EOF
      ;;
    cost|billing|dollar|credits)
      cat <<'EOF'
Cost is estimated from local tokens times the configured dollars-per-million value.
Purchased credits, billing, project attribution, and org totals must be verified in the official dashboard.
EOF
      ;;
    energy|watt|power|joule)
      cat <<'EOF'
Power reads a live Mac battery/adapter sample when macOS exposes it, then pairs it with token pace.
Joules per token and tokens per watt-hour remain estimates until TokenBar stores a power sample timeline.
EOF
      ;;
    antigravity|anti-gravity|google)
      cat <<'EOF'
Antigravity is treated as a connector target, not as a live source yet.
TokenBar should only wire it in through explicit usage APIs, exports, or user-approved local files.
EOF
      ;;
    controls|control|budget|hours|timing|time)
      controls_report
      ;;
    provider|providers|connector|connectors)
      provider_report
      ;;
    *)
      cat <<'EOF'
Topics: quota, forecast, cost, energy, providers, antigravity, controls.
Example: tokenbar explain forecast
EOF
      ;;
  esac
}

terminal_movie() {
  local app_path="${1:-}"
  local mode="${2:-prompt}"

  [[ "$animate" == true ]] || return 0
  if [[ "${force_animation:-false}" != true ]]; then
    supports_animation || return 1
  fi
  command -v python3 >/dev/null 2>&1 || return 0

  TOKENBAR_APP_PATH="$app_path" \
  TOKENBAR_MOVIE_MODE="$mode" \
  TOKENBAR_ANIM_CONTEXT="$(usage_context_json 2>/dev/null || printf '{}')" \
  TOKENBAR_API_STATUS="$(agent_api_status)" \
  python3 - <<'PY' >&2
import json
import os
import sys
import time

ctx = json.loads(os.environ.get("TOKENBAR_ANIM_CONTEXT") or "{}")
app_path = os.environ.get("TOKENBAR_APP_PATH", "")
api_status = os.environ.get("TOKENBAR_API_STATUS", "")
mode = os.environ.get("TOKENBAR_MOVIE_MODE", "prompt")

hide = "\033[?25l"
show = "\033[?25h"
cyan = "\033[36m"
green = "\033[32m"
amber = "\033[33m"
dim = "\033[2m"
red = "\033[31m"
reset = "\033[0m"

def compact_value(key, fallback="0"):
    return str(ctx.get(key) or fallback)

def gate(value, maximum, size=16):
    try:
        value = max(0, float(value or 0))
        maximum = max(1, float(maximum or 1))
    except Exception:
        value = 0
        maximum = 1
    filled = min(size, int(round((value / maximum) * size)))
    return "#" * filled + "-" * (size - filled)

def metric_text():
    if not ctx.get("ready"):
        return f"{amber}logs pending{reset} - {ctx.get('reason', 'usage index missing')}"
    return f"{green}today {compact_value('today_compact')}{reset} | 7d {compact_value('projected_next7_compact')} | 30d {compact_value('last30_compact')}"

if not ctx.get("ready"):
    print(f"{green}TokenBar opened{reset} · {metric_text()}")
    print(f"{dim}{app_path}{reset}")
    raise SystemExit(0)

today = int(ctx.get("today_tokens") or 0)
last30 = int(ctx.get("last30_tokens") or 0)
peak = max(1, int(ctx.get("peak_tokens") or 1))
budget = int(ctx.get("budget_tokens") or 0)
gate_value = last30 if budget > 0 else today
gate_max = budget if budget > 0 else max(today, peak, 1)
gate_name = "budget gate" if budget > 0 else "today gate"
quota_gate = gate(gate_value, gate_max, 18)
api_label = "OpenAI ready" if api_status.startswith("OpenAI") else "local analyst"
watts = ctx.get("mac_power_watts")
heat = f"{watts}W" if watts else "sample"
runout = ctx.get("estimated_runout_date") or "reset first"
budget_label = ctx.get("budget_compact") or "set budget"

scenes = [
    ("BOOT", 2, "o o", "opening native menu-bar surface", "[logs]----[forecast]----[budget]----[chat]"),
    ("LOGS", 10, "o ^", "reading local Codex sessions", "[LOGS]====[forecast]----[budget]----[chat]"),
    ("FORECAST", 22, "^ o", f"projecting next 7d {compact_value('projected_next7_compact')}", "[logs]====[FORECAST]====[budget]----[chat]"),
    ("BUDGET", 34, "- ^", f"checking limits: {budget_label}", "[logs]====[forecast]====[BUDGET]====[chat]"),
    ("HEAT", 45, "* ^", f"sampling Mac power: {heat}", "[logs]====[forecast]====[budget]====[HEAT]"),
    ("READY", 54, "^ ^", "chat prompt armed", "[logs]====[forecast]====[budget]====[CHAT]"),
]

def scene_rows(label, pos, eyes, caption, rail):
    width = 62
    sprite = [
        " .----. ",
        f"(  {eyes}  )",
        " /|__|\\ ",
        "  /  \\  ",
    ]
    pos = max(0, min(width - max(len(line) for line in sprite), pos))
    sprite_rows = [" " + " " * pos + cyan + line + reset for line in sprite]
    rail_colored = rail
    for word, color in (
        ("LOGS", green),
        ("FORECAST", cyan),
        ("BUDGET", amber),
        ("HEAT", red),
        ("CHAT", green),
    ):
        rail_colored = rail_colored.replace(word, color + word + reset)
    return [
        f"{dim}+----------------------------------------------------------------+{reset}",
        f"{green} TokenBar terminal movie{reset}  {dim}{api_label}{reset}  {amber}{label}{reset}",
        f" {rail_colored}",
        *sprite_rows,
        f" {caption}",
        f" {metric_text()}",
        f" {gate_name} {green}{quota_gate}{reset} | runout {runout}",
        f"{dim}+----------------------------------------------------------------+{reset}",
    ]

line_count = 10
sys.stdout.write(hide)
try:
    for index, scene in enumerate(scenes):
        rows = scene_rows(*scene)
        if index:
            sys.stdout.write(f"\033[{line_count}F")
        for row in rows:
            sys.stdout.write("\033[K" + row + "\n")
        sys.stdout.flush()
        time.sleep(0.23)
    if mode == "prompt":
        time.sleep(0.18)
        sys.stdout.write(f"\033[{line_count}F")
        for _ in range(line_count):
            sys.stdout.write("\033[K\n")
        sys.stdout.write(f"\033[{line_count}F")
finally:
    sys.stdout.write(show)
    sys.stdout.flush()
PY
}

animated_open_message() {
  local app_path="$1"

  if [[ "${sound_enabled:-false}" == true ]] && command -v afplay >/dev/null 2>&1; then
    afplay "/System/Library/Sounds/Tink.aiff" >/dev/null 2>&1 &
  fi

  if ! command -v python3 >/dev/null 2>&1; then
    printf 'TokenBar opened: %s\n' "$app_path"
    return 0
  fi

  terminal_movie "$app_path" "open" || plain_open_message "$app_path"
}

plain_open_message() {
  printf 'Opened TokenBar: %s\n' "$1"
}

chat_hint() {
  cat <<'EOF'
Ask TokenBar in plain English:
  will I run out this week?
  why did today spike?
  show folders
  set daily limit 100M
  set weekly limit 2B
  set daily cost 25
  how hot is my Mac?
EOF
}

clean_value() {
  printf '%s' "$1" | tr -d '[:space:],'
}

clean_token_value() {
  clean_value "$1" | tr '[:lower:]' '[:upper:]'
}

natural_limit_command() {
  local input="$1"
  local lower value
  lower="$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')"

  if [[ "$lower" =~ (clear|unset|remove|disable|turn[[:space:]]+off|off).*(daily|day|per[[:space:]]+day).*(cost|dollar|spend|usd) ]]; then
    guardrail_command daily-cost off
    return 0
  fi
  if [[ "$lower" =~ (clear|unset|remove|disable|turn[[:space:]]+off|off).*(weekly|week|per[[:space:]]+week).*(cost|dollar|spend|usd) ]]; then
    guardrail_command weekly-cost off
    return 0
  fi
  if [[ "$lower" =~ (daily|day|per[[:space:]]+day).*(cost|dollar|spend|usd)[^0-9\$]*\$?([0-9][0-9,.]*([.][0-9]+)?) ]]; then
    value="$(clean_value "${BASH_REMATCH[3]}")"
    guardrail_command daily-cost "$value"
    return 0
  fi
  if [[ "$lower" =~ (weekly|week|per[[:space:]]+week).*(cost|dollar|spend|usd)[^0-9\$]*\$?([0-9][0-9,.]*([.][0-9]+)?) ]]; then
    value="$(clean_value "${BASH_REMATCH[3]}")"
    guardrail_command weekly-cost "$value"
    return 0
  fi

  if [[ "$lower" =~ (clear|unset|remove|disable|turn[[:space:]]+off|off).*(daily|day|per[[:space:]]+day).*(limit|budget|guardrail|cap) ]]; then
    guardrail_command daily off
    return 0
  fi
  if [[ "$lower" =~ (clear|unset|remove|disable|turn[[:space:]]+off|off).*(weekly|week|per[[:space:]]+week).*(limit|budget|guardrail|cap) ]]; then
    guardrail_command weekly off
    return 0
  fi
  if [[ "$lower" =~ (clear|unset|remove|disable|turn[[:space:]]+off|off).*(30d|30-day|thirty|monthly|month|overall).*(limit|budget|guardrail|cap) ]]; then
    guardrail_command 30d off
    return 0
  fi

  if [[ "$lower" =~ (daily|day|per[[:space:]]+day).*(limit|budget|guardrail|cap)[^0-9]*([0-9][0-9,.]*[[:space:]]*[kmb]?) ]]; then
    value="$(clean_token_value "${BASH_REMATCH[3]}")"
    guardrail_command daily "$value"
    return 0
  fi
  if [[ "$lower" =~ (weekly|week|per[[:space:]]+week).*(limit|budget|guardrail|cap)[^0-9]*([0-9][0-9,.]*[[:space:]]*[kmb]?) ]]; then
    value="$(clean_token_value "${BASH_REMATCH[3]}")"
    guardrail_command weekly "$value"
    return 0
  fi
  if [[ "$lower" =~ (30d|30-day|thirty|monthly|month|overall|budget).*(limit|budget|guardrail|cap)?[^0-9]*([0-9][0-9,.]*[[:space:]]*[kmb]?) ]]; then
    value="$(clean_token_value "${BASH_REMATCH[3]}")"
    guardrail_command 30d "$value"
    return 0
  fi
  if [[ "$lower" =~ (set|cap|limit).*(budget|quota)[^0-9]*([0-9][0-9,.]*[[:space:]]*[kmb]?) ]]; then
    value="$(clean_token_value "${BASH_REMATCH[3]}")"
    guardrail_command 30d "$value"
    return 0
  fi

  return 1
}

answer_agent_prompt() {
  local app_path="$1"
  local input="$2"
  local lower
  lower="$(printf '%s' "$input" | tr '[:upper:]' '[:lower:]')"

  if natural_limit_command "$input"; then
    return 0
  fi
  if [[ "$lower" == *"how hot"* || "$lower" == *"heat"* || "$lower" == *"power"* || "$lower" == *"watt"* || "$lower" == *"joule"* ]]; then
    power_report
    return 0
  fi
  if [[ "$lower" == *"folder"* || "$lower" == *"project"* || "$lower" == *"workspace"* ]]; then
    folder_report
    return 0
  fi

  case "$lower" in
    help|commands|\?|"/help")
      chat_hint
      ;;
    open|launch|"open app"|"launch app")
      if [[ -n "$app_path" ]]; then
        open "$app_path"
        printf 'Opened TokenBar again: %s\n' "$app_path"
      else
        printf 'TokenBar app path is not installed yet. You can still ask questions from local logs.\n'
      fi
      ;;
    status|doctor-status)
      status_report
      ;;
    forecast)
      agent_answer "forecast my next usage window from the local logs"
      ;;
    controls)
      controls_report
      ;;
    limits|limit|budgets)
      guardrail_report
      ;;
    guardrails|guardrail|"guardrails "*|"guardrail "*)
      read -r _ arg1 arg2 <<< "$input"
      guardrail_command "${arg1:-}" "${arg2:-}"
      ;;
    power|heat|energy|"how hot is my mac"|"mac heat"|"mac power")
      power_report
      ;;
    folders|folder|projects|project|workspaces|workspace|"show folders"|"show projects")
      folder_report
      ;;
    tracking|"tracking "*|"track "*)
      read -r _ arg1 _ <<< "$input"
      tracking_command "${arg1:-status}"
      ;;
    activity|"activity "*)
      read -r _ arg1 _ <<< "$input"
      activity_command "${arg1:-status}"
      ;;
    doctor|bug|debug|diagnostic|diagnostics)
      doctor_report
      ;;
    site|website)
      open_site
      ;;
    skills|repo|github)
      open_skills_repo
      ;;
    install|download|update)
      printf '%s\n' "$install_command"
      ;;
    config|settings|"config "*|"settings "*|model|"model "*|budget|"budget "*|price|"price "*)
      configure_agent_session "$input"
      ;;
    quit|exit|bye)
      return 10
      ;;
    "")
      ;;
    *)
      agent_answer "$input"
      ;;
  esac
}

agent_config_report() {
  printf 'TokenBar Agent configuration\n'
  printf '%s\n' '----------------------------'
  printf 'Model: %s\n' "$agent_model"
  printf 'API: %s\n' "$(agent_api_status)"
  printf 'Usage index: %s\n' "$usage_index"
  printf 'Budget: %s\n' "${agent_budget_tokens:-not configured}"
  printf 'Price: %s\n' "${agent_price_per_million:-not configured}"
  printf '\n'
  printf 'Prompt config examples:\n'
  printf '  config model gpt-5.4\n'
  printf '  config budget 20B\n'
  printf '  config price 0.30\n'
}

configure_agent_session() {
  local input="$1"
  local words=()
  local first field value
  read -r -a words <<< "$input"
  first="${words[0]:-}"

  case "$first" in
    config|settings)
      field="${words[1]:-}"
      value="${words[*]:2}"
      ;;
    model|budget|price)
      field="$first"
      value="${words[*]:1}"
      ;;
    *)
      agent_config_report
      return 0
      ;;
  esac

  case "$field" in
    ""|show|status)
      agent_config_report
      ;;
    model)
      if [[ -z "$value" ]]; then
        printf 'Model is %s\n' "$agent_model"
      else
        agent_model="$value"
        printf 'Agent model set for this session: %s\n' "$agent_model"
      fi
      ;;
    budget|tokens|limit)
      if [[ -z "$value" ]]; then
        printf 'Budget is %s\n' "${agent_budget_tokens:-not configured}"
      else
        agent_budget_tokens="$value"
        printf 'Session budget set: %s tokens\n' "$agent_budget_tokens"
      fi
      ;;
    price|cost)
      if [[ -z "$value" ]]; then
        printf 'Price is %s\n' "${agent_price_per_million:-not configured}"
      else
        agent_price_per_million="$value"
        printf 'Session price set: $%s per million tokens\n' "$agent_price_per_million"
      fi
      ;;
    *)
      printf 'Unknown config field: %s\n' "$field"
      agent_config_report
      ;;
  esac
}

prompt_separator() {
  local dim=$'\033[2m'
  local reset=$'\033[0m'
  local label="${1:-}"
  if [[ -n "$label" ]]; then
    printf '%s-------- ---------- %s ----------%s\n' "$dim" "$label" "$reset"
  else
    printf '%s-------- ---------- -------- ----------%s\n' "$dim" "$reset"
  fi
}

agent_editor_header() {
  local green=$'\033[32m'
  local cyan=$'\033[36m'
  local amber=$'\033[33m'
  local dim=$'\033[2m'
  local reset=$'\033[0m'

  if ! command -v python3 >/dev/null 2>&1; then
    printf '%sTokenBar Chat%s\n' "$green" "$reset" >&2
    printf '%sAsk about tokens, budget, reset, folders, or cost.%s\n' "$dim" "$reset" >&2
    return 0
  fi

  TOKENBAR_CHAT_CONTEXT="$(usage_context_json 2>/dev/null || printf '{}')" \
  TOKENBAR_API_STATUS="$(agent_api_status)" \
  python3 - <<'PY' >&2
import json
import os

ctx = json.loads(os.environ.get("TOKENBAR_CHAT_CONTEXT") or "{}")
api_status = os.environ.get("TOKENBAR_API_STATUS") or "local analyst"

green = "\033[32m"
cyan = "\033[36m"
amber = "\033[33m"
dim = "\033[2m"
reset = "\033[0m"

api_label = "OpenAI ready" if str(api_status).startswith("OpenAI") else "local analyst"
today = ctx.get("today_compact", "0")
next7 = ctx.get("projected_next7_compact", "0")
last30 = ctx.get("last30_compact", "0")
reset_date = ctx.get("hypothetical_reset_date") or "set reset"
budget = ctx.get("budget_compact") or "set budget"
uncertainty = ctx.get("uncertainty_percent", 0)
watts = ctx.get("mac_power_watts")
power = f" | power {watts}W" if watts else ""

print(f"{green}TokenBar Chat{reset}  {cyan}.--. (o ^){reset}  {dim}{api_label}{reset}")
print(f"today {green}{today}{reset} | 7d {next7} | 30d {last30} | reset {reset_date} | budget {amber}{budget}{reset}{power}")
print(f"{dim}Ask naturally: will I run out? | why spike? | show folders | set daily limit 100M | heat? | +/-{uncertainty}%{reset}")
PY
}

prompt_agent_animation() {
  terminal_movie "" "prompt"
}

read_agent_input() {
  local cyan=$'\033[36m'
  local reset=$'\033[0m'
  printf '%sTokenBar >%s ' "$cyan" "$reset" >&2
  IFS= read -r REPLY
  local status=$?
  if [[ ! -t 0 ]]; then
    printf '\n' >&2
  fi
  printf '%s\n' "$REPLY"
  return "$status"
}

agent_interface() {
  local app_path="$1"
  local cyan=$'\033[36m'
  local green=$'\033[32m'
  local dim=$'\033[2m'
  local reset=$'\033[0m'
  local input status

  prompt_agent_animation
  agent_editor_header
  while true; do
    if ! input="$(read_agent_input)"; then
      break
    fi
    set +e
    answer_agent_prompt "$app_path" "$input"
    status=$?
    set -e
    if [[ "$status" == "10" ]]; then
      break
    fi
  done
}

quiet=false
animate=true
force_animation=false
agent=true
sound_enabled=false
command="open"
command_args=()

if [[ "${TOKENBAR_SOUND:-0}" == "1" || "${TOKENBAR_SOUND:-}" == "true" ]]; then
  sound_enabled=true
fi

if [[ $# -gt 0 && "$1" != --* && "$1" != -* ]]; then
  command="$1"
  shift
fi

while [[ $# -gt 0 ]]; do
  case "$1" in
    -h|--help|help)
      usage
      exit 0
      ;;
    --install)
      command="install"
      ;;
    --path)
      command="path"
      ;;
    --quiet|-q)
      quiet=true
      animate=false
      agent=false
      ;;
    --no-animation|--plain)
      animate=false
      ;;
    --movie)
      force_animation=true
      ;;
    --sound)
      sound_enabled=true
      ;;
    --no-agent)
      agent=false
      ;;
    --model)
      shift
      if [[ $# -eq 0 ]]; then
        echo "--model requires a value" >&2
        exit 2
      fi
      agent_model="$1"
      ;;
    --budget)
      shift
      if [[ $# -eq 0 ]]; then
        echo "--budget requires a value" >&2
        exit 2
      fi
      agent_budget_tokens="$1"
      ;;
    --price)
      shift
      if [[ $# -eq 0 ]]; then
        echo "--price requires a value" >&2
        exit 2
      fi
      agent_price_per_million="$1"
      ;;
    --activity)
      set_control_value activitySignalsEnabled true
      ;;
    *)
      if [[ "$command" == "open" && ${#command_args[@]} -eq 0 && "$1" != -* ]]; then
        command="$1"
      else
        command_args+=("$1")
      fi
      ;;
  esac
  shift
done

case "$command" in
  help|\?|chat-help)
    usage
    ;;
  install|update)
    install_tokenbar
    ;;
  path)
    find_app
    ;;
  status)
    status_report
    ;;
  forecast)
    forecast_report
    ;;
  controls|control|budget|timing|hours)
    controls_report
    ;;
  guardrails|guardrail|limits|limit|budgets)
    guardrail_command "${command_args[0]:-}" "${command_args[1]:-}"
    ;;
  power|heat|energy)
    power_report
    ;;
  folders|folder|projects|project|workspaces|workspace)
    folder_report
    ;;
  tracking|track|logs)
    tracking_command "${command_args[0]:-status}"
    ;;
  activity|activity-signals)
    activity_command "${command_args[0]:-status}"
    ;;
  ask)
    if [[ ${#command_args[@]} -gt 0 ]]; then
      agent_answer "${command_args[*]}"
    else
      app_path="$(find_app || true)"
      agent_interface "$app_path"
    fi
    ;;
  doctor|diagnose|diagnostic)
    doctor_report
    ;;
  providers|provider)
    provider_report
    ;;
  explain)
    explain_topic "${command_args[0]:-overview}"
    ;;
  site|website)
    open_site
    ;;
  skills|repo|github)
    open_skills_repo
    ;;
  copy-install|copy)
    copy_install_command
    ;;
  prompt|agent)
    app_path="$(find_app || true)"
    agent_interface "$app_path"
    ;;
  open|launch)
    if app_path="$(find_app)"; then
      open "$app_path"
      if [[ "$quiet" == true ]]; then
        exit 0
      elif [[ "$agent" == true ]] && supports_agent_interface; then
        agent_interface "$app_path"
      elif [[ "$animate" == true && "$force_animation" == true ]]; then
        animated_open_message "$app_path"
      elif [[ "$animate" == true ]] && supports_animation; then
        animated_open_message "$app_path"
      else
        plain_open_message "$app_path"
      fi
    else
      cat >&2 <<EOF
TokenBar CLI is installed, but the macOS menu-bar app is not installed yet.

Install the app with:
  tokenbar install

One-line CLI + app installer:
  $install_command

Manual app download:
  $download_url
EOF
      exit 1
    fi
    ;;
  *)
    natural_input="$command ${command_args[*]:-}"
    if natural_limit_command "$natural_input"; then
      exit 0
    fi
    agent_answer "$natural_input"
    ;;
esac
