From 0dec8c116aff3f95f3885f0defd195ec57cbda87 Mon Sep 17 00:00:00 2001 From: Ray Bell Date: Sat, 6 Jun 2026 23:17:55 -0400 Subject: [PATCH 1/3] add powershell statusline --- examples/statusline/statusline.ps1 | 186 +++++++++++++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 examples/statusline/statusline.ps1 diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1 new file mode 100644 index 000000000..dd820b365 --- /dev/null +++ b/examples/statusline/statusline.ps1 @@ -0,0 +1,186 @@ +# PowerShell status line script for Antigravity CLI +# Replicates the official statusline.sh example in PowerShell + +# Ensure standard output encoding is UTF-8 so Unicode characters display correctly +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- ANSI Helpers (Standard 16-color palette only) --- +$esc = [char]27 +$R = "$esc[0m" # Reset +$B = "$esc[1m" # Bold +$D = "$esc[2m" # Dim +$I = "$esc[3m" # Italic + +# Foreground accents +$FG_BLACK = "$esc[30m" +$FG_RED = "$esc[31m" +$FG_GREEN = "$esc[32m" +$FG_YELLOW = "$esc[33m" +$FG_BLUE = "$esc[34m" +$FG_MAGENTA = "$esc[35m" +$FG_CYAN = "$esc[36m" +$FG_WHITE = "$esc[37m" + +$FG_GRAY = "$esc[90m" +$FG_BRIGHT_RED = "$esc[91m" +$FG_BRIGHT_GREEN = "$esc[92m" +$FG_BRIGHT_YELLOW = "$esc[93m" +$FG_BRIGHT_BLUE = "$esc[94m" +$FG_BRIGHT_MAGENTA = "$esc[95m" +$FG_BRIGHT_CYAN = "$esc[96m" +$FG_BRIGHT_WHITE = "$esc[97m" + +$NUM_COLOR = "${FG_BRIGHT_WHITE}${B}" + +# --- Unicode Icons and Symbols --- +$readyIcon = [char]::ConvertFromUtf32(0x25cf) # โ— +$thinkingIcon = [char]::ConvertFromUtf32(0x23f3) # โณ +$workingIcon = [char]::ConvertFromUtf32(0x2699) # โš™ +$toolIcon = [char]::ConvertFromUtf32(0x1f527) # ๐Ÿ”ง +$diamondIcon = [char]::ConvertFromUtf32(0x25c6) # โ—† +$fullBlock = [char]::ConvertFromUtf32(0x2588) # โ–ˆ +$darkShade = [char]::ConvertFromUtf32(0x2593) # โ–“ +$medShade = [char]::ConvertFromUtf32(0x2592) # โ–’ +$lightShade = [char]::ConvertFromUtf32(0x2591) # โ–‘ +$middleDot = [char]::ConvertFromUtf32(0xb7) # ยท +$boxH = [char]::ConvertFromUtf32(0x2500) # โ”€ +$boxV = [char]::ConvertFromUtf32(0x2502) # โ”‚ +$boxTR = [char]::ConvertFromUtf32(0x256d) # โ•ญ +$boxBR = [char]::ConvertFromUtf32(0x2570) # โ•ฐ + +# Default values +$state = "idle" +$usedPct = 0.0 +$vcsBranch = "" +$vcsDirty = $false +$sandbox = $false +$artifacts = 0 +$subagentsCount = 0 +$bgTasks = 0 +$model = "" +$cols = 80 + +# --- Parse JSON from stdin --- +try { + $jsonString = [System.Console]::In.ReadToEnd() + if ($jsonString) { + $data = ConvertFrom-Json $jsonString + if ($data) { + if ($data.agent_state) { $state = $data.agent_state } + if ($data.context_window -and $data.context_window.used_percentage -ne $null) { + $usedPct = [double]$data.context_window.used_percentage + } + if ($data.vcs) { + if ($data.vcs.branch) { $vcsBranch = $data.vcs.branch } + if ($data.vcs.dirty -ne $null) { $vcsDirty = [bool]$data.vcs.dirty } + } + if ($data.sandbox -and $data.sandbox.enabled -ne $null) { $sandbox = [bool]$data.sandbox.enabled } + if ($data.artifact_count -ne $null) { $artifacts = [int]$data.artifact_count } + if ($data.subagents) { + if ($data.subagents -is [array]) { + $subagentsCount = $data.subagents.Length + } else { + $subagentsCount = 1 + } + } + if ($data.task_count -ne $null) { $bgTasks = [int]$data.task_count } + if ($data.model -and $data.model.display_name) { $model = $data.model.display_name } + if ($data.terminal_width -ne $null) { $cols = [int]$data.terminal_width } + } + } +} catch { + # Fallback to defaults if parsing fails +} + +# --- State Indicator --- +switch ($state) { + "idle" { $S = "${FG_BRIGHT_GREEN}${B}${readyIcon} READY${R}" } + "thinking" { $S = "${FG_BRIGHT_YELLOW}${B}${thinkingIcon} THINKING${R}" } + "working" { $S = "${FG_BRIGHT_CYAN}${B}${workingIcon} WORKING${R}" } + "tool_use" { $S = "${FG_BRIGHT_MAGENTA}${B}${toolIcon} TOOL${R}" } + default { $S = "${FG_WHITE}${B}${readyIcon} $($state.ToUpper())${R}" } +} + +# --- VCS Branch --- +$V = "" +if ($vcsBranch) { + if ($vcsDirty) { + $V = "${FG_GRAY} ${diamondIcon} ${FG_BRIGHT_RED}${vcsBranch}${FG_BRIGHT_YELLOW}*${R}" + } else { + $V = "${FG_GRAY} ${diamondIcon} ${FG_BRIGHT_BLUE}${vcsBranch}${R}" + } +} + +# --- Model --- +$M = "" +if ($model) { + $M = "${FG_GRAY} ${diamondIcon} ${FG_BRIGHT_MAGENTA}${I}${model}${R}" +} + +# --- Sandbox Badge --- +if ($sandbox) { + $SB = "${FG_GRAY}sandbox ${FG_BRIGHT_GREEN}${B}ON${R}" +} else { + $SB = "${FG_GRAY}sandbox off${R}" +} + +# --- Context Bar --- +$pctFmt = $usedPct.ToString("F1", [System.Globalization.CultureInfo]::InvariantCulture) +$pctInt = [int][Math]::Floor($usedPct) + +$barLen = 15 +$filled = [int][Math]::Floor($pctInt * $barLen / 100) +$remainder = ($pctInt * $barLen) % 100 + +if ($pctInt -ge 90) { + $barColor = $FG_BRIGHT_RED +} elseif ($pctInt -ge 60) { + $barColor = $FG_BRIGHT_YELLOW +} else { + $barColor = $FG_BRIGHT_WHITE +} + +$BAR = "" +for ($i = 0; $i -lt $barLen; $i++) { + if ($i -lt $filled) { + $BAR += $fullBlock + } elseif ($i -eq $filled) { + if ($remainder -ge 75) { + $BAR += $darkShade + } elseif ($remainder -ge 50) { + $BAR += $medShade + } elseif ($remainder -ge 25) { + $BAR += $lightShade + } else { + $BAR += $middleDot + } + } else { + $BAR += $middleDot + } +} + +# --- Stats --- +$CTX = "${FG_GRAY}ctx ${barColor}${BAR} ${NUM_COLOR}${pctFmt}%${R}" +$ART_FMT = "${FG_GRAY}artifacts ${NUM_COLOR}${artifacts}${R}" +$SUB_FMT = "${FG_GRAY}subagents ${NUM_COLOR}${subagentsCount}${R}" +$BG_FMT = "${FG_GRAY}tasks ${NUM_COLOR}${bgTasks}${R}" + +# Separator +$DOT = "${FG_GRAY} ${middleDot} ${R}" + +# --- Output --- +$LINE1 = "${S}${M}${V}" +$LINE2 = " ${CTX}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}" + +if ($cols -ge 120) { + # Wide: single line + Write-Output "${LINE1}${FG_GRAY} ${boxV} ${R}${LINE2}" +} elseif ($cols -ge 80) { + # Medium: two-line layout with border + Write-Output "${FG_GRAY}${boxTR}${boxH}${R} ${LINE1}" + Write-Output "${FG_GRAY}${boxBR}${boxH}${R}${LINE2}" +} else { + # Narrow: compact two-line, minimal chrome + Write-Output "${S}${M}" + Write-Output "${CTX}${DOT}${BG_FMT}" +} From 43c7378911d327c1c6d5c0c2ccc5a928860849a3 Mon Sep 17 00:00:00 2001 From: raybell-md Date: Sun, 7 Jun 2026 00:18:15 -0400 Subject: [PATCH 2/3] Detect sandbox mode via session log in statusline example `agy --sandbox` enables the terminal sandbox but does not populate .sandbox.enabled in the statusLine payload (it stays false as of 1.0.6), so the example's sandbox badge always reads "off" under --sandbox. The flag is not exported to the statusLine process's environment, and the process is reparented to init, so neither env nor the parent command line can be inspected. The only reliable signal is the session log line "--sandbox: enabling terminal sandbox for this session", which cli.log symlinks to. Keep .sandbox.enabled as the primary source and fall back to grepping the session log, so the badge self-corrects once the payload field is fixed upstream (see #321). --- examples/statusline/statusline.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh index 390fea1ee..4df35089a 100755 --- a/examples/statusline/statusline.sh +++ b/examples/statusline/statusline.sh @@ -87,6 +87,22 @@ if [ -n "$MODEL" ]; then M="${FG_GRAY} โ•ฑ ${FG_BRIGHT_MAGENTA}${I}${MODEL}${R}" fi +# โ”€โ”€โ”€ Sandbox State (workaround for unpopulated payload field) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# As of agy 1.0.6, `agy --sandbox` enables the terminal sandbox but does NOT set +# .sandbox.enabled in the statusLine payload (it stays false), so the badge below +# would always read "off". The flag is also not exported to this process's env, +# and the process is reparented to init, so neither env nor the parent command +# line can be inspected. The only reliable signal is the session log, which +# cli.log symlinks to ("--sandbox: enabling terminal sandbox for this session"). +# .sandbox.enabled remains the primary source, so this self-corrects once the +# payload field is populated upstream. See issue #321. +if [ "$SANDBOX" != "true" ]; then + SANDBOX_LOG="$HOME/.gemini/antigravity-cli/cli.log" + if [ -r "$SANDBOX_LOG" ] && grep -q 'enabling terminal sandbox' "$SANDBOX_LOG" 2>/dev/null; then + SANDBOX="true" + fi +fi + # โ”€โ”€โ”€ Sandbox Badge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if [ "$SANDBOX" = "true" ]; then SB="${FG_GRAY}sandbox ${FG_BRIGHT_GREEN}${B}ON${R}" From 01742196b70a15bbd5bc12fa149154d91ccb46f7 Mon Sep 17 00:00:00 2001 From: raybell-md Date: Mon, 15 Jun 2026 15:49:54 -0400 Subject: [PATCH 3/3] Show sandbox network mode in statusline badge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sandbox badge only showed ON/off, giving no indication of whether sandboxAllowNetwork was in effect โ€” a frequent source of confusion (#399). The payload's sandbox object only carries .enabled; there is no .sandbox.allow_network field, so it can't drive the badge on its own. The session log has no network-specific line either. Mirror the existing .enabled workaround: read sandboxAllowNetwork from settings.json as the authoritative source, while still preferring .sandbox.allow_network from the payload when present, so it self-corrects once that field is populated upstream (#321). The badge now reads "sandbox ON (net)" or "sandbox ON (no-net)". Co-Authored-By: Claude Opus 4.8 --- examples/statusline/statusline.sh | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh index 4df35089a..cebecfb3b 100755 --- a/examples/statusline/statusline.sh +++ b/examples/statusline/statusline.sh @@ -37,6 +37,7 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}" read -r VCS_BRANCH read -r VCS_DIRTY read -r SANDBOX + read -r SANDBOX_NET read -r ARTIFACTS read -r SUBAGENTS read -r BG_TASKS @@ -49,12 +50,13 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}" (.vcs.branch // ""), (.vcs.dirty // false), (.sandbox.enabled // false), + (.sandbox.allow_network // false), (.artifact_count // 0), (if .subagents | type == "array" then (.subagents | length) else 0 end), (.task_count // 0), (.model.display_name // ""), (.terminal_width // 80) - ' 2>/dev/null || printf "idle\n0\n\nfalse\nfalse\n0\n0\n0\n\n80\n" + ' 2>/dev/null || printf "idle\n0\n\nfalse\nfalse\nfalse\n0\n0\n0\n\n80\n" )" # โ”€โ”€โ”€ Computed Values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ @@ -103,9 +105,28 @@ if [ "$SANDBOX" != "true" ]; then fi fi +# โ”€โ”€โ”€ Sandbox Network (same unpopulated-payload problem as .enabled) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +# The payload's sandbox object only carries .enabled; there is no +# .sandbox.allow_network field, so SANDBOX_NET always falls back to "false" and +# the badge can't tell net from no-net. The session log has no network-specific +# line either. The authoritative source is the user's sandboxAllowNetwork +# setting, so read it from settings.json. Like the .enabled workaround above, +# this self-corrects once the payload field is populated upstream. See #321. +if [ "$SANDBOX_NET" != "true" ]; then + SETTINGS_FILE="$HOME/.gemini/antigravity-cli/settings.json" + if [ -r "$SETTINGS_FILE" ] \ + && jq -e '.sandboxAllowNetwork == true' "$SETTINGS_FILE" >/dev/null 2>&1; then + SANDBOX_NET="true" + fi +fi + # โ”€โ”€โ”€ Sandbox Badge โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ if [ "$SANDBOX" = "true" ]; then - SB="${FG_GRAY}sandbox ${FG_BRIGHT_GREEN}${B}ON${R}" + if [ "$SANDBOX_NET" = "true" ]; then + SB="${FG_GRAY}sandbox ${FG_BRIGHT_GREEN}${B}ON${R} ${FG_GRAY}(net)${R}" + else + SB="${FG_GRAY}sandbox ${FG_BRIGHT_GREEN}${B}ON${R} ${FG_GRAY}(no-net)${R}" + fi else SB="${FG_GRAY}sandbox off${R}" fi