<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wassim AZI</title>
    <description>The latest articles on DEV Community by Wassim AZI (@wassim-azi).</description>
    <link>https://dev.to/wassim-azi</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4192890%2F2a942e02-dafe-407c-bdc6-a106a2978a78.png</url>
      <title>DEV Community: Wassim AZI</title>
      <link>https://dev.to/wassim-azi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wassim-azi"/>
    <language>en</language>
    <item>
      <title>A live-preview terminal UI in plain Node.js, with zero dependencies</title>
      <dc:creator>Wassim AZI</dc:creator>
      <pubDate>Fri, 30 Oct 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/wassim-azi/a-live-preview-terminal-ui-in-plain-nodejs-with-zero-dependencies-2pi8</link>
      <guid>https://dev.to/wassim-azi/a-live-preview-terminal-ui-in-plain-nodejs-with-zero-dependencies-2pi8</guid>
      <description>&lt;p&gt;In Google's Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;), the &lt;code&gt;/statusline&lt;/code&gt; command turns the status bar on or off, and that's it. People &lt;a href="https://discuss.ai.google.dev/t/antigravitys-spaghetti-code-for-statusline/171395" rel="noopener noreferrer"&gt;expect more&lt;/a&gt;: a way to change what the bar shows without writing a custom shell script. Configuring a statusline by hand usually means wrestling with an undocumented JSON schema. You edit a property, save, switch windows, inspect the bar, and go back to edit again.&lt;/p&gt;

&lt;p&gt;When building &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;agy-powerline&lt;/a&gt;, I wanted the terminal customization experience to feel like &lt;strong&gt;Powerlevel10k for Google Antigravity&lt;/strong&gt;: one command that opens an interactive configurator with a live preview of your bar, where you browse presets, toggle widgets, and audition themes with arrow keys, with the preview following every change.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fwassim-azi%2Fagy-powerline%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fwassim-azi%2Fagy-powerline%2Fmain%2Fassets%2Fdemo.gif" alt="The configure editor: widgets, presets, themes and layouts, with the live preview following the cursor" width="760" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project has a hard rule: &lt;strong&gt;zero runtime dependencies&lt;/strong&gt;. No Ink, no blessed, no chalk. Partly for supply-chain reasons (people run it with &lt;code&gt;npx&lt;/code&gt;, and it touches their dev configuration), partly because every module it doesn't load is start-up time it doesn't pay. So the interactive TUI is built on Node's standard library and a handful of raw ANSI escape codes. Here's how it works, and what I'd do the same way again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The primitives: raw mode, keypresses, the alternate screen
&lt;/h2&gt;

&lt;p&gt;Everything starts with four calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;readline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;readline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;readline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;emitKeypressEvents&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setRawMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resume&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;keypress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;onKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;emitKeypressEvents&lt;/code&gt; parses the raw bytes into events like &lt;code&gt;{ name: "up" }&lt;/code&gt; or &lt;code&gt;{ name: "c", ctrl: true }&lt;/code&gt;, so arrow keys, Enter, Escape and letters all arrive in one handler.&lt;/p&gt;

&lt;p&gt;Then the editor switches to the terminal's alternate screen and hides the cursor, so it doesn't scroll away your shell history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CSI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;x1b[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?1049h&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?25l`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// alternate screen, hide cursor&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The most important part is the way out. In raw mode, Ctrl+C is just a key, so the key handler treats it as quit. &lt;code&gt;SIGINT&lt;/code&gt; and &lt;code&gt;SIGTERM&lt;/code&gt; get the same handler. Quitting always does the same three things: save, leave raw mode, and restore the screen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exiting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;exiting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nf"&gt;persist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setRawMode&lt;/span&gt;&lt;span class="p"&gt;?.(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?25h&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?1049l`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// show cursor, main screen&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A TUI that leaves your terminal in raw mode with no cursor is a TUI people uninstall.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redraw everything, every time
&lt;/h2&gt;

&lt;p&gt;There's no diffing and no virtual DOM. On every keypress, the editor builds the whole frame as an array of strings, moves the cursor home, clears to the end of the screen, and writes the frame:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;H&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;CSI&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;J`&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;frameLines&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;resize&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an editor that only redraws when you press a key, this is fast enough, and it's very simple to reason about: there's no previous state to get out of sync with. If it ever becomes a problem, writing only the lines that changed would be the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The frame is a pure function, so it's testable
&lt;/h2&gt;

&lt;p&gt;Because &lt;code&gt;draw()&lt;/code&gt; only writes what &lt;code&gt;frameLines()&lt;/code&gt; returns, the interesting part is a function of state and window size. The module exports a &lt;code&gt;renderFrame()&lt;/code&gt; for tests that sets the state, pins the size and the color depth, and returns the frame as a string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;defaultConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="na"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;layout&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes a terminal UI testable like any other output. One test, for example, checks that each layout's description in the details panel is exactly three lines under 90 columns, separated by blank lines. Another draws every screen at seven window sizes, from 60×16 to 220×50, in Unicode and ASCII mode, and checks that no frame is wider or taller than its window. No pseudo-terminal, no screenshots, no timing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The preview is the real renderer
&lt;/h2&gt;

&lt;p&gt;The live preview isn't a mock of the bar. It calls the same &lt;code&gt;renderStatusline()&lt;/code&gt; function the installed statusline runs on every refresh in &lt;code&gt;agy&lt;/code&gt;, with sample session data and a terminal width set to the preview panel's width:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;previewData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;terminal_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rendered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;renderStatusline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;previewData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;previewConfig&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;getActiveDepth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the preview fits, drops widgets and cuts lines exactly the way the real bar will in a terminal that wide. If it looks right in the editor, it looks right in &lt;code&gt;agy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;With no dependencies, the same modules also run in a browser. The &lt;a href="https://agy-powerline.com/configure/" rel="noopener noreferrer"&gt;web configurator&lt;/a&gt; bundles the runtime's own CommonJS modules into the page, so its preview comes from the same renderer as the bar in your terminal.&lt;/p&gt;

&lt;p&gt;Hovering is a preview, not a change. Moving the cursor over a theme, a preset or a layout redraws the preview with it, and the preview's title says so (&lt;code&gt;preview, enter to apply&lt;/code&gt;). Enter applies it; moving away leaves your setup as it was.&lt;/p&gt;

&lt;p&gt;One safety detail: the bar has an &lt;code&gt;exec&lt;/code&gt; widget that runs a shell command you configure. The preview never runs it, and shows &lt;code&gt;[cmd]&lt;/code&gt; instead. Scrolling through your config shouldn't execute anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsive, in a terminal
&lt;/h2&gt;

&lt;p&gt;A terminal can be 80 columns or 200, and 20 rows or 60. The editor changes shape at two widths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;From 80 columns, the descriptions line up in their own column.&lt;/li&gt;
&lt;li&gt;From 140 columns, the list gets a details panel next to it: the selected widget drawn on its own with its options, a preset's rows, or a theme's colors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Height is handled the other way around. When the window is short, the editor gives things up one step at a time: first the tagline, then the blank lines between panels, then the notes, then the preview's toggles. That way the list keeps at least six rows. A list longer than its panel scrolls, and the scroll position is kept between frames, so the list only moves when the cursor leaves the visible part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Counting columns is harder than it looks
&lt;/h2&gt;

&lt;p&gt;Every box, panel and padding depends on one function: how many columns a string takes on screen. &lt;code&gt;string.length&lt;/code&gt; is wrong in several ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Escape sequences (colors, hyperlinks) take no columns, so strip them first.&lt;/li&gt;
&lt;li&gt;Most emoji and CJK characters take two.&lt;/li&gt;
&lt;li&gt;Combining marks, zero-width joiners, variation selectors and skin-tone modifiers take none.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And terminals don't always agree. When I recorded the demo GIF with VHS, one emoji (🪙) was drawn one column wide, while the width count says two. Everything after it on that row moved over by one column. There's no perfect answer: you pick the width most terminals use and accept the odd exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Colors for every terminal
&lt;/h2&gt;

&lt;p&gt;The editor and the bar detect the color depth: truecolor, 256 colors, or none (&lt;code&gt;NO_COLOR&lt;/code&gt; is respected). In 256-color mode each color maps to the nearest one in the terminal's palette. Getting that right needed care: an early version turned near-black text into navy blue. There's also an ASCII mode for terminals without a Nerd Font, and a test checks that every screen draws in plain ASCII when it's on.&lt;/p&gt;

&lt;p&gt;Every theme is also checked in the test suite: WCAG AA contrast (4.5:1 minimum ratio) for text on each pill and for each color on the terminal background. For the &lt;code&gt;colorblind-*&lt;/code&gt; themes, the tests simulate protanopia, deuteranopia and tritanopia to mathematically guarantee that caution and danger states remain distinguishable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recording the demo
&lt;/h2&gt;

&lt;p&gt;The GIF at the top is recorded from a script with &lt;a href="https://github.com/charmbracelet/vhs" rel="noopener noreferrer"&gt;VHS&lt;/a&gt;, so it can be re-recorded after each change. The tape starts the editor with a demo config in a temporary folder, so recording it never touches my real settings, and drives it with keypresses: add a widget by typing its name, browse a few presets, themes and layouts, open the last tab.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;The editor is one file of about 2,100 lines. That was fine while the design was moving, but it's time to split it into one module per screen. Everything else (raw mode, full redraws, a pure frame function, the real renderer as the preview) I'd do the same way again.&lt;/p&gt;

&lt;p&gt;If you're building a small CLI and think you need a heavy TUI framework, try &lt;code&gt;readline&lt;/code&gt; and a few escape codes first. You may not need anything else.&lt;/p&gt;

&lt;p&gt;To test the editor yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or test all 16 themes, 3 layouts, and custom meter styles right in your browser with the &lt;a href="https://agy-powerline.com/configure/" rel="noopener noreferrer"&gt;web configurator&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤖 &lt;strong&gt;One-shot install with Antigravity:&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey agy, please install agy-powerline by running `npx agy-powerline init --yes`, then tell me to restart agy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;The code is MIT: &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;wassim-azi/agy-powerline&lt;/a&gt;, in &lt;code&gt;src/cli/configure.js&lt;/code&gt; and &lt;code&gt;src/cli/tui-style.js&lt;/code&gt;. Questions and suggestions are welcome in the comments.&lt;/p&gt;

</description>
      <category>node</category>
      <category>javascript</category>
      <category>cli</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Building a statusline for Google's Antigravity CLI that doesn't break on Windows</title>
      <dc:creator>Wassim AZI</dc:creator>
      <pubDate>Wed, 28 Oct 2026 11:00:00 +0000</pubDate>
      <link>https://dev.to/wassim-azi/building-a-statusline-for-googles-antigravity-cli-that-doesnt-break-on-windows-3k7n</link>
      <guid>https://dev.to/wassim-azi/building-a-statusline-for-googles-antigravity-cli-that-doesnt-break-on-windows-3k7n</guid>
      <description>&lt;p&gt;A statusline sounds like a weekend project. Read some JSON, print one colored line, done.&lt;/p&gt;

&lt;p&gt;Then you run it on Windows.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;agy-powerline&lt;/a&gt;, a statusline for Google's Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;). It's plain Node with no dependencies, and it runs on Windows, macOS, Linux and WSL. Most of the work wasn't drawing the bar. It was making sure the bar starts, every time, on every machine, and never takes the session down with it. Here's what I ran into.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvk78dgnc4igbmnzv0iit.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvk78dgnc4igbmnzv0iit.png" alt="agy-powerline under agy's input box in Windows Terminal: a short conversation above, the three-row bar below" width="800" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How agy runs a statusline
&lt;/h2&gt;

&lt;p&gt;You give &lt;code&gt;agy&lt;/code&gt; a command in its &lt;code&gt;settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"statusLine"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:/Users/you/.agy-powerline/statusline.cmd"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whenever its state changes, &lt;code&gt;agy&lt;/code&gt; starts that command, writes a JSON payload to its stdin (model, quotas, context window, terminal width and more), and shows what it prints.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://antigravity.google/docs/cli/statusline/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt; explain the payload and show a bash script you &lt;code&gt;chmod +x&lt;/code&gt;. They don't say how the command is started on each OS, how long it may take, or what happens when it fails. Everything below I found by testing.&lt;/p&gt;

&lt;p&gt;Two details matter a lot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On Windows, &lt;code&gt;agy&lt;/code&gt; runs the command through &lt;code&gt;cmd /c&lt;/code&gt;. On macOS and Linux, through &lt;code&gt;sh -c&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It runs &lt;strong&gt;all the time&lt;/strong&gt;. Anything slow, fragile or noisy shows up right away.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Trap 1: quotes that cmd doesn't understand
&lt;/h2&gt;

&lt;p&gt;The obvious Windows command is &lt;code&gt;"C:\Program Files\nodejs\node.exe" "C:\Users\you\...\statusline.js"&lt;/code&gt;. It doesn't work.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;agy&lt;/code&gt; is written in Go, and Go's &lt;code&gt;os/exec&lt;/code&gt; escapes embedded double quotes as &lt;code&gt;\"&lt;/code&gt; when it builds the Windows command line. That's the C runtime's convention, but &lt;code&gt;cmd&lt;/code&gt; doesn't follow it: it keeps the backslash and the quote as part of the program name, and can't find &lt;code&gt;node.exe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the setting holds no quotes at all. It points at a small launcher, written with forward slashes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;@echo &lt;span class="na"&gt;off&lt;/span&gt;
&lt;span class="nb"&gt;setlocal&lt;/span&gt; &lt;span class="kd"&gt;DisableDelayedExpansion&lt;/span&gt;
&lt;span class="s2"&gt;"C:\Program Files\nodejs\node.exe"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="vm"&gt;%~dp0&lt;/span&gt;&lt;span class="s2"&gt;statusline.js"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The quoting now happens inside the batch file, where &lt;code&gt;cmd&lt;/code&gt; handles it the normal way. A few details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;%~dp0&lt;/code&gt; is the launcher's own folder, so the script path is never written twice.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;%&lt;/code&gt; in the Node path is written as &lt;code&gt;%%&lt;/code&gt;, or &lt;code&gt;cmd&lt;/code&gt; would read it as a variable.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DisableDelayedExpansion&lt;/code&gt; keeps a &lt;code&gt;!&lt;/code&gt; in a path from being expanded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;cmd&lt;/code&gt; also splits a command line at &lt;code&gt;&amp;amp;&lt;/code&gt;, &lt;code&gt;(&lt;/code&gt;, &lt;code&gt;^&lt;/code&gt;, &lt;code&gt;,&lt;/code&gt;, &lt;code&gt;;&lt;/code&gt; and &lt;code&gt;=&lt;/code&gt;. A launcher in a folder whose path contains one of them never starts, so &lt;code&gt;init&lt;/code&gt; refuses such a folder and says how to pick another (&lt;code&gt;AGY_POWERLINE_HOME&lt;/code&gt;). An error at install time beats a bar that silently never appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 2: the right Node, the right shell quoting
&lt;/h2&gt;

&lt;p&gt;On macOS and Linux the command is &lt;code&gt;sh -c&lt;/code&gt;, so the paths are single-quoted, which keeps spaces and &lt;code&gt;$&lt;/code&gt; intact:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;'/opt/homebrew/bin/node' '/Users/you/.agy-powerline/statusline.js'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Node path is &lt;code&gt;process.execPath&lt;/code&gt;, the binary that ran &lt;code&gt;init&lt;/code&gt;, not &lt;code&gt;node&lt;/code&gt; from the &lt;code&gt;PATH&lt;/code&gt;. &lt;code&gt;agy&lt;/code&gt; may not start with your shell's &lt;code&gt;PATH&lt;/code&gt; (with nvm, for example), so a bare &lt;code&gt;node&lt;/code&gt; can resolve to nothing, or to an old system Node.&lt;/p&gt;

&lt;p&gt;WSL had its own version of this. Windows' &lt;code&gt;npx&lt;/code&gt; is often on the WSL &lt;code&gt;PATH&lt;/code&gt; through &lt;code&gt;/mnt/c/...&lt;/code&gt;, and running &lt;code&gt;init&lt;/code&gt; with it would write a Windows Node path into Linux settings. &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;doctor&lt;/code&gt; detect a Windows Node under WSL and refuse to continue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 3: the invisible BOM
&lt;/h2&gt;

&lt;p&gt;Windows PowerShell 5.1 writes UTF-8 files with a byte order mark when you ask for UTF-8 (&lt;code&gt;Set-Content -Encoding UTF8&lt;/code&gt;, &lt;code&gt;Out-File&lt;/code&gt;). &lt;code&gt;JSON.parse&lt;/code&gt; doesn't skip it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;uFEFF{}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// SyntaxError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So anything that reads JSON strips it first: agy's &lt;code&gt;settings.json&lt;/code&gt;, the bar's config, imported configs, and the payload on stdin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\u&lt;/span&gt;&lt;span class="sr"&gt;FEFF/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One line, but missing it means a config the user edited in the wrong editor breaks the bar for no visible reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 4: a stdin that never closes
&lt;/h2&gt;

&lt;p&gt;This one took a while to find. Sometimes &lt;code&gt;agy&lt;/code&gt; starts the statusline and keeps its stdin open without writing anything (at the time of writing it's open as &lt;a href="https://github.com/google-antigravity/antigravity-cli/issues/698" rel="noopener noreferrer"&gt;google-antigravity/antigravity-cli#698&lt;/a&gt;). A script that does a blocking read of stdin waits until &lt;code&gt;agy&lt;/code&gt; kills it. After a few kills in a row, &lt;code&gt;agy&lt;/code&gt; turns the custom statusline off.&lt;/p&gt;

&lt;p&gt;The fix on the script side: read stdin asynchronously, with a deadline.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;readStdin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;finish&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;done&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nf"&gt;clearTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="c1"&gt;// An open stdin would keep the process alive after the bar is printed.&lt;/span&gt;
      &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;timer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;data&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;end&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a 1-second deadline: if stdin closed, render it. If it didn't and nothing arrived, exit 0 with no output. The script is never killed, so it's never counted as a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trap 5: never crash where the bar goes
&lt;/h2&gt;

&lt;p&gt;Whatever the statusline prints ends up in the status bar. A stack trace there is ugly at best, and can quote payload text at worst. So the runtime has a few hard rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An uncaught error prints a short fixed marker (&lt;code&gt;[agy-powerline: error, see agy-powerline.log]&lt;/code&gt;), writes the details to a local log, and exits 0.&lt;/li&gt;
&lt;li&gt;Invalid JSON prints &lt;code&gt;[agy-powerline: invalid JSON]&lt;/code&gt; and says where it broke, without quoting the input.&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;agy&lt;/code&gt; closes the pipe before the bar is written (&lt;code&gt;EPIPE&lt;/code&gt;), that's a normal shutdown, not a crash: exit quietly.&lt;/li&gt;
&lt;li&gt;Text that isn't ours (branch names, the conversation title, command output) is stripped of terminal escape sequences before it's printed.&lt;/li&gt;
&lt;li&gt;The payload holds personal data: your email address, the conversation ID and the path to the transcript. None of it is printed, logged or written to the debug dumps users attach to bug reports.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start-up time is most of the cost
&lt;/h2&gt;

&lt;p&gt;A statusline is a new process on every refresh, so start-up time matters more than the rendering. On a Windows 11 laptop, agy-powerline's own work is about 0.1 s per refresh. Starting &lt;code&gt;cmd&lt;/code&gt; and Node adds about 0.1 to 0.25 s, depending on how busy the machine is.&lt;/p&gt;

&lt;p&gt;Two things made a measurable difference:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One file instead of eighteen.&lt;/strong&gt; On Windows, Node spent more time opening the 18 small module files than running them. &lt;code&gt;init&lt;/code&gt; now joins the runtime into a single &lt;code&gt;statusline.js&lt;/code&gt; (each file still gets its own scope, the way Node wraps modules). That cut agy-powerline's share of each refresh by about a third.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No &lt;code&gt;git&lt;/code&gt; process unless needed.&lt;/strong&gt; The branch and dirty state come from reading &lt;code&gt;.git&lt;/code&gt; directly. The ahead/behind counts need &lt;code&gt;git rev-list&lt;/code&gt; (about 60 ms on Windows), so they're cached per pair of commits and recomputed only when the branch or its upstream moves, with a 150 ms timeout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fitting any terminal width
&lt;/h2&gt;

&lt;p&gt;The payload includes &lt;code&gt;terminal_width&lt;/code&gt;, and the bar has to fit it. When a row is too wide, it shrinks in steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Widgets drop their labels and keep only their values, then the model name and the meters get shorter.&lt;/li&gt;
&lt;li&gt;Widgets drop out by priority (a custom badge goes before the conversation title, the title before the quotas, the model last).&lt;/li&gt;
&lt;li&gt;As a last resort, the row is cut with &lt;code&gt;…&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Measuring "too wide" is its own small problem. You strip escape sequences first, then count columns: emoji are two wide, combining marks and variation selectors are zero. And terminals don't always agree with each other on emoji width, so a little slack helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Never lose the user's settings
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;init&lt;/code&gt; edits a file that belongs to another program, so it's careful about it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It refuses to touch a &lt;code&gt;settings.json&lt;/code&gt; that isn't plain JSON (comments, trailing commas), and tells you what to add by hand.&lt;/li&gt;
&lt;li&gt;It backs up the file before every change, and keeps the very first version in a separate backup that's never replaced (&lt;code&gt;settings.json.agy-powerline.original.bak&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It only changes the &lt;code&gt;statusLine&lt;/code&gt; key.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;settings.json&lt;/code&gt; symlinked by a dotfile manager (stow, chezmoi, yadm) stays a link: the change goes into the file it points to.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uninstall&lt;/code&gt; backs the file up too and removes only the &lt;code&gt;statusLine&lt;/code&gt; key. &lt;code&gt;uninstall --delete&lt;/code&gt; also deletes agy-powerline's files, but only the ones in a fixed list (&lt;code&gt;INSTALLED_FILES&lt;/code&gt;), never a whole folder.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How it's tested
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CI runs on Windows, macOS and Linux, across Node 20, 22 and 24.&lt;/li&gt;
&lt;li&gt;On the Windows runner, a smoke test runs &lt;code&gt;init&lt;/code&gt; in a home folder with a space in its path, then starts the command it wrote through a real &lt;code&gt;cmd.exe /d /c&lt;/code&gt;, the way agy does. It fails on a non-zero exit code, on anything written to stderr, or if the bar didn't render.&lt;/li&gt;
&lt;li&gt;Snapshot tests pin the rendered bar for each layout and fixture payload, with the clock, time zone and terminal width frozen.&lt;/li&gt;
&lt;li&gt;Property-based tests with &lt;code&gt;fast-check&lt;/code&gt; (a fixed seed, 300 runs per property) throw random and malformed payloads at the renderer: it must never throw, print no control characters besides its own colors, and fit every row into widths from 40 to 240 columns. Another one pipes random input into the runtime script, which must exit 0 without a stack trace.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd tell someone writing a statusline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Test on Windows on day one. Most of these traps don't exist on macOS.&lt;/li&gt;
&lt;li&gt;Read stdin with a deadline.&lt;/li&gt;
&lt;li&gt;Exit 0, always. Print nothing rather than an error.&lt;/li&gt;
&lt;li&gt;Measure start-up, not just rendering.&lt;/li&gt;
&lt;li&gt;Kill what you start. A timeout that stops waiting for a child process doesn't always stop the process: on Windows, a command run through &lt;code&gt;cmd&lt;/code&gt; can outlive its timeout. Kill the whole process tree. (That one is still on my own list, for the bar's shell-command widget.)&lt;/li&gt;
&lt;li&gt;Treat the payload as untrusted text, and keep its personal fields out of your logs.&lt;/li&gt;
&lt;li&gt;Never start &lt;code&gt;agy&lt;/code&gt; itself, or anything slow, from the statusline. One user on Google's forum &lt;a href="https://discuss.ai.google.dev/t/antigravitys-spaghetti-code-for-statusline/171395" rel="noopener noreferrer"&gt;pointed it at &lt;code&gt;agy&lt;/code&gt;&lt;/a&gt; and ended up with about 1,000 &lt;code&gt;agy.exe&lt;/code&gt; processes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code is MIT on GitHub: &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;wassim-azi/agy-powerline&lt;/a&gt;. If you want to try it out on Windows, macOS, or Linux, you can install it manually:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or have your agent install it directly in an open &lt;code&gt;agy&lt;/code&gt; session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Hey agy, please install agy-powerline by running &lt;code&gt;npx agy-powerline init --yes&lt;/code&gt;, then tell me to restart agy."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you've hit other Windows traps with Node CLIs, I'd like to hear about them in the comments.&lt;/p&gt;

</description>
      <category>node</category>
      <category>windows</category>
      <category>cli</category>
      <category>showdev</category>
    </item>
    <item>
      <title>What Antigravity CLI actually sends to your statusline script (field by field)</title>
      <dc:creator>Wassim AZI</dc:creator>
      <pubDate>Tue, 27 Oct 2026 12:00:00 +0000</pubDate>
      <link>https://dev.to/wassim-azi/what-antigravity-cli-actually-sends-to-your-statusline-script-field-by-field-5a48</link>
      <guid>https://dev.to/wassim-azi/what-antigravity-cli-actually-sends-to-your-statusline-script-field-by-field-5a48</guid>
      <description>&lt;p&gt;If you write your own statusline for Google's Antigravity CLI (&lt;code&gt;agy&lt;/code&gt;), you start from the &lt;a href="https://antigravity.google/docs/cli/statusline/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;: &lt;code&gt;agy&lt;/code&gt; runs your command when its state changes, pipes a JSON payload to its stdin, and shows what the command prints.&lt;/p&gt;

&lt;p&gt;The docs list the payload's fields. But when I captured real payloads for &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;agy-powerline&lt;/a&gt;, they didn't quite match: some documented fields never showed up, a few useful ones arrived without being documented, and the quota buckets, the part most people want, aren't listed at all.&lt;/p&gt;

&lt;p&gt;This is the payload as it actually arrived, captured from &lt;code&gt;agy&lt;/code&gt; 1.3.0 to 1.4.0 on a Google AI Pro account, in October 2026. Your plan, version and OS may differ: treat every field as optional.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh61ouw9oygpkw1lc2u0t.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fh61ouw9oygpkw1lc2u0t.jpg" alt="agy starts your statusline script on every refresh and sends it JSON on stdin (model, context_window, quota with 5 buckets, subagents, vcs with only its type, terminal_width); the text the script prints on stdout becomes the bar under the input box" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A real payload
&lt;/h2&gt;

&lt;p&gt;Trimmed, with personal values replaced:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cwd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conversation_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00000000-0000-0000-0000-000000000000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conversation_title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hello"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"transcript_path"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/.gemini/antigravity-cli/conversations/…"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gemini 3.8 Flash (High)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"display_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Gemini 3.8 Flash (High)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"effort"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workspace"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"current_dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/project"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"project_dir"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/user/project"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1.3.3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context_window"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;33129&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"total_output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10161&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"context_window_size"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1048576&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"used_percentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.159&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"remaining_percentage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;96.841&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"current_usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3353&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;187&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cache_creation_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"cache_read_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24680&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exceeds_200k_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"product"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"antigravity"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"quota"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"gemini-5h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"remaining_fraction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.987&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reset_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-13T00:36:57Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"reset_in_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;15752&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"agent_state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"idle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vcs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"git"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cycle_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"accept-edits"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sandbox"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subagents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"research"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"List the files in the project folder."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"plan_tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Google AI Pro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user@example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"terminal_width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;198&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"vim"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NORMAL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The quota buckets
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;quota&lt;/code&gt; is an object keyed by bucket. The docs' example shows only &lt;code&gt;gemini-weekly&lt;/code&gt;. Here's every bucket I've seen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Window&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-5h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini models&lt;/td&gt;
&lt;td&gt;5 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini models&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3p-5h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party models (Claude, GPT)&lt;/td&gt;
&lt;td&gt;5 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3p-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party models&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-argon-wtu-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini 4 Argon's own quota, on Google AI Pro (since mid-October 2026)&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each bucket has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;remaining_fraction&lt;/code&gt;: from 0 to 1. Multiply by 100 for "percent left".&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reset_time&lt;/code&gt;: an ISO timestamp. Both &lt;code&gt;…T00:36:57Z&lt;/code&gt; and &lt;code&gt;…T00:36:57.000Z&lt;/code&gt; show up, depending on the version, so parse it with &lt;code&gt;Date.parse()&lt;/code&gt; rather than a regex.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reset_in_seconds&lt;/code&gt;: optional according to the docs. If it's missing, compute it from &lt;code&gt;reset_time&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sometimes &lt;code&gt;"disabled": true&lt;/code&gt;. I skip those buckets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Argon bucket arrives whatever model you're using&lt;/strong&gt;, not only when Argon is selected. To show "the quota that matters right now", pick the bucket group from &lt;code&gt;model.id&lt;/code&gt;: Argon models → the Argon bucket, other Gemini models → &lt;code&gt;gemini-*&lt;/code&gt;, everything else → &lt;code&gt;3p-*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How far into the window you are&lt;/strong&gt; needs no extra field: &lt;code&gt;1 - reset_in_seconds / window_length&lt;/code&gt;, with 18,000 s for 5-hour buckets and 604,800 s for 7-day ones. Compare it with the share you've used, and you know if you're ahead of or behind pace.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The values come from &lt;code&gt;agy&lt;/code&gt;&lt;/strong&gt;, as of its last refresh. A statusline can't be more up to date than the payload.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fields that arrive but aren't documented
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Seen as&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cycle_mode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"accept-edits"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The approval mode. Missing from some captures, so treat "absent" as the default mode. The docs list &lt;code&gt;execution_mode&lt;/code&gt; instead, which I've never seen.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;subagents&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;[{ name, role, status }]&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;status&lt;/code&gt; was &lt;code&gt;"running"&lt;/code&gt; or &lt;code&gt;"completed"&lt;/code&gt;. &lt;code&gt;role&lt;/code&gt; is a whole sentence (the task you gave it), so cut it before printing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;conversation_title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"hello"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Appears once the conversation has a title.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model.effort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"high"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The reasoning effort.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Fields that are documented but never showed up
&lt;/h2&gt;

&lt;p&gt;On my machine, with a Google AI Pro account, these never appeared:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;execution_mode&lt;/code&gt; (see &lt;code&gt;cycle_mode&lt;/code&gt; above).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;pending_input_count&lt;/code&gt;, even with a prompt queued.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tool_confirmation_pending&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;vcs.branch&lt;/code&gt;, &lt;code&gt;vcs.client&lt;/code&gt; and &lt;code&gt;vcs.dirty&lt;/code&gt;: &lt;code&gt;vcs&lt;/code&gt; only ever held &lt;code&gt;type&lt;/code&gt; (see &lt;a href="https://github.com/google-antigravity/antigravity-cli/issues/739" rel="noopener noreferrer"&gt;antigravity-cli#739&lt;/a&gt;). Starting &lt;code&gt;git&lt;/code&gt; on every refresh is slow on Windows (an ahead/behind count alone took about 60 ms), so read the branch from &lt;code&gt;.git/HEAD&lt;/code&gt; yourself and cache anything that needs &lt;code&gt;git&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sandbox.allow_network&lt;/code&gt;: only &lt;code&gt;enabled&lt;/code&gt; arrived.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your script depends on one of these, give it a fallback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;model.id&lt;/code&gt; isn't an API id.&lt;/strong&gt; It held the same text as &lt;code&gt;display_name&lt;/code&gt;, effort included: &lt;code&gt;"Gemini 3.8 Flash (High)"&lt;/code&gt;. Match on substrings (&lt;code&gt;includes("argon")&lt;/code&gt;), not on exact ids.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;used_percentage&lt;/code&gt; counts input, not output.&lt;/strong&gt; In every real capture it equals &lt;code&gt;total_input_tokens / context_window_size&lt;/code&gt; (33,129 / 1,048,576 = 3.159% above). &lt;code&gt;total_output_tokens&lt;/code&gt; isn't in it. If you add output tokens to your token count, the count stops matching &lt;code&gt;agy&lt;/code&gt;'s own percentage, so show the input count, or work the count out from the percentage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;exceeds_200k_tokens&lt;/code&gt; can be &lt;code&gt;null&lt;/code&gt;&lt;/strong&gt; before the first request, according to the docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;agent_state&lt;/code&gt;&lt;/strong&gt; takes &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;thinking&lt;/code&gt;, &lt;code&gt;working&lt;/code&gt;, &lt;code&gt;tool_use&lt;/code&gt; or &lt;code&gt;initializing&lt;/code&gt;, per the docs. I've seen &lt;code&gt;idle&lt;/code&gt;, &lt;code&gt;working&lt;/code&gt; and &lt;code&gt;tool_use&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;terminal_width&lt;/code&gt; is the live width.&lt;/strong&gt; Use it to fit your output: a line wider than the terminal wraps and pushes the bar out of place.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vim&lt;/code&gt; appears only with Vim mode on&lt;/strong&gt;, with &lt;code&gt;mode&lt;/code&gt; set to &lt;code&gt;NORMAL&lt;/code&gt;, &lt;code&gt;INSERT&lt;/code&gt; and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The cache numbers
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;context_window&lt;/code&gt; holds totals for the whole session, and &lt;code&gt;current_usage&lt;/code&gt; the numbers for the latest request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"current_usage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3353&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"output_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;187&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache_creation_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"cache_read_input_tokens"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;24680&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From those you can work out how much of the latest prompt was read from the prompt cache:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cache_hit_pct = cache_read_input_tokens / (input_tokens + cache_read_input_tokens) * 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the payload above, &lt;code&gt;24,680 / (3,353 + 24,680) * 100 = 88%&lt;/code&gt;: most of the prompt came from the cache. When &lt;code&gt;cache_read_input_tokens&lt;/code&gt; is 0 and &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; isn't, nothing was read from the cache and the prompt was written to it: a cold start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Subagents
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;subagents&lt;/code&gt; lists the child tasks of the conversation, with &lt;code&gt;status&lt;/code&gt; set to &lt;code&gt;"running"&lt;/code&gt; or &lt;code&gt;"completed"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"subagents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"research"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"List the files in the project folder."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;role&lt;/code&gt; is the whole instruction the subagent got, so cut it short before you print it, or it wraps the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Personal data in the payload
&lt;/h2&gt;

&lt;p&gt;The payload holds your &lt;code&gt;email&lt;/code&gt;, &lt;code&gt;session_id&lt;/code&gt; and &lt;code&gt;conversation_id&lt;/code&gt;, and the full &lt;code&gt;transcript_path&lt;/code&gt;. Your script receives them on every refresh, so be careful with them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't print them, and don't write them to a log.&lt;/li&gt;
&lt;li&gt;If you save payloads to debug, strip those fields first, and replace paths before you share a file in an issue.&lt;/li&gt;
&lt;li&gt;Treat every string (&lt;code&gt;cwd&lt;/code&gt;, &lt;code&gt;conversation_title&lt;/code&gt;, subagent roles) as untrusted text: strip terminal escape sequences before printing, or a crafted value can change your terminal's state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reading stdin safely
&lt;/h2&gt;

&lt;p&gt;One more thing the docs don't mention: &lt;code&gt;agy&lt;/code&gt; can start the command and keep stdin open without writing anything (&lt;a href="https://github.com/google-antigravity/antigravity-cli/issues/698" rel="noopener noreferrer"&gt;antigravity-cli#698&lt;/a&gt;). A blocking read then waits until &lt;code&gt;agy&lt;/code&gt; kills the script, and after a few kills &lt;code&gt;agy&lt;/code&gt; turns the statusline off. Read stdin with a deadline (one second works), and exit 0 with no output if nothing arrived. Also strip a leading byte order mark (&lt;code&gt;\uFEFF&lt;/code&gt;) before &lt;code&gt;JSON.parse&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture your own
&lt;/h2&gt;

&lt;p&gt;To see exactly what your &lt;code&gt;agy&lt;/code&gt; sends, point the statusline at a script that saves stdin to a file before rendering. With agy-powerline installed, set &lt;code&gt;AGY_POWERLINE_DEBUG=1&lt;/code&gt; before starting &lt;code&gt;agy&lt;/code&gt;: each refresh saves the payload to &lt;code&gt;~/.agy-powerline/last-payload.json&lt;/code&gt;, with the email and IDs removed and paths replaced, so it's safe to share.&lt;/p&gt;

&lt;p&gt;If you see a field or a bucket that isn't listed here (a &lt;code&gt;cost&lt;/code&gt; object, another quota bucket, &lt;code&gt;execution_mode&lt;/code&gt; in plan mode), I'd like to hear about it: comment below or &lt;a href="https://github.com/wassim-azi/agy-powerline/issues" rel="noopener noreferrer"&gt;open an issue&lt;/a&gt; with your dump. I'll update this list.&lt;/p&gt;

&lt;p&gt;To install &lt;code&gt;agy-powerline&lt;/code&gt; and start rendering these wire payloads right away:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;🤖 &lt;strong&gt;One-shot install with Antigravity:&lt;/strong&gt;&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey agy, please install agy-powerline by running `npx agy-powerline init --yes`, then tell me to restart agy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

</description>
      <category>gemini</category>
      <category>cli</category>
      <category>json</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>See your Antigravity CLI quotas without typing /usage (Gemini 5h/7d, Claude and Argon)</title>
      <dc:creator>Wassim AZI</dc:creator>
      <pubDate>Tue, 20 Oct 2026 19:21:53 +0000</pubDate>
      <link>https://dev.to/wassim-azi/see-your-antigravity-cli-quotas-without-typing-usage-gemini-5h7d-claudegpt-and-argon-19fc</link>
      <guid>https://dev.to/wassim-azi/see-your-antigravity-cli-quotas-without-typing-usage-gemini-5h7d-claudegpt-and-argon-19fc</guid>
      <description>&lt;p&gt;Think of it as &lt;strong&gt;&lt;code&gt;htop&lt;/code&gt; for Google Antigravity CLI&lt;/strong&gt;: an always-on display of your quotas, your context window and how fast you're using them.&lt;/p&gt;

&lt;p&gt;If you've ever hit your Google AI Pro limit in the middle of an intense coding session, you know the feeling of &lt;strong&gt;context window blindness&lt;/strong&gt;: one moment the agent is cruising through file edits, the next it grinds to a halt, and you discover your 5-hour quota ran out twenty minutes ago.&lt;/p&gt;

&lt;p&gt;The traditional workaround is typing &lt;code&gt;/usage&lt;/code&gt;, squinting at the numbers, and going back to work. A few prompt turns later, you find yourself typing &lt;code&gt;/usage&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;Since mid-October 2026, there is even more to keep track of. On Google AI Pro, Gemini 4 Argon has its own weekly quota pool, completely separate from the standard Gemini bucket. So developers now balance three distinct quota pools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemini&lt;/strong&gt;: a 5-hour window and a 7-day window, each with its own reset time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party models&lt;/strong&gt; (Claude, GPT): their own 5-hour and 7-day windows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gemini 4 Argon&lt;/strong&gt;: an independent weekly quota on Google AI Pro.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I wanted all of that on screen, all the time, without asking for it or breaking flow state. This post explains which quota metrics &lt;code&gt;agy&lt;/code&gt; exposes to its statusline wire format, and how to display them persistently using &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;agy-powerline&lt;/a&gt;, a zero-dependency statusline engine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffiah38nzjqfbl5ogsnmc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffiah38nzjqfbl5ogsnmc.png" alt="agy-powerline in agy's status bar, three rows: model, context and git; Gemini 5h and 7d quotas with pace markers and resets; Claude/GPT and Argon weekly quotas" width="800" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The quota buckets agy sends
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;agy&lt;/code&gt; has a &lt;code&gt;statusLine&lt;/code&gt; setting: it runs a command of your choice and pipes it a JSON payload, as described in the &lt;a href="https://antigravity.google/docs/cli/statusline/" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;. The docs' example payload shows a single quota bucket. In practice, &lt;code&gt;agy&lt;/code&gt; sends up to five:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Bucket&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-5h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini, 5-hour window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini, 7-day window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3p-5h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party models (Claude, GPT), 5-hour window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;3p-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Third-party models, 7-day window&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gemini-argon-wtu-weekly&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini 4 Argon's own weekly quota, on Google AI Pro&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each one looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"gemini-5h"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"remaining_fraction"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.979&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reset_time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-16T23:59:02.000Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reset_in_seconds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17042&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;remaining_fraction&lt;/code&gt; goes from 0 to 1. A bucket can also come with &lt;code&gt;"disabled": true&lt;/code&gt;, and is best ignored then. The Argon bucket is sent whatever model you're using, so you can keep an eye on it before you switch to Argon.&lt;/p&gt;

&lt;p&gt;So the data is there. A statusline only has to read it and draw it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;p&gt;You need Node.js 20 or newer. Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;init&lt;/code&gt; installs the runtime as one file in &lt;code&gt;~/.agy-powerline&lt;/code&gt;, backs up agy's &lt;code&gt;settings.json&lt;/code&gt;, and sets its &lt;code&gt;statusLine&lt;/code&gt; command. On a first install it asks you for a preset, a layout and a theme, with a live terminal preview of each. Restart &lt;code&gt;agy&lt;/code&gt; once and the bar is live.&lt;/p&gt;

&lt;p&gt;The setting points at the exact Node binary that ran &lt;code&gt;init&lt;/code&gt;, so the bar doesn't depend on &lt;code&gt;agy&lt;/code&gt; finding &lt;code&gt;node&lt;/code&gt; on its &lt;code&gt;PATH&lt;/code&gt;. That matters with nvm, or when &lt;code&gt;agy&lt;/code&gt; is started from somewhere other than your shell.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🤖 &lt;strong&gt;Vibe-coding? Have your agent install it for you:&lt;/strong&gt;&lt;br&gt;
If you already have an &lt;code&gt;agy&lt;/code&gt; session open, paste this prompt into your conversation:&lt;/p&gt;


&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Hey agy, please install agy-powerline by running `npx agy-powerline init --yes --preset quotas`, then tell me to restart agy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;If you'd rather stay inside &lt;code&gt;agy&lt;/code&gt; using native extensions, it's also an agy plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;agy plugin install https://github.com/wassim-azi/agy-powerline
/agy-powerline-setup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works natively on Windows (cmd and PowerShell), macOS, Linux and WSL. On Windows it doesn't need bash or &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quota watch preset
&lt;/h2&gt;

&lt;p&gt;The default bar shows the model, context, git and your Gemini quotas. If quotas are what you care about most, switch to the preset made for them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline preset quotas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It draws three rows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your plan and the model in use.&lt;/li&gt;
&lt;li&gt;Gemini 5h and 7d side by side, each with its reset timer.&lt;/li&gt;
&lt;li&gt;Third-party 5h and 7d, then Argon's weekly quota and its reset.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A widget with nothing to show hides itself: without the Argon bucket in the payload, the Argon widgets simply don't appear.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading the pace marker
&lt;/h2&gt;

&lt;p&gt;A percentage alone doesn't tell you much. "40% left" is fine if the window resets in 20 minutes, and bad news if it resets in 4 days.&lt;/p&gt;

&lt;p&gt;So each quota meter carries a pace marker: a mark at the point the time window has reached. Here are two meters from the demo data (in the terminal, the colors make the fill stand out):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⏳ •••••●••••••  72.0% rem   🔄 Reset: 2hr 47m
📅 •••••••••●••  21.0% rem   🔄 Reset: 1d 17h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meter fills from the left as you use quota. The marker (&lt;code&gt;●&lt;/code&gt;) shows how far into the window you are.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5h quota&lt;/strong&gt;: it resets in 2h 47m, so 2h 13m of the 5 hours have passed, about 44%. You've used 28%. The fill is behind the marker: you're using quota slower than time passes, so you can relax.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7d quota&lt;/strong&gt;: it resets in 1d 17h, so about 76% of the week has passed. You've used 79%. The fill is just past the marker: at this rate, you'll run out shortly before the reset.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The marker needs no extra data: it comes from &lt;code&gt;reset_in_seconds&lt;/code&gt; and the fixed 5-hour and 7-day window lengths. If you find it noisy, press &lt;code&gt;r&lt;/code&gt; in &lt;code&gt;configure&lt;/code&gt; to turn it off.&lt;/p&gt;

&lt;p&gt;Prefer to see &lt;em&gt;when&lt;/em&gt; a quota resets rather than how long until it does? Set the reset widget's format to a clock time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fiveHourReset"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"time"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One meter for "the quota that matters right now"
&lt;/h2&gt;

&lt;p&gt;If you want something smaller, the &lt;code&gt;quota&lt;/code&gt; widget shows only the bucket closest to running out, for the model you're using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the Argon bucket when Argon is the model,&lt;/li&gt;
&lt;li&gt;the other Gemini buckets for other Gemini models,&lt;/li&gt;
&lt;li&gt;the third-party buckets for Claude and GPT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;compact&lt;/code&gt; preset uses it to fit everything on one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline preset compact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still too wide for a small terminal? Minimalist mode (&lt;code&gt;m&lt;/code&gt; in &lt;code&gt;configure&lt;/code&gt;) drops every label and keeps the values. And on any width, the bar drops labels and shortens meters first, then the least important widgets, so it never wraps. Here's the bar from the top of this post in a window under 80 columns:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfoufej4axbyq6ypve8y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjfoufej4axbyq6ypve8y.png" alt="The same three-row bar in a window under 80 columns: the gaps between widgets shrink and every widget still fits" width="797" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Tracking daily and weekly token spend without API calls
&lt;/h2&gt;

&lt;p&gt;Quota windows (5-hour and 7-day resets) tell you how fast you're running right now, but they don't answer another common question: &lt;em&gt;How many total tokens did I burn today across all my conversations?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The statusline payload sends context for the active session, but doesn't track historical usage. To solve that, &lt;code&gt;agy-powerline&lt;/code&gt; includes &lt;code&gt;tokensToday&lt;/code&gt; and &lt;code&gt;tokensWeek&lt;/code&gt; widgets. They read Antigravity's local conversation files in &lt;code&gt;~/.gemini/antigravity-cli/conversations/&lt;/code&gt; directly: no API keys, no network calls, no telemetry. They need Node 22.13 or newer, which has SQLite built in; on older Node they stay hidden, and &lt;code&gt;npx agy-powerline doctor&lt;/code&gt; says why.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;tokens&lt;/code&gt; preset puts them on the bar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline preset tokens
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows the context tokens, the session's tokens in and out and the cache hit rate, with today's and the last 7 days' totals on a second row.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make it warn you
&lt;/h2&gt;

&lt;p&gt;Quota and context meters switch to a caution color, then a danger color, as they run low. You can pick the levels per widget, and make a widget blink when it crosses the danger level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agy-powerline configure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Select a widget, press Enter, and change its caution and danger levels. The live preview at the bottom of the screen follows every change, and &lt;code&gt;q&lt;/code&gt; saves and quits. A running &lt;code&gt;agy&lt;/code&gt; picks up the change on its next refresh.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fwassim-azi%2Fagy-powerline%2Fmain%2Fassets%2Fdemo.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fwassim-azi%2Fagy-powerline%2Fmain%2Fassets%2Fdemo.gif" alt="agy-powerline's configure editor: adding Argon's weekly quota, then browsing presets, themes and layouts with a live preview" width="760" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While you're in &lt;code&gt;configure&lt;/code&gt;, the tabs at the top let you try the presets, 16 themes (two of them color-blind safe), three layouts (Powerline pills, plain colored lines, or a classic text bar with block meters) and pill shapes. You can also customize how meters render: classic &lt;code&gt;blocks&lt;/code&gt;, minimalist &lt;code&gt;line&lt;/code&gt;, geometric &lt;code&gt;rings&lt;/code&gt; (&lt;code&gt;●●●○○○&lt;/code&gt;), or a &lt;code&gt;ramp&lt;/code&gt; where each filled cell is shaded from the theme's ok color through caution to danger.&lt;/p&gt;

&lt;p&gt;Prefer a browser? The same options are in the &lt;a href="https://agy-powerline.com/configure/" rel="noopener noreferrer"&gt;web configurator&lt;/a&gt;, which exports a config file you can import.&lt;/p&gt;

&lt;h2&gt;
  
  
  How fresh are the numbers?
&lt;/h2&gt;

&lt;p&gt;The bar shows exactly what &lt;code&gt;agy&lt;/code&gt; sends, as of its last refresh: &lt;code&gt;agy&lt;/code&gt; runs the statusline when its state changes, and the quota values come from &lt;code&gt;agy&lt;/code&gt; itself. agy-powerline makes no network calls of its own, so it can't be more up to date than &lt;code&gt;agy&lt;/code&gt;, but it also can't disagree with it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What else it won't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No telemetry, no runtime dependencies.&lt;/strong&gt; Everything comes from the payload, from your &lt;code&gt;.git&lt;/code&gt; folder and from your OS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your settings are safe.&lt;/strong&gt; &lt;code&gt;init&lt;/code&gt; refuses to edit a &lt;code&gt;settings.json&lt;/code&gt; it can't parse, and keeps a backup of the original.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want agy's own line too?&lt;/strong&gt; &lt;code&gt;npx agy-powerline init --stack&lt;/code&gt; draws the bar below the built-in one instead of replacing it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To remove it: &lt;code&gt;npx agy-powerline uninstall&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback welcome
&lt;/h2&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/wassim-azi/agy-powerline" rel="noopener noreferrer"&gt;github.com/wassim-azi/agy-powerline&lt;/a&gt;. If a quota or a field you care about is missing, open an issue: I build widgets from what &lt;code&gt;agy&lt;/code&gt; actually sends, so a payload from your setup helps a lot. &lt;code&gt;npx agy-powerline report&lt;/code&gt; writes a privacy-safe bug report, and the &lt;a href="https://github.com/wassim-azi/agy-powerline/blob/main/docs/troubleshooting.md#capturing-the-payload" rel="noopener noreferrer"&gt;troubleshooting page&lt;/a&gt; shows how to capture a payload.&lt;/p&gt;

&lt;p&gt;What do you keep checking with &lt;code&gt;/usage&lt;/code&gt; that you'd rather see all the time?&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>gemini</category>
      <category>cli</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
