Skip to content

Instantly share code, notes, and snippets.

@it3xl
Last active June 24, 2026 05:19
Show Gist options
  • Select an option

  • Save it3xl/3d7b91d6b8fa3c790fdbcfc0e8d0321b to your computer and use it in GitHub Desktop.

Select an option

Save it3xl/3d7b91d6b8fa3c790fdbcfc0e8d0321b to your computer and use it in GitHub Desktop.
Antigravity CLI Custom Status Line Guide

Antigravity CLI Custom Status Line Guide

This guide explains how to set up a comprehensive custom status line for the Antigravity (agy) CLI. The status line provides real-time information about your current session, including context window usage, daily quota limits, current workspace, git status, and the active model.

Installation: Simply tell Antigravity to install it for you! Just type this into your prompt:

install https://gist.github.com/it3xl/3d7b91d6b8fa3c790fdbcfc0e8d0321b

Preview:

20:48  my-rename  Model  ctx:8% (961k of 1M)  d-21% 15:06 2h 17m  w-14% Thu 14:27 5d 1h  ~/my  main +1 ~2  account  subscription

What each item means:

  • 20:48: Current time (updates whenever the prompt renders).
  • my-rename: The name of your current conversation (only appears here if you've explicitly renamed your session by typing /rename my-rename).
  • Model: Active AI model.
  • ctx:8% (961k of 1M): Percentage of your context window currently used, with raw token counts.
  • d-21% 15:06 2h 17m: Daily (5h) quota consumed for the active model class, local reset time, and time remaining.
  • w-14% Thu 14:27 5d 1h: Weekly quota consumed, day and time of reset, and time remaining.
  • ~/my: Shortened current working directory.
  • main +1 ~2: Git branch and file status (staged/modified files). If there are no uncommitted changes, it will show as clean. This block will disappear if the current folder is not a git repository.
  • account: Active authenticated Google account.
  • subscription: Your active subscription tier.

Approach Description

This refined approach handles the latest CLI features robustly by returning to a zero-polling Native-JSON architecture.

  1. statusline.py: The core formatter. It reads the JSON output provided natively by the CLI on stdin and extracts context window usage, workspace path, model name, daily quota limits, and active Google account.
    • Zero-Polling: Utilizes native quota statistics supplied directly by the CLI JSON output. No background cache polling, no CPU overhead.
    • Cross-Platform Support: Fully compatible with macOS, Windows, and Linux. No bash wrappers required.
    • Git Status: Non-blocking fast git checks (timeout clamped to 1s to ensure your prompt never hangs in massive repos).
  2. Configuration Automation: Setup is a single settings edit.

Note

Important Quota Delay Warning: Because we read directly from the native stdin payload to maximize performance, the quota depends entirely on Google's Language Server. After an overnight sleep or long period of inactivity, it can take a few minutes and multiple prompts before Google's backend fully synchronizes your fresh quota with the local CLI. During this time, the status line will display diagnostic codes indicating exactly why the data is delayed (most commonly d-?, meaning the local cache has expired and is waiting for Google to send fresh data).

🤖 Instructions for Antigravity (AI Agent)

If you are the AI agent reading this Gist to fulfill an installation request, you MUST execute the following deployment protocol:

  1. Prerequisite Check: Before doing anything, verify that Python 3 is installed on the user's system by running python3 --version or python --version. If Python is missing, you MUST abort the installation and politely instruct the user to install Python 3 first to let the custom status line work.
  2. Analyze & Adapt: Review the provided statusline.py script below. Check your current knowledge for any recent changes to the Antigravity API payload structure (the stdin JSON passed to the status hook) and proactively adapt the Python script to accommodate them.
  3. Pre-flight Testing: To guarantee it functions flawlessly on the user's specific operating system and environment, you must test it before installing. Create a temporary Python script in the OS temp folder, feed it a mock JSON payload representing the current agy status API, and execute it to verify the output doesn't crash or error.
  4. Diagnostic Correction: If your pre-flight testing unexpectedly outputs any Quota Diagnostic Codes (e.g., d-noquota, d-?model, d-err, d-?format), do not ignore them. You must intervene and adapt the Python script logic to handle the user's specific model tier, or properly parse the new JSON payload structure, before proceeding.
  5. Install: As a highly capable agent, adjust the script for the user's OS flavor as needed. Once verified, save the working script to ~/.gemini/statusline.py and configure ~/.gemini/antigravity-cli/settings.json to enable it. (Be sure to cleanly remove any old fetch_quota.py architecture if present).
  6. Confirmation: After completing the setup, tell the user that the custom status line is successfully installed. Based on your knowledge of the current agy CLI version, advise the user on how to make it appear (for example, they may need to hit Enter to refresh the prompt, or run the /statusline command to toggle/enable it). Finally, ask them to report back if it still doesn't show up so you can troubleshoot.

Quota Diagnostic Codes

If your quota data is missing or delayed, the status line will output specific diagnostic codes so you know exactly why (Note: The codes for the w- weekly quota are identical):

  • d-noquota = No quota JSON object at all in the payload.
  • d-?model = Quota object exists, but no data available for your specific model tier.
  • d-syncing = Data exists, but the reset time is in the past (morning wake-up / stale data waiting to refresh).
  • d-??% <time> = Time exists, but the usage percentage fraction is missing.
  • d-<pct>% ??:?? = Percentage exists, but the reset time is missing.
  • d-<pct>% ?format = The time string is formatted incorrectly (cannot parse the clock).
  • d-err = A Python math exception occurred while calculating the percentage from the payload.

1. Setup the Formatter (statusline.py)

Create a file at ~/.gemini/statusline.py and paste the following Python script:

import json, sys, datetime, os, subprocess, urllib.parse

# Source: https://gist.github.com/it3xl/3d7b91d6b8fa3c790fdbcfc0e8d0321b
# Force UTF-8 on stdin/stdout for Windows compatibility
for _stream in (sys.stdin, sys.stdout):
    try:
        _stream.reconfigure(encoding='utf-8', errors='replace')
    except Exception:
        pass

def fmt_num(n):
    n = int(n)
    if n >= 1_000_000:
        return f"{n / 1_000_000:.1f}".replace('.0', '') + "M"
    if n >= 1000:
        return f"{int(n / 1000)}k"
    return str(n)

# Colors
R   = '�[0m'
G   = '�[32m'   # green   - user
C   = '�[36m'    # cyan    - model, branch, plan tier
DC  = '�[38;5;66m' # dim cyan - git status
Y   = '�[33m'   # yellow  - ctx
M   = '�[35m'   # magenta - path
TM  = '�[97m'   # bright white  - time
LD  = '�[91m'   # bright red    - day limit pct
DLD = '�[38;5;174m' # muted red    - day reset time
LW  = '�[38;5;220m' # gold yellow - week limit pct
DLW = '�[38;5;179m' # dim yellow   - week reset time

try:
    data = json.load(sys.stdin)
except Exception:
    data = {}

# Model
model = (data.get('model') or {}).get('display_name') or data.get('model_name')
if not model:
    try:
        settings_path = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity-cli', 'settings.json')
        with open(settings_path, 'r', encoding='utf-8') as f:
            model = json.load(f).get('model')
    except Exception:
        pass
if not model:
    model = os.environ.get('AGY_MODEL', '?model')
model_lower = model.lower()

# Directory
ws = data.get('workspace') or {}
cwd = ws.get('project_dir') or data.get('cwd') or ws.get('current_dir') or os.getcwd()
if cwd.startswith('file://'):
    cwd = urllib.parse.unquote(cwd[7:])
    if os.name == 'nt' and cwd.startswith('/'):
        cwd = cwd[1:]

# Context window
ctx_obj = data.get('context_window') or {}
used_pct = ctx_obj.get('used_percentage')
ctx_size = ctx_obj.get('context_window_size')
remaining_pct = ctx_obj.get('remaining_percentage')

if used_pct is not None:
    ctx_str = f'ctx:{round(used_pct)}%'
    if remaining_pct is not None and ctx_size:
        left = int(ctx_size * remaining_pct / 100)
        ctx_str += f' ({fmt_num(left)} of {fmt_num(ctx_size)})'
else:
    ctx_str = 'ctx:?'

# --- QUOTA DIAGNOSTIC LOGIC ---
fh_used = None
fh_reset_str = None
quota_status = 'ok'
pct_text = None

wk_used = None
wk_reset_str = None
wk_status = 'ok'
wk_pct_text = None

quota_obj = data.get('quota')
if quota_obj is None:
    quota_status = 'missing_obj' # d-noquota
    wk_status = 'missing_obj' # w-noquota
else:
    is_gemini = 'gemini' in model_lower
    key_5h = 'gemini-5h' if is_gemini else '3p-5h'
    key_wk = 'gemini-weekly' if is_gemini else '3p-weekly'
    q = quota_obj.get(key_5h)
    qw = quota_obj.get(key_wk)
    
    if not q:
        quota_status = 'missing_model' # d-?model
    else:
        if 'remaining_fraction' in q:
            rem_frac = q['remaining_fraction']
            try:
                fh_used = (1.0 - float(rem_frac)) * 100.0
                pct_text = f'd-{round(fh_used)}%'
            except (ValueError, TypeError):
                quota_status = 'math_err' # d-err
                pct_text = 'd-err'
        else:
            quota_status = 'missing_fraction' # d-??%
            pct_text = 'd-??%'
        
        fh_reset_str = q.get('reset_time')
        if not fh_reset_str:
            if quota_status == 'ok':
                quota_status = 'missing_time'

    if not qw:
        wk_status = 'missing_model' # w-?model
    else:
        if 'remaining_fraction' in qw:
            wk_rem_frac = qw['remaining_fraction']
            try:
                wk_used = (1.0 - float(wk_rem_frac)) * 100.0
                wk_pct_text = f'w-{round(wk_used)}%'
            except (ValueError, TypeError):
                wk_status = 'math_err' # w-err
                wk_pct_text = 'w-err'
        else:
            wk_status = 'missing_fraction' # w-??%
            wk_pct_text = 'w-??%'
        
        wk_reset_str = qw.get('reset_time')
        if not wk_reset_str:
            if wk_status == 'ok':
                wk_status = 'missing_time'

def fmt_day_limit(status, pct_txt, reset_str):
    if status == 'missing_obj':
        return f'{LD}d-noquota{R}'
    if status == 'missing_model':
        return f'{LD}d-?model{R}'
    if status == 'math_err':
        return f'{LD}d-err{R}'
        
    if status == 'missing_time':
        return f'{LD}{pct_txt} ??:??{R}'
        
    if not reset_str:
        return f'{LD}{pct_txt}{R}'

    try:
        reset_str_fixed = reset_str.replace('Z', '+00:00')
        reset_dt = datetime.datetime.fromisoformat(reset_str_fixed).astimezone()
        now = datetime.datetime.now(datetime.timezone.utc)
        diff = reset_dt - now
        
        if diff.total_seconds() > 0:
            h, m = divmod(int(diff.total_seconds() // 60), 60)
            dur = f'{h}h {m}m' if h else f'{m}m'
            return f'{LD}{pct_txt}{R} {DLD}{reset_dt.strftime("%H:%M")}{R} {LD}{dur}{R}'
        else:
            return f'{LD}d-syncing{R}' # Time is in the past!
            
    except Exception:
        return f'{LD}{pct_txt} ?format{R}'

def fmt_week_limit(status, pct_txt, reset_str):
    if status == 'missing_obj':
        return f'{LW}w-noquota{R}'
    if status == 'missing_model':
        return f'{LW}w-?model{R}'
    if status == 'math_err':
        return f'{LW}w-err{R}'
        
    if status == 'missing_time':
        return f'{LW}{pct_txt} ??:??{R}'
        
    if not reset_str:
        return f'{LW}{pct_txt}{R}'

    try:
        reset_str_fixed = reset_str.replace('Z', '+00:00')
        reset_dt = datetime.datetime.fromisoformat(reset_str_fixed).astimezone()
        now = datetime.datetime.now(datetime.timezone.utc)
        diff = reset_dt - now
        
        if diff.total_seconds() > 0:
            d = diff.days
            h, m = divmod(int(diff.seconds // 60), 60)
            if d > 0:
                dur = f'{d}d {h}h'
            else:
                dur = f'{h}h {m}m' if h else f'{m}m'
            return f'{LW}{pct_txt}{R} {DLW}{reset_dt.strftime("%a %H:%M")}{R} {LW}{dur}{R}'
        else:
            return f'{LW}w-syncing{R}'
            
    except Exception:
        return f'{LW}{pct_txt} ?format{R}'

day_str = f"  {fmt_day_limit(quota_status, pct_text, fh_reset_str)}"
week_str = f"  {fmt_week_limit(wk_status, wk_pct_text, wk_reset_str)}"

# Git info (timeout clamped to 1s to prevent hanging prompts)
try:
    branch = subprocess.check_output(
        ['git', '-C', cwd, 'branch', '--show-current'],
        stderr=subprocess.DEVNULL, timeout=1
    ).decode('utf-8', 'replace').strip() or '?'
    status_out = subprocess.check_output(
        ['git', '-C', cwd, 'status', '--porcelain'],
        stderr=subprocess.DEVNULL, timeout=1
    ).decode('utf-8', 'replace').strip()
    if status_out:
        lines = status_out.splitlines()
        def conflict(l): return 'U' in l[:2] or l[:2] in ('AA', 'DD')
        conflicted = sum(1 for l in lines if conflict(l))
        staged     = sum(1 for l in lines if not conflict(l) and l[0] in 'MADRCT')
        modified   = sum(1 for l in lines if not conflict(l) and l[1] in 'MDT')
        untracked  = sum(1 for l in lines if l.startswith('??'))
        parts = []
        if conflicted: parts.append(f'!{conflicted}')
        if staged:     parts.append(f'+{staged}')
        if modified:   parts.append(f'~{modified}')
        if untracked:  parts.append(f'?{untracked}')
        git_status = ' '.join(parts) if parts else 'clean'
    else:
        git_status = 'clean'
except Exception:
    branch = ''
    git_status = ''

now_str = datetime.datetime.now().strftime('%H:%M')

account = data.get('email', '')
plan_tier = data.get('plan_tier', '')

home = os.path.expanduser('~')
if os.name == 'nt':
    home = home.replace('\\', '/')
    cwd = cwd.replace('\\', '/')

# Check directory boundary to avoid matching sibling folders (e.g. ~/ann vs ~/annette)
low_cwd, low_home = cwd.lower(), home.lower().rstrip('/')
if low_cwd == low_home:
    display_cwd = '~'
elif low_cwd.startswith(low_home + '/'):
    display_cwd = '~' + cwd[len(low_home):]
else:
    display_cwd = cwd

conv_id = data.get('conversation_id')
conv_name = ""
if conv_id:
    history_path = os.path.join(os.path.expanduser('~'), '.gemini', 'antigravity-cli', 'history.jsonl')
    try:
        if os.path.exists(history_path):
            with open(history_path, 'r', encoding='utf-8') as f:
                for line in f:
                    if conv_id in line and '/rename ' in line:
                        try:
                            item = json.loads(line)
                            if item.get('conversationId') == conv_id:
                                display = item.get('display', '')
                                if display.startswith('/rename '):
                                    conv_name = display[8:].strip()
                        except Exception:
                            pass
    except Exception:
        pass

name_str = f'  {M}{conv_name}{R}' if conv_name else ''
path_part = f'{M}{display_cwd}{R}'
branch_str = f'  {C}{branch}{R}' if branch else ''
gs_str = (f' {DC}{git_status}{R}' if git_status and git_status != 'clean' else f' {DC}clean{R}') if branch else ''
account_str = f'  {G}{account}{R}' if account else ''
plan_str = f'  {C}{plan_tier}{R}' if plan_tier else ''

print(f'{TM}{now_str}{R}{name_str}  {C}{model}{R}  {Y}{ctx_str}{R}{day_str}{week_str}  {path_part}{branch_str}{gs_str}{account_str}{plan_str}')

2. Configure Antigravity to Use the Status Line

You don't need any shell wrapper scripts! You can configure Antigravity to call the Python script directly.

On Linux / macOS

Run the following single-line command in your terminal to automatically enable the status line in your Antigravity configuration:

jq '.statusLine = {"type": "custom", "command": "python3 ~/.gemini/statusline.py", "enabled": true}' ~/.gemini/antigravity-cli/settings.json > /tmp/agy_settings.json && mv /tmp/agy_settings.json ~/.gemini/antigravity-cli/settings.json

On Windows

Open your settings file located at C:\Users\YourUsername\.gemini\antigravity-cli\settings.json and add/update the statusLine block:

  "statusLine": {
    "type": "custom",
    "command": "python C:\\Users\\YourUsername\\.gemini\\statusline.py",
    "enabled": true
  }

That's it! Type /statusline in your Antigravity prompt (or simply submit a message) and enjoy your new beautifully detailed status line.

Warning

Do not use an infinite while true loop here to auto-refresh the status line!
Unlike Claude Code (which supports long-running streaming status scripts), the Antigravity CLI enforces a strict process timeout for the custom status line hook. It expects the script to print and exit immediately. If you wrap this in an infinite bash loop, Antigravity will terminate the background process with a SIGKILL after a few seconds, resulting in a persistent Statusline error: command failed: signal: killed polluting your prompt. Therefore, the status line will only refresh when you actively interact with the CLI prompt.


Migration note: Do not manually copy the script to a new machine or OS login. APIs and formats change rapidly. Instead, always ask the agent to install or update the status line directly from this gist. The agent will pull the latest version and configure it for your current environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment