Last active
October 22, 2026 14:20
-
-
Save 7shi/5a1e1b83fbe6c5c6b8114583d545a8ee to your computer and use it in GitHub Desktop.
Status line UI for Claude Code and Antigravity CLI
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Antigravity CLI Custom Statusline Script. | |
| Displays: | |
| - Default elements: cycle mode, model name, effort level (e.g. accept-edits · Gemini 3.7 Flash · hi) | |
| - Context window size and usage percentage | |
| - 5-hour quota limit and remaining time | |
| - 1-week quota limit and remaining time | |
| """ | |
| import json | |
| import re | |
| import sys | |
| def format_tokens(num): | |
| """Format token counts into human-readable strings (e.g., 60.4k, 1M).""" | |
| if num is None: | |
| return "--" | |
| try: | |
| num = float(num) | |
| if num >= 1_000_000: | |
| val = num / 1_000_000 | |
| return f"{int(val)}M" if val.is_integer() else f"{val:.1f}M" | |
| elif num >= 1_000: | |
| val = num / 1_000 | |
| return f"{int(val)}k" if val.is_integer() else f"{val:.1f}k" | |
| else: | |
| return str(int(num)) | |
| except Exception: | |
| return str(num) | |
| def format_duration(seconds): | |
| """Format seconds into readable duration (e.g., 1d0h, 3h14m, 45m).""" | |
| if seconds is None: | |
| return "" | |
| try: | |
| s = int(seconds) | |
| if s <= 0: | |
| return "0m" | |
| days = s // 86400 | |
| hours = (s % 86400) // 3600 | |
| mins = (s % 3600) // 60 | |
| if days > 0: | |
| return f"{days}d{hours}h" | |
| elif hours > 0: | |
| return f"{hours}h{mins}m" | |
| else: | |
| return f"{mins}m" | |
| except Exception: | |
| return "" | |
| def sum_tokens(val): | |
| """Safely sum token counts whether passed as an int, float, or dict.""" | |
| if val is None: | |
| return None | |
| if isinstance(val, (int, float)): | |
| return val | |
| if isinstance(val, dict): | |
| total = 0 | |
| found = False | |
| keys_to_check = [ | |
| "input_tokens", | |
| "output_tokens", | |
| "cache_creation_input_tokens", | |
| "cache_read_input_tokens", | |
| "prompt_tokens", | |
| "completion_tokens", | |
| "total_tokens", | |
| ] | |
| for k in keys_to_check: | |
| if k in val and isinstance(val[k], (int, float)): | |
| total += val[k] | |
| found = True | |
| if found: | |
| return total | |
| num_sum = sum(v for v in val.values() if isinstance(v, (int, float))) | |
| return num_sum if num_sum > 0 else None | |
| try: | |
| return float(val) | |
| except Exception: | |
| return None | |
| def extract_default_info(data): | |
| """Extract default statusline elements: cycle_mode, model name, effort.""" | |
| items = [] | |
| # 1. Cycle Mode / Execution Mode | |
| mode = data.get("cycle_mode") or data.get("execution_mode") | |
| if mode: | |
| items.append(str(mode)) | |
| # 2. Model & Effort | |
| model_obj = data.get("model") or {} | |
| if isinstance(model_obj, dict): | |
| display_name = model_obj.get("display_name") or model_obj.get("id") or "" | |
| # Clean up '(High)' etc. from model name if effort is displayed separately | |
| clean_name = re.sub(r"\s*\([^)]*\)", "", display_name).strip() if display_name else "" | |
| if clean_name: | |
| items.append(clean_name) | |
| elif display_name: | |
| items.append(display_name) | |
| effort = model_obj.get("effort") | |
| if effort: | |
| effort_map = {"high": "hi", "low": "lo", "medium": "med"} | |
| items.append(effort_map.get(str(effort).lower(), str(effort))) | |
| elif model_obj: | |
| items.append(str(model_obj)) | |
| return items | |
| def extract_context_info(data): | |
| """Extract context usage and window size.""" | |
| cw = data.get("context_window") or {} | |
| curr_tokens = sum_tokens(cw.get("current_usage")) | |
| if curr_tokens is None: | |
| in_t = sum_tokens(cw.get("total_input_tokens") or cw.get("input_tokens")) or 0 | |
| out_t = sum_tokens(cw.get("total_output_tokens") or cw.get("output_tokens")) or 0 | |
| if in_t or out_t: | |
| curr_tokens = in_t + out_t | |
| max_tokens = sum_tokens(cw.get("context_window_size") or cw.get("max_tokens") or cw.get("limit")) | |
| pct = cw.get("used_percentage") | |
| if pct is None and curr_tokens is not None and max_tokens and max_tokens > 0: | |
| pct = (curr_tokens / max_tokens) * 100 | |
| if curr_tokens is not None and max_tokens is not None: | |
| pct_str = f"({pct:.1f}%)" if pct is not None else "" | |
| return f"{format_tokens(curr_tokens)}/{format_tokens(max_tokens)}{pct_str}" | |
| elif curr_tokens is not None: | |
| pct_str = f"({pct:.1f}%)" if pct is not None else "" | |
| return f"{format_tokens(curr_tokens)}{pct_str}" | |
| elif pct is not None: | |
| return f"{pct:.1f}%" | |
| return "--" | |
| def get_model_category(model_obj): | |
| """Detect model category: "argon", "gemini", or "3p".""" | |
| if isinstance(model_obj, dict): | |
| mid = model_obj.get("id") or "" | |
| mdn = model_obj.get("display_name") or "" | |
| name = f"{mid} {mdn}".lower() | |
| elif model_obj: | |
| name = str(model_obj).lower() | |
| else: | |
| name = "" | |
| if "argon" in name: | |
| return "argon" | |
| elif "gemini" in name: | |
| return "gemini" | |
| elif any(k in name for k in ["claude", "sonnet", "opus", "haiku", "gpt", "openai", "3p", "anthropic"]): | |
| return "3p" | |
| return "gemini" | |
| def extract_quota_bucket(quota, patterns, model_category="gemini"): | |
| """Find a specific bucket from quota payload using pattern keywords and model category.""" | |
| if not isinstance(quota, dict): | |
| return None | |
| is_weekly = any(p in ["weekly", "7d", "7_day", "week"] for p in patterns) | |
| is_5h = any(p in ["5h", "5_hour", "five_hour"] for p in patterns) | |
| # Priority candidates by model category | |
| candidate_keys = [] | |
| if is_weekly: | |
| if model_category == "argon": | |
| candidate_keys = ["gemini-argon-wtu-weekly", "gemini-argon-weekly", "gemini-weekly"] | |
| elif model_category == "gemini": | |
| candidate_keys = ["gemini-weekly", "gemini-argon-wtu-weekly"] | |
| elif model_category == "3p": | |
| candidate_keys = ["3p-weekly", "gemini-weekly"] | |
| elif is_5h: | |
| if model_category in ("argon", "gemini"): | |
| candidate_keys = ["gemini-5h", "gemini-argon-5h", "3p-5h"] | |
| elif model_category == "3p": | |
| candidate_keys = ["3p-5h", "gemini-5h"] | |
| for k in candidate_keys: | |
| if k in quota: | |
| return quota[k] | |
| # Dynamic fallback matching patterns and category keywords | |
| for pattern in patterns: | |
| for k, v in quota.items(): | |
| k_lower = k.lower() | |
| if pattern in k_lower: | |
| if model_category == "argon" and "argon" in k_lower: | |
| return v | |
| elif model_category == "gemini" and "gemini" in k_lower and "argon" not in k_lower: | |
| return v | |
| elif model_category == "3p" and ("3p" in k_lower or "claude" in k_lower or "gpt" in k_lower): | |
| return v | |
| # Generic fallback matching pattern only | |
| for pattern in patterns: | |
| if pattern in quota: | |
| return quota[pattern] | |
| for k, v in quota.items(): | |
| if pattern in k.lower(): | |
| return v | |
| return None | |
| from datetime import datetime, timedelta | |
| def parse_reset_datetime(bucket_data): | |
| """Parse reset datetime and convert to local time.""" | |
| if not isinstance(bucket_data, dict): | |
| return None | |
| # 1. Try ISO reset_time | |
| reset_time_str = bucket_data.get("reset_time") | |
| if reset_time_str: | |
| try: | |
| iso_str = reset_time_str | |
| if iso_str.endswith("Z"): | |
| iso_str = iso_str[:-1] + "+00:00" | |
| dt_utc = datetime.fromisoformat(iso_str) | |
| return dt_utc.astimezone() | |
| except Exception: | |
| pass | |
| # 2. Try reset_in_seconds | |
| reset_sec = bucket_data.get("reset_in_seconds") | |
| if reset_sec is not None: | |
| try: | |
| return datetime.now().astimezone() + timedelta(seconds=float(reset_sec)) | |
| except Exception: | |
| pass | |
| return None | |
| def format_quota_entry(bucket_data, mode="time", fallback_quota=None): | |
| """Format a single quota bucket entry. | |
| mode="time" -> e.g. 22%(01:50) | |
| mode="datetime" -> e.g. 82%(08/23 01:00) | |
| """ | |
| pct_str = "--" | |
| dt = None | |
| if isinstance(bucket_data, dict): | |
| if bucket_data.get("disabled"): | |
| return "--" | |
| rem = bucket_data.get("remaining_fraction") | |
| if rem is not None: | |
| rem_val = rem * 100 if rem <= 1.0 else rem | |
| pct_str = f"{(100 - rem_val):.0f}%" | |
| else: | |
| used = bucket_data.get("used_percentage") | |
| pct_str = f"{used:.0f}%" if used is not None else "--" | |
| dt = parse_reset_datetime(bucket_data) | |
| elif fallback_quota and isinstance(fallback_quota, dict): | |
| rem = fallback_quota.get("remaining_fraction") | |
| if rem is not None: | |
| rem_val = rem * 100 if rem <= 1.0 else rem | |
| pct_str = f"{(100 - rem_val):.0f}%" | |
| dt = parse_reset_datetime(fallback_quota) | |
| elif bucket_data is not None and not isinstance(bucket_data, (dict, list)): | |
| return str(bucket_data) | |
| if dt: | |
| if mode == "datetime": | |
| time_str = dt.strftime("%m/%d %H:%M") | |
| else: | |
| time_str = dt.strftime("%H:%M") | |
| return f"{pct_str}({time_str})" | |
| elif pct_str != "--": | |
| return pct_str | |
| return "--" | |
| def main(): | |
| try: | |
| raw = sys.stdin.read() | |
| if not raw.strip(): | |
| print("") | |
| return | |
| # Save raw JSON for debugging | |
| try: | |
| with open("/tmp/antigravity_statusline_debug.json", "w") as f: | |
| f.write(raw) | |
| except Exception: | |
| pass | |
| data = json.loads(raw) | |
| except Exception: | |
| print("") | |
| return | |
| # ANSI Colors | |
| CYAN = "\033[36m" | |
| GREEN = "\033[32m" | |
| MAGENTA = "\033[35m" | |
| DIM = "\033[2m" | |
| RESET = "\033[0m" | |
| parts = [] | |
| # 1. Context Size | |
| ctx_info = extract_context_info(data) | |
| parts.append(f"{CYAN}Ctx:{RESET}{ctx_info}") | |
| # 2. Quota / Rate Limits (5h & weekly/7d) | |
| quota = data.get("quota") or data.get("rate_limits") or {} | |
| model_obj = data.get("model") or {} | |
| model_category = get_model_category(model_obj) | |
| q_5h = extract_quota_bucket(quota, ["5h", "5_hour", "five_hour", "short_term"], model_category) | |
| val_5h = format_quota_entry(q_5h, mode="time", fallback_quota=quota if not q_5h else None) | |
| parts.append(f"{GREEN}5h:{RESET}{val_5h}") | |
| q_7d = extract_quota_bucket(quota, ["weekly", "7d", "7_day", "seven_day", "week", "long_term"], model_category) | |
| val_7d = format_quota_entry(q_7d, mode="datetime") | |
| parts.append(f"{MAGENTA}7d:{RESET}{val_7d}") | |
| # 3. Default info (cycle_mode, model, effort) at the end, joined with middle dot | |
| default_items = extract_default_info(data) | |
| if default_items: | |
| dot = f" {DIM}·{RESET} " | |
| print(dot.join([" ".join(parts), *default_items])) | |
| else: | |
| # Join main sections with single space | |
| print(" ".join(parts)) | |
| if __name__ == "__main__": | |
| main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import json, sys | |
| from datetime import datetime, timezone | |
| from pathlib import Path | |
| data = json.load(sys.stdin) | |
| RED = "\033[0;31m" | |
| BLUE = "\033[01;34m" | |
| YELLOW = "\033[0;33m" | |
| CYAN = "\033[0;36m" | |
| RESET = "\033[00m" | |
| cwd = data.get("cwd") or "." | |
| home = str(Path.home()) | |
| if cwd == home: | |
| cwd = "~" | |
| elif cwd.startswith(home + "/"): | |
| cwd = "~" + cwd[len(home):] | |
| parts = [f"{BLUE}{cwd}{RESET}"] | |
| model = data.get("model", {}).get("display_name") | |
| if model: | |
| model = model.replace(" context", "") | |
| parts.append(f"{YELLOW}{model}{RESET}") | |
| used = data.get("context_window", {}).get("used_percentage") | |
| if used is not None: | |
| parts.append(f"{CYAN}ctx:{used:.0f}%{RESET}") | |
| rl = data.get("rate_limits", {}) | |
| def parse_reset(val): | |
| if isinstance(val, (int, float)): | |
| return datetime.fromtimestamp(val, tz=timezone.utc).astimezone() | |
| return datetime.fromisoformat(str(val)).astimezone() | |
| five = rl.get("five_hour", {}) | |
| if five.get("used_percentage") is not None: | |
| t = parse_reset(five["resets_at"]) | |
| parts.append(f"{YELLOW}5h:{five['used_percentage']:.0f}%({t:%H:%M}){RESET}") | |
| seven = rl.get("seven_day", {}) | |
| if seven.get("used_percentage") is not None: | |
| t = parse_reset(seven["resets_at"]) | |
| parts.append(f"{RED}7d:{seven['used_percentage']:.0f}%({t:%m/%d %H:%M}){RESET}") | |
| print(" ".join(parts), end="") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment