diff --git a/README-ZH.md b/README-ZH.md
index ebc773174..b02b1ffe5 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -2,80 +2,235 @@
English | 中文 | Español | Français | Português | Українська | Deutsch
-# Antigravity CLI
+ # 通过特定 ID 加载会话
+agy --sandbox # 在隔离的 Docker 容器中运行
+agy update # 更新二进制文件到最新版本
+agy plugin list # 列出已安装的插件
+```
+
+### 聊天界面中的斜杠命令 (Slash Commands)
+* `/help` — 获取可用工具的帮助信息。
+* `/settings` — 交互式配置设置项。
+* `/usage` — 消耗的令牌(Token)数量和配额统计。
+* `/diff` — 查看项目中当前未保存的更改。
+* `/statusline` — 配置终端状态栏的显示。
---
-## 服务条款与数据使用
+## 🔄 从 Gemini CLI 迁移
> [!WARNING]
-> 众所周知,AI 编码智能体存在一定的安全风险,包括自主代码执行、数据外泄、提示词注入以及供应链风险。请务必监控并验证智能体执行的所有操作。
+> 原始的 Gemini CLI (`gemini`) 将于 **2026 年 6 月 18 日**起停止对非企业账户的支持。请及时迁移到 Antigravity CLI。
+
+### 快速迁移步骤
+1. 安装新客户端:`curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
+2. 重命名本地配置文件:
+ ```bash
+ mv GEMINI.md .antigravity.md
+ mv ~/.gemini/agents/ ~/.antigravity/agents/
+ ```
+3. 更新 GitHub Actions 中的 CI/CD 配置,将所有的 `gemini` 调用替换为 `agy`。
+4. 卸载旧的依赖库:`npm uninstall -g @google/gemini-cli`
+
+### 对比表
+
+| 功能 / 属性 | Gemini CLI (旧版) | Antigravity CLI (新版) |
+| :--- | :--- | :--- |
+| **开发语言/平台** | Node.js / TypeScript | Go (原生编译的二进制文件) |
+| **执行命令** | `gemini` | `agy` |
+| **启动速度** | ~1.2秒 (Node.js 启动延迟) | **~0.05秒 (原生瞬间启动)** |
+| **项目配置文件** | `GEMINI.md` | `.antigravity.md` |
+| **自动更新** | 通过 `npm update` | 内置的自我更新机制 |
+| **维护状态** | ⛔ 已停止支持 (2026年6月18日) | ✅ 持续活跃开发 (上游支持) |
+
+---
+
+## 📁 存储库结构
+
+```
+antigravity-cli/
+├── install.sh # Linux/macOS 安装程序 (离线/在线)
+├── install.ps1 # Windows PowerShell 安装程序 (离线/在线)
+├── install.cmd # Windows CMD 安装程序
+├── Makefile # 自动化目标 (make install/reinstall/uninstall)
+├── .antigravity.md # 项目上下文模板文件
+├── packages/ # 本地离线分发包
+│ ├── manifests/ # 适用于所有平台的版本清单
+│ └── binaries/ # (离线模式下需手动创建)
+└── CHANGELOG.md # 变更日志和发布记录
+```
+
+---
-通过使用 Antigravity CLI,您同意允许 Google 收集并使用您的交互数据以帮助改善产品,这受 Google 服务条款和 Google 隐私权政策的约束。您可以随时通过设置选择退出。
+## 🤝 参与贡献与社区
-### 法律与隐私链接
+本仓库是原始上游项目 [google-antigravity/antigravity-cli](https://github.com/google-antigravity/antigravity-cli) 的独立社区分叉版本。
+
+**我们的改进:**
+* 🌍 支持文档和指南的多语言本地化。
+* 📦 自主性:具备完全离线安装能力,无需从 Google API 服务器下载。
+* 🛠️ 便利性:添加了 `Makefile`,简化工具生命周期管理。
+* 🛡️ 安全性:针对沙箱环境进行持续的安全改进和漏洞修复。
+
+---
-- **服务条款**: [antigravity.google/terms](https://antigravity.google/terms)
-- **隐私权政策**: [policies.google.com/privacy](https://policies.google.com/privacy)
+## 📜 法律声明与商标说明
+
+* **官方链接**: [官方文档仓库](https://github.com/google-antigravity/antigravity-cli) · [官方 CLI 代码库](https://github.com/google-gemini/gemini-cli) · [官方网站](https://antigravity.google)
+* **使用条款**: [antigravity.google/terms](https://antigravity.google/terms) · [policies.google.com/privacy](https://policies.google.com/privacy)
+
+> [!IMPORTANT]
+> **分叉版法律地位:**
+> 本存储库是原始客户端的独立非商业副本(社区分叉版本)。它**不是** Google LLC 的官方产品。Google LLC 对此分叉版本的性能、修改或安全性不承担任何责任。
+>
+> **许可与版权信息:**
+> 原始软件基于 [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) 协议分发。所有原始代码的知识产权均归 **Copyright © 2025 Google LLC** 所有。
+>
+> **商标使用说明:**
+> "Antigravity CLI" 名称及相关徽标仅在合理使用(Customary Use)限制范围内使用,以描述软件的来源、兼容性和功能用途。本分叉项目不声称拥有 Google LLC 任何商标的所有权。
+>
+> **免责声明:**
+> 本软件按“原样”提供,不提供任何明示或暗示的保证。您需自行承担使用该软件的所有责任和风险。
+
+> [!CAUTION]
+> AI 编码智能体运行具有自主性。在确认执行前,请务必仔细检查其建议的 diff 代码块和执行命令,尤其是在处理系统文件或防火墙配置时。
diff --git a/README.md b/README.md
index fc239558f..3df5438df 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
🚀 Antigravity CLI
- Community-Fork & Hardened Offline Version of google-antigravity/antigravity-cli
+ Community-Fork & Hardened Offline Version of google-antigravity/antigravity-cli with automatic statusline & window title setup
From 5d4fc5434e6aa97ffa68938dce32250fdbe5e510 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 28 May 2026 11:08:05 +0300
Subject: [PATCH 16/28] docs: correct settings.json file path and config schema
in all READMEs
---
README-DE.md | 21 ++++++++++++++-------
README-ES.md | 21 ++++++++++++++-------
README-FR.md | 21 ++++++++++++++-------
README-PT.md | 21 ++++++++++++++-------
README-UA.md | 21 ++++++++++++++-------
README-ZH.md | 21 ++++++++++++++-------
README.md | 21 ++++++++++++++-------
7 files changed, 98 insertions(+), 49 deletions(-)
diff --git a/README-DE.md b/README-DE.md
index ee26f3456..83be42447 100644
--- a/README-DE.md
+++ b/README-DE.md
@@ -88,16 +88,23 @@ Erstellen Sie eine `.antigravity.md`-Datei im Stammverzeichnis Ihres Projekts, u
- STRIKTE REGEL: Keine fest codierten Passwörter im Code. Importieren Sie alle Geheimnisse aus `.env`.
```
-### 2. Globale Einstellungen (`~/.gemini/settings.json`)
-Die Konfigurationsdatei steuert das UI-Verhalten und die MCP-Server:
+### 2. Globale Einstellungen (`~/.gemini/antigravity-cli/settings.json`)
+Die globale Einstellungsdatei steuert Berechtigungen zur Tool-Ausführung, Statuszeilen-/Titel-Skripte und MCP-Server:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README-ES.md b/README-ES.md
index 3995803ad..fec44358f 100644
--- a/README-ES.md
+++ b/README-ES.md
@@ -88,16 +88,23 @@ Cree un archivo `.antigravity.md` en la raíz de su proyecto para proporcionar u
- REGLA ESTRICTA: No use contraseñas hardcoded en el código. Importe todos los secretos desde `.env`.
```
-### 2. Configuración global (`~/.gemini/settings.json`)
-El archivo de configuración controla el comportamiento de la interfaz de usuario y los servidores MCP:
+### 2. Configuración global (`~/.gemini/antigravity-cli/settings.json`)
+El archivo de configuración global controla los permisos de ejecución de herramientas, los scripts de statusline/título y los servidores MCP:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README-FR.md b/README-FR.md
index 174bee0de..05c92611f 100644
--- a/README-FR.md
+++ b/README-FR.md
@@ -88,16 +88,23 @@ Créez un fichier `.antigravity.md` à la racine de votre projet pour fournir un
- RÈGLE STRICTE : Pas de mots de passe codés en dur dans le code. Importez tous les secrets depuis le fichier `.env`.
```
-### 2. Paramètres globaux (`~/.gemini/settings.json`)
-Le fichier de configuration contrôle le comportement de l'interface utilisateur et les serveurs MCP :
+### 2. Paramètres globaux (`~/.gemini/antigravity-cli/settings.json`)
+Le fichier de configuration globale contrôle les autorisations d'exécution des outils, les scripts de statusline/titre et les serveurs MCP :
+
+- **Linux/Unix** : `~/.gemini/antigravity-cli/settings.json`
+- **macOS** : `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows** : `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README-PT.md b/README-PT.md
index 8bf3b9197..19fba2504 100644
--- a/README-PT.md
+++ b/README-PT.md
@@ -88,16 +88,23 @@ Crie um arquivo `.antigravity.md` na raiz do seu projeto para fornecer contexto
- REGRA ESTRITA: Sem senhas expostas no código (hardcoded). Importe todos os segredos do `.env`.
```
-### 2. Configurações Globais (`~/.gemini/settings.json`)
-O arquivo de configuração controla o comportamento da interface do usuário e os servidores MCP:
+### 2. Configurações Globais (`~/.gemini/antigravity-cli/settings.json`)
+O arquivo de configuração global controla as permissões de execução de ferramentas, scripts de statusline/título e servidores MCP:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README-UA.md b/README-UA.md
index 353313c64..6285c9292 100644
--- a/README-UA.md
+++ b/README-UA.md
@@ -88,16 +88,23 @@ install.cmd
- СУВОРЕ ПРАВИЛО: Жодних хардкод-паролів у коді. Всі секрети імпортувати з `.env`.
```
-### 2. Глобальні налаштування (`~/.gemini/settings.json`)
-Конфігураційний файл керує поведінкою інтерфейсу та MCP-серверами:
+### 2. Глобальні налаштування (`~/.gemini/antigravity-cli/settings.json`)
+Глобальний файл налаштувань керує дозволами на виконання інструментів, скриптами статуслайну/заголовка та MCP-серверами:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README-ZH.md b/README-ZH.md
index b02b1ffe5..fe697d022 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -88,16 +88,23 @@ install.cmd
- 严格规则:严禁在代码中硬编码任何密码。所有机密信息必须从 `.env` 文件导入。
```
-### 2. 全局设置 (`~/.gemini/settings.json`)
-此配置文件控制 UI 行为和 MCP 服务器:
+### 2. 全局设置 (`~/.gemini/antigravity-cli/settings.json`)
+全局配置文件控制工具执行权限、状态栏/窗口标题脚本以及 MCP 服务器:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
diff --git a/README.md b/README.md
index 3df5438df..520de81ee 100644
--- a/README.md
+++ b/README.md
@@ -88,16 +88,23 @@ Create an `.antigravity.md` file in the root of your project to provide specific
- STRICT RULE: No hardcoded passwords in code. Import all secrets from `.env`.
```
-### 2. Global Settings (`~/.gemini/settings.json`)
-The configuration file controls the UI behavior and MCP servers:
+### 2. Global Settings (`~/.gemini/antigravity-cli/settings.json`)
+The global settings file controls tool execution permissions, statusline/title scripts, and MCP servers:
+
+- **Linux/Unix**: `~/.gemini/antigravity-cli/settings.json`
+- **macOS**: `~/Library/Application Support/antigravity-cli/settings.json`
+- **Windows**: `%APPDATA%\antigravity-cli\settings.json`
```json
{
- "theme": "terminal",
- "sandbox": false,
- "defaultApprovalMode": "auto_edit",
- "ui": {
- "showFooter": true
+ "toolPermission": "always-proceed",
+ "statusLine": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ },
+ "title": {
+ "enabled": true,
+ "command": "/root/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
From e27d0c95291cc700612b6b3cae5d8fc66973fa55 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 28 May 2026 14:23:52 +0300
Subject: [PATCH 17/28] fix: correct project context filename to GEMINI.md and
specialized agents schema in all READMEs
---
.antigravity.md => GEMINI.md | 0
README-DE.md | 44 ++++++++++++++++------------------
README-ES.md | 44 ++++++++++++++++------------------
README-FR.md | 44 ++++++++++++++++------------------
README-PT.md | 46 +++++++++++++++++-------------------
README-UA.md | 44 ++++++++++++++++------------------
README-ZH.md | 44 ++++++++++++++++------------------
README.md | 44 ++++++++++++++++------------------
8 files changed, 148 insertions(+), 162 deletions(-)
rename .antigravity.md => GEMINI.md (100%)
diff --git a/.antigravity.md b/GEMINI.md
similarity index 100%
rename from .antigravity.md
rename to GEMINI.md
diff --git a/README-DE.md b/README-DE.md
index 83be42447..be6164056 100644
--- a/README-DE.md
+++ b/README-DE.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Konfiguration
-### 1. Projektkonfiguration (`.antigravity.md`)
-Erstellen Sie eine `.antigravity.md`-Datei im Stammverzeichnis Ihres Projekts, um dem KI-Agenten spezifischen Kontext und Entwicklungsregeln bereitzustellen:
+### 1. Projektkonfiguration (`GEMINI.md`)
+Erstellen Sie eine `GEMINI.md`-Datei im Stammverzeichnis Ihres Projekts, um dem KI-Agenten spezifischen Kontext und Entwicklungsregeln bereitzustellen:
```markdown
# Projektkontext
@@ -100,11 +100,11 @@ Die globale Einstellungsdatei steuert Berechtigungen zur Tool-Ausführung, Statu
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ Die globale Einstellungsdatei steuert Berechtigungen zur Tool-Ausführung, Statu
}
```
-### 3. Spezialisierte Agenten (`~/.antigravity/agents/`)
-Sie können benutzerdefinierte Rollen und Anweisungen für KI-Agenten im YAML-Format beschreiben:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Analysiert Code vor dem Commit auf Schwachstellen"
-instructions: |
- Änderungen überprüfen auf:
- - OWASP Top 10 Schwachstellen
- - Durchsickern von API-Schlüsseln oder Geheimnissen
- - Richtigkeit der Firewall-/nftables-Konfiguration
+### 3. Spezialisierte Agenten
+Sie können benutzerdefinierte Rollen und Anweisungen für KI-Agenten im JSON-Format beschreiben. Jeder Agent muss sein eigenes Verzeichnis haben, das eine `agent.json`-Datei enthält:
+
+- **Globale Agenten (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Globale Agenten (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Globale Agenten (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Lokale Agenten (Projektarbeitsbereich)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Analysiert Code vor dem Commit auf Schwachstellen",
+ "instructions": "Änderungen überprüfen auf:\n- OWASP Top 10 Schwachstellen\n- Durchsickern von API-Schlüsseln oder Geheimnissen\n- Richtigkeit der Firewall-/nftables-Konfiguration"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # Installierte Plugins auflisten
### Schritte für einen schnellen Übergang
1. Installieren Sie den neuen Client: `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Benennen Sie lokale Konfigurationsdateien um:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Migrieren Sie lokale benutzerdefinierte Agentenkonfigurationsdateien (konvertieren Sie z. B. benutzerdefinierte Agenten von YAML in `~/.gemini/agents/` in JSON-Dateien unter `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Aktualisieren Sie CI/CD-Konfigurationen in GitHub Actions und ersetzen Sie `gemini`-Aufrufe durch `agy`.
4. Deinstallieren Sie die alte Bibliothek: `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # Installierte Plugins auflisten
| **Entwicklungsplattform** | Node.js / TypeScript | Go (Nativ kompilierte Binärdatei) |
| **Befehlsname** | `gemini` | `agy` |
| **Startgeschwindigkeit** | ~1.2s (Node.js-Start) | **~0.05s (sofortiger nativer Start)** |
-| **Konfigurationsdatei** | `GEMINI.md` | `.antigravity.md` |
+| **Konfigurationsdatei** | `GEMINI.md` | `GEMINI.md` |
| **Automatische Aktualisierung** | Über `npm update` | Integrierter Selbstaktualisierungsmechanismus |
| **Support-Status** | ⛔ EOL (18. Juni 2026) | ✅ Aktive Entwicklung (Upstream) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Installer für Windows PowerShell (offline/online)
├── install.cmd # Installer für Windows CMD
├── Makefile # Automatisierungsziele (make install/reinstall/uninstall)
-├── .antigravity.md # Projektkontext-Dateivorlage
+├── GEMINI.md # Projektkontext-Dateivorlage
├── packages/ # Lokaler Offline-Vertrieb
│ ├── manifests/ # Versionsmanifeste für alle Plattformen
│ └── binaries/ # (Manuell erstellt für den Offline-Modus)
diff --git a/README-ES.md b/README-ES.md
index fec44358f..b64f212d1 100644
--- a/README-ES.md
+++ b/README-ES.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Configuración
-### 1. Configuración del proyecto (`.antigravity.md`)
-Cree un archivo `.antigravity.md` en la raíz de su proyecto para proporcionar un contexto específico y reglas de desarrollo al agente de IA:
+### 1. Configuración del proyecto (`GEMINI.md`)
+Cree un archivo `GEMINI.md` en la raíz de su proyecto para proporcionar un contexto específico y reglas de desarrollo al agente de IA:
```markdown
# Contexto del proyecto
@@ -100,11 +100,11 @@ El archivo de configuración global controla los permisos de ejecución de herra
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ El archivo de configuración global controla los permisos de ejecución de herra
}
```
-### 3. Agentes especializados (`~/.antigravity/agents/`)
-Puede describir roles e instrucciones personalizados para agentes de IA en formato YAML:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Analiza el código en busca de vulnerabilidades antes de confirmar"
-instructions: |
- Buscar en los cambios:
- - Vulnerabilidades OWASP Top 10
- - Filtración de claves de API o secretos
- - Corrección de la configuración del firewall/nftables
+### 3. Agentes especializados
+Puede describir roles e instrucciones personalizados para agentes de IA en formato JSON. Cada agente debe tener su propio directorio que contenga un archivo `agent.json`:
+
+- **Agentes Globales (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agentes Globales (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agentes Globales (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Agentes Locales (Espacio de Trabajo)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Analiza el código en busca de vulnerabilidades antes de confirmar",
+ "instructions": "Buscar en los cambios:\n- Vulnerabilidades OWASP Top 10\n- Filtración de claves de API o secretos\n- Corrección de la configuración del firewall/nftables"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # Listar los complementos instalados
### Pasos para una transición rápida
1. Instale el nuevo cliente: `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Cambie el nombre de los archivos de configuración locales:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Migre los archivos de configuración de agentes personalizados locales (por ejemplo, convierta agentes personalizados de YAML dentro de `~/.gemini/agents/` a archivos JSON en `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Actualice las configuraciones de CI/CD en GitHub Actions, reemplazando las llamadas a `gemini` por `agy`.
4. Desinstale la biblioteca antigua: `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # Listar los complementos instalados
| **Plataforma de desarrollo** | Node.js / TypeScript | Go (Binario compilado nativo) |
| **Nombre del comando** | `gemini` | `agy` |
| **Velocidad de inicio** | ~1.2s (inicio de Node.js) | **~0.05s (inicio nativo instantáneo)** |
-| **Archivo de configuración** | `GEMINI.md` | `.antigravity.md` |
+| **Archivo de configuración** | `GEMINI.md` | `GEMINI.md` |
| **Actualización automática** | A través de `npm update` | Mecanismo de auto-actualización incorporado |
| **Estado del soporte** | ⛔ Fin de la vida útil (18 de junio de 2026) | ✅ Desarrollo activo (Upstream) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Instalador para Windows PowerShell (fuera de línea/en línea)
├── install.cmd # Instalador para Windows CMD
├── Makefile # Destinos de automatización (make install/reinstall/uninstall)
-├── .antigravity.md # Plantilla de archivo de contexto del proyecto
+├── GEMINI.md # Plantilla de archivo de contexto del proyecto
├── packages/ # Distribución local fuera de línea
│ ├── manifests/ # Manifiestos de versión para todas las plataformas
│ └── binaries/ # (Creado manualmente para el modo fuera de línea)
diff --git a/README-FR.md b/README-FR.md
index 05c92611f..3c7548b5b 100644
--- a/README-FR.md
+++ b/README-FR.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Configuration
-### 1. Configuration du projet (`.antigravity.md`)
-Créez un fichier `.antigravity.md` à la racine de votre projet pour fournir un contexte spécifique et des règles de développement à l'agent IA :
+### 1. Configuration du projet (`GEMINI.md`)
+Créez un fichier `GEMINI.md` à la racine de votre projet pour fournir un contexte spécifique et des règles de développement à l'agent IA :
```markdown
# Contexte du projet
@@ -100,11 +100,11 @@ Le fichier de configuration globale contrôle les autorisations d'exécution des
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ Le fichier de configuration globale contrôle les autorisations d'exécution des
}
```
-### 3. Agents spécialisés (`~/.antigravity/agents/`)
-Vous pouvez décrire des rôles et des instructions personnalisés pour les agents IA au format YAML :
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Analyse le code à la recherche de vulnérabilités avant le commit"
-instructions: |
- Vérifier les modifications pour :
- - Les 10 vulnérabilités majeures de l'OWASP
- - Les fuites de clés API ou de secrets
- - L'exactitude de la configuration du pare-feu/nftables
+### 3. Agents spécialisés
+Vous pouvez décrire des rôles et des instructions personnalisés pour les agents IA au format JSON. Chaque agent doit avoir son propre répertoire contenant un fichier `agent.json` :
+
+- **Agents Globaux (Linux/Unix)** : `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agents Globaux (macOS)** : `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agents Globaux (Windows)** : `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Agents Locaux (Espace de Travail)** : `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Analyse le code à la recherche de vulnérabilités avant le commit",
+ "instructions": "Vérifier les modifications pour :\n- Les 10 vulnérabilités majeures de l'OWASP\n- Les fuites de clés API ou de secrets\n- L'exactitude de la configuration du pare-feu/nftables"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # Lister les plug-ins installés
### Étapes pour une transition rapide
1. Installez le nouveau client : `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Renommez les fichiers de configuration locaux :
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Migrez les fichiers de configuration des agents personnalisés locaux (par exemple, convertissez les agents personnalisés de YAML dans `~/.gemini/agents/` en fichiers JSON sous `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Mettez à jour les configurations CI/CD dans GitHub Actions, en remplaçant les appels `gemini` par `agy`.
4. Désinstallez l'ancienne bibliothèque : `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # Lister les plug-ins installés
| **Plateforme de développement** | Node.js / TypeScript | Go (Binaire natif compilé) |
| **Nom de la commande** | `gemini` | `agy` |
| **Vitesse de démarrage** | ~1.2s (démarrage Node.js) | **~0.05s (démarrage natif instantané)** |
-| **Fichier de configuration** | `GEMINI.md` | `.antigravity.md` |
+| **Fichier de configuration** | `GEMINI.md` | `GEMINI.md` |
| **Mise à jour automatique** | Via `npm update` | Mécanisme d'auto-mise à jour intégré |
| **Statut du support** | ⛔ EOL (18 juin 2026) | ✅ Développement actif (Upstream) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Programme d'installation pour Windows PowerShell (hors ligne/en ligne)
├── install.cmd # Programme d'installation pour Windows CMD
├── Makefile # Cibles d'automatisation (make install/reinstall/uninstall)
-├── .antigravity.md # Modèle de fichier de contexte de projet
+├── GEMINI.md # Modèle de fichier de contexte de projet
├── packages/ # Distribution hors ligne locale
│ ├── manifests/ # Manifestes de version pour toutes les plateformes
│ └── binaries/ # (Créé manuellement pour le mode hors ligne)
diff --git a/README-PT.md b/README-PT.md
index 19fba2504..12d7a231f 100644
--- a/README-PT.md
+++ b/README-PT.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Configuração
-### 1. Configuração do Projeto (`.antigravity.md`)
-Crie um arquivo `.antigravity.md` na raiz do seu projeto para fornecer contexto específico e regras de desenvolvimento ao agente de IA:
+### 1. Configuração do Projeto (`GEMINI.md`)
+Crie um arquivo `GEMINI.md` na raiz do seu projeto para fornecer contexto específico e regras de desenvolvimento ao agente de IA:
```markdown
# Contexto do Projeto
@@ -100,11 +100,11 @@ O arquivo de configuração global controla as permissões de execução de ferr
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ O arquivo de configuração global controla as permissões de execução de ferr
}
```
-### 3. Agentes Especializados (`~/.antigravity/agents/`)
-Você pode descrever funções e instruções personalizadas para agentes de IA no formato YAML:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Analisa o código em busca de vulnerabilidades antes do commit"
-instructions: |
- Verificar alterações para:
- - Vulnerabilidades OWASP Top 10
- - Vazamento de chaves de API ou segredos
- - Correção da configuração do firewall/nftables
+### 3. Agentes Especializados
+Você pode descrever funções e instruções personalizadas para agentes de IA no formato JSON. Cada agente deve ter seu próprio diretório contendo um arquivo `agent.json`:
+
+- **Agentes Globais (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agentes Globais (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Agentes Globais (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Agentes Locais (Espaço de Trabalho)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Analisa o código em busca de vulnerabilidades antes do commit",
+ "instructions": "Verificar alterações para:\n- Vulnerabilidades OWASP Top 10\n- Vazamento de chaves de API ou segredos\n- Correção da configuração do firewall/nftables"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # Listar os plugins instalados
### Passos para Transição Rápida
1. Instale o novo cliente: `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Renomeie os arquivos de configuração locais:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Migre os arquivos de configuração de agentes personalizados locais (por exemplo, converta agentes personalizados de YAML dentro de `~/.gemini/agents/` em arquivos JSON em `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Atualize as configurações de CI/CD no GitHub Actions, substituindo as chamadas de `gemini` por `agy`.
4. Desinstale a biblioteca antiga: `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # Listar os plugins instalados
| **Plataforma de Desenvolvimento** | Node.js / TypeScript | Go (Binário Nativo Compilado) |
| **Nome do Comando** | `gemini` | `agy` |
| **Velocidade de Inicialização** | ~1.2s (inicialização do Node.js) | **~0.05s (inicialização nativa instantânea)** |
-| **Arquivo de Configuração** | `GEMINI.md` | `.antigravity.md` |
+| **Arquivo de Configuração** | `GEMINI.md` | `GEMINI.md` |
| **Atualização Automática** | Via `npm update` | Mecanismo de auto-atualização integrado |
| **Status do Suporte** | ⛔ Fim da vida útil (18 de junho de 2026) | ✅ Desenvolvimento Ativo (Upstream) |
@@ -200,9 +198,9 @@ antigravity-cli/
├── install.ps1 # Instalador para Windows PowerShell (offline/online)
├── install.cmd # Instalador para Windows CMD
├── Makefile # Metas de automação (make install/reinstall/uninstall)
-├── .antigravity.md # Modelo de arquivo de contexto de projeto
+├── GEMINI.md # Modelo de arquivo de contexto de projeto
├── packages/ # Distribuição offline local
-│ ├── manifests/ # Manifestos de versão para todas as plataformas
+│ ├── manifests/ # Manifestes de versão para todas as plataformas
│ └── binaries/ # (Criado manualmente para o modo offline)
└── CHANGELOG.md # Registro de alterações e lançamentos
```
diff --git a/README-UA.md b/README-UA.md
index 6285c9292..0478cc9b1 100644
--- a/README-UA.md
+++ b/README-UA.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Конфігурація
-### 1. Конфігурація проєкту (`.antigravity.md`)
-Створіть файл `.antigravity.md` у корені вашого проєкту, щоб передати ШІ-агенту специфічний контекст та правила розробки:
+### 1. Конфігурація проєкту (`GEMINI.md`)
+Створіть файл `GEMINI.md` у корені вашого проєкту, щоб передати ШІ-агенту специфічний контекст та правила розробки:
```markdown
# Контекст проєкту
@@ -100,11 +100,11 @@ install.cmd
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ install.cmd
}
```
-### 3. Спеціалізовані агенти (`~/.antigravity/agents/`)
-Ви можете описувати кастомні ролі та інструкції для ШІ-агентів у форматі YAML:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Аналізує код на вразливості перед комітом"
-instructions: |
- Перевіряй зміни на:
- - OWASP Top 10 вразливості
- - Витік API ключів чи секретів
- - Правильність налаштування firewall/nftables
+### 3. Спеціалізовані агенти
+Ви можете описувати кастомні ролі та інструкції для ШІ-агентів у форматі JSON. Кожен агент повинен мати власну директорію, яка містить файл `agent.json`:
+
+- **Глобальні агенти (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Глобальні агенти (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Глобальні агенти (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Локальні агенти (Робочий простір)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Аналізує код на вразливості перед комітом",
+ "instructions": "Перевіряти зміни на:\n- OWASP Top 10 вразливості\n- Витік API ключів чи секретів\n- Правильність налаштування firewall/nftables"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # Список встановлених пла
### Кроки для швидкого переходу
1. Встановіть новий клієнт: `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Перейменуйте локальні файли конфігурації:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Мігруйте локальні файли конфігурації кастомних агентів (наприклад, конвертуйте конфігурації YAML у папці `~/.gemini/agents/` в JSON-файли за шляхом `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Оновіть CI/CD конфігурації в GitHub Actions, замінивши виклики `gemini` на `agy`.
4. Видаліть стару бібліотеку: `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # Список встановлених пла
| **Платформа розробки** | Node.js / TypeScript | Go (Native Compiled Binary) |
| **Назва команди** | `gemini` | `agy` |
| **Швидкість старту** | ~1.2 сек (запуск Node.js) | **~0.05 сек (миттєвий нативний старт)** |
-| **Файл конфігурації** | `GEMINI.md` | `.antigravity.md` |
+| **Файл конфігурації** | `GEMINI.md` | `GEMINI.md` |
| **Авто-оновлення** | Через `npm update` | Вбудований механізм self-update |
| **Статус підтримки** | ⛔ EOL (18.06.2026) | ✅ Активна розробка (Upstream) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Інсталятор для Windows PowerShell (офлайн/онлайн)
├── install.cmd # Інсталятор для Windows CMD
├── Makefile # Автоматизовані цілі (make install/reinstall/uninstall)
-├── .antigravity.md # Шаблон файлу контексту проєкту
+├── GEMINI.md # Шаблон файлу контексту проєкту
├── packages/ # Локальний офлайн-дистрибутив
│ ├── manifests/ # Маніфести версій для всіх платформ
│ └── binaries/ # (Створюється вручну для офлайн-режиму)
diff --git a/README-ZH.md b/README-ZH.md
index fe697d022..5a816eab0 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ 配置方式
-### 1. 项目配置 (`.antigravity.md`)
-在项目的根目录下创建一个 `.antigravity.md` 文件,为 AI 智能体提供特定的项目上下文和开发规则:
+### 1. 项目配置 (`GEMINI.md`)
+在项目的根目录下创建一个 `GEMINI.md` 文件,为 AI 智能体提供特定的项目上下文和开发规则:
```markdown
# 项目上下文
@@ -100,11 +100,11 @@ install.cmd
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ install.cmd
}
```
-### 3. 专业智能体 (`~/.antigravity/agents/`)
-您可以使用 YAML 格式定义 AI 智能体的自定义角色 and 说明:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "在提交前分析代码漏洞"
-instructions: |
- 检查更改内容:
- - OWASP Top 10 漏洞
- - API 密钥或凭证泄露
- - 防火墙/nftables 配置正确性
+### 3. 专业智能体
+您可以使用 JSON 格式定义 AI 智能体的自定义角色和说明。每个智能体必须有自己的目录,其中包含一个 `agent.json` 文件:
+
+- **全局智能体 (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **全局智能体 (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **全局智能体 (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **本地智能体 (项目工作区)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "在提交前分析代码漏洞",
+ "instructions": "检查更改内容:\n- OWASP Top 10 漏洞\n- API 密钥或凭证泄露\n- 防火墙/nftables 配置正确性"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # 列出已安装的插件
### 快速迁移步骤
1. 安装新客户端:`curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. 重命名本地配置文件:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. 迁移本地自定义智能体配置文件(例如,将 `~/.gemini/agents/` 下的 YAML 格式自定义智能体转换为 `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json` 下的 JSON 文件)。
3. 更新 GitHub Actions 中的 CI/CD 配置,将所有的 `gemini` 调用替换为 `agy`。
4. 卸载旧的依赖库:`npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # 列出已安装的插件
| **开发语言/平台** | Node.js / TypeScript | Go (原生编译的二进制文件) |
| **执行命令** | `gemini` | `agy` |
| **启动速度** | ~1.2秒 (Node.js 启动延迟) | **~0.05秒 (原生瞬间启动)** |
-| **项目配置文件** | `GEMINI.md` | `.antigravity.md` |
+| **项目配置文件** | `GEMINI.md` | `GEMINI.md` |
| **自动更新** | 通过 `npm update` | 内置的自我更新机制 |
| **维护状态** | ⛔ 已停止支持 (2026年6月18日) | ✅ 持续活跃开发 (上游支持) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Windows PowerShell 安装程序 (离线/在线)
├── install.cmd # Windows CMD 安装程序
├── Makefile # 自动化目标 (make install/reinstall/uninstall)
-├── .antigravity.md # 项目上下文模板文件
+├── GEMINI.md # 项目上下文模板文件
├── packages/ # 本地离线分发包
│ ├── manifests/ # 适用于所有平台的版本清单
│ └── binaries/ # (离线模式下需手动创建)
diff --git a/README.md b/README.md
index 520de81ee..06e5d46d6 100644
--- a/README.md
+++ b/README.md
@@ -77,8 +77,8 @@ install.cmd
## ⚙️ Configuration
-### 1. Project Configuration (`.antigravity.md`)
-Create an `.antigravity.md` file in the root of your project to provide specific context and development rules to the AI agent:
+### 1. Project Configuration (`GEMINI.md`)
+Create a `GEMINI.md` file in the root of your project to provide specific context and development rules to the AI agent:
```markdown
# Project Context
@@ -100,11 +100,11 @@ The global settings file controls tool execution permissions, statusline/title s
"toolPermission": "always-proceed",
"statusLine": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/statusline.sh"
+ "command": "/home/user/.gemini/antigravity-cli/statusline.sh"
},
"title": {
"enabled": true,
- "command": "/root/.gemini/antigravity-cli/title.sh"
+ "command": "/home/user/.gemini/antigravity-cli/title.sh"
},
"mcpServers": {
"github": {
@@ -115,18 +115,20 @@ The global settings file controls tool execution permissions, statusline/title s
}
```
-### 3. Specialized Agents (`~/.antigravity/agents/`)
-You can describe custom roles and instructions for AI agents in YAML format:
-
-```yaml
-# ~/.antigravity/agents/security-reviewer.yaml
-name: security-reviewer
-description: "Analyzes code for vulnerabilities before commit"
-instructions: |
- Check changes for:
- - OWASP Top 10 vulnerabilities
- - Leakage of API keys or secrets
- - Correctness of firewall/nftables configuration
+### 3. Specialized Agents
+You can describe custom roles and instructions for AI agents in JSON format. Each agent must have its own directory containing an `agent.json` file:
+
+- **Global Agents (Linux/Unix)**: `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`
+- **Global Agents (macOS)**: `~/Library/Application Support/antigravity-cli/agents/{agent_name}/agent.json`
+- **Global Agents (Windows)**: `%APPDATA%\antigravity-cli\agents\{agent_name}\agent.json`
+- **Local Agents (Project Workspace)**: `{workspace_root}/.agents/agents/{agent_name}/agent.json`
+
+```json
+{
+ "name": "security-reviewer",
+ "description": "Analyzes code for vulnerabilities before commit",
+ "instructions": "Check changes for:\n- OWASP Top 10 vulnerabilities\n- Leakage of API keys or secrets\n- Correctness of firewall/nftables configuration"
+}
```
---
@@ -171,11 +173,7 @@ agy plugin list # List installed plugins
### Steps for Quick Transition
1. Install the new client: `curl -fsSL https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/install.sh | bash`
-2. Rename local configuration files:
- ```bash
- mv GEMINI.md .antigravity.md
- mv ~/.gemini/agents/ ~/.antigravity/agents/
- ```
+2. Migrate local custom agent configuration files (e.g. convert custom agents from YAML inside `~/.gemini/agents/` to JSON files at `~/.gemini/antigravity-cli/agents/{agent_name}/agent.json`).
3. Update CI/CD configurations in GitHub Actions, replacing `gemini` calls with `agy`.
4. Uninstall the old library: `npm uninstall -g @google/gemini-cli`
@@ -186,7 +184,7 @@ agy plugin list # List installed plugins
| **Development Platform** | Node.js / TypeScript | Go (Native Compiled Binary) |
| **Command Name** | `gemini` | `agy` |
| **Startup Speed** | ~1.2s (Node.js startup) | **~0.05s (instant native startup)** |
-| **Configuration File** | `GEMINI.md` | `.antigravity.md` |
+| **Configuration File** | `GEMINI.md` | `GEMINI.md` |
| **Auto-update** | Via `npm update` | Built-in self-update mechanism |
| **Support Status** | ⛔ EOL (June 18, 2026) | ✅ Active Development (Upstream) |
@@ -200,7 +198,7 @@ antigravity-cli/
├── install.ps1 # Installer for Windows PowerShell (offline/online)
├── install.cmd # Installer for Windows CMD
├── Makefile # Automation targets (make install/reinstall/uninstall)
-├── .antigravity.md # Project context file template
+├── GEMINI.md # Project context file template
├── packages/ # Local offline distribution
│ ├── manifests/ # Version manifests for all platforms
│ └── binaries/ # (Created manually for offline mode)
From 1826f1e4bbef895aa5ecc828559201c8063de008 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 28 May 2026 19:39:17 +0300
Subject: [PATCH 18/28] feat(statusline): enhance statusline with all
documented fields
---
examples/statusline/statusline.sh | 154 ++++++++++++++++++++++++------
1 file changed, 125 insertions(+), 29 deletions(-)
diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh
index 390fea1ee..a97db950c 100755
--- a/examples/statusline/statusline.sh
+++ b/examples/statusline/statusline.sh
@@ -30,38 +30,95 @@ FG_BRIGHT_WHITE="\033[97m"
NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
# ─── Parse JSON from stdin (Single jq pass for performance) ──────────────────
-# Extract all fields in one pass to prevent spawning jq 8 times.
{
read -r STATE
read -r USED_PCT
read -r VCS_BRANCH
read -r VCS_DIRTY
+ read -r VCS_TYPE
+ read -r VCS_CLIENT
read -r SANDBOX
+ read -r SANDBOX_NET
read -r ARTIFACTS
read -r SUBAGENTS
read -r BG_TASKS
- read -r MODEL
+ read -r MODEL_ID
+ read -r MODEL_NAME
read -r COLS
+ read -r CWD
+ read -r CONV_ID
+ read -r PRODUCT
+ read -r INPUT_TOKENS
+ read -r OUTPUT_TOKENS
+ read -r CTX_LIMIT
+ read -r CTX_USED
+ read -r REM_PCT
} <<< "$(
jq -r '
(.agent_state // "idle"),
(.context_window.used_percentage // 0),
(.vcs.branch // ""),
(.vcs.dirty // false),
+ (.vcs.type // ""),
+ (.vcs.client // ""),
(.sandbox.enabled // false),
+ (.sandbox.allow_network // false),
(.artifact_count // 0),
(if .subagents | type == "array" then (.subagents | length) else 0 end),
(.task_count // 0),
+ (.model.id // ""),
(.model.display_name // ""),
- (.terminal_width // 80)
- ' 2>/dev/null || printf "idle\n0\n\nfalse\nfalse\n0\n0\n0\n\n80\n"
+ (.terminal_width // 80),
+ (.cwd // ""),
+ (.conversation_id // ""),
+ (.product // ""),
+ (.context_window.total_input_tokens // 0),
+ (.context_window.total_output_tokens // 0),
+ (.context_window.context_window_size // 0),
+ (.context_window.current_usage // 0),
+ (.context_window.remaining_percentage // 100)
+ ' 2>/dev/null || printf "idle\n0\n\nfalse\n\n\nfalse\nfalse\n0\n0\n0\n\n\n80\n\n\n\n0\n0\n0\n0\n100\n"
)"
-# ─── Computed Values ─────────────────────────────────────────────────────────
-# Use LC_NUMERIC=C to prevent bash printf errors in locales that use commas for decimals
+# ─── Computed & Formatted Values ─────────────────────────────────────────────
PCT_FMT=$(LC_NUMERIC=C printf "%.1f" "$USED_PCT")
PCT_INT=${USED_PCT%.*}; PCT_INT=${PCT_INT:-0}
+human_format() {
+ local num=$1
+ if [ -z "$num" ] || [ "$num" -eq 0 ] 2>/dev/null; then
+ echo "0"
+ return
+ fi
+ if [ "$num" -ge 1000000 ] 2>/dev/null; then
+ echo "$((num / 1000000)).$(((num % 1000000) / 100000))M"
+ elif [ "$num" -ge 1000 ] 2>/dev/null; then
+ echo "$((num / 1000)).$(((num % 1000) / 100))K"
+ else
+ echo "$num"
+ fi
+}
+
+INPUT_TOK_FMT=$(human_format "$INPUT_TOKENS")
+OUTPUT_TOK_FMT=$(human_format "$OUTPUT_TOKENS")
+CTX_LIMIT_FMT=$(human_format "$CTX_LIMIT")
+CTX_USED_FMT=$(human_format "$CTX_USED")
+
+shorten_path() {
+ local path=$1
+ if [ -z "$path" ]; then
+ echo ""
+ return
+ fi
+ path="${path/#$HOME/\~}"
+ if [ "${#path}" -gt 25 ]; then
+ echo "...$(basename "$path")"
+ else
+ echo "$path"
+ fi
+}
+CWD_SHORT=$(shorten_path "$CWD")
+
# ─── State Indicator (No background colors) ──────────────────────────────────
case "$STATE" in
idle) S="${FG_BRIGHT_GREEN}${B}● READY${R}" ;;
@@ -71,27 +128,34 @@ case "$STATE" in
*) S="${FG_WHITE}${B}⏳ $(echo "$STATE" | tr '[:lower:]' '[:upper:]')${R}" ;;
esac
-# ─── VCS Branch ──────────────────────────────────────────────────────────────
+# ─── VCS Branch & Type ───────────────────────────────────────────────────────
V=""
if [ -n "$VCS_BRANCH" ]; then
+ VCS_LABEL="${VCS_TYPE:-git}"
if [ "$VCS_DIRTY" = "true" ]; then
- V="${FG_GRAY} ╱ ${FG_BRIGHT_RED}${VCS_BRANCH}${FG_BRIGHT_YELLOW}*${R}"
+ V="${FG_GRAY} ╱ ${FG_GRAY}${VCS_LABEL}:${FG_BRIGHT_RED}${VCS_BRANCH}${FG_BRIGHT_YELLOW}*${R}"
else
- V="${FG_GRAY} ╱ ${FG_BRIGHT_BLUE}${VCS_BRANCH}${R}"
+ V="${FG_GRAY} ╱ ${FG_GRAY}${VCS_LABEL}:${FG_BRIGHT_BLUE}${VCS_BRANCH}${R}"
fi
fi
# ─── Model ───────────────────────────────────────────────────────────────────
+# Fallback to model ID if display name is empty
+MODEL_DISP="${MODEL_NAME:-$MODEL_ID}"
M=""
-if [ -n "$MODEL" ]; then
- M="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${MODEL}${R}"
+if [ -n "$MODEL_DISP" ]; then
+ M="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${MODEL_DISP}${R}"
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 (net)${R}"
+ else
+ SB="${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
+ fi
else
- SB="${FG_GRAY}sandbox off${R}"
+ SB="${FG_GRAY}🛡️ sandbox off${R}"
fi
# ─── Context Bar (15 segments, fine-grain Unicode) ────────────────────────────
@@ -99,7 +163,6 @@ BAR_LEN=15
FILLED=$((PCT_INT * BAR_LEN / 100))
REMAINDER=$(( (PCT_INT * BAR_LEN) % 100 ))
-# Pick color based on percentage
if [ "$PCT_INT" -ge 90 ]; then
BAR_COLOR="$FG_BRIGHT_RED"
elif [ "$PCT_INT" -ge 60 ]; then
@@ -108,7 +171,6 @@ else
BAR_COLOR="$FG_BRIGHT_WHITE"
fi
-# Build bar with partial-fill last block
BAR=""
for ((i = 0; i < BAR_LEN; i++)); do
if [ "$i" -lt "$FILLED" ]; then
@@ -128,28 +190,62 @@ for ((i = 0; i < BAR_LEN; i++)); do
fi
done
-# ─── Stats ───────────────────────────────────────────────────────────────────
-CTX="${FG_GRAY}ctx ${BAR_COLOR}${BAR} ${NUM_COLOR}${PCT_FMT}%${R}"
-ART_FMT="${FG_GRAY}artifacts ${NUM_COLOR}${ARTIFACTS}${R}"
-SUB_FMT="${FG_GRAY}subagents ${NUM_COLOR}${SUBAGENTS}${R}"
-BG_FMT="${FG_GRAY}tasks ${NUM_COLOR}${BG_TASKS}${R}"
+# ─── Stats & Metadata formatting ─────────────────────────────────────────────
+CTX_BAR="${FG_GRAY}ctx ${BAR_COLOR}${BAR} ${NUM_COLOR}${PCT_FMT}%${R}"
+ART_FMT="${FG_GRAY}📦 ${NUM_COLOR}${ARTIFACTS}${R}"
+SUB_FMT="${FG_GRAY}🤖 ${NUM_COLOR}${SUBAGENTS}${R}"
+BG_FMT="${FG_GRAY}⏳ ${NUM_COLOR}${BG_TASKS}${R}"
+
+# ─── New elements (CWD, Conversation ID, Token counts) ──────────────────────
+DIR_FMT=""
+if [ -n "$CWD_SHORT" ]; then
+ DIR_FMT="${FG_GRAY} ╱ 📂 ${CWD_SHORT}${R}"
+fi
+
+CONV_FMT=""
+if [ -n "$CONV_ID" ]; then
+ CONV_FMT="${FG_GRAY} ╱ id:${CONV_ID:0:8}${R}"
+fi
+
+# Token stats detailed vs simple
+TOK_DETAILS=""
+if [ "$CTX_USED" -gt 0 ] 2>/dev/null; then
+ TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT})"
+fi
# ─── Separators ──────────────────────────────────────────────────────────────
DOT="${FG_GRAY} · ${R}"
-# ─── Output ──────────────────────────────────────────────────────────────────
-LINE1="${S}${M}${V}"
-LINE2=" ${CTX}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
-
+# ─── Output Assembly ──────────────────────────────────────────────────────────
if [ "$COLS" -ge 120 ]; then
- # Wide: single line
+ # Wide Layout: One line containing state, model, vcs, directory, conversation id
+ # and bottom bar metrics inline.
+ LINE1="${S}${M}${V}${DIR_FMT}${CONV_FMT}"
+
+ # Detailed tokens in wide layout: (used/limit · in/out)
+ if [ "$CTX_USED" -gt 0 ] 2>/dev/null; then
+ TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT} · ${INPUT_TOK_FMT} in/${OUTPUT_TOK_FMT} out)"
+ fi
+
+ LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
echo -e "${LINE1}${FG_GRAY} │ ${R}${LINE2}"
+
elif [ "$COLS" -ge 80 ]; then
- # Medium: two-line layout with border
+ # Medium Layout: Two-line layout with border
+ LINE1="${S}${M}${V}${DIR_FMT}"
+ LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
+
echo -e "${FG_GRAY}╭─${R} ${LINE1}"
echo -e "${FG_GRAY}╰─${R}${LINE2}"
+
else
- # Narrow: compact two-line, minimal chrome
- echo -e "${S}${M}"
- echo -e "${CTX}${DOT}${BG_FMT}"
+ # Narrow Layout: Compact two-line, minimal layout
+ # Shorten model display for narrow screens
+ M_SHORT=""
+ if [ -n "$MODEL_DISP" ]; then
+ M_SHORT="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${MODEL_DISP:0:12}${R}"
+ fi
+
+ echo -e "${S}${M_SHORT}"
+ echo -e "${CTX_BAR}${DOT}${BG_FMT}"
fi
From d41671a4b8b8569899a96732b4989366e182cb44 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 28 May 2026 22:33:44 +0300
Subject: [PATCH 19/28] fix(statusline): sum total input and output tokens
instead of parsing current_usage object directly
---
examples/statusline/statusline.sh | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh
index a97db950c..893b38fa7 100755
--- a/examples/statusline/statusline.sh
+++ b/examples/statusline/statusline.sh
@@ -1,5 +1,7 @@
#!/bin/bash
set -euo pipefail
+INPUT_JSON=$(cat)
+
# ─── ANSI Helpers (Standard 16-color palette only) ───────────────────────────
R="\033[0m" # Reset
@@ -54,7 +56,7 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
read -r CTX_USED
read -r REM_PCT
} <<< "$(
- jq -r '
+ echo "$INPUT_JSON" | jq -r '
(.agent_state // "idle"),
(.context_window.used_percentage // 0),
(.vcs.branch // ""),
@@ -75,7 +77,7 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
(.context_window.total_input_tokens // 0),
(.context_window.total_output_tokens // 0),
(.context_window.context_window_size // 0),
- (.context_window.current_usage // 0),
+ ((.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)),
(.context_window.remaining_percentage // 100)
' 2>/dev/null || printf "idle\n0\n\nfalse\n\n\nfalse\nfalse\n0\n0\n0\n\n\n80\n\n\n\n0\n0\n0\n0\n100\n"
)"
From c8e2fcc092727d60adaeddb3f36356da7f3a5548 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 28 May 2026 22:47:05 +0300
Subject: [PATCH 20/28] feat(examples): add automated setup scripts and fix
token displays in all statusline implementations
---
examples/statusline/README.md | 126 ++++++++++++++--
examples/statusline/setup.sh | 98 ++++++++++++
examples/statusline/statusline.fish | 225 ++++++++++++++++++++++++++++
examples/statusline/statusline.js | 220 +++++++++++++++++++++++++++
examples/statusline/statusline.ps1 | 223 +++++++++++++++++++++++++++
examples/title/README.md | 94 +++++++++++-
examples/title/setup.sh | 98 ++++++++++++
examples/title/title.fish | 37 +++++
examples/title/title.js | 53 +++++++
examples/title/title.ps1 | 39 +++++
10 files changed, 1196 insertions(+), 17 deletions(-)
create mode 100644 examples/statusline/setup.sh
create mode 100755 examples/statusline/statusline.fish
create mode 100755 examples/statusline/statusline.js
create mode 100644 examples/statusline/statusline.ps1
create mode 100644 examples/title/setup.sh
create mode 100755 examples/title/title.fish
create mode 100755 examples/title/title.js
create mode 100644 examples/title/title.ps1
diff --git a/examples/statusline/README.md b/examples/statusline/README.md
index 923d66d2d..6b3a6ed88 100644
--- a/examples/statusline/README.md
+++ b/examples/statusline/README.md
@@ -1,21 +1,117 @@
-# CLI Statusline Example
+# Custom Status Line
-This directory contains an example script (`statusline.sh`) that demonstrates how to create a custom, dynamic statusline for the Antigravity CLI.
+This directory contains reference implementations for a custom, dynamic status line for the Antigravity CLI across different shell environments:
-For more details on how to use and configure the statusline script, please refer to the official public documentation:
-[https://antigravity.google/docs/cli-statusline](https://antigravity.google/docs/cli-statusline)
+1. **`statusline.sh`** (Bash/Zsh - Linux/macOS)
+2. **`statusline.js`** (Node.js - Cross-platform: Windows/Linux/macOS)
+3. **`statusline.ps1`** (PowerShell - Windows PowerShell or PowerShell Core `pwsh`)
+4. **`statusline.fish`** (Fish shell - Linux/macOS)
+
+## Quick Start
+
+### Option 1: Automatic Setup (Recommended for Bash/Zsh)
+
+Run the included setup script from the root of the repository:
+
+```bash
+bash examples/statusline/setup.sh
+```
+
+This script will automatically:
+1. Copy `statusline.sh` to your platform's global settings directory (so it stays configured even if you move or delete this repository).
+2. Configure and enable it in your global `settings.json` file.
+
+### Option 2: Manual configuration
+
+1. Copy the script corresponding to your shell to a directory of your choice.
+2. Edit your `settings.json` file to point `statusLine.command` to the absolute path of the script and set `statusLine.enabled` to `true`:
+
+#### Bash/Zsh (Linux/macOS)
+```json
+{
+ "statusLine": {
+ "command": "/absolute/path/to/statusline.sh",
+ "enabled": true
+ }
+}
+```
+
+#### Node.js (Cross-platform)
+```json
+{
+ "statusLine": {
+ "command": "node /absolute/path/to/statusline.js",
+ "enabled": true
+ }
+}
+```
+
+#### PowerShell (Windows / pwsh)
+```json
+{
+ "statusLine": {
+ "command": "powershell.exe -ExecutionPolicy Bypass -File C:\\absolute\\path\\to\\statusline.ps1",
+ "enabled": true
+ }
+}
+```
+*For PowerShell Core, use `pwsh.exe` or `pwsh` instead of `powershell.exe`.*
+
+#### Fish Shell
+```json
+{
+ "statusLine": {
+ "command": "/absolute/path/to/statusline.fish",
+ "enabled": true
+ }
+}
+```
+
+**Settings file locations:**
+
+| Platform | Path |
+| :--- | :--- |
+| Linux | `~/.gemini/antigravity-cli/settings.json` |
+| macOS | `~/Library/Application Support/antigravity-cli/settings.json` |
+| Windows | `%APPDATA%\antigravity-cli\settings.json` |
+
+> [!IMPORTANT]
+> The `command` field must be an **absolute path** to the script. Relative paths and `~` expansion are not supported.
+
+After saving, restart `agy` for changes to take effect.
## How it works
-The `statusline.sh` script reads a JSON payload from standard input, containing various state information from the CLI. It then:
-1. Extracts multiple fields like `agent_state`, `vcs` info, context usage, and terminal dimensions using `jq`.
-2. Computes visual indicators, such as a Unicode progress bar for context window usage.
-3. Formats the data with standard ANSI 16-color codes for visual distinction.
-4. Dynamically adjusts the layout to be 1 or 2 lines based on the available terminal width.
+The CLI pipes a JSON payload into the script's stdin on each state change. The script:
+
+1. Extracts fields like `agent_state`, `vcs` info, `context_window` usage, and `terminal_width`.
+2. Computes visual indicators (e.g., a Unicode progress bar for context window usage).
+3. Formats the output using standard ANSI 16-color codes.
+4. Dynamically adjusts the layout based on the available terminal width (single-line for wide terminals, two-line for narrower ones).
+
+### JSON payload fields
+
+| Field | Type | Description |
+| :--- | :--- | :--- |
+| `agent_state` | string | Current state: `idle`, `thinking`, `working`, `tool_use` |
+| `context_window.used_percentage` | number | Context window utilization (0–100) |
+| `vcs.branch` | string | Current Git branch name |
+| `vcs.dirty` | boolean | Whether the working tree has uncommitted changes |
+| `sandbox.enabled` | boolean | Whether sandbox mode is active |
+| `artifact_count` | number | Number of artifacts in the current session |
+| `subagents` | array | List of active subagents |
+| `task_count` | number | Number of background tasks |
+| `model.display_name` | string | Human-readable model name |
+| `terminal_width` | number | Current terminal width in columns |
+
+### Prerequisites
+
+- **Bash (`statusline.sh`) & Fish (`statusline.fish`)**: Require [`jq`](https://jqlang.org/) to be installed and available in `$PATH`.
+- **Node.js (`statusline.js`) & PowerShell (`statusline.ps1`)**: Have **zero external dependencies** and work out-of-the-box.
## Examples
-### Default Statusline
+### Default Status Line

### Review Mode
@@ -23,3 +119,13 @@ The `statusline.sh` script reads a JSON payload from standard input, containing
### Tool Execution

+
+## Writing your own
+
+You can use any of the provided scripts as a starting point. The only contract is:
+
+1. Read JSON from stdin.
+2. Write one or more lines of ANSI-formatted text to stdout.
+3. Exit with code 0.
+
+For the official documentation, see [antigravity.google/docs/cli-statusline](https://antigravity.google/docs/cli-statusline).
diff --git a/examples/statusline/setup.sh b/examples/statusline/setup.sh
new file mode 100644
index 000000000..584c78044
--- /dev/null
+++ b/examples/statusline/setup.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+# setup.sh - Installs and enables the custom statusline for Antigravity CLI
+
+set -euo pipefail
+
+# 1. Determine the settings directory
+OS="$(uname -s)"
+case "${OS}" in
+ Darwin*)
+ CONFIG_DIR="$HOME/Library/Application Support/antigravity-cli"
+ ;;
+ Linux*)
+ CONFIG_DIR="$HOME/.gemini/antigravity-cli"
+ ;;
+ CYGWIN*|MINGW*|MSYS*)
+ # Windows environments (Git Bash, MSYS)
+ if [ -n "${APPDATA:-}" ]; then
+ CONFIG_DIR="${APPDATA}/antigravity-cli"
+ else
+ CONFIG_DIR="$HOME/AppData/Roaming/antigravity-cli"
+ fi
+ ;;
+ *)
+ # Default fallback to Linux path
+ CONFIG_DIR="$HOME/.gemini/antigravity-cli"
+ ;;
+esac
+
+# Convert config dir to absolute path if needed
+mkdir -p "$CONFIG_DIR"
+CONFIG_DIR="$(cd "$CONFIG_DIR" && pwd)"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SOURCE_SCRIPT="$SCRIPT_DIR/statusline.sh"
+TARGET_SCRIPT="$CONFIG_DIR/statusline.sh"
+
+echo "Installing statusline script to: $TARGET_SCRIPT"
+cp "$SOURCE_SCRIPT" "$TARGET_SCRIPT"
+chmod +x "$TARGET_SCRIPT"
+
+SETTINGS_FILE="$CONFIG_DIR/settings.json"
+echo "Configuring settings file at: $SETTINGS_FILE"
+
+# Make sure settings.json exists
+if [ ! -f "$SETTINGS_FILE" ]; then
+ echo "{}" > "$SETTINGS_FILE"
+fi
+
+# Update settings.json using python if available, otherwise fallback to jq
+if command -v python3 >/dev/null 2>&1; then
+ python3 -c '
+import json, sys
+file_path, script_path = sys.argv[1], sys.argv[2]
+try:
+ with open(file_path, "r") as f:
+ data = json.load(f)
+except Exception:
+ data = {}
+if "statusLine" not in data or not isinstance(data["statusLine"], dict):
+ data["statusLine"] = {}
+data["statusLine"]["command"] = script_path
+data["statusLine"]["enabled"] = True
+with open(file_path, "w") as f:
+ json.dump(data, f, indent=2)
+' "$SETTINGS_FILE" "$TARGET_SCRIPT"
+elif command -v python >/dev/null 2>&1; then
+ python -c '
+import json, sys
+file_path, script_path = sys.argv[1], sys.argv[2]
+try:
+ with open(file_path, "r") as f:
+ data = json.load(f)
+except Exception:
+ data = {}
+if "statusLine" not in data or not isinstance(data["statusLine"], dict):
+ data["statusLine"] = {}
+data["statusLine"]["command"] = script_path
+data["statusLine"]["enabled"] = True
+with open(file_path, "w") as f:
+ json.dump(data, f, indent=2)
+' "$SETTINGS_FILE" "$TARGET_SCRIPT"
+elif command -v jq >/dev/null 2>&1; then
+ TEMP_FILE=$(mktemp)
+ jq --arg cmd "$TARGET_SCRIPT" '.statusLine = ((.statusLine // {}) + {command: $cmd, enabled: true})' "$SETTINGS_FILE" > "$TEMP_FILE"
+ mv "$TEMP_FILE" "$SETTINGS_FILE"
+else
+ echo "Error: Neither python3, python, nor jq is installed. Please manually add the following to your $SETTINGS_FILE:"
+ echo "{"
+ echo " \"statusLine\": {"
+ echo " \"command\": \"$TARGET_SCRIPT\","
+ echo " \"enabled\": true"
+ echo " }"
+ echo "}"
+ exit 1
+fi
+
+echo "Status line successfully installed and enabled!"
+echo "Please restart Antigravity CLI (agy) to see the changes."
diff --git a/examples/statusline/statusline.fish b/examples/statusline/statusline.fish
new file mode 100755
index 000000000..fae25d7d5
--- /dev/null
+++ b/examples/statusline/statusline.fish
@@ -0,0 +1,225 @@
+#!/usr/bin/fish
+
+# Read JSON payload from stdin
+set -l DATA (cat)
+
+# Extract fields using jq. Since Fish is on Unix, jq will be available.
+if not type -q jq
+ echo "READY"
+ echo "ctx · 0%"
+ exit 0
+end
+
+set -l STATE (echo $DATA | jq -r '.agent_state // "idle"')
+set -l USED_PCT (echo $DATA | jq -r '.context_window.used_percentage // 0')
+set -l VCS_BRANCH (echo $DATA | jq -r '.vcs.branch // ""')
+set -l VCS_DIRTY (echo $DATA | jq -r '.vcs.dirty // false')
+set -l VCS_TYPE (echo $DATA | jq -r '.vcs.type // ""')
+set -l SANDBOX (echo $DATA | jq -r '.sandbox.enabled // false')
+set -l SANDBOX_NET (echo $DATA | jq -r '.sandbox.allow_network // false')
+set -l ARTIFACTS (echo $DATA | jq -r '.artifact_count // 0')
+set -l SUBAGENTS (echo $DATA | jq -r 'if .subagents | type == "array" then (.subagents | length) else 0 end')
+set -l BG_TASKS (echo $DATA | jq -r '.task_count // 0')
+set -l MODEL_ID (echo $DATA | jq -r '.model.id // ""')
+set -l MODEL_NAME (echo $DATA | jq -r '.model.display_name // ""')
+set -l COLS (echo $DATA | jq -r '.terminal_width // 80')
+set -l CWD (echo $DATA | jq -r '.cwd // ""')
+set -l CONV_ID (echo $DATA | jq -r '.conversation_id // ""')
+set -l INPUT_TOKENS (echo $DATA | jq -r '.context_window.total_input_tokens // 0')
+set -l OUTPUT_TOKENS (echo $DATA | jq -r '.context_window.total_output_tokens // 0')
+set -l CTX_LIMIT (echo $DATA | jq -r '.context_window.context_window_size // 0')
+set -l CTX_USED (echo $DATA | jq -r '(.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)')
+
+# ANSI Helpers
+set -l R (set_color normal)
+set -l B (set_color -o)
+set -l I (set_color -i)
+
+set -l FG_RED (set_color red)
+set -l FG_GREEN (set_color green)
+set -l FG_YELLOW (set_color yellow)
+set -l FG_BLUE (set_color blue)
+set -l FG_MAGENTA (set_color magenta)
+set -l FG_CYAN (set_color cyan)
+set -l FG_WHITE (set_color white)
+set -l FG_GRAY (set_color 909090)
+
+set -l FG_BRIGHT_RED (set_color brred)
+set -l FG_BRIGHT_GREEN (set_color brgreen)
+set -l FG_BRIGHT_YELLOW (set_color bryellow)
+set -l FG_BRIGHT_BLUE (set_color brblue)
+set -l FG_BRIGHT_MAGENTA (set_color brmagenta)
+set -l FG_BRIGHT_CYAN (set_color brcyan)
+set -l FG_BRIGHT_WHITE (set_color brwhite)
+
+set -l NUM_COLOR "$FG_BRIGHT_WHITE$B"
+
+function human_format -a num
+ if test -z "$num"; or test "$num" -eq 0
+ echo "0"
+ return
+ end
+ if test "$num" -ge 1000000
+ set -l main (math -s0 "$num / 1000000")
+ set -l dec (math -s0 "($num % 1000000) / 100000")
+ echo "$main.$dec"M
+ elif test "$num" -ge 1000
+ set -l main (math -s0 "$num / 1000")
+ set -l dec (math -s0 "($num % 1000) / 100")
+ echo "$main.$dec"K
+ else
+ echo "$num"
+ end
+end
+
+set -l INPUT_TOK_FMT (human_format $INPUT_TOKENS)
+set -l OUTPUT_TOK_FMT (human_format $OUTPUT_TOKENS)
+set -l CTX_LIMIT_FMT (human_format $CTX_LIMIT)
+set -l CTX_USED_FMT (human_format $CTX_USED)
+
+function shorten_path -a path
+ if test -z "$path"
+ echo ""
+ return
+ end
+ set -l shortened (string replace -r "^$HOME" "~" $path)
+ if test (string length $shortened) -gt 25
+ set -l base_name (basename $shortened)
+ echo "...$base_name"
+ else
+ echo $shortened
+ end
+end
+set -l CWD_SHORT (shorten_path $CWD)
+
+# State Indicator
+set -l S ""
+switch $STATE
+ case idle
+ set S "$FG_BRIGHT_GREEN$B● READY$R"
+ case thinking
+ set S "$FG_BRIGHT_YELLOW$B◆ THINKING$R"
+ case working
+ set S "$FG_BRIGHT_CYAN$B⚙ WORKING$R"
+ case tool_use
+ set S "$FG_BRIGHT_MAGENTA$B🔧 TOOL$R"
+ case '*'
+ set -l upper_state (string upper $STATE)
+ set S "$FG_WHITE$B⏳ $upper_state$R"
+end
+
+# VCS Branch & Type
+set -l V ""
+if test -n "$VCS_BRANCH"
+ set -l vcs_label "git"
+ if test -n "$VCS_TYPE"
+ set vcs_label $VCS_TYPE
+ end
+ if test "$VCS_DIRTY" = "true"
+ set V "$FG_GRAY ╱ $FG_GRAY$vcs_label:$FG_BRIGHT_RED$VCS_BRANCH$FG_BRIGHT_YELLOW*$R"
+ else
+ set V "$FG_GRAY ╱ $FG_GRAY$vcs_label:$FG_BRIGHT_BLUE$VCS_BRANCH$R"
+ end
+end
+
+# Model
+set -l MODEL_DISP $MODEL_NAME
+if test -z "$MODEL_DISP"
+ set MODEL_DISP $MODEL_ID
+end
+set -l M ""
+if test -n "$MODEL_DISP"
+ set M "$FG_GRAY ╱ $FG_BRIGHT_MAGENTA$I$MODEL_DISP$R"
+end
+
+# Sandbox Badge
+set -l SB ""
+if test "$SANDBOX" = "true"
+ if test "$SANDBOX_NET" = "true"
+ set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B"ON (net)"$R"
+ else
+ set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B"ON (no-net)"$R"
+ end
+else
+ set SB "$FG_GRAY🛡️ sandbox off$R"
+end
+
+# Context Bar
+set -l BAR_LEN 15
+set -l PCT_INT (math -s0 "$USED_PCT")
+set -l FILLED (math -s0 "$PCT_INT * $BAR_LEN / 100")
+set -l REMAINDER (math -s0 "($PCT_INT * $BAR_LEN) % 100")
+
+set -l BAR_COLOR $FG_BRIGHT_WHITE
+if test "$PCT_INT" -ge 90
+ set BAR_COLOR $FG_BRIGHT_RED
+elif test "$PCT_INT" -ge 60
+ set BAR_COLOR $FG_BRIGHT_YELLOW
+end
+
+set -l BAR ""
+for i in (seq 0 (math "$BAR_LEN - 1"))
+ if test "$i" -lt "$FILLED"
+ set BAR "$BAR"█
+ elif test "$i" -eq "$FILLED"
+ if test "$REMAINDER" -ge 75
+ set BAR "$BAR"▓
+ elif test "$REMAINDER" -ge 50
+ set BAR "$BAR"▒
+ elif test "$REMAINDER" -ge 25
+ set BAR "$BAR"░
+ else
+ set BAR "$BAR"·
+ end
+ else
+ set BAR "$BAR"·
+ end
+end
+
+# Stats & Metadata
+set -l PCT_FMT (printf "%.1f" $USED_PCT)
+set -l CTX_BAR "$FG_GRAY"ctx "$BAR_COLOR$BAR $NUM_COLOR$PCT_FMT%$R"
+set -l ART_FMT "$FG_GRAY"📦 "$NUM_COLOR$ARTIFACTS$R"
+set -l SUB_FMT "$FG_GRAY"🤖 "$NUM_COLOR$SUBAGENTS$R"
+set -l BG_FMT "$FG_GRAY"⏳ "$NUM_COLOR$BG_TASKS$R"
+
+set -l DIR_FMT ""
+if test -n "$CWD_SHORT"
+ set DIR_FMT "$FG_GRAY ╱ 📂 $CWD_SHORT$R"
+end
+
+set -l CONV_FMT ""
+if test -n "$CONV_ID"
+ set -l sub_conv (string sub -l 8 $CONV_ID)
+ set CONV_FMT "$FG_GRAY ╱ id:$sub_conv$R"
+end
+
+set -l TOK_DETAILS ""
+if test "$CTX_USED" -gt 0
+ set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT)"
+end
+
+set -l DOT "$FG_GRAY · $R"
+
+# Output Assembly
+if test "$COLS" -ge 120
+ set -l line1 "$S$M$V$DIR_FMT$CONV_FMT"
+ if test "$CTX_USED" -gt 0
+ set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT · $INPUT_TOK_FMT in/$OUTPUT_TOK_FMT out)"
+ end
+ set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB"
+ echo -e "$line1$FG_GRAY │ $R$line2"
+elif test "$COLS" -ge 80
+ set -l line1 "$S$M$V$DIR_FMT"
+ set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB"
+ echo -e "$FG_GRAY╭─$R $line1"
+ echo -e "$FG_GRAY╰─$R$line2"
+else
+ set -l M_SHORT ""
+ if test -n "$MODEL_DISP"
+ set -l sub_model (string sub -l 12 $MODEL_DISP)
+ set M_SHORT "$FG_GRAY ╱ $FG_BRIGHT_MAGENTA$sub_model$R"
+ end
+ echo -e "$S$M_SHORT"
+ echo -e "$CTX_BAR$DOT$BG_FMT"
+end
diff --git a/examples/statusline/statusline.js b/examples/statusline/statusline.js
new file mode 100755
index 000000000..a8fcc8142
--- /dev/null
+++ b/examples/statusline/statusline.js
@@ -0,0 +1,220 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+const os = require('os');
+
+// ─── Read JSON from stdin ────────────────────────────────────────────────────
+let rawData = '';
+try {
+ rawData = fs.readFileSync(0, 'utf-8');
+} catch (e) {
+ // Silence read errors
+}
+
+let data = {};
+try {
+ data = JSON.parse(rawData || '{}');
+} catch (e) {
+ // Silence JSON parse errors
+}
+
+// ─── ANSI Helpers (Standard 16-color palette only) ───────────────────────────
+const R = "\x1b[0m"; // Reset
+const B = "\x1b[1m"; // Bold
+const D = "\x1b[2m"; // Dim
+const I = "\x1b[3m"; // Italic
+
+// Foreground accents (Standard 16 colors)
+const FG_BLACK = "\x1b[30m";
+const FG_RED = "\x1b[31m";
+const FG_GREEN = "\x1b[32m";
+const FG_YELLOW = "\x1b[33m";
+const FG_BLUE = "\x1b[34m";
+const FG_MAGENTA = "\x1b[35m";
+const FG_CYAN = "\x1b[36m";
+const FG_WHITE = "\x1b[37m";
+
+const FG_GRAY = "\x1b[90m";
+const FG_BRIGHT_RED = "\x1b[91m";
+const FG_BRIGHT_GREEN = "\x1b[92m";
+const FG_BRIGHT_YELLOW = "\x1b[93m";
+const FG_BRIGHT_BLUE = "\x1b[94m";
+const FG_BRIGHT_MAGENTA = "\x1b[95m";
+const FG_BRIGHT_CYAN = "\x1b[96m";
+const FG_BRIGHT_WHITE = "\x1b[97m";
+
+const NUM_COLOR = FG_BRIGHT_WHITE + B;
+
+// ─── Extract fields with fallbacks ───────────────────────────────────────────
+const state = data.agent_state || "idle";
+const usedPct = (data.context_window && typeof data.context_window.used_percentage === 'number') ? data.context_window.used_percentage : 0;
+const vcsBranch = (data.vcs && data.vcs.branch) ? data.vcs.branch : "";
+const vcsDirty = (data.vcs && data.vcs.dirty) ? data.vcs.dirty : false;
+const vcsType = (data.vcs && data.vcs.type) ? data.vcs.type : "";
+const sandboxEnabled = (data.sandbox && data.sandbox.enabled) ? data.sandbox.enabled : false;
+const sandboxNet = (data.sandbox && data.sandbox.allow_network) ? data.sandbox.allow_network : false;
+const artifactCount = data.artifact_count || 0;
+const subagentsCount = Array.isArray(data.subagents) ? data.subagents.length : 0;
+const taskCount = data.task_count || 0;
+const modelId = (data.model && data.model.id) ? data.model.id : "";
+const modelName = (data.model && data.model.display_name) ? data.model.display_name : "";
+const cols = data.terminal_width || 80;
+const cwd = data.cwd || "";
+const convId = data.conversation_id || "";
+const inputTokens = (data.context_window && data.context_window.total_input_tokens) ? data.context_window.total_input_tokens : 0;
+const outputTokens = (data.context_window && data.context_window.total_output_tokens) ? data.context_window.total_output_tokens : 0;
+const ctxLimit = (data.context_window && data.context_window.context_window_size) ? data.context_window.context_window_size : 0;
+const ctxUsed = inputTokens + outputTokens;
+
+// ─── Helper Formatting Functions ─────────────────────────────────────────────
+function humanFormat(num) {
+ if (!num || isNaN(num) || num === 0) return "0";
+ if (num >= 1000000) {
+ return (num / 1000000).toFixed(1).replace(/\.0$/, '') + "M";
+ }
+ if (num >= 1000) {
+ return (num / 1000).toFixed(1).replace(/\.0$/, '') + "K";
+ }
+ return num.toString();
+}
+
+function shortenPath(p) {
+ if (!p) return "";
+ const home = os.homedir();
+ if (p.startsWith(home)) {
+ p = "~" + p.slice(home.length);
+ }
+ if (p.length > 25) {
+ return "..." + path.basename(p);
+ }
+ return p;
+}
+
+const cwdShort = shortenPath(cwd);
+
+// ─── State Indicator (No background colors) ──────────────────────────────────
+let S = "";
+switch (state) {
+ case 'idle':
+ S = `${FG_BRIGHT_GREEN}${B}● READY${R}`;
+ break;
+ case 'thinking':
+ S = `${FG_BRIGHT_YELLOW}${B}◆ THINKING${R}`;
+ break;
+ case 'working':
+ S = `${FG_BRIGHT_CYAN}${B}⚙ WORKING${R}`;
+ break;
+ case 'tool_use':
+ S = `${FG_BRIGHT_MAGENTA}${B}🔧 TOOL${R}`;
+ break;
+ default:
+ S = `${FG_WHITE}${B}⏳ ${state.toUpperCase()}${R}`;
+}
+
+// ─── VCS Branch & Type ───────────────────────────────────────────────────────
+let V = "";
+if (vcsBranch) {
+ const vcsLabel = vcsType || "git";
+ if (vcsDirty || vcsDirty === "true") {
+ V = `${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_RED}${vcsBranch}${FG_BRIGHT_YELLOW}*${R}`;
+ } else {
+ V = `${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_BLUE}${vcsBranch}${R}`;
+ }
+}
+
+// ─── Model ───────────────────────────────────────────────────────────────────
+const modelDisp = modelName || modelId;
+let M = "";
+if (modelDisp) {
+ M = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${modelDisp}${R}`;
+}
+
+// ─── Sandbox Badge ───────────────────────────────────────────────────────────
+let SB = "";
+if (sandboxEnabled || sandboxEnabled === "true") {
+ if (sandboxNet || sandboxNet === "true") {
+ SB = `${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (net)${R}`;
+ } else {
+ SB = `${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}`;
+ }
+} else {
+ SB = `${FG_GRAY}🛡️ sandbox off${R}`;
+}
+
+// ─── Context Bar (15 segments, fine-grain Unicode) ────────────────────────────
+const barLen = 15;
+const pctInt = Math.floor(usedPct);
+const filled = Math.floor((pctInt * barLen) / 100);
+const remainder = (pctInt * barLen) % 100;
+
+let barColor = FG_BRIGHT_WHITE;
+if (pctInt >= 90) {
+ barColor = FG_BRIGHT_RED;
+} else if (pctInt >= 60) {
+ barColor = FG_BRIGHT_YELLOW;
+}
+
+let bar = "";
+for (let i = 0; i < barLen; i++) {
+ if (i < filled) {
+ bar += "█";
+ } else if (i === filled) {
+ if (remainder >= 75) {
+ bar += "▓";
+ } else if (remainder >= 50) {
+ bar += "▒";
+ } else if (remainder >= 25) {
+ bar += "░";
+ } else {
+ bar += "·";
+ }
+ } else {
+ bar += "·";
+ }
+}
+
+// ─── Stats & Metadata formatting ─────────────────────────────────────────────
+const pctFmt = usedPct.toFixed(1);
+const ctxBar = `${FG_GRAY}ctx ${barColor}${bar} ${NUM_COLOR}${pctFmt}%${R}`;
+const artFmt = `${FG_GRAY}📦 ${NUM_COLOR}${artifactCount}${R}`;
+const subFmt = `${FG_GRAY}🤖 ${NUM_COLOR}${subagentsCount}${R}`;
+const bgFmt = `${FG_GRAY}⏳ ${NUM_COLOR}${taskCount}${R}`;
+
+let dirFmt = "";
+if (cwdShort) {
+ dirFmt = `${FG_GRAY} ╱ 📂 ${cwdShort}${R}`;
+}
+
+let convFmt = "";
+if (convId) {
+ convFmt = `${FG_GRAY} ╱ id:${convId.slice(0, 8)}${R}`;
+}
+
+let tokDetails = "";
+if (ctxUsed > 0) {
+ tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)})`;
+}
+
+const dot = `${FG_GRAY} · ${R}`;
+
+// ─── Output Assembly ──────────────────────────────────────────────────────────
+if (cols >= 120) {
+ let line1 = `${S}${M}${V}${dirFmt}${convFmt}`;
+ if (ctxUsed > 0) {
+ tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)} · ${humanFormat(inputTokens)} in/${humanFormat(outputTokens)} out)`;
+ }
+ let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}`;
+ console.log(`${line1}${FG_GRAY} │ ${R}${line2}`);
+} else if (cols >= 80) {
+ let line1 = `${S}${M}${V}${dirFmt}`;
+ let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}`;
+ console.log(`${FG_GRAY}╭─${R} ${line1}`);
+ console.log(`${FG_GRAY}╰─${R}${line2}`);
+} else {
+ let mShort = "";
+ if (modelDisp) {
+ mShort = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${modelDisp.slice(0, 12)}${R}`;
+ }
+ console.log(`${S}${mShort}`);
+ console.log(`${ctxBar}${dot}${bgFmt}`);
+}
diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1
new file mode 100644
index 000000000..e46f8c602
--- /dev/null
+++ b/examples/statusline/statusline.ps1
@@ -0,0 +1,223 @@
+# ─── Read JSON from stdin ────────────────────────────────────────────────────
+$inputJson = [Console]::In.ReadToEnd()
+if ([string]::IsNullOrWhiteSpace($inputJson)) {
+ $inputJson = '{}'
+}
+
+try {
+ $data = ConvertFrom-Json $inputJson -ErrorAction SilentlyContinue
+} catch {
+ $data = $null
+}
+
+if ($null -eq $data) {
+ $data = [PSCustomObject]@{}
+}
+
+# ─── ANSI Helpers (Standard 16-color palette only) ───────────────────────────
+$R = "$([char]0x1b)[0m" # Reset
+$B = "$([char]0x1b)[1m" # Bold
+$D = "$([char]0x1b)[2m" # Dim
+$I = "$([char]0x1b)[3m" # Italic
+
+# Foreground accents (Standard 16 colors)
+$FG_BLACK = "$([char]0x1b)[30m"
+$FG_RED = "$([char]0x1b)[31m"
+$FG_GREEN = "$([char]0x1b)[32m"
+$FG_YELLOW = "$([char]0x1b)[33m"
+$FG_BLUE = "$([char]0x1b)[34m"
+$FG_MAGENTA = "$([char]0x1b)[35m"
+$FG_CYAN = "$([char]0x1b)[36m"
+$FG_WHITE = "$([char]0x1b)[37m"
+
+$FG_GRAY = "$([char]0x1b)[90m"
+$FG_BRIGHT_RED = "$([char]0x1b)[91m"
+$FG_BRIGHT_GREEN = "$([char]0x1b)[92m"
+$FG_BRIGHT_YELLOW = "$([char]0x1b)[93m"
+$FG_BRIGHT_BLUE = "$([char]0x1b)[94m"
+$FG_BRIGHT_MAGENTA = "$([char]0x1b)[95m"
+$FG_BRIGHT_CYAN = "$([char]0x1b)[96m"
+$FG_BRIGHT_WHITE = "$([char]0x1b)[97m"
+
+$NUM_COLOR = "${FG_BRIGHT_WHITE}${B}"
+
+# ─── Extract fields with fallback ────────────────────────────────────────────
+$state = if ($data.agent_state) { $data.agent_state } else { "idle" }
+$usedPct = if ($data.context_window -and $null -ne $data.context_window.used_percentage) { [double]$data.context_window.used_percentage } else { 0.0 }
+$vcsBranch = if ($data.vcs -and $data.vcs.branch) { $data.vcs.branch } else { "" }
+$vcsDirty = if ($data.vcs -and $null -ne $data.vcs.dirty) { $data.vcs.dirty } else { $false }
+$vcsType = if ($data.vcs -and $data.vcs.type) { $data.vcs.type } else { "" }
+$sandboxEnabled = if ($data.sandbox -and $null -ne $data.sandbox.enabled) { $data.sandbox.enabled } else { $false }
+$sandboxNet = if ($data.sandbox -and $null -ne $data.sandbox.allow_network) { $data.sandbox.allow_network } else { $false }
+$artifactCount = if ($data.artifact_count) { $data.artifact_count } else { 0 }
+$subagentsCount = if ($data.subagents) { @($data.subagents).Count } else { 0 }
+$taskCount = if ($data.task_count) { $data.task_count } else { 0 }
+$modelId = if ($data.model -and $data.model.id) { $data.model.id } else { "" }
+$modelName = if ($data.model -and $data.model.display_name) { $data.model.display_name } else { "" }
+$cols = if ($data.terminal_width) { [int]$data.terminal_width } else { 80 }
+$cwd = if ($data.cwd) { $data.cwd } else { "" }
+$convId = if ($data.conversation_id) { $data.conversation_id } else { "" }
+$inputTokens = if ($data.context_window -and $null -ne $data.context_window.total_input_tokens) { $data.context_window.total_input_tokens } else { 0 }
+$outputTokens = if ($data.context_window -and $null -ne $data.context_window.total_output_tokens) { $data.context_window.total_output_tokens } else { 0 }
+$ctxLimit = if ($data.context_window -and $null -ne $data.context_window.context_window_size) { $data.context_window.context_window_size } else { 0 }
+$ctxUsed = $inputTokens + $outputTokens
+
+# ─── Helper Functions ────────────────────────────────────────────────────────
+function Get-HumanFormat {
+ param ($num)
+ if ($null -eq $num -or $num -eq 0) { return "0" }
+ if ($num -ge 1000000) {
+ $main = [Math]::Floor($num / 1000000)
+ $dec = [Math]::Floor(($num % 1000000) / 100000)
+ return "${main}.${dec}M"
+ }
+ if ($num -ge 1000) {
+ $main = [Math]::Floor($num / 1000)
+ $dec = [Math]::Floor(($num % 1000) / 100)
+ return "${main}.${dec}K"
+ }
+ return "$num"
+}
+
+function Get-ShortenPath {
+ param ($path)
+ if ([string]::IsNullOrEmpty($path)) { return "" }
+ $homePath = $env:USERPROFILE
+ if (-not $homePath) {
+ $homePath = $env:HOME
+ }
+ if ($homePath -and $path.StartsWith($homePath)) {
+ $path = "~" + $path.Substring($homePath.Length)
+ }
+ if ($path.Length -gt 25) {
+ $leaf = Split-Path $path -Leaf
+ return "...$leaf"
+ }
+ return $path
+}
+
+$cwdShort = Get-ShortenPath $cwd
+
+# ─── State Indicator ──────────────────────────────────────────────────────────
+switch ($state) {
+ "idle" { $S = "${FG_BRIGHT_GREEN}${B}● READY${R}" }
+ "thinking" { $S = "${FG_BRIGHT_YELLOW}${B}◆ THINKING${R}" }
+ "working" { $S = "${FG_BRIGHT_CYAN}${B}⚙ WORKING${R}" }
+ "tool_use" { $S = "${FG_BRIGHT_MAGENTA}${B}🔧 TOOL${R}" }
+ Default { $S = "${FG_WHITE}${B}⏳ $($state.ToUpper())${R}" }
+}
+
+# ─── VCS Branch & Type ───────────────────────────────────────────────────────
+$V = ""
+if (-not [string]::IsNullOrEmpty($vcsBranch)) {
+ $vcsLabel = if (-not [string]::IsNullOrEmpty($vcsType)) { $vcsType } else { "git" }
+ if ($vcsDirty -eq $true -or $vcsDirty -eq "true") {
+ $V = "${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_RED}${vcsBranch}${FG_BRIGHT_YELLOW}*${R}"
+ } else {
+ $V = "${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_BLUE}${vcsBranch}${R}"
+ }
+}
+
+# ─── Model ───────────────────────────────────────────────────────────────────
+$modelDisp = if (-not [string]::IsNullOrEmpty($modelName)) { $modelName } else { $modelId }
+$M = ""
+if (-not [string]::IsNullOrEmpty($modelDisp)) {
+ $M = "${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${modelDisp}${R}"
+}
+
+# ─── Sandbox Badge ───────────────────────────────────────────────────────────
+if ($sandboxEnabled -eq $true -or $sandboxEnabled -eq "true") {
+ if ($sandboxNet -eq $true -or $sandboxNet -eq "true") {
+ $SB = "${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (net)${R}"
+ } else {
+ $SB = "${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
+ }
+} else {
+ $SB = "${FG_GRAY}🛡️ sandbox off${R}"
+}
+
+# ─── Context Bar ─────────────────────────────────────────────────────────────
+$BAR_LEN = 15
+$pctInt = [int][Math]::Floor($usedPct)
+$filled = [int][Math]::Floor($pctInt * $BAR_LEN / 100)
+$remainder = ($pctInt * $BAR_LEN) % 100
+
+$barColor = $FG_BRIGHT_WHITE
+if ($pctInt -ge 90) {
+ $barColor = $FG_BRIGHT_RED
+} elseif ($pctInt -ge 60) {
+ $barColor = $FG_BRIGHT_YELLOW
+}
+
+$BAR = ""
+for ($i = 0; $i -lt $BAR_LEN; $i++) {
+ if ($i -lt $filled) {
+ $BAR += "█"
+ } elseif ($i -eq $filled) {
+ if ($remainder -ge 75) {
+ $BAR += "▓"
+ } elseif ($remainder -ge 50) {
+ $BAR += "▒"
+ } elseif ($remainder -ge 25) {
+ $BAR += "░"
+ } else {
+ $BAR += "·"
+ }
+ } else {
+ $BAR += "·"
+ }
+}
+
+# ─── Stats Formatting ────────────────────────────────────────────────────────
+$pctFmt = $usedPct.ToString("0.0", [System.Globalization.CultureInfo]::InvariantCulture)
+$CTX_BAR = "${FG_GRAY}ctx ${barColor}${BAR} ${NUM_COLOR}${pctFmt}%${R}"
+$ART_FMT = "${FG_GRAY}📦 ${NUM_COLOR}${artifactCount}${R}"
+$SUB_FMT = "${FG_GRAY}🤖 ${NUM_COLOR}${subagentsCount}${R}"
+$BG_FMT = "${FG_GRAY}⏳ ${NUM_COLOR}${taskCount}${R}"
+
+$DIR_FMT = ""
+if (-not [string]::IsNullOrEmpty($cwdShort)) {
+ $DIR_FMT = "${FG_GRAY} ╱ 📂 ${cwdShort}${R}"
+}
+
+$CONV_FMT = ""
+if (-not [string]::IsNullOrEmpty($convId)) {
+ $subConvId = if ($convId.Length -gt 8) { $convId.Substring(0, 8) } else { $convId }
+ $CONV_FMT = "${FG_GRAY} ╱ id:${subConvId}${R}"
+}
+
+$tokDetails = ""
+if ($ctxUsed -gt 0) {
+ $ctxUsedFmt = Get-HumanFormat $ctxUsed
+ $ctxLimitFmt = Get-HumanFormat $ctxLimit
+ $tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt})"
+}
+
+$DOT = "${FG_GRAY} · ${R}"
+
+# ─── Output Assembly ──────────────────────────────────────────────────────────
+if ($cols -ge 120) {
+ $line1 = "${S}${M}${V}${DIR_FMT}${CONV_FMT}"
+ if ($ctxUsed -gt 0) {
+ $ctxUsedFmt = Get-HumanFormat $ctxUsed
+ $ctxLimitFmt = Get-HumanFormat $ctxLimit
+ $inputTokFmt = Get-HumanFormat $inputTokens
+ $outputTokFmt = Get-HumanFormat $outputTokens
+ $tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt} · ${inputTokFmt} in/${outputTokFmt} out)"
+ }
+ $line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
+ Write-Output "${line1}${FG_GRAY} │ ${R}${line2}"
+} elseif ($cols -ge 80) {
+ $line1 = "${S}${M}${V}${DIR_FMT}"
+ $line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
+ Write-Output "${FG_GRAY}╭─${R} ${line1}"
+ Write-Output "${FG_GRAY}╰─${R}${line2}"
+} else {
+ $mShort = ""
+ if (-not [string]::IsNullOrEmpty($modelDisp)) {
+ $subModelDisp = if ($modelDisp.Length -gt 12) { $modelDisp.Substring(0, 12) } else { $modelDisp }
+ $mShort = "${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${subModelDisp}${R}"
+ }
+ Write-Output "${S}${mShort}"
+ Write-Output "${CTX_BAR}${DOT}${BG_FMT}"
+}
diff --git a/examples/title/README.md b/examples/title/README.md
index 1430ae61b..7070ff1be 100644
--- a/examples/title/README.md
+++ b/examples/title/README.md
@@ -1,18 +1,98 @@
-# CLI Title Example
+# Custom Window Title
-This directory contains an example script (`title.sh`) that demonstrates how to dynamically customize the window title for the Antigravity CLI based on the agent's current state.
+This directory contains reference implementations for dynamically customizing the terminal window title for the Antigravity CLI across different shell environments:
-For more details on how to use and configure the title script, please refer to the official public documentation:
-[https://antigravity.google/docs/cli-title](https://antigravity.google/docs/cli-title)
+1. **`title.sh`** (Bash/Zsh - Linux/macOS)
+2. **`title.js`** (Node.js - Cross-platform: Windows/Linux/macOS)
+3. **`title.ps1`** (PowerShell - Windows PowerShell or PowerShell Core `pwsh`)
+4. **`title.fish`** (Fish shell - Linux/macOS)
+
+## Quick Start
+
+### Option 1: Automatic Setup (Recommended for Bash/Zsh)
+
+Run the included setup script from the root of the repository:
+
+```bash
+bash examples/title/setup.sh
+```
+
+This script will automatically:
+1. Copy `title.sh` to your platform's global settings directory (so it stays configured even if you move or delete this repository).
+2. Configure and enable it in your global `settings.json` file.
+
+### Option 2: Manual configuration
+
+1. Copy the script corresponding to your shell to a directory of your choice.
+2. Edit your `settings.json` file to point `title.command` to the absolute path of the script and set `title.enabled` to `true`:
+
+#### Bash/Zsh (Linux/macOS)
+```json
+{
+ "title": {
+ "command": "/absolute/path/to/title.sh",
+ "enabled": true
+ }
+}
+```
+
+#### Node.js (Cross-platform)
+```json
+{
+ "title": {
+ "command": "node /absolute/path/to/title.js",
+ "enabled": true
+ }
+}
+```
+
+#### PowerShell (Windows / pwsh)
+```json
+{
+ "title": {
+ "command": "powershell.exe -ExecutionPolicy Bypass -File C:\\absolute\\path\\to\\title.ps1",
+ "enabled": true
+ }
+}
+```
+*For PowerShell Core, use `pwsh.exe` or `pwsh` instead of `powershell.exe`.*
+
+#### Fish Shell
+```json
+{
+ "title": {
+ "command": "/absolute/path/to/title.fish",
+ "enabled": true
+ }
+}
+```
+
+**Settings file locations:**
+
+| Platform | Path |
+| :--- | :--- |
+| Linux | `~/.gemini/antigravity-cli/settings.json` |
+| macOS | `~/Library/Application Support/antigravity-cli/settings.json` |
+| Windows | `%APPDATA%\antigravity-cli\settings.json` |
+
+> [!IMPORTANT]
+> The `command` field must be an **absolute path** to the script. Relative paths and `~` expansion are not supported.
+
+After saving, restart `agy` for changes to take effect.
## How it works
-The `title.sh` script reads a JSON payload from standard input, which contains real-time information about the agent's state and context. It then:
-1. Extracts the `agent_state` and `workspace.current_dir` using `jq`.
-2. Parses the current directory to determine a short workspace name (with special handling for CitC workspaces).
+The title script reads a JSON payload from standard input containing the CLI's state. It then:
+1. Extracts the `agent_state` and workspace directory path.
+2. Parses the current directory to determine a short workspace name.
3. Maps the agent state to a corresponding emoji (e.g., 🤔 for thinking, 🛠️ for tool use, 😴 for idle).
4. Outputs the formatted title string in the format: `[Emoji] [State] | [Workspace]`.
+### Prerequisites
+
+- **Bash (`title.sh`) & Fish (`title.fish`)**: Require [`jq`](https://jqlang.org/) to be installed and available in `$PATH`.
+- **Node.js (`title.js`) & PowerShell (`title.ps1`)**: Have **zero external dependencies** and work out-of-the-box.
+
## Examples
### Idle State
diff --git a/examples/title/setup.sh b/examples/title/setup.sh
new file mode 100644
index 000000000..1e7bd96cd
--- /dev/null
+++ b/examples/title/setup.sh
@@ -0,0 +1,98 @@
+#!/bin/bash
+# setup.sh - Installs and enables the custom window title for Antigravity CLI
+
+set -euo pipefail
+
+# 1. Determine the settings directory
+OS="$(uname -s)"
+case "${OS}" in
+ Darwin*)
+ CONFIG_DIR="$HOME/Library/Application Support/antigravity-cli"
+ ;;
+ Linux*)
+ CONFIG_DIR="$HOME/.gemini/antigravity-cli"
+ ;;
+ CYGWIN*|MINGW*|MSYS*)
+ # Windows environments (Git Bash, MSYS)
+ if [ -n "${APPDATA:-}" ]; then
+ CONFIG_DIR="${APPDATA}/antigravity-cli"
+ else
+ CONFIG_DIR="$HOME/AppData/Roaming/antigravity-cli"
+ fi
+ ;;
+ *)
+ # Default fallback to Linux path
+ CONFIG_DIR="$HOME/.gemini/antigravity-cli"
+ ;;
+esac
+
+# Convert config dir to absolute path if needed
+mkdir -p "$CONFIG_DIR"
+CONFIG_DIR="$(cd "$CONFIG_DIR" && pwd)"
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+SOURCE_SCRIPT="$SCRIPT_DIR/title.sh"
+TARGET_SCRIPT="$CONFIG_DIR/title.sh"
+
+echo "Installing title script to: $TARGET_SCRIPT"
+cp "$SOURCE_SCRIPT" "$TARGET_SCRIPT"
+chmod +x "$TARGET_SCRIPT"
+
+SETTINGS_FILE="$CONFIG_DIR/settings.json"
+echo "Configuring settings file at: $SETTINGS_FILE"
+
+# Make sure settings.json exists
+if [ ! -f "$SETTINGS_FILE" ]; then
+ echo "{}" > "$SETTINGS_FILE"
+fi
+
+# Update settings.json using python if available, otherwise fallback to jq
+if command -v python3 >/dev/null 2>&1; then
+ python3 -c '
+import json, sys
+file_path, script_path = sys.argv[1], sys.argv[2]
+try:
+ with open(file_path, "r") as f:
+ data = json.load(f)
+except Exception:
+ data = {}
+if "title" not in data or not isinstance(data["title"], dict):
+ data["title"] = {}
+data["title"]["command"] = script_path
+data["title"]["enabled"] = True
+with open(file_path, "w") as f:
+ json.dump(data, f, indent=2)
+' "$SETTINGS_FILE" "$TARGET_SCRIPT"
+elif command -v python >/dev/null 2>&1; then
+ python -c '
+import json, sys
+file_path, script_path = sys.argv[1], sys.argv[2]
+try:
+ with open(file_path, "r") as f:
+ data = json.load(f)
+except Exception:
+ data = {}
+if "title" not in data or not isinstance(data["title"], dict):
+ data["title"] = {}
+data["title"]["command"] = script_path
+data["title"]["enabled"] = True
+with open(file_path, "w") as f:
+ json.dump(data, f, indent=2)
+' "$SETTINGS_FILE" "$TARGET_SCRIPT"
+elif command -v jq >/dev/null 2>&1; then
+ TEMP_FILE=$(mktemp)
+ jq --arg cmd "$TARGET_SCRIPT" '.title = ((.title // {}) + {command: $cmd, enabled: true})' "$SETTINGS_FILE" > "$TEMP_FILE"
+ mv "$TEMP_FILE" "$SETTINGS_FILE"
+else
+ echo "Error: Neither python3, python, nor jq is installed. Please manually add the following to your $SETTINGS_FILE:"
+ echo "{"
+ echo " \"title\": {"
+ echo " \"command\": \"$TARGET_SCRIPT\","
+ echo " \"enabled\": true"
+ echo " }"
+ echo "}"
+ exit 1
+fi
+
+echo "Terminal window title script successfully installed and enabled!"
+echo "Please restart Antigravity CLI (agy) to see the changes."
diff --git a/examples/title/title.fish b/examples/title/title.fish
new file mode 100755
index 000000000..2090bc13a
--- /dev/null
+++ b/examples/title/title.fish
@@ -0,0 +1,37 @@
+#!/usr/bin/fish
+
+# Read JSON payload from stdin
+set -l DATA (cat)
+
+if not type -q jq
+ echo "idle | unknown"
+ exit 0
+end
+
+set -l STATE (echo $DATA | jq -r '.agent_state // "idle"')
+set -l CWD (echo $DATA | jq -r '.workspace.current_dir // .cwd // ""')
+
+set -l WORKSPACE "unknown"
+if test -n "$CWD"
+ if string match -r '/google/src/cloud/[^/]+/([^/]+)' $CWD >/dev/null
+ set WORKSPACE (string replace -r '.*/google/src/cloud/[^/]+/([^/]+).*' '$1' $CWD)
+ else
+ set WORKSPACE (basename $CWD)
+ end
+end
+
+set -l EMOJI "🤖"
+switch $STATE
+ case initializing
+ set EMOJI "🚀"
+ case idle
+ set EMOJI "😴"
+ case thinking
+ set EMOJI "🤔"
+ case working
+ set EMOJI "🏃"
+ case tool_use
+ set EMOJI "🛠️"
+end
+
+echo "$EMOJI $STATE | $WORKSPACE"
diff --git a/examples/title/title.js b/examples/title/title.js
new file mode 100755
index 000000000..bad3305aa
--- /dev/null
+++ b/examples/title/title.js
@@ -0,0 +1,53 @@
+#!/usr/bin/env node
+const fs = require('fs');
+const path = require('path');
+
+// ─── Read JSON from stdin ────────────────────────────────────────────────────
+let rawData = '';
+try {
+ rawData = fs.readFileSync(0, 'utf-8');
+} catch (e) {
+ // Silence read errors
+}
+
+let data = {};
+try {
+ data = JSON.parse(rawData || '{}');
+} catch (e) {
+ // Silence JSON parse errors
+}
+
+const state = data.agent_state || "idle";
+const cwd = (data.workspace && data.workspace.current_dir) || data.cwd || "";
+
+let workspace = "unknown";
+if (cwd) {
+ const match = cwd.match(/\/google\/src\/cloud\/[^/]+\/([^/]+)/);
+ if (match) {
+ workspace = match[1];
+ } else {
+ workspace = path.basename(cwd);
+ }
+}
+
+// Map state to emoji
+let emoji = "🤖";
+switch (state) {
+ case 'initializing':
+ emoji = "🚀";
+ break;
+ case 'idle':
+ emoji = "😴";
+ break;
+ case 'thinking':
+ emoji = "🤔";
+ break;
+ case 'working':
+ emoji = "🏃";
+ break;
+ case 'tool_use':
+ emoji = "🛠️";
+ break;
+}
+
+console.log(`${emoji} ${state} | ${workspace}`);
diff --git a/examples/title/title.ps1 b/examples/title/title.ps1
new file mode 100644
index 000000000..520c4384b
--- /dev/null
+++ b/examples/title/title.ps1
@@ -0,0 +1,39 @@
+# ─── Read JSON from stdin ────────────────────────────────────────────────────
+$inputJson = [Console]::In.ReadToEnd()
+if ([string]::IsNullOrWhiteSpace($inputJson)) {
+ $inputJson = '{}'
+}
+
+try {
+ $data = ConvertFrom-Json $inputJson -ErrorAction SilentlyContinue
+} catch {
+ $data = $null
+}
+
+if ($null -eq $data) {
+ $data = [PSCustomObject]@{}
+}
+
+$state = if ($data.agent_state) { $data.agent_state } else { "idle" }
+$cwd = if ($data.workspace -and $data.workspace.current_dir) { $data.workspace.current_dir } elseif ($data.cwd) { $data.cwd } else { "" }
+
+$workspace = "unknown"
+if (-not [string]::IsNullOrEmpty($cwd)) {
+ if ($cwd -match '/google/src/cloud/[^/]+/([^/]+)') {
+ $workspace = $Matches[1]
+ } else {
+ $workspace = Split-Path $cwd -Leaf
+ }
+}
+
+# Map state to emoji
+switch ($state) {
+ "initializing" { $emoji = "🚀" }
+ "idle" { $emoji = "😴" }
+ "thinking" { $emoji = "🤔" }
+ "working" { $emoji = "🏃" }
+ "tool_use" { $emoji = "🛠️" }
+ Default { $emoji = "🤖" }
+}
+
+Write-Output "${emoji} ${state} | ${workspace}"
From 896b0b9eed6a5765329c7ffbb88eae317ce80572 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Fri, 29 May 2026 14:46:54 +0300
Subject: [PATCH 21/28] fix(statusline,title): improve stdin parsing and JSON
parsing validation for cross-platform scripts
---
examples/statusline/statusline.fish | 12 ++++++------
examples/statusline/statusline.js | 4 +++-
examples/statusline/statusline.ps1 | 18 +++++++++++++++---
examples/title/title.js | 3 ++-
examples/title/title.ps1 | 16 +++++++++++++---
5 files changed, 39 insertions(+), 14 deletions(-)
diff --git a/examples/statusline/statusline.fish b/examples/statusline/statusline.fish
index fae25d7d5..ed35a1200 100755
--- a/examples/statusline/statusline.fish
+++ b/examples/statusline/statusline.fish
@@ -136,9 +136,9 @@ end
set -l SB ""
if test "$SANDBOX" = "true"
if test "$SANDBOX_NET" = "true"
- set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B"ON (net)"$R"
+ set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B ON (net)$R"
else
- set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B"ON (no-net)"$R"
+ set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B ON (no-net)$R"
end
else
set SB "$FG_GRAY🛡️ sandbox off$R"
@@ -178,10 +178,10 @@ end
# Stats & Metadata
set -l PCT_FMT (printf "%.1f" $USED_PCT)
-set -l CTX_BAR "$FG_GRAY"ctx "$BAR_COLOR$BAR $NUM_COLOR$PCT_FMT%$R"
-set -l ART_FMT "$FG_GRAY"📦 "$NUM_COLOR$ARTIFACTS$R"
-set -l SUB_FMT "$FG_GRAY"🤖 "$NUM_COLOR$SUBAGENTS$R"
-set -l BG_FMT "$FG_GRAY"⏳ "$NUM_COLOR$BG_TASKS$R"
+set -l CTX_BAR "$FG_GRAY ctx $BAR_COLOR$BAR $NUM_COLOR$PCT_FMT%$R"
+set -l ART_FMT "$FG_GRAY 📦 $NUM_COLOR$ARTIFACTS$R"
+set -l SUB_FMT "$FG_GRAY 🤖 $NUM_COLOR$SUBAGENTS$R"
+set -l BG_FMT "$FG_GRAY ⏳ $NUM_COLOR$BG_TASKS$R"
set -l DIR_FMT ""
if test -n "$CWD_SHORT"
diff --git a/examples/statusline/statusline.js b/examples/statusline/statusline.js
index a8fcc8142..2010ec36e 100755
--- a/examples/statusline/statusline.js
+++ b/examples/statusline/statusline.js
@@ -15,7 +15,9 @@ let data = {};
try {
data = JSON.parse(rawData || '{}');
} catch (e) {
- // Silence JSON parse errors
+ // Output a clear error message in the statusline to help debugging
+ console.log(`\x1b[31m● ERROR: Invalid JSON input to statusline.js (${e.message})\x1b[0m`);
+ process.exit(0);
}
// ─── ANSI Helpers (Standard 16-color palette only) ───────────────────────────
diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1
index e46f8c602..eee453280 100644
--- a/examples/statusline/statusline.ps1
+++ b/examples/statusline/statusline.ps1
@@ -1,13 +1,25 @@
# ─── Read JSON from stdin ────────────────────────────────────────────────────
-$inputJson = [Console]::In.ReadToEnd()
+param(
+ [Parameter(ValueFromPipeline)]
+ [string]$inputJson
+)
+
+if ([string]::IsNullOrWhiteSpace($inputJson)) {
+ # Try reading from Console stdin (for redirected stdin from Go/agy.exe)
+ $inputJson = [Console]::In.ReadToEnd()
+}
+
if ([string]::IsNullOrWhiteSpace($inputJson)) {
$inputJson = '{}'
}
try {
- $data = ConvertFrom-Json $inputJson -ErrorAction SilentlyContinue
+ # -ErrorAction Stop is needed so catch block is triggered on invalid JSON
+ $data = ConvertFrom-Json $inputJson -ErrorAction Stop
} catch {
- $data = $null
+ # Output the JSON parsing error in red to make debugging copy-pasted blocks easy
+ Write-Output "$([char]0x1b)[31m● ERROR: Invalid JSON input to statusline.ps1 ($($_.Exception.Message))$([char]0x1b)[0m"
+ exit
}
if ($null -eq $data) {
diff --git a/examples/title/title.js b/examples/title/title.js
index bad3305aa..8e5089b36 100755
--- a/examples/title/title.js
+++ b/examples/title/title.js
@@ -14,7 +14,8 @@ let data = {};
try {
data = JSON.parse(rawData || '{}');
} catch (e) {
- // Silence JSON parse errors
+ console.log(`🤖 ERROR: Invalid JSON (${e.message})`);
+ process.exit(0);
}
const state = data.agent_state || "idle";
diff --git a/examples/title/title.ps1 b/examples/title/title.ps1
index 520c4384b..975bcda31 100644
--- a/examples/title/title.ps1
+++ b/examples/title/title.ps1
@@ -1,13 +1,23 @@
# ─── Read JSON from stdin ────────────────────────────────────────────────────
-$inputJson = [Console]::In.ReadToEnd()
+param(
+ [Parameter(ValueFromPipeline)]
+ [string]$inputJson
+)
+
+if ([string]::IsNullOrWhiteSpace($inputJson)) {
+ # Try reading from Console stdin (for redirected stdin from Go/agy.exe)
+ $inputJson = [Console]::In.ReadToEnd()
+}
+
if ([string]::IsNullOrWhiteSpace($inputJson)) {
$inputJson = '{}'
}
try {
- $data = ConvertFrom-Json $inputJson -ErrorAction SilentlyContinue
+ $data = ConvertFrom-Json $inputJson -ErrorAction Stop
} catch {
- $data = $null
+ Write-Output "🤖 ERROR: Invalid JSON ($($_.Exception.Message))"
+ exit
}
if ($null -eq $data) {
From f6449bfa4c33b23d8f7e015fb2425d1372f23c13 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Tue, 2 Jun 2026 22:54:16 +0300
Subject: [PATCH 22/28] feat(statusline): redesign statusline with Nerd Font
styling
---
examples/statusline/statusline.fish | 111 ++++++++++++--------
examples/statusline/statusline.js | 101 +++++++++++-------
examples/statusline/statusline.ps1 | 92 +++++++++++------
examples/statusline/statusline.sh | 154 ++++++++++++++++------------
4 files changed, 286 insertions(+), 172 deletions(-)
diff --git a/examples/statusline/statusline.fish b/examples/statusline/statusline.fish
index ed35a1200..45e9fabaa 100755
--- a/examples/statusline/statusline.fish
+++ b/examples/statusline/statusline.fish
@@ -1,4 +1,4 @@
-#!/usr/bin/fish
+#!/usr/bin/env fish
# Read JSON payload from stdin
set -l DATA (cat)
@@ -30,6 +30,22 @@ set -l OUTPUT_TOKENS (echo $DATA | jq -r '.context_window.total_output_tokens //
set -l CTX_LIMIT (echo $DATA | jq -r '.context_window.context_window_size // 0')
set -l CTX_USED (echo $DATA | jq -r '(.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)')
+# ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
+set -l GIT_DIR $CWD
+if test -z "$GIT_DIR"
+ set GIT_DIR "."
+end
+set -l GIT_BRANCH (git -C "$GIT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null)
+if test -n "$GIT_BRANCH"
+ set VCS_BRANCH $GIT_BRANCH
+ set VCS_TYPE "git"
+ if git -C "$GIT_DIR" status --porcelain 2>/dev/null | grep -q .
+ set VCS_DIRTY "true"
+ else
+ set VCS_DIRTY "false"
+ end
+end
+
# ANSI Helpers
set -l R (set_color normal)
set -l B (set_color -o)
@@ -55,15 +71,15 @@ set -l FG_BRIGHT_WHITE (set_color brwhite)
set -l NUM_COLOR "$FG_BRIGHT_WHITE$B"
function human_format -a num
- if test -z "$num"; or test "$num" -eq 0
+ if test -z "$num"; or test "$num" -eq 0 2>/dev/null
echo "0"
return
end
- if test "$num" -ge 1000000
+ if test "$num" -ge 1000000 2>/dev/null
set -l main (math -s0 "$num / 1000000")
set -l dec (math -s0 "($num % 1000000) / 100000")
echo "$main.$dec"M
- elif test "$num" -ge 1000
+ elif test "$num" -ge 1000 2>/dev/null
set -l main (math -s0 "$num / 1000")
set -l dec (math -s0 "($num % 1000) / 100")
echo "$main.$dec"K
@@ -92,33 +108,50 @@ function shorten_path -a path
end
set -l CWD_SHORT (shorten_path $CWD)
+# Helper for visible length
+function visible_len -a str
+ set -l stripped (string replace -a -r '\x1b\[[0-9;]*m' '' $str)
+ string length $stripped
+end
+
+# Right align helper
+function print_right_aligned -a left right total_cols
+ set -l left_vis (visible_len $left)
+ set -l right_vis (visible_len $right)
+ set -l pad (math "$total_cols - $left_vis - $right_vis")
+ if test "$pad" -lt 1
+ set pad 1
+ end
+ set -l spaces (string repeat -n $pad " ")
+ echo -e "$left$spaces$right"
+end
+
# State Indicator
set -l S ""
switch $STATE
case idle
- set S "$FG_BRIGHT_GREEN$B● READY$R"
+ set S "$FG_BRIGHT_GREEN$B READY$R"
case thinking
- set S "$FG_BRIGHT_YELLOW$B◆ THINKING$R"
+ set S "$FG_BRIGHT_YELLOW$B THINKING$R"
case working
- set S "$FG_BRIGHT_CYAN$B⚙ WORKING$R"
+ set S "$FG_BRIGHT_CYAN$B WORKING$R"
case tool_use
- set S "$FG_BRIGHT_MAGENTA$B🔧 TOOL$R"
+ set S "$FG_BRIGHT_MAGENTA$B TOOL$R"
case '*'
set -l upper_state (string upper $STATE)
- set S "$FG_WHITE$B⏳ $upper_state$R"
+ set S "$FG_WHITE$B $upper_state$R"
end
+# Separators
+set -l DOT "$FG_GRAY | $R"
+
# VCS Branch & Type
set -l V ""
if test -n "$VCS_BRANCH"
- set -l vcs_label "git"
- if test -n "$VCS_TYPE"
- set vcs_label $VCS_TYPE
- end
if test "$VCS_DIRTY" = "true"
- set V "$FG_GRAY ╱ $FG_GRAY$vcs_label:$FG_BRIGHT_RED$VCS_BRANCH$FG_BRIGHT_YELLOW*$R"
+ set V "$DOT$R$FG_BRIGHT_RED $VCS_BRANCH$FG_BRIGHT_YELLOW*$R"
else
- set V "$FG_GRAY ╱ $FG_GRAY$vcs_label:$FG_BRIGHT_BLUE$VCS_BRANCH$R"
+ set V "$DOT$R$FG_BRIGHT_BLUE $VCS_BRANCH$R"
end
end
@@ -129,28 +162,28 @@ if test -z "$MODEL_DISP"
end
set -l M ""
if test -n "$MODEL_DISP"
- set M "$FG_GRAY ╱ $FG_BRIGHT_MAGENTA$I$MODEL_DISP$R"
+ set M "$FG_GRAY$DOT$FG_BRIGHT_MAGENTA$I $R$MODEL_DISP$R"
end
# Sandbox Badge
set -l SB ""
if test "$SANDBOX" = "true"
if test "$SANDBOX_NET" = "true"
- set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B ON (net)$R"
+ set SB "$FG_GREEN $FG_BRIGHT_GREEN$B ON (net)$R"
else
- set SB "$FG_GRAY🛡️ sandbox $FG_BRIGHT_GREEN$B ON (no-net)$R"
+ set SB "$FG_GREEN $FG_BRIGHT_GREEN$B ON (no-net)$R"
end
else
- set SB "$FG_GRAY🛡️ sandbox off$R"
+ set SB "$FG_RED $FG_BRIGHT_RED$B OFF$R"
end
-# Context Bar
-set -l BAR_LEN 15
+# Context Bar (20 segments)
+set -l BAR_LEN 20
set -l PCT_INT (math -s0 "$USED_PCT")
set -l FILLED (math -s0 "$PCT_INT * $BAR_LEN / 100")
set -l REMAINDER (math -s0 "($PCT_INT * $BAR_LEN) % 100")
-set -l BAR_COLOR $FG_BRIGHT_WHITE
+set -l BAR_COLOR $FG_YELLOW
if test "$PCT_INT" -ge 90
set BAR_COLOR $FG_BRIGHT_RED
elif test "$PCT_INT" -ge 60
@@ -178,41 +211,39 @@ end
# Stats & Metadata
set -l PCT_FMT (printf "%.1f" $USED_PCT)
-set -l CTX_BAR "$FG_GRAY ctx $BAR_COLOR$BAR $NUM_COLOR$PCT_FMT%$R"
-set -l ART_FMT "$FG_GRAY 📦 $NUM_COLOR$ARTIFACTS$R"
-set -l SUB_FMT "$FG_GRAY 🤖 $NUM_COLOR$SUBAGENTS$R"
-set -l BG_FMT "$FG_GRAY ⏳ $NUM_COLOR$BG_TASKS$R"
+set -l CTX_BAR "$FG_YELLOW $R$BAR_COLOR$BAR$R $NUM_COLOR$PCT_FMT%$R"
+set -l ART_FMT "$FG_BLUE $NUM_COLOR$ARTIFACTS$R"
+set -l SUB_FMT "$FG_CYAN $NUM_COLOR$SUBAGENTS$R"
+set -l BG_FMT "$FG_MAGENTA $NUM_COLOR$BG_TASKS$R"
set -l DIR_FMT ""
if test -n "$CWD_SHORT"
- set DIR_FMT "$FG_GRAY ╱ 📂 $CWD_SHORT$R"
+ set DIR_FMT "$FG_GRAY$DOT$FG_CYAN $R$CWD_SHORT$R"
end
set -l CONV_FMT ""
if test -n "$CONV_ID"
set -l sub_conv (string sub -l 8 $CONV_ID)
- set CONV_FMT "$FG_GRAY ╱ id:$sub_conv$R"
+ set CONV_FMT "$FG_GRAY$DOT$FG_GRAY $sub_conv$R"
end
set -l TOK_DETAILS ""
-if test "$CTX_USED" -gt 0
+if test "$CTX_USED" -gt 0 2>/dev/null
set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT)"
end
-set -l DOT "$FG_GRAY · $R"
-
# Output Assembly
-if test "$COLS" -ge 120
- set -l line1 "$S$M$V$DIR_FMT$CONV_FMT"
- if test "$CTX_USED" -gt 0
- set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT · $INPUT_TOK_FMT in/$OUTPUT_TOK_FMT out)"
+if test "$COLS" -ge 180
+ set -l line1 "$S$M$DIR_FMT$V$CONV_FMT"
+ if test "$CTX_USED" -gt 0 2>/dev/null
+ set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT)$DOT$FG_YELLOW $R ($INPUT_TOK_FMT in/$OUTPUT_TOK_FMT out)"
end
+ set -l line2 "$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB$DOT$CTX_BAR$TOK_DETAILS"
+ print_right_aligned $line1 $line2 $COLS
+elif test "$COLS" -ge 90
+ set -l line1 "$S$M$DIR_FMT$V"
set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB"
- echo -e "$line1$FG_GRAY │ $R$line2"
-elif test "$COLS" -ge 80
- set -l line1 "$S$M$V$DIR_FMT"
- set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB"
- echo -e "$FG_GRAY╭─$R $line1"
+ echo -e "$FG_GRAY╭─$R$line1"
echo -e "$FG_GRAY╰─$R$line2"
else
set -l M_SHORT ""
diff --git a/examples/statusline/statusline.js b/examples/statusline/statusline.js
index 2010ec36e..caec895c2 100755
--- a/examples/statusline/statusline.js
+++ b/examples/statusline/statusline.js
@@ -2,6 +2,7 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
+const execSync = require('child_process').execSync;
// ─── Read JSON from stdin ────────────────────────────────────────────────────
let rawData = '';
@@ -50,9 +51,9 @@ const NUM_COLOR = FG_BRIGHT_WHITE + B;
// ─── Extract fields with fallbacks ───────────────────────────────────────────
const state = data.agent_state || "idle";
const usedPct = (data.context_window && typeof data.context_window.used_percentage === 'number') ? data.context_window.used_percentage : 0;
-const vcsBranch = (data.vcs && data.vcs.branch) ? data.vcs.branch : "";
-const vcsDirty = (data.vcs && data.vcs.dirty) ? data.vcs.dirty : false;
-const vcsType = (data.vcs && data.vcs.type) ? data.vcs.type : "";
+let vcsBranch = (data.vcs && data.vcs.branch) ? data.vcs.branch : "";
+let vcsDirty = (data.vcs && data.vcs.dirty) ? data.vcs.dirty : false;
+let vcsType = (data.vcs && data.vcs.type) ? data.vcs.type : "";
const sandboxEnabled = (data.sandbox && data.sandbox.enabled) ? data.sandbox.enabled : false;
const sandboxNet = (data.sandbox && data.sandbox.allow_network) ? data.sandbox.allow_network : false;
const artifactCount = data.artifact_count || 0;
@@ -68,6 +69,20 @@ const outputTokens = (data.context_window && data.context_window.total_output_to
const ctxLimit = (data.context_window && data.context_window.context_window_size) ? data.context_window.context_window_size : 0;
const ctxUsed = inputTokens + outputTokens;
+// ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
+try {
+ const gitDir = cwd || ".";
+ const branch = execSync(`git -C "${gitDir}" rev-parse --abbrev-ref HEAD 2>/dev/null`, { encoding: 'utf8' }).trim();
+ if (branch) {
+ vcsBranch = branch;
+ vcsType = "git";
+ const status = execSync(`git -C "${gitDir}" status --porcelain 2>/dev/null`, { encoding: 'utf8' }).trim();
+ vcsDirty = status.length > 0;
+ }
+} catch (e) {
+ // Ignore git errors
+}
+
// ─── Helper Formatting Functions ─────────────────────────────────────────────
function humanFormat(num) {
if (!num || isNaN(num) || num === 0) return "0";
@@ -94,33 +109,49 @@ function shortenPath(p) {
const cwdShort = shortenPath(cwd);
-// ─── State Indicator (No background colors) ──────────────────────────────────
+// ─── Strip ANSI escapes to measure visible length ────────────────────────────
+function visibleLen(str) {
+ return str.replace(/\x1b\[[0-9;]*m/g, '').length;
+}
+
+// ─── Right-align helper ──────────────────────────────────────────────────────
+function printRightAligned(left, right, totalCols) {
+ const leftVis = visibleLen(left);
+ const rightVis = visibleLen(right);
+ let pad = totalCols - leftVis - rightVis;
+ if (pad < 1) pad = 1;
+ console.log(left + " ".repeat(pad) + right);
+}
+
+// ─── State Indicator ─────────────────────────────────────────────────────────
let S = "";
switch (state) {
case 'idle':
- S = `${FG_BRIGHT_GREEN}${B}● READY${R}`;
+ S = `${FG_BRIGHT_GREEN}${B} READY${R}`;
break;
case 'thinking':
- S = `${FG_BRIGHT_YELLOW}${B}◆ THINKING${R}`;
+ S = `${FG_BRIGHT_YELLOW}${B} THINKING${R}`;
break;
case 'working':
- S = `${FG_BRIGHT_CYAN}${B}⚙ WORKING${R}`;
+ S = `${FG_BRIGHT_CYAN}${B} WORKING${R}`;
break;
case 'tool_use':
- S = `${FG_BRIGHT_MAGENTA}${B}🔧 TOOL${R}`;
+ S = `${FG_BRIGHT_MAGENTA}${B} TOOL${R}`;
break;
default:
- S = `${FG_WHITE}${B}⏳ ${state.toUpperCase()}${R}`;
+ S = `${FG_WHITE}${B} ${state.toUpperCase()}${R}`;
}
+// ─── Separators ──────────────────────────────────────────────────────────────
+const dot = `${FG_GRAY} | ${R}`;
+
// ─── VCS Branch & Type ───────────────────────────────────────────────────────
let V = "";
if (vcsBranch) {
- const vcsLabel = vcsType || "git";
if (vcsDirty || vcsDirty === "true") {
- V = `${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_RED}${vcsBranch}${FG_BRIGHT_YELLOW}*${R}`;
+ V = `${dot}${R}${FG_BRIGHT_RED} ${vcsBranch}${FG_BRIGHT_YELLOW}*${R}`;
} else {
- V = `${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_BLUE}${vcsBranch}${R}`;
+ V = `${dot}${R}${FG_BRIGHT_BLUE} ${vcsBranch}${R}`;
}
}
@@ -128,28 +159,28 @@ if (vcsBranch) {
const modelDisp = modelName || modelId;
let M = "";
if (modelDisp) {
- M = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${modelDisp}${R}`;
+ M = `${FG_GRAY}${dot}${FG_BRIGHT_MAGENTA}${I} ${R}${modelDisp}${R}`;
}
// ─── Sandbox Badge ───────────────────────────────────────────────────────────
let SB = "";
if (sandboxEnabled || sandboxEnabled === "true") {
if (sandboxNet || sandboxNet === "true") {
- SB = `${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (net)${R}`;
+ SB = `${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (net)${R}`;
} else {
- SB = `${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}`;
+ SB = `${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}`;
}
} else {
- SB = `${FG_GRAY}🛡️ sandbox off${R}`;
+ SB = `${FG_RED} ${FG_BRIGHT_RED}${B}OFF${R}`;
}
-// ─── Context Bar (15 segments, fine-grain Unicode) ────────────────────────────
-const barLen = 15;
+// ─── Context Bar (20 segments) ───────────────────────────────────────────────
+const barLen = 20;
const pctInt = Math.floor(usedPct);
const filled = Math.floor((pctInt * barLen) / 100);
const remainder = (pctInt * barLen) % 100;
-let barColor = FG_BRIGHT_WHITE;
+let barColor = FG_YELLOW;
if (pctInt >= 90) {
barColor = FG_BRIGHT_RED;
} else if (pctInt >= 60) {
@@ -177,19 +208,19 @@ for (let i = 0; i < barLen; i++) {
// ─── Stats & Metadata formatting ─────────────────────────────────────────────
const pctFmt = usedPct.toFixed(1);
-const ctxBar = `${FG_GRAY}ctx ${barColor}${bar} ${NUM_COLOR}${pctFmt}%${R}`;
-const artFmt = `${FG_GRAY}📦 ${NUM_COLOR}${artifactCount}${R}`;
-const subFmt = `${FG_GRAY}🤖 ${NUM_COLOR}${subagentsCount}${R}`;
-const bgFmt = `${FG_GRAY}⏳ ${NUM_COLOR}${taskCount}${R}`;
+const ctxBar = `${FG_YELLOW} ${R}${barColor}${bar}${R} ${NUM_COLOR}${pctFmt}%${R}`;
+const artFmt = `${FG_BLUE} ${NUM_COLOR}${artifactCount}${R}`;
+const subFmt = `${FG_CYAN} ${NUM_COLOR}${subagentsCount}${R}`;
+const bgFmt = `${FG_MAGENTA} ${NUM_COLOR}${taskCount}${R}`;
let dirFmt = "";
if (cwdShort) {
- dirFmt = `${FG_GRAY} ╱ 📂 ${cwdShort}${R}`;
+ dirFmt = `${FG_GRAY}${dot}${FG_CYAN} ${R}${cwdShort}${R}`;
}
let convFmt = "";
if (convId) {
- convFmt = `${FG_GRAY} ╱ id:${convId.slice(0, 8)}${R}`;
+ convFmt = `${FG_GRAY}${dot}${FG_GRAY} ${convId.slice(0, 8)}${R}`;
}
let tokDetails = "";
@@ -197,25 +228,23 @@ if (ctxUsed > 0) {
tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)})`;
}
-const dot = `${FG_GRAY} · ${R}`;
-
// ─── Output Assembly ──────────────────────────────────────────────────────────
-if (cols >= 120) {
- let line1 = `${S}${M}${V}${dirFmt}${convFmt}`;
+if (cols >= 180) {
+ let line1 = `${S}${M}${dirFmt}${V}${convFmt}`;
if (ctxUsed > 0) {
- tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)} · ${humanFormat(inputTokens)} in/${humanFormat(outputTokens)} out)`;
+ tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)})${dot}${FG_YELLOW} ${R} (${humanFormat(inputTokens)} in/${humanFormat(outputTokens)} out)`;
}
+ let line2 = `${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}${dot}${ctxBar}${tokDetails}`;
+ printRightAligned(line1, line2, cols);
+} else if (cols >= 90) {
+ let line1 = `${S}${M}${dirFmt}${V}`;
let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}`;
- console.log(`${line1}${FG_GRAY} │ ${R}${line2}`);
-} else if (cols >= 80) {
- let line1 = `${S}${M}${V}${dirFmt}`;
- let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}`;
- console.log(`${FG_GRAY}╭─${R} ${line1}`);
+ console.log(`${FG_GRAY}╭─${R}${line1}`);
console.log(`${FG_GRAY}╰─${R}${line2}`);
} else {
let mShort = "";
if (modelDisp) {
- mShort = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${modelDisp.slice(0, 12)}${R}`;
+ mShort = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${modelDisp}${R}`;
}
console.log(`${S}${mShort}`);
console.log(`${ctxBar}${dot}${bgFmt}`);
diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1
index eee453280..199314676 100644
--- a/examples/statusline/statusline.ps1
+++ b/examples/statusline/statusline.ps1
@@ -74,6 +74,20 @@ $outputTokens = if ($data.context_window -and $null -ne $data.context_window.tot
$ctxLimit = if ($data.context_window -and $null -ne $data.context_window.context_window_size) { $data.context_window.context_window_size } else { 0 }
$ctxUsed = $inputTokens + $outputTokens
+# ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
+try {
+ $gitDir = if ($cwd) { $cwd } else { "." }
+ $branch = & git -C "$gitDir" rev-parse --abbrev-ref HEAD 2>$null
+ if ($branch) {
+ $vcsBranch = $branch.Trim()
+ $vcsType = "git"
+ $status = & git -C "$gitDir" status --porcelain 2>$null
+ $vcsDirty = if ($status) { $true } else { $false }
+ }
+} catch {
+ # Ignore git errors
+}
+
# ─── Helper Functions ────────────────────────────────────────────────────────
function Get-HumanFormat {
param ($num)
@@ -108,25 +122,43 @@ function Get-ShortenPath {
return $path
}
+function Get-VisibleLen {
+ param ($str)
+ $stripped = $str -replace '\x1b\[[0-9;]*m', ''
+ return $stripped.Length
+}
+
+function Write-RightAligned {
+ param ($left, $right, $totalCols)
+ $leftVis = Get-VisibleLen $left
+ $rightVis = Get-VisibleLen $right
+ $pad = $totalCols - $leftVis - $rightVis
+ if ($pad -lt 1) { $pad = 1 }
+ $spaces = " " * $pad
+ Write-Output "${left}${spaces}${right}"
+}
+
$cwdShort = Get-ShortenPath $cwd
# ─── State Indicator ──────────────────────────────────────────────────────────
switch ($state) {
- "idle" { $S = "${FG_BRIGHT_GREEN}${B}● READY${R}" }
- "thinking" { $S = "${FG_BRIGHT_YELLOW}${B}◆ THINKING${R}" }
- "working" { $S = "${FG_BRIGHT_CYAN}${B}⚙ WORKING${R}" }
- "tool_use" { $S = "${FG_BRIGHT_MAGENTA}${B}🔧 TOOL${R}" }
- Default { $S = "${FG_WHITE}${B}⏳ $($state.ToUpper())${R}" }
+ "idle" { $S = "${FG_BRIGHT_GREEN}${B} READY${R}" }
+ "thinking" { $S = "${FG_BRIGHT_YELLOW}${B} THINKING${R}" }
+ "working" { $S = "${FG_BRIGHT_CYAN}${B} WORKING${R}" }
+ "tool_use" { $S = "${FG_BRIGHT_MAGENTA}${B} TOOL${R}" }
+ Default { $S = "${FG_WHITE}${B} $($state.ToUpper())${R}" }
}
+# ─── Separators ──────────────────────────────────────────────────────────────
+$DOT = "${FG_GRAY} | ${R}"
+
# ─── VCS Branch & Type ───────────────────────────────────────────────────────
$V = ""
if (-not [string]::IsNullOrEmpty($vcsBranch)) {
- $vcsLabel = if (-not [string]::IsNullOrEmpty($vcsType)) { $vcsType } else { "git" }
if ($vcsDirty -eq $true -or $vcsDirty -eq "true") {
- $V = "${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_RED}${vcsBranch}${FG_BRIGHT_YELLOW}*${R}"
+ $V = "${DOT}${R}${FG_BRIGHT_RED} ${vcsBranch}${FG_BRIGHT_YELLOW}*${R}"
} else {
- $V = "${FG_GRAY} ╱ ${FG_GRAY}${vcsLabel}:${FG_BRIGHT_BLUE}${vcsBranch}${R}"
+ $V = "${DOT}${R}${FG_BRIGHT_BLUE} ${vcsBranch}${R}"
}
}
@@ -134,27 +166,27 @@ if (-not [string]::IsNullOrEmpty($vcsBranch)) {
$modelDisp = if (-not [string]::IsNullOrEmpty($modelName)) { $modelName } else { $modelId }
$M = ""
if (-not [string]::IsNullOrEmpty($modelDisp)) {
- $M = "${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${modelDisp}${R}"
+ $M = "${FG_GRAY}${DOT}${FG_BRIGHT_MAGENTA}${I} ${R}${modelDisp}${R}"
}
# ─── Sandbox Badge ───────────────────────────────────────────────────────────
if ($sandboxEnabled -eq $true -or $sandboxEnabled -eq "true") {
if ($sandboxNet -eq $true -or $sandboxNet -eq "true") {
- $SB = "${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (net)${R}"
+ $SB = "${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (net)${R}"
} else {
- $SB = "${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
+ $SB = "${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
}
} else {
- $SB = "${FG_GRAY}🛡️ sandbox off${R}"
+ $SB = "${FG_RED} ${FG_BRIGHT_RED}${B}OFF${R}"
}
-# ─── Context Bar ─────────────────────────────────────────────────────────────
-$BAR_LEN = 15
+# ─── Context Bar (20 segments) ───────────────────────────────────────────────
+$BAR_LEN = 20
$pctInt = [int][Math]::Floor($usedPct)
$filled = [int][Math]::Floor($pctInt * $BAR_LEN / 100)
$remainder = ($pctInt * $BAR_LEN) % 100
-$barColor = $FG_BRIGHT_WHITE
+$barColor = $FG_YELLOW
if ($pctInt -ge 90) {
$barColor = $FG_BRIGHT_RED
} elseif ($pctInt -ge 60) {
@@ -182,20 +214,20 @@ for ($i = 0; $i -lt $BAR_LEN; $i++) {
# ─── Stats Formatting ────────────────────────────────────────────────────────
$pctFmt = $usedPct.ToString("0.0", [System.Globalization.CultureInfo]::InvariantCulture)
-$CTX_BAR = "${FG_GRAY}ctx ${barColor}${BAR} ${NUM_COLOR}${pctFmt}%${R}"
-$ART_FMT = "${FG_GRAY}📦 ${NUM_COLOR}${artifactCount}${R}"
-$SUB_FMT = "${FG_GRAY}🤖 ${NUM_COLOR}${subagentsCount}${R}"
-$BG_FMT = "${FG_GRAY}⏳ ${NUM_COLOR}${taskCount}${R}"
+$CTX_BAR = "${FG_YELLOW} ${R}${barColor}${BAR}${R} ${NUM_COLOR}${pctFmt}%${R}"
+$ART_FMT = "${FG_BLUE} ${NUM_COLOR}${artifactCount}${R}"
+$SUB_FMT = "${FG_CYAN} ${NUM_COLOR}${subagentsCount}${R}"
+$BG_FMT = "${FG_MAGENTA} ${NUM_COLOR}${taskCount}${R}"
$DIR_FMT = ""
if (-not [string]::IsNullOrEmpty($cwdShort)) {
- $DIR_FMT = "${FG_GRAY} ╱ 📂 ${cwdShort}${R}"
+ $DIR_FMT = "${FG_GRAY}${DOT}${FG_CYAN} ${R}${cwdShort}${R}"
}
$CONV_FMT = ""
if (-not [string]::IsNullOrEmpty($convId)) {
$subConvId = if ($convId.Length -gt 8) { $convId.Substring(0, 8) } else { $convId }
- $CONV_FMT = "${FG_GRAY} ╱ id:${subConvId}${R}"
+ $CONV_FMT = "${FG_GRAY}${DOT}${FG_GRAY} ${subConvId}${R}"
}
$tokDetails = ""
@@ -205,24 +237,22 @@ if ($ctxUsed -gt 0) {
$tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt})"
}
-$DOT = "${FG_GRAY} · ${R}"
-
# ─── Output Assembly ──────────────────────────────────────────────────────────
-if ($cols -ge 120) {
- $line1 = "${S}${M}${V}${DIR_FMT}${CONV_FMT}"
+if ($cols -ge 180) {
+ $line1 = "${S}${M}${DIR_FMT}${V}${CONV_FMT}"
if ($ctxUsed -gt 0) {
$ctxUsedFmt = Get-HumanFormat $ctxUsed
$ctxLimitFmt = Get-HumanFormat $ctxLimit
$inputTokFmt = Get-HumanFormat $inputTokens
$outputTokFmt = Get-HumanFormat $outputTokens
- $tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt} · ${inputTokFmt} in/${outputTokFmt} out)"
+ $tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt})${DOT}${FG_YELLOW} ${R} (${inputTokFmt} in/${outputTokFmt} out)"
}
+ $line2 = "${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${tokDetails}"
+ Write-RightAligned -left $line1 -right $line2 -totalCols $cols
+} elseif ($cols -ge 90) {
+ $line1 = "${S}${M}${DIR_FMT}${V}"
$line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
- Write-Output "${line1}${FG_GRAY} │ ${R}${line2}"
-} elseif ($cols -ge 80) {
- $line1 = "${S}${M}${V}${DIR_FMT}"
- $line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
- Write-Output "${FG_GRAY}╭─${R} ${line1}"
+ Write-Output "${FG_GRAY}╭─${R}${line1}"
Write-Output "${FG_GRAY}╰─${R}${line2}"
} else {
$mShort = ""
diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh
index 893b38fa7..c255cb07f 100755
--- a/examples/statusline/statusline.sh
+++ b/examples/statusline/statusline.sh
@@ -82,6 +82,26 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
' 2>/dev/null || printf "idle\n0\n\nfalse\n\n\nfalse\nfalse\n0\n0\n0\n\n\n80\n\n\n\n0\n0\n0\n0\n100\n"
)"
+
+# ─── Separators ──────────────────────────────────────────────────────────────
+DOT="${FG_GRAY} | ${R}"
+
+
+# ─── VCS directly from git (bypasses JSON parsing entirely) ──────────────────
+GIT_DIR="${CWD:-.}"
+VCS_BRANCH=$(git -C "$GIT_DIR" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
+if [ -n "$VCS_BRANCH" ]; then
+ VCS_TYPE="git"
+ if git -C "$GIT_DIR" status --porcelain 2>/dev/null | grep -q .; then
+ VCS_DIRTY="true"
+ else
+ VCS_DIRTY="false"
+ fi
+else
+ VCS_TYPE=""
+ VCS_DIRTY="false"
+fi
+
# ─── Computed & Formatted Values ─────────────────────────────────────────────
PCT_FMT=$(LC_NUMERIC=C printf "%.1f" "$USED_PCT")
PCT_INT=${USED_PCT%.*}; PCT_INT=${PCT_INT:-0}
@@ -121,133 +141,137 @@ shorten_path() {
}
CWD_SHORT=$(shorten_path "$CWD")
-# ─── State Indicator (No background colors) ──────────────────────────────────
+# ─── Strip ANSI escapes to measure visible length ────────────────────────────
+visible_len() {
+ # Strips ESC sequences and counts remaining bytes
+ printf '%s' "$(echo -e "$1" | sed 's/\x1b\[[0-9;]*m//g')" | wc -m
+}
+
+# ─── State Indicator ─────────────────────────────────────────────────────────
case "$STATE" in
- idle) S="${FG_BRIGHT_GREEN}${B}● READY${R}" ;;
- thinking) S="${FG_BRIGHT_YELLOW}${B}◆ THINKING${R}" ;;
- working) S="${FG_BRIGHT_CYAN}${B}⚙ WORKING${R}" ;;
- tool_use) S="${FG_BRIGHT_MAGENTA}${B}🔧 TOOL${R}" ;;
- *) S="${FG_WHITE}${B}⏳ $(echo "$STATE" | tr '[:lower:]' '[:upper:]')${R}" ;;
+ idle) S="${FG_BRIGHT_GREEN}${B} READY${R}" ;;
+ thinking) S="${FG_BRIGHT_YELLOW}${B} THINKING${R}" ;;
+ working) S="${FG_BRIGHT_CYAN}${B} WORKING${R}" ;;
+ tool_use) S="${FG_BRIGHT_MAGENTA}${B} TOOL${R}" ;;
+ *) S="${FG_WHITE}${B} $(echo "$STATE" | tr '[:lower:]' '[:upper:]')${R}" ;;
esac
-# ─── VCS Branch & Type ───────────────────────────────────────────────────────
+# ─── VCS Branch & Type (fixed: color applied correctly in both branches) ─────
V=""
if [ -n "$VCS_BRANCH" ]; then
VCS_LABEL="${VCS_TYPE:-git}"
if [ "$VCS_DIRTY" = "true" ]; then
- V="${FG_GRAY} ╱ ${FG_GRAY}${VCS_LABEL}:${FG_BRIGHT_RED}${VCS_BRANCH}${FG_BRIGHT_YELLOW}*${R}"
+ V="${DOT}${R}${FG_BRIGHT_RED} ${VCS_BRANCH}${FG_BRIGHT_YELLOW}*${R}"
else
- V="${FG_GRAY} ╱ ${FG_GRAY}${VCS_LABEL}:${FG_BRIGHT_BLUE}${VCS_BRANCH}${R}"
+ V="${DOT}${R}${FG_BRIGHT_BLUE} ${VCS_BRANCH}${R}"
fi
fi
# ─── Model ───────────────────────────────────────────────────────────────────
-# Fallback to model ID if display name is empty
-MODEL_DISP="${MODEL_NAME:-$MODEL_ID}"
+MODEL_DISP=" ${R}${MODEL_NAME:-$MODEL_ID}"
M=""
if [ -n "$MODEL_DISP" ]; then
- M="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${I}${MODEL_DISP}${R}"
+ M="${FG_GRAY}${DOT}${FG_BRIGHT_MAGENTA}${I}${MODEL_DISP}${R}"
fi
# ─── Sandbox Badge ───────────────────────────────────────────────────────────
if [ "$SANDBOX" = "true" ]; then
if [ "$SANDBOX_NET" = "true" ]; then
- SB="${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (net)${R}"
+ SB="${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (net)${R}"
else
- SB="${FG_GRAY}🛡️ sandbox ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
+ SB="${FG_GREEN} ${FG_BRIGHT_GREEN}${B}ON (no-net)${R}"
fi
else
- SB="${FG_GRAY}🛡️ sandbox off${R}"
+ SB="${FG_RED} ${FG_BRIGHT_RED}${B}OFF${R}"
fi
-# ─── Context Bar (15 segments, fine-grain Unicode) ────────────────────────────
-BAR_LEN=15
+# ─── Context Bar (20 segments) ───────────────────────────────────────────────
+BAR_LEN=20
FILLED=$((PCT_INT * BAR_LEN / 100))
REMAINDER=$(( (PCT_INT * BAR_LEN) % 100 ))
-if [ "$PCT_INT" -ge 90 ]; then
- BAR_COLOR="$FG_BRIGHT_RED"
-elif [ "$PCT_INT" -ge 60 ]; then
- BAR_COLOR="$FG_BRIGHT_YELLOW"
-else
- BAR_COLOR="$FG_BRIGHT_WHITE"
+if [ "$PCT_INT" -ge 90 ]; then FILL_COLOR="$FG_BRIGHT_RED"
+elif [ "$PCT_INT" -ge 60 ]; then FILL_COLOR="$FG_BRIGHT_YELLOW"
+else FILL_COLOR="$FG_YELLOW"
fi
BAR=""
for ((i = 0; i < BAR_LEN; i++)); do
- if [ "$i" -lt "$FILLED" ]; then
- BAR="${BAR}█"
+ if [ "$i" -lt "$FILLED" ]; then
+ BAR="${BAR}${FILL_COLOR}█${R}"
elif [ "$i" -eq "$FILLED" ]; then
- if [ "$REMAINDER" -ge 75 ]; then
- BAR="${BAR}▓"
- elif [ "$REMAINDER" -ge 50 ]; then
- BAR="${BAR}▒"
- elif [ "$REMAINDER" -ge 25 ]; then
- BAR="${BAR}░"
- else
- BAR="${BAR}·"
+ if [ "$REMAINDER" -ge 75 ]; then BAR="${BAR}${FILL_COLOR}▓${R}${FG_GRAY}"
+ elif [ "$REMAINDER" -ge 50 ]; then BAR="${BAR}${FILL_COLOR}▒${R}${FG_GRAY}"
+ else BAR="${BAR}${FILL_COLOR}░${R}${FG_GRAY}"
fi
- else
- BAR="${BAR}·"
+ else BAR="${BAR}${FG_GRAY}░${R}"
fi
done
# ─── Stats & Metadata formatting ─────────────────────────────────────────────
-CTX_BAR="${FG_GRAY}ctx ${BAR_COLOR}${BAR} ${NUM_COLOR}${PCT_FMT}%${R}"
-ART_FMT="${FG_GRAY}📦 ${NUM_COLOR}${ARTIFACTS}${R}"
-SUB_FMT="${FG_GRAY}🤖 ${NUM_COLOR}${SUBAGENTS}${R}"
-BG_FMT="${FG_GRAY}⏳ ${NUM_COLOR}${BG_TASKS}${R}"
+CTX_BAR="${FG_YELLOW} ${R}${BAR} ${NUM_COLOR}${PCT_FMT}%${R}"
+ART_FMT="${FG_BLUE} ${NUM_COLOR}${ARTIFACTS}${R}"
+SUB_FMT="${FG_CYAN} ${NUM_COLOR}${SUBAGENTS}${R}"
+BG_FMT="${FG_MAGENTA} ${NUM_COLOR}${BG_TASKS}${R}"
-# ─── New elements (CWD, Conversation ID, Token counts) ──────────────────────
DIR_FMT=""
if [ -n "$CWD_SHORT" ]; then
- DIR_FMT="${FG_GRAY} ╱ 📂 ${CWD_SHORT}${R}"
+ DIR_FMT="${FG_GRAY}${DOT}${FG_CYAN} ${R}${CWD_SHORT}${R}"
fi
CONV_FMT=""
if [ -n "$CONV_ID" ]; then
- CONV_FMT="${FG_GRAY} ╱ id:${CONV_ID:0:8}${R}"
+ CONV_FMT="${FG_GRAY}${DOT}${FG_GRAY} ${CONV_ID:0:8}${R}"
fi
-# Token stats detailed vs simple
TOK_DETAILS=""
if [ "$CTX_USED" -gt 0 ] 2>/dev/null; then
TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT})"
fi
-# ─── Separators ──────────────────────────────────────────────────────────────
-DOT="${FG_GRAY} · ${R}"
-
-# ─── Output Assembly ──────────────────────────────────────────────────────────
-if [ "$COLS" -ge 120 ]; then
- # Wide Layout: One line containing state, model, vcs, directory, conversation id
- # and bottom bar metrics inline.
- LINE1="${S}${M}${V}${DIR_FMT}${CONV_FMT}"
-
- # Detailed tokens in wide layout: (used/limit · in/out)
+# ─── Right-align helper ──────────────────────────────────────────────────────
+# Prints LINE1 left-aligned and LINE2 right-aligned on the same terminal row.
+print_right_aligned() {
+ local left="$1"
+ local right="$2"
+ local total_cols="$3"
+
+ local left_vis right_vis pad
+ left_vis=$(visible_len "$left")
+ right_vis=$(visible_len "$right")
+
+ # How many spaces needed between left and right
+ pad=$(( total_cols - left_vis - right_vis ))
+ [ "$pad" -lt 1 ] && pad=1
+
+ printf "%b%*s%b\n" "$left" "$pad" "" "$right"
+}
+
+# ─── Output Assembly ─────────────────────────────────────────────────────────
+if [ "$COLS" -ge 180 ]; then
+ LINE1="${S}${M}${DIR_FMT}${V}${CONV_FMT}"
+
if [ "$CTX_USED" -gt 0 ] 2>/dev/null; then
- TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT} · ${INPUT_TOK_FMT} in/${OUTPUT_TOK_FMT} out)"
+ TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT})${DOT}${FG_YELLOW} ${R} (${INPUT_TOK_FMT} in/${OUTPUT_TOK_FMT} out)"
fi
-
- LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
- echo -e "${LINE1}${FG_GRAY} │ ${R}${LINE2}"
-elif [ "$COLS" -ge 80 ]; then
+ LINE2="${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${TOK_DETAILS}"
+ print_right_aligned "$LINE1" "$LINE2" "$COLS"
+
+elif [ "$COLS" -ge 90 ]; then
# Medium Layout: Two-line layout with border
- LINE1="${S}${M}${V}${DIR_FMT}"
+ LINE1="${S}${M}${DIR_FMT}${V}"
LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
-
- echo -e "${FG_GRAY}╭─${R} ${LINE1}"
+
+ echo -e "${FG_GRAY}╭─${R}${LINE1}"
echo -e "${FG_GRAY}╰─${R}${LINE2}"
else
- # Narrow Layout: Compact two-line, minimal layout
- # Shorten model display for narrow screens
M_SHORT=""
if [ -n "$MODEL_DISP" ]; then
- M_SHORT="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${MODEL_DISP:0:12}${R}"
+ M_SHORT="${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${MODEL_DISP}${R}"
fi
-
+
echo -e "${S}${M_SHORT}"
echo -e "${CTX_BAR}${DOT}${BG_FMT}"
fi
From 6241334c80ff6f34d995ed0d5676ceeb121a5b83 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Tue, 2 Jun 2026 23:03:04 +0300
Subject: [PATCH 23/28] feat: upgrade to v1.0.4 and update manifests
---
README-DE.md | 2 +-
README-ES.md | 2 +-
README-FR.md | 2 +-
README-PT.md | 2 +-
README-UA.md | 2 +-
README-ZH.md | 2 +-
README.md | 2 +-
packages/manifests/darwin_amd64.json | 8 ++++----
packages/manifests/darwin_arm64.json | 8 ++++----
packages/manifests/linux_amd64.json | 8 ++++----
packages/manifests/linux_arm64.json | 8 ++++----
packages/manifests/windows_amd64.json | 8 ++++----
12 files changed, 27 insertions(+), 27 deletions(-)
diff --git a/README-DE.md b/README-DE.md
index be6164056..42715bd91 100644
--- a/README-DE.md
+++ b/README-DE.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README-ES.md b/README-ES.md
index b64f212d1..0845b32f0 100644
--- a/README-ES.md
+++ b/README-ES.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README-FR.md b/README-FR.md
index 3c7548b5b..b14ad2a8f 100644
--- a/README-FR.md
+++ b/README-FR.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README-PT.md b/README-PT.md
index 12d7a231f..1c5939fcc 100644
--- a/README-PT.md
+++ b/README-PT.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README-UA.md b/README-UA.md
index 0478cc9b1..a1f65f95d 100644
--- a/README-UA.md
+++ b/README-UA.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README-ZH.md b/README-ZH.md
index 5a816eab0..e7eac63be 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -14,7 +14,7 @@
-
+
diff --git a/README.md b/README.md
index 06e5d46d6..6b6c6243f 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
diff --git a/packages/manifests/darwin_amd64.json b/packages/manifests/darwin_amd64.json
index e122b35ab..abda3e9f6 100644
--- a/packages/manifests/darwin_amd64.json
+++ b/packages/manifests/darwin_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.3",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.3/cli_mac_x64.tar.gz",
- "sha512": "5e5de4e2b5a9c788930699d0f5f7288eae4ae91fb705dc6e62f79d96a67619f58e2ba4357d6c2dbbfd5309aeb8f16934ebf09a573ef62b489b37e92c2754c74e"
-}
+ "version": "1.0.4",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_mac_x64.tar.gz",
+ "sha512": "1acce9221efc36d1470b1c5faef0358df1b0fa848aa51661617551145da575c5244855c89ec26c94652a1d7634cc91376da0a41be5b5cac94ace5f9c1ae640e0"
+}
\ No newline at end of file
diff --git a/packages/manifests/darwin_arm64.json b/packages/manifests/darwin_arm64.json
index cb434ad5a..81c70e4ff 100644
--- a/packages/manifests/darwin_arm64.json
+++ b/packages/manifests/darwin_arm64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.3",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.3/cli_mac_arm64.tar.gz",
- "sha512": "b2c0d21876ae9b6faf6164b15b573d76e379f643e040e77d528ebafa5d5bd8893cc3c8ebd9a78faf67bd7c676dd4bff0f028f3bbb508e544769d61de3da6b8b1"
-}
+ "version": "1.0.4",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_mac_arm64.tar.gz",
+ "sha512": "7f72c8a31da304111468f43023e3c611b9b1035955a9155e5fc0fa770830976e2a23785e15f03041831b044fef2eec23bdbfa2394b39f6cd541d9e449da1c7db"
+}
\ No newline at end of file
diff --git a/packages/manifests/linux_amd64.json b/packages/manifests/linux_amd64.json
index 852660bcd..2b4c6c415 100644
--- a/packages/manifests/linux_amd64.json
+++ b/packages/manifests/linux_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.3",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.3/cli_linux_x64.tar.gz",
- "sha512": "f6cf890d494f5fd00c696b4d2e541c894d5b10ff50bfd9f6dc02b915386e08b61c56140f17115898fc49f4aa4534581393098f35db70be9aae20dfde3ba5787c"
-}
+ "version": "1.0.4",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_linux_x64.tar.gz",
+ "sha512": "fd95930b06384ed4b5672d1a8f32041ccd9686630187d981db7172180a2a0bb77ec63a887adc66291d2bcb50ff5b08a4996b9cbacc589c56c861c40209d57f10"
+}
\ No newline at end of file
diff --git a/packages/manifests/linux_arm64.json b/packages/manifests/linux_arm64.json
index f96da1c18..47fd9a34c 100644
--- a/packages/manifests/linux_arm64.json
+++ b/packages/manifests/linux_arm64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.3",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.3/cli_linux_arm64.tar.gz",
- "sha512": "0f6d326ef28e57e473c7825583314636fa0bcda50e27117ff3d019196d048d764af29846910a2f238dc87a3f90062e29b5252232c7d586a86abaaea921697e52"
-}
+ "version": "1.0.4",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_linux_arm64.tar.gz",
+ "sha512": "cfbbec3cfefc18de3f0d57bc71c28193a6e804e2ba1c6c319db67ef7ce18201380906cba424b4cbbe0786efd574e2c9677a3434dcb106cd29314015513925a41"
+}
\ No newline at end of file
diff --git a/packages/manifests/windows_amd64.json b/packages/manifests/windows_amd64.json
index 80f0f0ee9..fd5642f52 100644
--- a/packages/manifests/windows_amd64.json
+++ b/packages/manifests/windows_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.3",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.3/cli_windows_x64.zip",
- "sha512": "2c3dbb4e443fd910b03e9b75e4f33616a535dff402534b4115fb4c10e8cd81edefcce7d3968d581552630f7ada473e8944a904701400543fbdd39296171722ad"
-}
+ "version": "1.0.4",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_windows_x64.zip",
+ "sha512": "b48c80d892582a595ed8f1d30c0e2fce584f9fbecf79432d676e270b43057d6cb4a1107439ea3fd09bcc82fb97a4cbd0a2baf7683e80f2bb71cce08865befc61"
+}
\ No newline at end of file
From 02caa24c3f0ed63082e2b3839eade6322c034c70 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Mon, 15 Jun 2026 13:57:02 +0300
Subject: [PATCH 24/28] feat(statusline): add quota reset time to progress bar
displays
---
examples/statusline/statusline.fish | 124 +++++++++++++++++++++++++-
examples/statusline/statusline.js | 105 +++++++++++++++++++++-
examples/statusline/statusline.ps1 | 129 ++++++++++++++++++++++++++-
examples/statusline/statusline.sh | 133 ++++++++++++++++++++++++++--
4 files changed, 477 insertions(+), 14 deletions(-)
diff --git a/examples/statusline/statusline.fish b/examples/statusline/statusline.fish
index 45e9fabaa..254eedf0b 100755
--- a/examples/statusline/statusline.fish
+++ b/examples/statusline/statusline.fish
@@ -30,6 +30,16 @@ set -l OUTPUT_TOKENS (echo $DATA | jq -r '.context_window.total_output_tokens //
set -l CTX_LIMIT (echo $DATA | jq -r '.context_window.context_window_size // 0')
set -l CTX_USED (echo $DATA | jq -r '(.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)')
+set -l GEMINI_5H (echo $DATA | jq -r 'if .quota["gemini-5h"].remaining_fraction != null then ((.quota["gemini-5h"].remaining_fraction * 1000 | round) / 10) else -1 end')
+set -l GEMINI_WK (echo $DATA | jq -r 'if .quota["gemini-weekly"].remaining_fraction != null then ((.quota["gemini-weekly"].remaining_fraction * 1000 | round) / 10) else -1 end')
+set -l TP_5H (echo $DATA | jq -r 'if .quota["3p-5h"].remaining_fraction != null then ((.quota["3p-5h"].remaining_fraction * 1000 | round) / 10) else -1 end')
+set -l TP_WK (echo $DATA | jq -r 'if .quota["3p-weekly"].remaining_fraction != null then ((.quota["3p-weekly"].remaining_fraction * 1000 | round) / 10) else -1 end')
+
+set -l GEMINI_5H_RESET (echo $DATA | jq -r '.quota["gemini-5h"].reset_in_seconds // -1')
+set -l GEMINI_WK_RESET (echo $DATA | jq -r '.quota["gemini-weekly"].reset_in_seconds // -1')
+set -l TP_5H_RESET (echo $DATA | jq -r '.quota["3p-5h"].reset_in_seconds // -1')
+set -l TP_WK_RESET (echo $DATA | jq -r '.quota["3p-weekly"].reset_in_seconds // -1')
+
# ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
set -l GIT_DIR $CWD
if test -z "$GIT_DIR"
@@ -232,17 +242,125 @@ if test "$CTX_USED" -gt 0 2>/dev/null
set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT)"
end
+# ─── Quota formatting ────────────────────────────────────────────────────────
+function format_reset_time -a sec
+ if test -z "$sec"; or test "$sec" -le 0
+ echo -n ""
+ return
+ end
+
+ set -l days (math -s0 "$sec / 86400")
+ set -l rem (math -s0 "$sec % 86400")
+ set -l hours (math -s0 "$rem / 3600")
+ set -l rem2 (math -s0 "$rem % 3600")
+ set -l mins (math -s0 "$rem2 / 60")
+
+ if test "$days" -gt 0
+ if test "$hours" -gt 0
+ echo -n " in "$days"d "$hours"h"
+ else
+ echo -n " in "$days"d"
+ fi
+ elif test "$hours" -gt 0
+ if test "$mins" -gt 0
+ echo -n " in "$hours"h "$mins"m"
+ else
+ echo -n " in "$hours"h"
+ fi
+ elif test "$mins" -gt 0
+ echo -n " in "$mins"m"
+ else
+ echo -n " in <1m"
+ fi
+end
+
+function make_quota_bar -a val label bar_color reset_sec R FG_GRAY FG_BRIGHT_GREEN FG_BRIGHT_YELLOW FG_BRIGHT_RED
+ if test -z "$val"; or string match -q "-*" -- "$val"
+ set -l bar ""
+ for i in (seq 1 20)
+ set bar "$bar░"
+ end
+ echo -n "$FG_GRAY$bar N/A ($label)$R"
+ return
+ end
+
+ set -l val_int (string split -m 1 "." $val)[1]
+ if test -z "$val_int"
+ set val_int 0
+ end
+
+ set -l fill_color $FG_BRIGHT_GREEN
+ if test "$val_int" -lt 20
+ set fill_color $FG_BRIGHT_RED
+ elif test "$val_int" -lt 50
+ set fill_color $FG_BRIGHT_YELLOW
+ end
+
+ set -l bar_len 20
+ set -l filled (math -s0 "$val_int * $bar_len / 100")
+ set -l remainder (math -s0 "($val_int * $bar_len) % 100")
+
+ set -l bar ""
+ for i in (seq 0 (math "$bar_len - 1"))
+ if test "$i" -lt "$filled"
+ set bar "$bar$bar_color"█"$R"
+ elif test "$i" -eq "$filled"
+ if test "$remainder" -ge 75
+ set bar "$bar$bar_color"▓"$R$FG_GRAY"
+ elif test "$remainder" -ge 50
+ set bar "$bar$bar_color"▒"$R$FG_GRAY"
+ elif test "$remainder" -ge 25
+ set bar "$bar$bar_color"░"$R$FG_GRAY"
+ else
+ set bar "$bar$FG_GRAY"░"$R"
+ end
+ else
+ set bar "$bar$FG_GRAY"░"$R"
+ end
+ end
+
+ set -l reset_str ""
+ if test -n "$reset_sec"; and test "$reset_sec" -gt 0
+ set reset_str (format_reset_time $reset_sec)
+ end
+ echo -n "$bar $fill_color$val%$R $FG_GRAY($label$reset_str)$R"
+end
+
+set -l MODEL_LOWER (string lower "$MODEL_DISP")
+set -l Q_5H ""
+set -l Q_WK ""
+set -l Q_5H_R ""
+set -l Q_WK_R ""
+if string match -q -r 'gemini' "$MODEL_LOWER"
+ set Q_5H $GEMINI_5H
+ set Q_WK $GEMINI_WK
+ set Q_5H_R $GEMINI_5H_RESET
+ set Q_WK_R $GEMINI_WK_RESET
+else
+ set Q_5H $TP_5H
+ set Q_WK $TP_WK
+ set Q_5H_R $TP_5H_RESET
+ set Q_WK_R $TP_WK_RESET
+end
+
+set -l QUOTA_FMT ""
+if test -n "$Q_5H"; or test -n "$Q_WK"
+ set -l fmt_5h (make_quota_bar $Q_5H "5H" "$FG_BRIGHT_CYAN" "$Q_5H_R" "$R" "$FG_GRAY" "$FG_BRIGHT_GREEN" "$FG_BRIGHT_YELLOW" "$FG_BRIGHT_RED")
+ set -l fmt_wk (make_quota_bar $Q_WK "7D" "$FG_BRIGHT_MAGENTA" "$Q_WK_R" "$R" "$FG_GRAY" "$FG_BRIGHT_GREEN" "$FG_BRIGHT_YELLOW" "$FG_BRIGHT_RED")
+ set QUOTA_FMT "$fmt_5h $fmt_wk"
+end
+
# Output Assembly
if test "$COLS" -ge 180
set -l line1 "$S$M$DIR_FMT$V$CONV_FMT"
if test "$CTX_USED" -gt 0 2>/dev/null
set TOK_DETAILS " ($CTX_USED_FMT/$CTX_LIMIT_FMT)$DOT$FG_YELLOW $R ($INPUT_TOK_FMT in/$OUTPUT_TOK_FMT out)"
end
- set -l line2 "$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB$DOT$CTX_BAR$TOK_DETAILS"
+ set -l line2 "$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB$DOT$CTX_BAR$TOK_DETAILS$DOT$QUOTA_FMT"
print_right_aligned $line1 $line2 $COLS
elif test "$COLS" -ge 90
set -l line1 "$S$M$DIR_FMT$V"
- set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB"
+ set -l line2 " $CTX_BAR$TOK_DETAILS$DOT$ART_FMT$DOT$SUB_FMT$DOT$BG_FMT$DOT$SB$DOT$QUOTA_FMT"
echo -e "$FG_GRAY╭─$R$line1"
echo -e "$FG_GRAY╰─$R$line2"
else
@@ -252,5 +370,5 @@ else
set M_SHORT "$FG_GRAY ╱ $FG_BRIGHT_MAGENTA$sub_model$R"
end
echo -e "$S$M_SHORT"
- echo -e "$CTX_BAR$DOT$BG_FMT"
+ echo -e "$CTX_BAR$DOT$BG_FMT$DOT$QUOTA_FMT"
end
diff --git a/examples/statusline/statusline.js b/examples/statusline/statusline.js
index caec895c2..52c247bd8 100755
--- a/examples/statusline/statusline.js
+++ b/examples/statusline/statusline.js
@@ -69,6 +69,16 @@ const outputTokens = (data.context_window && data.context_window.total_output_to
const ctxLimit = (data.context_window && data.context_window.context_window_size) ? data.context_window.context_window_size : 0;
const ctxUsed = inputTokens + outputTokens;
+const gemini5h = (data.quota && data.quota["gemini-5h"] && typeof data.quota["gemini-5h"].remaining_fraction === 'number') ? data.quota["gemini-5h"].remaining_fraction * 100 : -1;
+const geminiWk = (data.quota && data.quota["gemini-weekly"] && typeof data.quota["gemini-weekly"].remaining_fraction === 'number') ? data.quota["gemini-weekly"].remaining_fraction * 100 : -1;
+const tp5h = (data.quota && data.quota["3p-5h"] && typeof data.quota["3p-5h"].remaining_fraction === 'number') ? data.quota["3p-5h"].remaining_fraction * 100 : -1;
+const tpWk = (data.quota && data.quota["3p-weekly"] && typeof data.quota["3p-weekly"].remaining_fraction === 'number') ? data.quota["3p-weekly"].remaining_fraction * 100 : -1;
+
+const gemini5hReset = (data.quota && data.quota["gemini-5h"] && typeof data.quota["gemini-5h"].reset_in_seconds === 'number') ? data.quota["gemini-5h"].reset_in_seconds : -1;
+const geminiWkReset = (data.quota && data.quota["gemini-weekly"] && typeof data.quota["gemini-weekly"].reset_in_seconds === 'number') ? data.quota["gemini-weekly"].reset_in_seconds : -1;
+const tp5hReset = (data.quota && data.quota["3p-5h"] && typeof data.quota["3p-5h"].reset_in_seconds === 'number') ? data.quota["3p-5h"].reset_in_seconds : -1;
+const tpWkReset = (data.quota && data.quota["3p-weekly"] && typeof data.quota["3p-weekly"].reset_in_seconds === 'number') ? data.quota["3p-weekly"].reset_in_seconds : -1;
+
// ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
try {
const gitDir = cwd || ".";
@@ -228,17 +238,106 @@ if (ctxUsed > 0) {
tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)})`;
}
+// ─── Quota formatting ────────────────────────────────────────────────────────
+function formatResetTime(sec) {
+ if (sec === undefined || sec === null || sec <= 0) return "";
+ const days = Math.floor(sec / 86400);
+ const rem = sec % 86400;
+ const hours = Math.floor(rem / 3600);
+ const mins = Math.floor((rem % 3600) / 60);
+
+ if (days > 0) {
+ return hours > 0 ? ` in ${days}d ${hours}h` : ` in ${days}d`;
+ } else if (hours > 0) {
+ return mins > 0 ? ` in ${hours}h ${mins}m` : ` in ${hours}h`;
+ } else if (mins > 0) {
+ return ` in ${mins}m`;
+ } else {
+ return " in <1m";
+ }
+}
+
+function makeQuotaBar(val, label, barColor, resetSec) {
+ if (val === undefined || val === null || val < 0) {
+ return `${FG_GRAY}${"░".repeat(20)} N/A (${label})${R}`;
+ }
+ let color = FG_BRIGHT_GREEN;
+ if (val < 20) {
+ color = FG_BRIGHT_RED;
+ } else if (val < 50) {
+ color = FG_BRIGHT_YELLOW;
+ }
+
+ const barLen = 20;
+ const pctInt = Math.floor(val);
+ const filled = Math.floor((pctInt * barLen) / 100);
+ const remainder = (pctInt * barLen) % 100;
+
+ let bar = "";
+ for (let i = 0; i < barLen; i++) {
+ if (i < filled) {
+ bar += "█";
+ } else if (i === filled) {
+ if (remainder >= 75) {
+ bar += "▓";
+ } else if (remainder >= 50) {
+ bar += "▒";
+ } else if (remainder >= 25) {
+ bar += "░";
+ } else {
+ bar += "░";
+ }
+ } else {
+ bar += "░";
+ }
+ }
+
+ let coloredBar = "";
+ let inColor = false;
+ for (let i = 0; i < barLen; i++) {
+ const char = bar[i];
+ if (char === "█" || char === "▓" || char === "▒") {
+ if (!inColor) {
+ coloredBar += barColor;
+ inColor = true;
+ }
+ coloredBar += char;
+ } else {
+ if (inColor) {
+ coloredBar += R;
+ inColor = false;
+ }
+ coloredBar += `${FG_GRAY}${char}${R}`;
+ }
+ }
+ if (inColor) {
+ coloredBar += R;
+ }
+
+ const valFmt = val.toFixed(1).replace(/\.0$/, '');
+ const resetStr = resetSec > 0 ? formatResetTime(resetSec) : "";
+ return `${coloredBar} ${color}${valFmt}%${R} ${FG_GRAY}(${label}${resetStr})${R}`;
+}
+
+const isGemini = (modelDisp || "").toLowerCase().includes("gemini");
+const q5h = isGemini ? gemini5h : tp5h;
+const qWk = isGemini ? geminiWk : tpWk;
+const q5hReset = isGemini ? gemini5hReset : tp5hReset;
+const qWkReset = isGemini ? geminiWkReset : tpWkReset;
+
+const quotaFmt = (q5h >= 0 || qWk >= 0) ? `${makeQuotaBar(q5h, "5H", FG_BRIGHT_CYAN, q5hReset)} ${makeQuotaBar(qWk, "7D", FG_BRIGHT_MAGENTA, qWkReset)}` : "";
+
// ─── Output Assembly ──────────────────────────────────────────────────────────
if (cols >= 180) {
let line1 = `${S}${M}${dirFmt}${V}${convFmt}`;
if (ctxUsed > 0) {
tokDetails = ` (${humanFormat(ctxUsed)}/${humanFormat(ctxLimit)})${dot}${FG_YELLOW} ${R} (${humanFormat(inputTokens)} in/${humanFormat(outputTokens)} out)`;
}
- let line2 = `${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}${dot}${ctxBar}${tokDetails}`;
+ let line2 = `${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}${dot}${ctxBar}${tokDetails}${dot}${quotaFmt}`;
printRightAligned(line1, line2, cols);
} else if (cols >= 90) {
let line1 = `${S}${M}${dirFmt}${V}`;
- let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}`;
+ let line2 = ` ${ctxBar}${tokDetails}${dot}${artFmt}${dot}${subFmt}${dot}${bgFmt}${dot}${SB}${dot}${quotaFmt}`;
console.log(`${FG_GRAY}╭─${R}${line1}`);
console.log(`${FG_GRAY}╰─${R}${line2}`);
} else {
@@ -247,5 +346,5 @@ if (cols >= 180) {
mShort = `${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${modelDisp}${R}`;
}
console.log(`${S}${mShort}`);
- console.log(`${ctxBar}${dot}${bgFmt}`);
+ console.log(`${ctxBar}${dot}${bgFmt}${dot}${quotaFmt}`);
}
diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1
index 199314676..4d4743bfe 100644
--- a/examples/statusline/statusline.ps1
+++ b/examples/statusline/statusline.ps1
@@ -74,6 +74,16 @@ $outputTokens = if ($data.context_window -and $null -ne $data.context_window.tot
$ctxLimit = if ($data.context_window -and $null -ne $data.context_window.context_window_size) { $data.context_window.context_window_size } else { 0 }
$ctxUsed = $inputTokens + $outputTokens
+$gemini5h = if ($data.quota -and $data.quota.'gemini-5h' -and $null -ne $data.quota.'gemini-5h'.remaining_fraction) { [double]$data.quota.'gemini-5h'.remaining_fraction * 100 } else { -1.0 }
+$geminiWk = if ($data.quota -and $data.quota.'gemini-weekly' -and $null -ne $data.quota.'gemini-weekly'.remaining_fraction) { [double]$data.quota.'gemini-weekly'.remaining_fraction * 100 } else { -1.0 }
+$tp5h = if ($data.quota -and $data.quota.'3p-5h' -and $null -ne $data.quota.'3p-5h'.remaining_fraction) { [double]$data.quota.'3p-5h'.remaining_fraction * 100 } else { -1.0 }
+$tpWk = if ($data.quota -and $data.quota.'3p-weekly' -and $null -ne $data.quota.'3p-weekly'.remaining_fraction) { [double]$data.quota.'3p-weekly'.remaining_fraction * 100 } else { -1.0 }
+
+$gemini5hReset = if ($data.quota -and $data.quota.'gemini-5h' -and $null -ne $data.quota.'gemini-5h'.reset_in_seconds) { [int]$data.quota.'gemini-5h'.reset_in_seconds } else { -1 }
+$geminiWkReset = if ($data.quota -and $data.quota.'gemini-weekly' -and $null -ne $data.quota.'gemini-weekly'.reset_in_seconds) { [int]$data.quota.'gemini-weekly'.reset_in_seconds } else { -1 }
+$tp5hReset = if ($data.quota -and $data.quota.'3p-5h' -and $null -ne $data.quota.'3p-5h'.reset_in_seconds) { [int]$data.quota.'3p-5h'.reset_in_seconds } else { -1 }
+$tpWkReset = if ($data.quota -and $data.quota.'3p-weekly' -and $null -ne $data.quota.'3p-weekly'.reset_in_seconds) { [int]$data.quota.'3p-weekly'.reset_in_seconds } else { -1 }
+
# ─── VCS directly from git (bypasses JSON parsing entirely for accuracy) ──────
try {
$gitDir = if ($cwd) { $cwd } else { "." }
@@ -237,6 +247,119 @@ if ($ctxUsed -gt 0) {
$tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt})"
}
+# ─── Quota formatting ────────────────────────────────────────────────────────
+function Get-ResetTimeFmt {
+ param ($sec)
+ if ($null -eq $sec -or $sec -le 0) { return "" }
+ $days = [Math]::Floor($sec / 86400)
+ $rem = $sec % 86400
+ $hours = [Math]::Floor($rem / 3600)
+ $mins = [Math]::Floor(($rem % 3600) / 60)
+
+ if ($days -gt 0) {
+ if ($hours -gt 0) { return " in ${days}d ${hours}h" }
+ return " in ${days}d"
+ } elseif ($hours -gt 0) {
+ if ($mins -gt 0) { return " in ${hours}h ${mins}m" }
+ return " in ${hours}h"
+ } elseif ($mins -gt 0) {
+ return " in ${mins}m"
+ } else {
+ return " in <1m"
+ }
+}
+
+function Get-QuotaBar {
+ param ($val, $label, $barColor, $resetSec)
+ if ($null -eq $val -or $val -lt 0) {
+ $emptyBar = "░" * 20
+ return "${FG_GRAY}${emptyBar} N/A (${label})${R}"
+ }
+ $color = $FG_BRIGHT_GREEN
+ if ($val -lt 20) {
+ $color = $FG_BRIGHT_RED
+ } elseif ($val -lt 50) {
+ $color = $FG_BRIGHT_YELLOW
+ }
+
+ $barLen = 20
+ $pctInt = [int][Math]::Floor($val)
+ $filled = [int][Math]::Floor($pctInt * $barLen / 100)
+ $remainder = ($pctInt * $barLen) % 100
+
+ $bar = ""
+ for ($i = 0; $i -lt $barLen; $i++) {
+ if ($i -lt $filled) {
+ $bar += "█"
+ } elseif ($i -eq $filled) {
+ if ($remainder -ge 75) {
+ $bar += "▓"
+ } elseif ($remainder -ge 50) {
+ $bar += "▒"
+ } elseif ($remainder -ge 25) {
+ $bar += "░"
+ } else {
+ $bar += "░"
+ }
+ } else {
+ $bar += "░"
+ }
+ }
+
+ $coloredBar = ""
+ $inColor = $false
+ for ($i = 0; $i -lt $barLen; $i++) {
+ $char = $bar[$i]
+ if ($char -eq "█" -or $char -eq "▓" -or $char -eq "▒") {
+ if (-not $inColor) {
+ $coloredBar += $barColor
+ $inColor = $true
+ }
+ $coloredBar += $char
+ } else {
+ if ($inColor) {
+ $coloredBar += $R
+ $inColor = $false
+ }
+ $coloredBar += "${FG_GRAY}${char}${R}"
+ }
+ }
+ if ($inColor) {
+ $coloredBar += $R
+ }
+
+ $valFmt = $val.ToString("0.0", [System.Globalization.CultureInfo]::InvariantCulture)
+ if ($valFmt.EndsWith(".0")) {
+ $valFmt = $valFmt.Substring(0, $valFmt.Length - 2)
+ }
+
+ $resetStr = ""
+ if ($null -ne $resetSec -and $resetSec -gt 0) {
+ $resetStr = Get-ResetTimeFmt -sec $resetSec
+ }
+ return "${coloredBar} ${color}${valFmt}%${R} ${FG_GRAY}(${label}${resetStr})${R}"
+}
+
+$isGemini = $modelDisp.ToLower().Contains("gemini")
+if ($isGemini) {
+ $q5h = $gemini5h
+ $qWk = $geminiWk
+ $q5hR = $gemini5hReset
+ $qWkR = $geminiWkReset
+} else {
+ $q5h = $tp5h
+ $qWk = $tpWk
+ $q5hR = $tp5hReset
+ $qWkR = $tpWkReset
+}
+
+$QUOTA_FMT = ""
+if ($q5h -ge 0 -or $qWk -ge 0) {
+ $fmt5h = Get-QuotaBar -val $q5h -label "5H" -barColor $FG_BRIGHT_CYAN -resetSec $q5hR
+ $fmtWk = Get-QuotaBar -val $qWk -label "7D" -barColor $FG_BRIGHT_MAGENTA -resetSec $qWkR
+ $QUOTA_FMT = "${fmt5h} ${fmtWk}"
+}
+
# ─── Output Assembly ──────────────────────────────────────────────────────────
if ($cols -ge 180) {
$line1 = "${S}${M}${DIR_FMT}${V}${CONV_FMT}"
@@ -247,11 +370,11 @@ if ($cols -ge 180) {
$outputTokFmt = Get-HumanFormat $outputTokens
$tokDetails = " (${ctxUsedFmt}/${ctxLimitFmt})${DOT}${FG_YELLOW} ${R} (${inputTokFmt} in/${outputTokFmt} out)"
}
- $line2 = "${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${tokDetails}"
+ $line2 = "${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${tokDetails}${DOT}${QUOTA_FMT}"
Write-RightAligned -left $line1 -right $line2 -totalCols $cols
} elseif ($cols -ge 90) {
$line1 = "${S}${M}${DIR_FMT}${V}"
- $line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
+ $line2 = " ${CTX_BAR}${tokDetails}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${QUOTA_FMT}"
Write-Output "${FG_GRAY}╭─${R}${line1}"
Write-Output "${FG_GRAY}╰─${R}${line2}"
} else {
@@ -261,5 +384,5 @@ if ($cols -ge 180) {
$mShort = "${FG_GRAY} ╱ ${FG_BRIGHT_MAGENTA}${subModelDisp}${R}"
}
Write-Output "${S}${mShort}"
- Write-Output "${CTX_BAR}${DOT}${BG_FMT}"
+ Write-Output "${CTX_BAR}${DOT}${BG_FMT}${DOT}${QUOTA_FMT}"
}
diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh
index c255cb07f..4ef769066 100755
--- a/examples/statusline/statusline.sh
+++ b/examples/statusline/statusline.sh
@@ -55,6 +55,14 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
read -r CTX_LIMIT
read -r CTX_USED
read -r REM_PCT
+ read -r GEMINI_5H
+ read -r GEMINI_WK
+ read -r TP_5H
+ read -r TP_WK
+ read -r GEMINI_5H_RESET
+ read -r GEMINI_WK_RESET
+ read -r TP_5H_RESET
+ read -r TP_WK_RESET
} <<< "$(
echo "$INPUT_JSON" | jq -r '
(.agent_state // "idle"),
@@ -78,8 +86,16 @@ NUM_COLOR="${FG_BRIGHT_WHITE}${B}"
(.context_window.total_output_tokens // 0),
(.context_window.context_window_size // 0),
((.context_window.total_input_tokens // 0) + (.context_window.total_output_tokens // 0)),
- (.context_window.remaining_percentage // 100)
- ' 2>/dev/null || printf "idle\n0\n\nfalse\n\n\nfalse\nfalse\n0\n0\n0\n\n\n80\n\n\n\n0\n0\n0\n0\n100\n"
+ (.context_window.remaining_percentage // 100),
+ (if .quota["gemini-5h"].remaining_fraction != null then ((.quota["gemini-5h"].remaining_fraction * 1000 | round) / 10) else -1 end),
+ (if .quota["gemini-weekly"].remaining_fraction != null then ((.quota["gemini-weekly"].remaining_fraction * 1000 | round) / 10) else -1 end),
+ (if .quota["3p-5h"].remaining_fraction != null then ((.quota["3p-5h"].remaining_fraction * 1000 | round) / 10) else -1 end),
+ (if .quota["3p-weekly"].remaining_fraction != null then ((.quota["3p-weekly"].remaining_fraction * 1000 | round) / 10) else -1 end),
+ (.quota["gemini-5h"].reset_in_seconds // -1),
+ (.quota["gemini-weekly"].reset_in_seconds // -1),
+ (.quota["3p-5h"].reset_in_seconds // -1),
+ (.quota["3p-weekly"].reset_in_seconds // -1)
+ ' 2>/dev/null || printf "idle\n0\n\nfalse\n\n\nfalse\nfalse\n0\n0\n0\n\n\n80\n\n\n\n0\n0\n0\n0\n100\n-1\n-1\n-1\n-1\n-1\n-1\n-1\n-1\n"
)"
@@ -229,6 +245,113 @@ if [ "$CTX_USED" -gt 0 ] 2>/dev/null; then
TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT})"
fi
+# ─── Quota formatting ────────────────────────────────────────────────────────
+format_reset_time() {
+ local sec=$1
+ if [ -z "$sec" ] || [ "$sec" -le 0 ]; then
+ echo -n ""
+ return
+ fi
+
+ local days=$((sec / 86400))
+ local rem=$((sec % 86400))
+ local hours=$((rem / 3600))
+ rem=$((rem % 3600))
+ local mins=$((rem / 60))
+
+ if [ "$days" -gt 0 ]; then
+ if [ "$hours" -gt 0 ]; then
+ echo -n " in ${days}d ${hours}h"
+ else
+ echo -n " in ${days}d"
+ fi
+ elif [ "$hours" -gt 0 ]; then
+ if [ "$mins" -gt 0 ]; then
+ echo -n " in ${hours}h ${mins}m"
+ else
+ echo -n " in ${hours}h"
+ fi
+ elif [ "$mins" -gt 0 ]; then
+ echo -n " in ${mins}m"
+ else
+ echo -n " in <1m"
+ fi
+}
+
+make_quota_bar() {
+ local val=$1
+ local label=$2
+ local bar_color=$3
+ local reset_sec=$4
+ if [ -z "$val" ] || [[ "$val" == -* ]]; then
+ local bar=""
+ for ((i = 0; i < 20; i++)); do
+ bar="${bar}░"
+ done
+ echo -n "${FG_GRAY}${bar} N/A (${label})${R}"
+ return
+ fi
+
+ local val_int=${val%.*}
+ val_int=${val_int:-0}
+
+ local text_color="$FG_BRIGHT_GREEN"
+ if [ "$val_int" -lt 20 ]; then
+ text_color="$FG_BRIGHT_RED"
+ elif [ "$val_int" -lt 50 ]; then
+ text_color="$FG_BRIGHT_YELLOW"
+ fi
+
+ local bar_len=20
+ local filled=$((val_int * bar_len / 100))
+ local remainder=$(( (val_int * bar_len) % 100 ))
+
+ local bar=""
+ for ((i = 0; i < bar_len; i++)); do
+ if [ "$i" -lt "$filled" ]; then
+ bar="${bar}${bar_color}█${R}"
+ elif [ "$i" -eq "$filled" ]; then
+ if [ "$remainder" -ge 75 ]; then
+ bar="${bar}${bar_color}▓${R}${FG_GRAY}"
+ elif [ "$remainder" -ge 50 ]; then
+ bar="${bar}${bar_color}▒${R}${FG_GRAY}"
+ elif [ "$remainder" -ge 25 ]; then
+ bar="${bar}${bar_color}░${R}${FG_GRAY}"
+ else
+ bar="${bar}${FG_GRAY}░${R}"
+ fi
+ else
+ bar="${bar}${FG_GRAY}░${R}"
+ fi
+ done
+
+ local reset_str=""
+ if [ -n "$reset_sec" ] && [ "$reset_sec" -gt 0 ]; then
+ reset_str=$(format_reset_time "$reset_sec")
+ fi
+
+ echo -n "${bar} ${text_color}${val}%${R} ${FG_GRAY}(${label}${reset_str})${R}"
+}
+
+# Determine active quota
+MODEL_LOWER=$(echo "${MODEL_NAME:-$MODEL_ID}" | tr '[:upper:]' '[:lower:]')
+if [[ "$MODEL_LOWER" == *gemini* ]]; then
+ Q_5H="$GEMINI_5H"
+ Q_WK="$GEMINI_WK"
+ Q_5H_R="$GEMINI_5H_RESET"
+ Q_WK_R="$GEMINI_WK_RESET"
+else
+ Q_5H="$TP_5H"
+ Q_WK="$TP_WK"
+ Q_5H_R="$TP_5H_RESET"
+ Q_WK_R="$TP_WK_RESET"
+fi
+
+QUOTA_FMT=""
+if [ -n "$Q_5H" ] || [ -n "$Q_WK" ]; then
+ QUOTA_FMT="$(make_quota_bar "$Q_5H" "5H" "$FG_BRIGHT_CYAN" "$Q_5H_R") $(make_quota_bar "$Q_WK" "7D" "$FG_BRIGHT_MAGENTA" "$Q_WK_R")"
+fi
+
# ─── Right-align helper ──────────────────────────────────────────────────────
# Prints LINE1 left-aligned and LINE2 right-aligned on the same terminal row.
print_right_aligned() {
@@ -255,13 +378,13 @@ if [ "$COLS" -ge 180 ]; then
TOK_DETAILS=" (${CTX_USED_FMT}/${CTX_LIMIT_FMT})${DOT}${FG_YELLOW} ${R} (${INPUT_TOK_FMT} in/${OUTPUT_TOK_FMT} out)"
fi
- LINE2="${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${TOK_DETAILS}"
+ LINE2="${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${CTX_BAR}${TOK_DETAILS}${DOT}${QUOTA_FMT}"
print_right_aligned "$LINE1" "$LINE2" "$COLS"
elif [ "$COLS" -ge 90 ]; then
# Medium Layout: Two-line layout with border
LINE1="${S}${M}${DIR_FMT}${V}"
- LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}"
+ LINE2=" ${CTX_BAR}${TOK_DETAILS}${DOT}${ART_FMT}${DOT}${SUB_FMT}${DOT}${BG_FMT}${DOT}${SB}${DOT}${QUOTA_FMT}"
echo -e "${FG_GRAY}╭─${R}${LINE1}"
echo -e "${FG_GRAY}╰─${R}${LINE2}"
@@ -273,5 +396,5 @@ else
fi
echo -e "${S}${M_SHORT}"
- echo -e "${CTX_BAR}${DOT}${BG_FMT}"
+ echo -e "${CTX_BAR}${DOT}${BG_FMT}${DOT}${QUOTA_FMT}"
fi
From 896ead23dfa57978fc3a69c48f6da1baf1f19dec Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Mon, 15 Jun 2026 14:02:49 +0300
Subject: [PATCH 25/28] style(statusline): change quota layout to use pipes and
hourglass emoji
---
examples/statusline/statusline.fish | 21 +++++++++++----------
examples/statusline/statusline.js | 16 ++++++++--------
examples/statusline/statusline.ps1 | 21 +++++++++++----------
examples/statusline/statusline.sh | 20 ++++++++++----------
4 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/examples/statusline/statusline.fish b/examples/statusline/statusline.fish
index 254eedf0b..b8b28e33b 100755
--- a/examples/statusline/statusline.fish
+++ b/examples/statusline/statusline.fish
@@ -257,20 +257,20 @@ function format_reset_time -a sec
if test "$days" -gt 0
if test "$hours" -gt 0
- echo -n " in "$days"d "$hours"h"
+ echo -n "$days"d" "$hours"h"
else
- echo -n " in "$days"d"
+ echo -n "$days"d"
fi
elif test "$hours" -gt 0
if test "$mins" -gt 0
- echo -n " in "$hours"h "$mins"m"
+ echo -n "$hours"h" "$mins"m"
else
- echo -n " in "$hours"h"
+ echo -n "$hours"h"
fi
elif test "$mins" -gt 0
- echo -n " in "$mins"m"
+ echo -n "$mins"m"
else
- echo -n " in <1m"
+ echo -n "<1m"
fi
end
@@ -280,7 +280,7 @@ function make_quota_bar -a val label bar_color reset_sec R FG_GRAY FG_BRIGHT_GRE
for i in (seq 1 20)
set bar "$bar░"
end
- echo -n "$FG_GRAY$bar N/A ($label)$R"
+ echo -n "$FG_GRAY| $R$FG_BRIGHT_WHITE$B$label$R $FG_GRAY$bar N/A$R"
return
end
@@ -321,9 +321,10 @@ function make_quota_bar -a val label bar_color reset_sec R FG_GRAY FG_BRIGHT_GRE
set -l reset_str ""
if test -n "$reset_sec"; and test "$reset_sec" -gt 0
- set reset_str (format_reset_time $reset_sec)
+ set -l time_fmt (format_reset_time $reset_sec)
+ set reset_str " ⌛️ $time_fmt"
end
- echo -n "$bar $fill_color$val%$R $FG_GRAY($label$reset_str)$R"
+ echo -n "$FG_GRAY| $R$FG_BRIGHT_WHITE$B$label$R $bar $fill_color$val%$R$reset_str"
end
set -l MODEL_LOWER (string lower "$MODEL_DISP")
@@ -347,7 +348,7 @@ set -l QUOTA_FMT ""
if test -n "$Q_5H"; or test -n "$Q_WK"
set -l fmt_5h (make_quota_bar $Q_5H "5H" "$FG_BRIGHT_CYAN" "$Q_5H_R" "$R" "$FG_GRAY" "$FG_BRIGHT_GREEN" "$FG_BRIGHT_YELLOW" "$FG_BRIGHT_RED")
set -l fmt_wk (make_quota_bar $Q_WK "7D" "$FG_BRIGHT_MAGENTA" "$Q_WK_R" "$R" "$FG_GRAY" "$FG_BRIGHT_GREEN" "$FG_BRIGHT_YELLOW" "$FG_BRIGHT_RED")
- set QUOTA_FMT "$fmt_5h $fmt_wk"
+ set QUOTA_FMT "$fmt_5h $fmt_wk"
end
# Output Assembly
diff --git a/examples/statusline/statusline.js b/examples/statusline/statusline.js
index 52c247bd8..3402fae10 100755
--- a/examples/statusline/statusline.js
+++ b/examples/statusline/statusline.js
@@ -247,19 +247,19 @@ function formatResetTime(sec) {
const mins = Math.floor((rem % 3600) / 60);
if (days > 0) {
- return hours > 0 ? ` in ${days}d ${hours}h` : ` in ${days}d`;
+ return hours > 0 ? `${days}d ${hours}h` : `${days}d`;
} else if (hours > 0) {
- return mins > 0 ? ` in ${hours}h ${mins}m` : ` in ${hours}h`;
+ return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
} else if (mins > 0) {
- return ` in ${mins}m`;
+ return `${mins}m`;
} else {
- return " in <1m";
+ return "<1m";
}
}
function makeQuotaBar(val, label, barColor, resetSec) {
if (val === undefined || val === null || val < 0) {
- return `${FG_GRAY}${"░".repeat(20)} N/A (${label})${R}`;
+ return `${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${FG_GRAY}${"░".repeat(20)} N/A${R}`;
}
let color = FG_BRIGHT_GREEN;
if (val < 20) {
@@ -315,8 +315,8 @@ function makeQuotaBar(val, label, barColor, resetSec) {
}
const valFmt = val.toFixed(1).replace(/\.0$/, '');
- const resetStr = resetSec > 0 ? formatResetTime(resetSec) : "";
- return `${coloredBar} ${color}${valFmt}%${R} ${FG_GRAY}(${label}${resetStr})${R}`;
+ const resetStr = resetSec > 0 ? ` ⌛️ ${formatResetTime(resetSec)}` : "";
+ return `${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${coloredBar} ${color}${valFmt}%${R}${resetStr}`;
}
const isGemini = (modelDisp || "").toLowerCase().includes("gemini");
@@ -325,7 +325,7 @@ const qWk = isGemini ? geminiWk : tpWk;
const q5hReset = isGemini ? gemini5hReset : tp5hReset;
const qWkReset = isGemini ? geminiWkReset : tpWkReset;
-const quotaFmt = (q5h >= 0 || qWk >= 0) ? `${makeQuotaBar(q5h, "5H", FG_BRIGHT_CYAN, q5hReset)} ${makeQuotaBar(qWk, "7D", FG_BRIGHT_MAGENTA, qWkReset)}` : "";
+const quotaFmt = (q5h >= 0 || qWk >= 0) ? `${makeQuotaBar(q5h, "5H", FG_BRIGHT_CYAN, q5hReset)} ${makeQuotaBar(qWk, "7D", FG_BRIGHT_MAGENTA, qWkReset)}` : "";
// ─── Output Assembly ──────────────────────────────────────────────────────────
if (cols >= 180) {
diff --git a/examples/statusline/statusline.ps1 b/examples/statusline/statusline.ps1
index 4d4743bfe..4e540e844 100644
--- a/examples/statusline/statusline.ps1
+++ b/examples/statusline/statusline.ps1
@@ -257,15 +257,15 @@ function Get-ResetTimeFmt {
$mins = [Math]::Floor(($rem % 3600) / 60)
if ($days -gt 0) {
- if ($hours -gt 0) { return " in ${days}d ${hours}h" }
- return " in ${days}d"
+ if ($hours -gt 0) { return "${days}d ${hours}h" }
+ return "${days}d"
} elseif ($hours -gt 0) {
- if ($mins -gt 0) { return " in ${hours}h ${mins}m" }
- return " in ${hours}h"
+ if ($mins -gt 0) { return "${hours}h ${mins}m" }
+ return "${hours}h"
} elseif ($mins -gt 0) {
- return " in ${mins}m"
+ return "${mins}m"
} else {
- return " in <1m"
+ return "<1m"
}
}
@@ -273,7 +273,7 @@ function Get-QuotaBar {
param ($val, $label, $barColor, $resetSec)
if ($null -eq $val -or $val -lt 0) {
$emptyBar = "░" * 20
- return "${FG_GRAY}${emptyBar} N/A (${label})${R}"
+ return "${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${FG_GRAY}${emptyBar} N/A${R}"
}
$color = $FG_BRIGHT_GREEN
if ($val -lt 20) {
@@ -335,9 +335,10 @@ function Get-QuotaBar {
$resetStr = ""
if ($null -ne $resetSec -and $resetSec -gt 0) {
- $resetStr = Get-ResetTimeFmt -sec $resetSec
+ $timeFmt = Get-ResetTimeFmt -sec $resetSec
+ $resetStr = " ⌛️ ${timeFmt}"
}
- return "${coloredBar} ${color}${valFmt}%${R} ${FG_GRAY}(${label}${resetStr})${R}"
+ return "${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${coloredBar} ${color}${valFmt}%${R}${resetStr}"
}
$isGemini = $modelDisp.ToLower().Contains("gemini")
@@ -357,7 +358,7 @@ $QUOTA_FMT = ""
if ($q5h -ge 0 -or $qWk -ge 0) {
$fmt5h = Get-QuotaBar -val $q5h -label "5H" -barColor $FG_BRIGHT_CYAN -resetSec $q5hR
$fmtWk = Get-QuotaBar -val $qWk -label "7D" -barColor $FG_BRIGHT_MAGENTA -resetSec $qWkR
- $QUOTA_FMT = "${fmt5h} ${fmtWk}"
+ $QUOTA_FMT = "${fmt5h} ${fmtWk}"
}
# ─── Output Assembly ──────────────────────────────────────────────────────────
diff --git a/examples/statusline/statusline.sh b/examples/statusline/statusline.sh
index 4ef769066..523e883fd 100755
--- a/examples/statusline/statusline.sh
+++ b/examples/statusline/statusline.sh
@@ -261,20 +261,20 @@ format_reset_time() {
if [ "$days" -gt 0 ]; then
if [ "$hours" -gt 0 ]; then
- echo -n " in ${days}d ${hours}h"
+ echo -n "${days}d ${hours}h"
else
- echo -n " in ${days}d"
+ echo -n "${days}d"
fi
elif [ "$hours" -gt 0 ]; then
if [ "$mins" -gt 0 ]; then
- echo -n " in ${hours}h ${mins}m"
+ echo -n "${hours}h ${mins}m"
else
- echo -n " in ${hours}h"
+ echo -n "${hours}h"
fi
elif [ "$mins" -gt 0 ]; then
- echo -n " in ${mins}m"
+ echo -n "${mins}m"
else
- echo -n " in <1m"
+ echo -n "<1m"
fi
}
@@ -288,7 +288,7 @@ make_quota_bar() {
for ((i = 0; i < 20; i++)); do
bar="${bar}░"
done
- echo -n "${FG_GRAY}${bar} N/A (${label})${R}"
+ echo -n "${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${FG_GRAY}${bar} N/A${R}"
return
fi
@@ -327,10 +327,10 @@ make_quota_bar() {
local reset_str=""
if [ -n "$reset_sec" ] && [ "$reset_sec" -gt 0 ]; then
- reset_str=$(format_reset_time "$reset_sec")
+ reset_str=" ⌛️ $(format_reset_time "$reset_sec")"
fi
- echo -n "${bar} ${text_color}${val}%${R} ${FG_GRAY}(${label}${reset_str})${R}"
+ echo -n "${FG_GRAY}| ${R}${FG_BRIGHT_WHITE}${B}${label}${R} ${bar} ${text_color}${val}%${R}${reset_str}"
}
# Determine active quota
@@ -349,7 +349,7 @@ fi
QUOTA_FMT=""
if [ -n "$Q_5H" ] || [ -n "$Q_WK" ]; then
- QUOTA_FMT="$(make_quota_bar "$Q_5H" "5H" "$FG_BRIGHT_CYAN" "$Q_5H_R") $(make_quota_bar "$Q_WK" "7D" "$FG_BRIGHT_MAGENTA" "$Q_WK_R")"
+ QUOTA_FMT="$(make_quota_bar "$Q_5H" "5H" "$FG_BRIGHT_CYAN" "$Q_5H_R") $(make_quota_bar "$Q_WK" "7D" "$FG_BRIGHT_MAGENTA" "$Q_WK_R")"
fi
# ─── Right-align helper ──────────────────────────────────────────────────────
From 789cfcd4239d31873de3d4c504abbe883c13ee93 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Tue, 16 Jun 2026 15:47:32 +0300
Subject: [PATCH 26/28] chore: upgrade manifests and badges to v1.0.8, add
statusline cross-references
---
README-DE.md | 6 +++++-
README-ES.md | 6 +++++-
README-FR.md | 6 +++++-
README-PT.md | 6 +++++-
README-UA.md | 6 +++++-
README-ZH.md | 6 +++++-
README.md | 6 +++++-
packages/manifests/darwin_amd64.json | 6 +++---
packages/manifests/darwin_arm64.json | 6 +++---
packages/manifests/linux_amd64.json | 6 +++---
packages/manifests/linux_arm64.json | 6 +++---
packages/manifests/windows_amd64.json | 6 +++---
12 files changed, 50 insertions(+), 22 deletions(-)
diff --git a/README-DE.md b/README-DE.md
index 42715bd91..2f252f7e1 100644
--- a/README-DE.md
+++ b/README-DE.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ Die globale Einstellungsdatei steuert Berechtigungen zur Tool-Ausführung, Statu
}
```
+> [!TIP]
+> Für eine erweiterte, reaktionsschnelle Statuszeile mit Echtzeit-Git-Status, Token-Zählern, Modellquoten und Strom-/Batteriestatus verwenden Sie das Plugin **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)**.
+
+
### 3. Spezialisierte Agenten
Sie können benutzerdefinierte Rollen und Anweisungen für KI-Agenten im JSON-Format beschreiben. Jeder Agent muss sein eigenes Verzeichnis haben, das eine `agent.json`-Datei enthält:
diff --git a/README-ES.md b/README-ES.md
index 0845b32f0..40a0ea4da 100644
--- a/README-ES.md
+++ b/README-ES.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ El archivo de configuración global controla los permisos de ejecución de herra
}
```
+> [!TIP]
+> Para obtener una línea de estado avanzada y adaptable con seguimiento de estado de Git en tiempo real, contadores de tokens, cuotas de modelos y estado de energía/batería, use el complemento **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)**.
+
+
### 3. Agentes especializados
Puede describir roles e instrucciones personalizados para agentes de IA en formato JSON. Cada agente debe tener su propio directorio que contenga un archivo `agent.json`:
diff --git a/README-FR.md b/README-FR.md
index b14ad2a8f..a5cedec12 100644
--- a/README-FR.md
+++ b/README-FR.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ Le fichier de configuration globale contrôle les autorisations d'exécution des
}
```
+> [!TIP]
+> Pour une ligne de statut avancée et réactive avec suivi du statut Git en temps réel, compteurs de jetons, quotas de modèles et état d'alimentation/batterie, utilisez le plugin **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)**.
+
+
### 3. Agents spécialisés
Vous pouvez décrire des rôles et des instructions personnalisés pour les agents IA au format JSON. Chaque agent doit avoir son propre répertoire contenant un fichier `agent.json` :
diff --git a/README-PT.md b/README-PT.md
index 1c5939fcc..65a74a16a 100644
--- a/README-PT.md
+++ b/README-PT.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ O arquivo de configuração global controla as permissões de execução de ferr
}
```
+> [!TIP]
+> Para obter uma linha de status avançada e responsiva com rastreamento de status Git em tempo real, medidores de tokens, cotas de modelos e estado de energia/batería, use o plugin **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)**.
+
+
### 3. Agentes Especializados
Você pode descrever funções e instruções personalizadas para agentes de IA no formato JSON. Cada agente deve ter seu próprio diretório contendo um arquivo `agent.json`:
diff --git a/README-UA.md b/README-UA.md
index a1f65f95d..9fc77396b 100644
--- a/README-UA.md
+++ b/README-UA.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ install.cmd
}
```
+> [!TIP]
+> Для отримання просунутого та адаптивного статус-бару з відстеженням Git-статусу в реальному часі, лічильником токенів, квотами моделей та станом живлення/батареї, скористайтеся плагіном **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)**.
+
+
### 3. Спеціалізовані агенти
Ви можете описувати кастомні ролі та інструкції для ШІ-агентів у форматі JSON. Кожен агент повинен мати власну директорію, яка містить файл `agent.json`:
diff --git a/README-ZH.md b/README-ZH.md
index e7eac63be..6c6c1b29e 100644
--- a/README-ZH.md
+++ b/README-ZH.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ install.cmd
}
```
+> [!TIP]
+> 如需具有实时 Git 状态跟踪、Token 计数器、模型配额以及电源/电池状态的高级响应式状态栏,请使用 **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)** 插件。
+
+
### 3. 专业智能体
您可以使用 JSON 格式定义 AI 智能体的自定义角色和说明。每个智能体必须有自己的目录,其中包含一个 `agent.json` 文件:
diff --git a/README.md b/README.md
index 6b6c6243f..ea1cea5a3 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@
-
+
@@ -115,6 +115,10 @@ The global settings file controls tool execution permissions, statusline/title s
}
```
+> [!TIP]
+> For an advanced, responsive statusline with real-time Git status, token meters, model quotas, and power/battery state, check out the **[Antigravity CLI Statusline (Max Edition)](https://github.com/weby-homelab/antigravity-cli-statusline)** plugin.
+
+
### 3. Specialized Agents
You can describe custom roles and instructions for AI agents in JSON format. Each agent must have its own directory containing an `agent.json` file:
diff --git a/packages/manifests/darwin_amd64.json b/packages/manifests/darwin_amd64.json
index abda3e9f6..2dd2933d7 100644
--- a/packages/manifests/darwin_amd64.json
+++ b/packages/manifests/darwin_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.4",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_mac_x64.tar.gz",
- "sha512": "1acce9221efc36d1470b1c5faef0358df1b0fa848aa51661617551145da575c5244855c89ec26c94652a1d7634cc91376da0a41be5b5cac94ace5f9c1ae640e0"
+ "version": "1.0.8",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.8/cli_mac_x64.tar.gz",
+ "sha512": "ff99cc36aafab9a37bdeef6ba2cc9c1dfeefaf133052e21a65aaec46dd5a96603d5b48352bc7d51d46e5db63c2a9c05c5d1226b69400c01cca0195217f2dce41"
}
\ No newline at end of file
diff --git a/packages/manifests/darwin_arm64.json b/packages/manifests/darwin_arm64.json
index 81c70e4ff..9452f6281 100644
--- a/packages/manifests/darwin_arm64.json
+++ b/packages/manifests/darwin_arm64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.4",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_mac_arm64.tar.gz",
- "sha512": "7f72c8a31da304111468f43023e3c611b9b1035955a9155e5fc0fa770830976e2a23785e15f03041831b044fef2eec23bdbfa2394b39f6cd541d9e449da1c7db"
+ "version": "1.0.8",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.8/cli_mac_arm64.tar.gz",
+ "sha512": "8fc657eea3a13c0662c614ba4051f04050cb0abba7555518aadb36d88da7d3bd212ae4cc6cf4392038b21656896979aeab32f8bf05c80e69fc720c7651034636"
}
\ No newline at end of file
diff --git a/packages/manifests/linux_amd64.json b/packages/manifests/linux_amd64.json
index 2b4c6c415..06f7433bb 100644
--- a/packages/manifests/linux_amd64.json
+++ b/packages/manifests/linux_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.4",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_linux_x64.tar.gz",
- "sha512": "fd95930b06384ed4b5672d1a8f32041ccd9686630187d981db7172180a2a0bb77ec63a887adc66291d2bcb50ff5b08a4996b9cbacc589c56c861c40209d57f10"
+ "version": "1.0.8",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.8/cli_linux_x64.tar.gz",
+ "sha512": "78426a61f9295d75285d9cdd39e9b9dc2736346468c92ce86a6fde1bcc7a9d6dd32c7bed1903a5deacd874a05d75babf5b907e991a117ad63f33548113c61bc2"
}
\ No newline at end of file
diff --git a/packages/manifests/linux_arm64.json b/packages/manifests/linux_arm64.json
index 47fd9a34c..246e40322 100644
--- a/packages/manifests/linux_arm64.json
+++ b/packages/manifests/linux_arm64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.4",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_linux_arm64.tar.gz",
- "sha512": "cfbbec3cfefc18de3f0d57bc71c28193a6e804e2ba1c6c319db67ef7ce18201380906cba424b4cbbe0786efd574e2c9677a3434dcb106cd29314015513925a41"
+ "version": "1.0.8",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.8/cli_linux_arm64.tar.gz",
+ "sha512": "982b344defd2e63b08d4a9ef608a770aba3a92e9a4118d318e0082f29743457e8a16aaf1c00f7c3ade75c2d07aef5a8fa5da660e408cb199b47a8778b31228a0"
}
\ No newline at end of file
diff --git a/packages/manifests/windows_amd64.json b/packages/manifests/windows_amd64.json
index fd5642f52..698354771 100644
--- a/packages/manifests/windows_amd64.json
+++ b/packages/manifests/windows_amd64.json
@@ -1,5 +1,5 @@
{
- "version": "1.0.4",
- "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.4/cli_windows_x64.zip",
- "sha512": "b48c80d892582a595ed8f1d30c0e2fce584f9fbecf79432d676e270b43057d6cb4a1107439ea3fd09bcc82fb97a4cbd0a2baf7683e80f2bb71cce08865befc61"
+ "version": "1.0.8",
+ "url": "https://github.com/weby-homelab/antigravity-cli/releases/download/v1.0.8/cli_windows_x64.zip",
+ "sha512": "c3b2e00020f3ebb0626f365f77ea74991058209e16bd1f22ef3ef6da1d2babe3415358470c7930a2c9a647edb1f5c61faeb65f675cb2ff45095fae7b91c16b48"
}
\ No newline at end of file
From 00491d5d8c8e54837bed9ebb5f10775863248815 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Thu, 9 Jul 2026 11:41:49 +0300
Subject: [PATCH 27/28] fix(installer): preserve existing statusLine and title
configurations in settings.json
---
install.ps1 | 16 ++++++++++------
install.sh | 10 ++++++----
2 files changed, 16 insertions(+), 10 deletions(-)
diff --git a/install.ps1 b/install.ps1
index 2769ab526..2e4d91a5b 100644
--- a/install.ps1
+++ b/install.ps1
@@ -240,13 +240,17 @@ function Invoke-Install {
} catch {}
}
- $settings["statusLine"] = @{
- "command" = $statusScript
- "enabled" = $true
+ if (-not $settings.ContainsKey("statusLine") -or $null -eq $settings["statusLine"] -or -not $settings["statusLine"].ContainsKey("command") -or [string]::IsNullOrEmpty($settings["statusLine"]["command"])) {
+ $settings["statusLine"] = @{
+ "command" = $statusScript
+ "enabled" = $true
+ }
}
- $settings["title"] = @{
- "command" = $titleScript
- "enabled" = $true
+ if (-not $settings.ContainsKey("title") -or $null -eq $settings["title"] -or -not $settings["title"].ContainsKey("command") -or [string]::IsNullOrEmpty($settings["title"]["command"])) {
+ $settings["title"] = @{
+ "command" = $titleScript
+ "enabled" = $true
+ }
}
$settings | ConvertTo-Json -Depth 5 | Out-File $settingsFile -Encoding utf8 -Force
diff --git a/install.sh b/install.sh
index e5ba9fcc8..cc9d1e900 100755
--- a/install.sh
+++ b/install.sh
@@ -315,8 +315,9 @@ except Exception:
for key, path in [("statusLine", stat_path), ("title", title_path)]:
if key not in data or not isinstance(data[key], dict):
data[key] = {}
- data[key]["command"] = path
- data[key]["enabled"] = True
+ if not data[key].get("command"):
+ data[key]["command"] = path
+ data[key]["enabled"] = True
with open(file_path, "w") as f:
json.dump(data, f, indent=2)
' "$SETTINGS_FILE" "$CONFIG_DIR/statusline.sh" "$CONFIG_DIR/title.sh" || true
@@ -332,8 +333,9 @@ except Exception:
for key, path in [("statusLine", stat_path), ("title", title_path)]:
if key not in data or not isinstance(data[key], dict):
data[key] = {}
- data[key]["command"] = path
- data[key]["enabled"] = True
+ if not data[key].get("command"):
+ data[key]["command"] = path
+ data[key]["enabled"] = True
with open(file_path, "w") as f:
json.dump(data, f, indent=2)
' "$SETTINGS_FILE" "$CONFIG_DIR/statusline.sh" "$CONFIG_DIR/title.sh" || true
From 4f6ef16c43e512507260178b5cf5696fe4d1cd16 Mon Sep 17 00:00:00 2001
From: weby-homelab
Date: Wed, 22 Jul 2026 20:26:46 +0300
Subject: [PATCH 28/28] feat(installer): add --no-statusline and --no-title
flags and non-destructively preserve existing settings.json
---
install.sh | 130 +++++++++++++++++++++++++++--------------------------
1 file changed, 67 insertions(+), 63 deletions(-)
diff --git a/install.sh b/install.sh
index cc9d1e900..ae72c210e 100755
--- a/install.sh
+++ b/install.sh
@@ -12,13 +12,18 @@ DOWNLOAD_BASE_URL="https://raw.githubusercontent.com/weby-homelab/antigravity-cl
TARGET_DIR="$HOME/.local/bin"
CUSTOM_DIR=""
+SKIP_STATUSLINE=false
+SKIP_TITLE=false
+
# Helper: Display usage instructions
show_usage() {
echo "Usage: install.sh [options]"
echo ""
echo "Options:"
- echo " -d, --dir Specify a custom directory to install the binary"
- echo " -h, --help Display this help menu"
+ echo " -d, --dir Specify a custom directory to install the binary"
+ echo " --no-statusline Skip automatic statusline script configuration"
+ echo " --no-title Skip automatic terminal title script configuration"
+ echo " -h, --help Display this help menu"
echo ""
}
@@ -33,6 +38,12 @@ while [ "$#" -gt 0 ]; do
CUSTOM_DIR="$2"
shift
;;
+ --no-statusline)
+ SKIP_STATUSLINE=true
+ ;;
+ --no-title)
+ SKIP_TITLE=true
+ ;;
-h|--help)
show_usage
exit 0
@@ -271,76 +282,69 @@ else
fi
# 8. Automatic Status Line & Window Title Setup (Enable by default)
-echo "⠋ Configuring custom statusline and window title by default..."
-
-CONFIG_DIR="$HOME/.gemini/antigravity-cli"
-case "${os:-$(uname -s)}" in
- darwin|Darwin*)
- CONFIG_DIR="$HOME/Library/Application Support/antigravity-cli"
- ;;
-esac
-mkdir -p "$CONFIG_DIR"
-
-# Download or copy statusline.sh
-if [ "$LOCAL_MODE" = true ] && [ -f "$SCRIPT_DIR/examples/statusline/statusline.sh" ]; then
- cp "$SCRIPT_DIR/examples/statusline/statusline.sh" "$CONFIG_DIR/statusline.sh"
-else
- download_file "https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/examples/statusline/statusline.sh" "$CONFIG_DIR/statusline.sh" || true
-fi
-chmod +x "$CONFIG_DIR/statusline.sh" 2>/dev/null || true
-
-# Download or copy title.sh
-if [ "$LOCAL_MODE" = true ] && [ -f "$SCRIPT_DIR/examples/title/title.sh" ]; then
- cp "$SCRIPT_DIR/examples/title/title.sh" "$CONFIG_DIR/title.sh"
-else
- download_file "https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/examples/title/title.sh" "$CONFIG_DIR/title.sh" || true
-fi
-chmod +x "$CONFIG_DIR/title.sh" 2>/dev/null || true
-
-# Update settings.json to enable them
-SETTINGS_FILE="$CONFIG_DIR/settings.json"
-if [ ! -f "$SETTINGS_FILE" ]; then
- echo "{}" > "$SETTINGS_FILE"
-fi
+if [ "$SKIP_STATUSLINE" = false ] || [ "$SKIP_TITLE" = false ]; then
+ echo "⠋ Configuring custom statusline and window title by default..."
+
+ CONFIG_DIR="$HOME/.gemini/antigravity-cli"
+ case "${os:-$(uname -s)}" in
+ darwin|Darwin*)
+ CONFIG_DIR="$HOME/Library/Application Support/antigravity-cli"
+ ;;
+ esac
+ mkdir -p "$CONFIG_DIR"
+
+ if [ "$SKIP_STATUSLINE" = false ]; then
+ if [ "$LOCAL_MODE" = true ] && [ -f "$SCRIPT_DIR/examples/statusline/statusline.sh" ]; then
+ cp "$SCRIPT_DIR/examples/statusline/statusline.sh" "$CONFIG_DIR/statusline.sh"
+ else
+ download_file "https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/examples/statusline/statusline.sh" "$CONFIG_DIR/statusline.sh" || true
+ fi
+ chmod +x "$CONFIG_DIR/statusline.sh" 2>/dev/null || true
+ fi
-if command -v python3 >/dev/null 2>&1; then
- python3 -c '
-import json, sys
-file_path, stat_path, title_path = sys.argv[1], sys.argv[2], sys.argv[3]
-try:
- with open(file_path, "r") as f:
- data = json.load(f)
-except Exception:
- data = {}
-for key, path in [("statusLine", stat_path), ("title", title_path)]:
- if key not in data or not isinstance(data[key], dict):
- data[key] = {}
- if not data[key].get("command"):
- data[key]["command"] = path
- data[key]["enabled"] = True
-with open(file_path, "w") as f:
- json.dump(data, f, indent=2)
-' "$SETTINGS_FILE" "$CONFIG_DIR/statusline.sh" "$CONFIG_DIR/title.sh" || true
-elif command -v python >/dev/null 2>&1; then
- python -c '
+ if [ "$SKIP_TITLE" = false ]; then
+ if [ "$LOCAL_MODE" = true ] && [ -f "$SCRIPT_DIR/examples/title/title.sh" ]; then
+ cp "$SCRIPT_DIR/examples/title/title.sh" "$CONFIG_DIR/title.sh"
+ else
+ download_file "https://raw.githubusercontent.com/weby-homelab/antigravity-cli/main/examples/title/title.sh" "$CONFIG_DIR/title.sh" || true
+ fi
+ chmod +x "$CONFIG_DIR/title.sh" 2>/dev/null || true
+ fi
+
+ SETTINGS_FILE="$CONFIG_DIR/settings.json"
+ if [ ! -f "$SETTINGS_FILE" ]; then
+ echo "{}" > "$SETTINGS_FILE"
+ fi
+
+ if command -v python3 >/dev/null 2>&1 || command -v python >/dev/null 2>&1; then
+ PY_BIN="python3"
+ command -v python3 >/dev/null 2>&1 || PY_BIN="python"
+ "$PY_BIN" -c '
import json, sys
-file_path, stat_path, title_path = sys.argv[1], sys.argv[2], sys.argv[3]
+file_path, stat_path, title_path, skip_stat, skip_title = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
try:
with open(file_path, "r") as f:
data = json.load(f)
except Exception:
data = {}
-for key, path in [("statusLine", stat_path), ("title", title_path)]:
- if key not in data or not isinstance(data[key], dict):
- data[key] = {}
- if not data[key].get("command"):
- data[key]["command"] = path
- data[key]["enabled"] = True
+if skip_stat != "true":
+ if "statusLine" not in data or not isinstance(data["statusLine"], dict):
+ data["statusLine"] = {}
+ if not data["statusLine"].get("command"):
+ data["statusLine"]["command"] = stat_path
+ data["statusLine"]["enabled"] = True
+if skip_title != "true":
+ if "title" not in data or not isinstance(data["title"], dict):
+ data["title"] = {}
+ if not data["title"].get("command"):
+ data["title"]["command"] = title_path
+ data["title"]["enabled"] = True
with open(file_path, "w") as f:
json.dump(data, f, indent=2)
-' "$SETTINGS_FILE" "$CONFIG_DIR/statusline.sh" "$CONFIG_DIR/title.sh" || true
-fi
+' "$SETTINGS_FILE" "$CONFIG_DIR/statusline.sh" "$CONFIG_DIR/title.sh" "$SKIP_STATUSLINE" "$SKIP_TITLE" || true
+ fi
-echo "✓ Custom statusline and terminal title configured and enabled by default."
+ echo "✓ Custom statusline and terminal title configured and enabled by default."
+fi