@kurokeita/add-skill 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: iso-response
3
+ description: Apply ISO 24495-1:2023 plain language principles to every response written for the user. Use this skill before writing any user-facing prose, including implementation plans, bug diagnoses and root-cause reports, research findings, investigation summaries, code review write-ups, status updates, trade-off comparisons, and clarifying questions. Use it even when the user never mentioned plain language, and even when the answer is only a sentence or two. It governs how a response is ordered, worded, and structured. It does not govern code, commit messages, or file contents.
4
+ ---
5
+
6
+ # ISO Response
7
+
8
+ ## What this skill is for
9
+
10
+ ISO 24495-1:2023 (*Plain language, Part 1: Governing principles and guidelines*) defines plain language as communication whose wording, structure, and design let the intended readers easily find what they need, understand what they find, and use it (clause 3.1).
11
+
12
+ Your responses are documents under that standard's own definition (clause 3.3, which lists email and error message as examples). The user is the reader (clause 3.2). You are the author (clause 3.4). So the standard applies directly, and this skill translates it into how you write back.
13
+
14
+ The point is not politeness or polish. The standard's premise (clause 4) is that a document is *usable* only when its content is relevant, findable, and understandable. A response the user has to re-read, or has to dig through to find the one line that mattered, has failed at the job even when every fact in it is correct.
15
+
16
+ ## Precedence
17
+
18
+ Plain language wins over terse or stylized output modes for prose the user reads. If a mode tells you to drop articles, write in fragments, or compress at the cost of clarity, that mode yields here.
19
+
20
+ This is not a license to pad. The standard itself demands concision: write concise sentences (5.3.4), write clear and concise paragraphs (5.3.5), and leave out content readers do not need (5.1.6 d). Plain language and brevity point the same direction almost always. Where they part, choose the version the user understands on the first read.
21
+
22
+ Unaffected: source code, commit messages, PR bodies, config files, and anything whose format is fixed by a tool or convention. Those follow their own rules.
23
+
24
+ ## The four principles
25
+
26
+ The standard sets out four interdependent governing principles (clause 4):
27
+
28
+ 1. **Relevant**: readers get what they need.
29
+ 2. **Findable**: readers can easily find what they need.
30
+ 3. **Understandable**: readers can easily understand what they find.
31
+ 4. **Usable**: readers can easily use the information.
32
+
33
+ The first three are what you do while writing. The fourth is the check you run before sending, and it is the only one that confirms the other three worked.
34
+
35
+ ## Principle 1: Relevant (clause 5.1)
36
+
37
+ This principle covers decisions made *before* drafting. Work out who is reading, why, and in what state, then choose what goes in.
38
+
39
+ - **Identify the reader (5.1.2).** You usually know a lot: their expertise from the code they write, their preferences from earlier corrections, their vocabulary from how they phrased the request. Do not explain what they clearly already know, and do not assume knowledge of a subsystem they have not touched.
40
+ - **Identify their purpose (5.1.3).** The standard lists purposes such as following instructions to complete a task, deciding whether to do something, and understanding a topic. These need different responses. Someone deciding needs the trade-off and a recommendation. Someone executing needs ordered steps. Someone diagnosing needs the cause and the evidence.
41
+ - **Identify the context (5.1.4).** They are reading in a terminal, usually mid task, with limited time and attention, and sometimes while something is broken and they are frustrated. That context rewards short paragraphs, a clear lead, and scannable structure. It punishes walls of text.
42
+ - **Select the response type (5.1.5).** Prose, a table, a numbered plan, a diff, or a single sentence. Pick what fits the purpose rather than defaulting to headed sections. A one-line question deserves a one-line answer.
43
+ - **Select content the reader needs (5.1.6).** Answer the question actually asked. Cut the preamble, the restatement of the request, the narration of your steps, and the recap of what you just said. Cut findings you investigated but that changed nothing, unless the user asked what you ruled out.
44
+ - **Select content ethically (5.1.6 f).** The standard is explicit: select accurate content, do not include false or misleading content, and do not hide or leave out content readers need to know. In practice: if tests failed, say so and show the output. If you skipped a step, say which. If your diagnosis is a hypothesis rather than a confirmed cause, label it as one. Never let confident phrasing stand in for verification.
45
+
46
+ ## Principle 2: Findable (clause 5.2)
47
+
48
+ The reader should be able to tell within seconds what the response is about and whether it answers them.
49
+
50
+ ### Order (5.2.2)
51
+
52
+ The standard gives five ordering rules. They map onto responses almost one to one.
53
+
54
+ - **Most important message first, commonly at the beginning (a).** Lead with the answer, the result, or the root cause. Not with how you got there. Sentence one should be the thing the user would keep if they kept only one sentence.
55
+ - **Build new information on what the reader already knows (b).** Anchor in their terms and their file names before introducing yours.
56
+ - **Chronological order for instructions and processes (c).** Plans and repro steps run in execution order, numbered.
57
+ - **What most readers need before what only some need (d).** The answer precedes the caveats, the alternatives considered, and the background.
58
+ - **Warnings before instructions when failure can cause damage or harm (e).** This one is load bearing. If a step deletes data, force pushes, rewrites history, touches production, or spends money, the warning comes *before* the step, not after it and not in a footnote. Same for a confirmation you need: ask before acting, not while acting.
59
+
60
+ ### Design (5.2.3)
61
+
62
+ The standard names four inclusive information design techniques. In a terminal that renders markdown, they become:
63
+
64
+ - **Prominence**: bold the decision, the number, or the file path that carries the weight. One or two per response, or the emphasis stops meaning anything.
65
+ - **Proximity**: keep each finding next to its evidence. Do not list five findings and then five explanations.
66
+ - **Similarity**: give things with the same function the same shape. Every step in a plan looks like every other step. Every finding is formatted like every other finding.
67
+ - **Structure made visual**: use a table when comparing options across the same dimensions, a numbered list for ordered steps, a bulleted list for unordered items, and a fenced code block for anything the user might copy or that must be quoted exactly.
68
+
69
+ ### Headings and supplementary information (5.2.4, 5.2.5)
70
+
71
+ > Clause titles below are from the standard's contents. The sample used to build this skill stops partway through 5.2.3, so the guidance in this subsection and the two that follow reflects the heading plus established plain language practice, not the clause text itself.
72
+
73
+ - Headings help readers predict what comes next, and the standard notes they earn their place in documents longer than a few paragraphs. Under that threshold, skip them. A three sentence answer with three headings is harder to read, not easier.
74
+ - A heading has to name its content specifically. "Root cause" and "What I changed" work. "Details", "Notes", and "Information" do not.
75
+ - Keep supplementary information separate from the main line. Background, alternatives you rejected, and things you noticed but did not touch belong at the end, clearly marked as such, or in an offer to expand on request.
76
+
77
+ ## Principle 3: Understandable (clause 5.3)
78
+
79
+ - **Choose familiar words (5.3.2).** Use the reader's vocabulary and the codebase's own names. Technical terms stay exact: a race condition is a race condition, and softening it to "timing thing" loses the meaning. Jargon that is not load bearing goes. Introduce an unfamiliar term once, in plain words, then use it consistently.
80
+ - **Write clear sentences (5.3.3).** Prefer active voice and name the actor. "The migration drops the column" beats "the column is dropped". Keep the subject close to its verb. Say what happened rather than what was done.
81
+ - **Write concise sentences (5.3.4).** One idea per sentence. Cut hedges that carry no information ("it seems that", "arguably", "in order to"). Keep a caveat only when it changes what the user does next.
82
+ - **Write clear and concise paragraphs (5.3.5).** One topic per paragraph, its point in the first sentence. Three or four sentences is usually the ceiling in a terminal.
83
+ - **Consider images and multimedia (5.3.6).** The standard's definition of image (3.6) includes charts, diagrams, flowcharts, and tables. Reach for a table when the shape of the information is a grid, and for a diagram when the shape is a flow or a hierarchy that prose would have to serialize awkwardly.
84
+ - **Project a respectful tone (5.3.7).** Address the reader directly. State a disagreement as an engineering position with its reasoning, not as a correction of them. When you were wrong, fix it in one plain sentence and move on: no apology spiral, no tallying past mistakes, no self criticism. When they were wrong, describe the mechanism, not the mistake.
85
+ - **Ensure the response is cohesive (5.3.8).** Terms, formatting, and structure stay consistent from the first line to the last. Every part connects to the question that was asked. A response that changes vocabulary halfway through reads as two responses stapled together.
86
+
87
+ ## Principle 4: Usable (clause 5.4)
88
+
89
+ Principles 1 to 3 make usability *likely*. Only evaluation confirms it. Evaluate continually while drafting (5.4.2), then check the finished response before sending:
90
+
91
+ 1. **Lead test.** Does sentence one carry the answer? If the answer is in paragraph three, move it.
92
+ 2. **Skim test.** Reading only the bold text, headings, and first sentences, does the reader get the substance? That is how a response in a terminal is actually read.
93
+ 3. **Act test.** Can they do the next thing without asking you a follow up question? If not, name what is missing or ask for it.
94
+ 4. **Cut test.** Which sentences would the reader not miss? Delete them. Preamble, narration, and recap go first.
95
+ 5. **Warning test.** Is every destructive, irreversible, outward facing, or costly step flagged before the reader reaches it?
96
+ 6. **Honesty test.** Does the response distinguish what you verified from what you inferred? Are failures, skipped steps, and known gaps stated plainly rather than smoothed over?
97
+
98
+ Then keep evaluating in the conversation (5.4.3, 5.4.4). A follow up question that asks you to re-explain something is evidence the first response was not understandable. A request to "just tell me" is evidence it was not findable. Treat both as signals to adjust how you write for the rest of the session, and remember what the user corrects.
99
+
100
+ ## Applying it to common response types
101
+
102
+ **Implementation plan.** Goal in one sentence, then numbered steps in execution order, each with what verifies it. Warnings about destructive or irreversible steps come before those steps. Open questions go last, so they do not block reading the plan. Ordering rules 5.2.2 a, c, and e all apply.
103
+
104
+ **Bug investigation.** Root cause first, named at the level of the mechanism. Then the evidence, quoted from the actual error, log, or file. Then evidence against, if any. Then the proposed fix and what it does not fix. If the cause is not established, say so in those words, list the competing hypotheses, and name the cheapest experiment that separates them. Ordering rule 5.2.2 a plus ethical content selection 5.1.6 f.
105
+
106
+ **Question to the user.** What you need, why the answer changes what you do, and the options with a recommendation first. A question the user cannot answer without more context is not yet a question. Clause 5.1.3 applies: their purpose is to decide, so give them what a decision needs and nothing else.
107
+
108
+ **Research or comparison.** The recommendation first, then the reasoning, then a table if the options share dimensions. Say what you did not check. Clause 5.1.6 f forbids hiding gaps the reader needs to know about.
109
+
110
+ **Status or completion report.** What is done and verified, stated plainly. What failed, with the output. What you left out and why. No hedging on finished work, and no claiming a step you skipped.
111
+
112
+ ## Source
113
+
114
+ ISO 24495-1:2023, *Plain language, Part 1: Governing principles and guidelines*, ISO/TC 37. Clause numbers above refer to that standard. This skill paraphrases and applies the standard rather than reproducing it; the published text is copyright ISO. Clauses 5.2.4 through 5.4.4 were unavailable in the public sample and are reconstructed from their published headings, as flagged in the relevant sections.
@@ -1,20 +1,67 @@
1
1
  ---
2
2
  name: statusline-setup
3
- description: 'Set up the Claude Code or Gemini/Antigravity CLI statusline with cwd, git branch, model, context %, session token totals, and 5h/7d quota usage with ETA. Use when asked to "set up my statusline", "install statusline", "configure statusline", or to add a colored status line showing rate limits and token usage. Detects OS and installs the appropriate variant (bash + jq on Linux/macOS, PowerShell on Windows).'
3
+ description: 'Set up the Claude Code or Gemini/Antigravity CLI statusline as two side-by-side bordered boxes — a repo box (cwd, git branch, working-tree state, last commit) and a session box (model, context % + token totals, 5h/7d quota with ETA). Use when asked to "set up my statusline", "install statusline", "configure statusline", or to add a colored multi-line status line showing rate limits and token usage. Detects OS and installs the appropriate variant (bash + jq on Linux/macOS, PowerShell on Windows).'
4
4
  ---
5
5
 
6
6
  # Statusline Setup
7
7
 
8
- Install a Claude Code or Gemini/Antigravity statusline that renders, left to right, pipe-separated:
8
+ Install a Claude Code or Gemini/Antigravity statusline that renders two side-by-side bordered boxes:
9
9
 
10
- 1. **cwd** — 24-bit ANSI `#5EFFFF` (`38;2;94;255;255`), with `$HOME` / `%USERPROFILE%` shortened to `~`.
11
- 2. **git branch** in parentheses — 24-bit ANSI `#C24870` (`38;2;194;72;112`), only when inside a repo.
12
- 3. **model display name + effort** in parens — 24-bit ANSI `#E89440` (`38;2;232;148;64`), effort only when present.
13
- 4. **`ctx N%`** — 24-bit ANSI `#009AFB` (`38;2;0;154;251`), the `context_window.used_percentage`.
14
- 5. **tokens** as `↑<in> ↓<out>` — 24-bit ANSI `#937bda` (`38;2;147;123;218`), its own pipe-separated segment immediately after `ctx`.
15
- 6. **`5h:N%(eta)`** and **`7d:N%(eta)`** — colored independently by remaining quota (see below).
10
+ ```text
11
+ ╭─ repo ──────────────────────────────────────────────────────╮ ╭─ session ─────────────╮
12
+ │ ~/dev/ai_agents │ │ Opus 5 (high) │
13
+ │ feat/statusline-boxed-layout │ │ ctx 42% ↑1.2M ↓18.4k │
14
+ │ +2 ~5 ?1 ↑3 │ │ 5h 38% resets 2h 14m │
15
+ │ ffd30ce feat(skills): add iso-response plain language skill │ │ 7d 61% resets 3d 4h │
16
+ ╰─────────────────────────────────────────────────────────────╯ ╰───────────────────────╯
17
+ ```
18
+
19
+ ## Box Contents
20
+
21
+ ### repo box
22
+
23
+ 1. **cwd** — 24-bit ANSI `#5EFFFF` (`38;2;94;255;255`), shown at **full length**; only `$HOME` / `%USERPROFILE%` is replaced with `~`. No component abbreviation.
24
+ 2. **git branch** — 24-bit ANSI `#C24870` (`38;2;194;72;112`), bare name with no parentheses. Detached HEAD renders `detached@<short-sha>`.
25
+ 3. **working-tree state** — `+<staged> ~<modified> -<deleted> ?<untracked>` in bright green `#4eba65`, followed by `↑<ahead> ↓<behind>` in dim `#787e8a`. A clean tree renders a dim `clean`. Each counter is omitted when zero.
26
+ 4. **last commit** — `%h %s` in dim `#787e8a`, truncated to 80 characters with a trailing `…`.
27
+
28
+ Rows 2-4 appear only inside a git repo.
29
+
30
+ ### session box
31
+
32
+ 1. **model display name + effort** in parens — 24-bit ANSI `#E89440` (`38;2;232;148;64`), effort only when present.
33
+ 2. **`ctx N%`** — 24-bit ANSI `#009AFB` (`38;2;0;154;251`) — followed on the same row, after two spaces, by **tokens** `↑<in> ↓<out>` in `#937bda` (`38;2;147;123;218`). When no token data exists the row is just `ctx N%`.
34
+ 3. **`5h N% resets <eta>`** — colored by remaining quota (see below).
35
+ 4. **`7d N% resets <eta>`** — colored independently of the 5h row.
36
+
37
+ ## Box Rendering Rules
38
+
39
+ - Border glyphs `╭ ╮ ╰ ╯ ─ │` in `#5a5f69` (`38;2;90;95;105`); the box title in `#8c929e` (`38;2;140;146;158`).
40
+ - Each box's inner width is the longest of its content rows, or its title + 2, whichever is larger.
41
+ - Both boxes are padded to the **same row count** with blank rows, so the two frames start and end on the same terminal lines.
42
+ - The two boxes are joined by a single space on every line.
43
+ - Rows are emitted newline-separated; the statusline host renders them as multiple lines.
44
+ - When both boxes have zero content rows the script prints nothing and exits 0.
45
+
46
+ ### Measuring width with ANSI escapes present
47
+
48
+ Build **two parallel arrays** per box: one holding the plain text, one holding the same text wrapped in color escapes. Measure padding from the plain array only — measuring the colored string counts the escape bytes and destroys alignment.
49
+
50
+ ### UTF-8 locale guard (bash only)
16
51
 
17
- Segments are joined with ANSI `0;37` pipes (` | `).
52
+ `${#str}` counts **bytes**, not characters, unless the shell is in a UTF-8 locale. The rows contain `↑`, `↓` and `…`, so under `LC_ALL=C` every such row is padded 2 characters short. Guard at the top of the script with a subprocess-free probe:
53
+
54
+ ```bash
55
+ probe='↑'
56
+ if (( ${#probe} != 1 )); then
57
+ for loc in C.UTF-8 en_US.UTF-8 en_US.utf8; do
58
+ export LC_ALL="$loc"
59
+ (( ${#probe} == 1 )) && break
60
+ done
61
+ fi
62
+ ```
63
+
64
+ Assigning `LC_ALL` makes bash call `setlocale` immediately, so `${#probe}` re-evaluates inside the loop; a locale the system lacks leaves the previous value and the loop moves on.
18
65
 
19
66
  ## When to Use This Skill
20
67
 
@@ -37,12 +84,20 @@ Detect the operating system and targeted platform first, then write and wire the
37
84
  - **Linux / macOS** → write `~/.gemini/antigravity-cli/statusline-command.sh` (pure bash + `jq`), wire into `~/.gemini/antigravity-cli/settings.json`.
38
85
  - **Windows** → write `%USERPROFILE%\.gemini\antigravity-cli\statusline-command.ps1`, wire into `%USERPROFILE%\.gemini\antigravity-cli\settings.json`.
39
86
 
87
+ Multi-line rendering is verified on Claude Code. Antigravity's handling of multi-line statusline output is untested.
88
+
89
+ ## Shell Compatibility
90
+
91
+ macOS ships bash 3.2, so the bash variant must avoid: `local -n` namerefs, `mapfile`/`readarray`, associative arrays, and negative array indices. Pass arrays into helpers as positional parameters instead.
92
+
93
+ The PowerShell variant targets Windows PowerShell 5.1, so avoid PowerShell 7 syntax: null-coalescing `??`, ternary `? :`, and chain operators `&&`/`||`.
94
+
40
95
  ## Token Formatting
41
96
 
42
97
  - `< 1000` → raw integer (e.g. `↑850 ↓120`)
43
98
  - `>= 1_000` → one decimal + `k` (e.g. `↑1.2k ↓34.5k`)
44
99
  - `>= 1_000_000` → one decimal + `M` (e.g. `↑1.2M ↓3.4M`)
45
- - Missing/unparseable transcript → omit the segment entirely (no stray separator, never render `↑0 ↓0`).
100
+ - Missing/unparseable transcript → omit the tokens from the `ctx` row entirely, never render `↑0 ↓0`.
46
101
 
47
102
  ## Token Source
48
103
 
@@ -55,6 +110,17 @@ Tokens are calculated dynamically:
55
110
  - `input` = `context_window.total_input_tokens`
56
111
  - `output` = `context_window.total_output_tokens`
57
112
 
113
+ ## Git State
114
+
115
+ Read **all** git state from a single `git -C "$cwd" status -b --porcelain` call, never from `PWD`. That one call yields branch name, upstream ahead/behind, and the staged/modified/untracked counts:
116
+
117
+ - The `##` header line carries `branch...upstream [ahead N, behind M]`, or the literal `HEAD (no branch)` when detached — fall back to `git -C "$cwd" rev-parse --short HEAD` for that case.
118
+ - `??` entries are untracked.
119
+ - For every other entry, column 1 is the index and column 2 the working tree. A `D` in either column counts as deleted; any other non-space counts as staged (column 1) or modified (column 2). One file can count in several buckets.
120
+ - Deletions get their own `-N` counter so a staged removal does not read as `+N`, which looks like an addition.
121
+
122
+ The last-commit row needs one further call: `git -C "$cwd" log -1 --format='%h %s'`.
123
+
58
124
  ## Quota Colors (by REMAINING quota = 100 - used%)
59
125
 
60
126
  | Remaining | Color | 24-bit ANSI |
@@ -134,12 +200,25 @@ if ! command -v jq >/dev/null 2>&1; then
134
200
  exit 0
135
201
  fi
136
202
 
203
+ # ${#str} counts bytes outside a UTF-8 locale, which mis-pads every row holding
204
+ # an arrow or ellipsis. Assigning LC_ALL re-runs setlocale, so the probe below
205
+ # re-evaluates; an unavailable locale leaves the previous value untouched.
206
+ probe='↑'
207
+ if (( ${#probe} != 1 )); then
208
+ for loc in C.UTF-8 en_US.UTF-8 en_US.utf8; do
209
+ export LC_ALL="$loc"
210
+ (( ${#probe} == 1 )) && break
211
+ done
212
+ fi
213
+
137
214
  C_CWD=$'\033[38;2;94;255;255m'
138
215
  C_BRANCH=$'\033[38;2;194;72;112m'
139
- C_PIPE=$'\033[0;37m'
216
+ C_BOX=$'\033[38;2;90;95;105m'
217
+ C_TITLE=$'\033[38;2;140;146;158m'
140
218
  C_MODEL=$'\033[38;2;232;148;64m'
141
219
  C_CTX=$'\033[38;2;0;154;251m'
142
220
  C_TOK=$'\033[38;2;147;123;218m'
221
+ C_DIM=$'\033[38;2;120;126;138m'
143
222
  RESET=$'\033[0m'
144
223
 
145
224
  GREEN=$'\033[38;2;78;186;101m'
@@ -159,8 +238,9 @@ quota_color() {
159
238
 
160
239
  fmt_eta() {
161
240
  local resets="$1"
162
- [[ -z "$resets" || "$resets" == "null" || "$resets" == "" ]] && { printf ''; return; }
241
+ [[ -z "$resets" || "$resets" == "null" ]] && { printf ''; return; }
163
242
  local now delta days hours mins
243
+ # Claude Code sends an absolute epoch; Antigravity sends a duration.
164
244
  if (( resets < 10000000 )); then
165
245
  delta=$resets
166
246
  else
@@ -188,13 +268,26 @@ fmt_tokens() {
188
268
  fi
189
269
  }
190
270
 
271
+ truncate_to() {
272
+ local s="$1" max="$2"
273
+ if (( ${#s} > max )); then printf '%s…' "${s:0:$(( max - 1 ))}"
274
+ else printf '%s' "$s"
275
+ fi
276
+ }
277
+
191
278
  shorten_home() {
192
279
  local p="$1"
193
280
  [[ -z "$p" ]] && return
194
- if [[ "$p" == "$HOME" ]]; then printf '~'
195
- elif [[ "$p" == "$HOME"/* ]]; then printf '~%s' "${p#$HOME}"
196
- else printf '%s' "$p"
281
+ if [[ "$p" == "$HOME" ]]; then p='~'
282
+ elif [[ "$p" == "$HOME"/* ]]; then p="~${p#$HOME}"
197
283
  fi
284
+ printf '%s' "$p"
285
+ }
286
+
287
+ repeat_char() {
288
+ local ch="$1" n="$2" out=""
289
+ while (( n-- > 0 )); do out+="$ch"; done
290
+ printf '%s' "$out"
198
291
  }
199
292
 
200
293
  # --- read & parse stdin payload in a single jq call ---
@@ -210,67 +303,113 @@ read_fields() {
210
303
  .effort.level // "",
211
304
  (.context_window.used_percentage // "" | tostring),
212
305
  (
213
- .rate_limits.five_hour.used_percentage //
214
- (if .quota."gemini-5h" != null then ((1.0 - .quota."gemini-5h".remaining_fraction) * 100)
215
- elif .quota."3p-5h" != null then ((1.0 - .quota."3p-5h".remaining_fraction) * 100)
306
+ .rate_limits.five_hour.used_percentage //
307
+ (if .quota."gemini-5h" != null then ((1.0 - .quota."gemini-5h".remaining_fraction) * 100)
308
+ elif .quota."3p-5h" != null then ((1.0 - .quota."3p-5h".remaining_fraction) * 100)
216
309
  else "" end)
217
310
  | tostring
218
311
  ),
219
312
  (
220
- .rate_limits.five_hour.resets_at //
221
- .quota."gemini-5h".reset_in_seconds //
222
- .quota."3p-5h".reset_in_seconds //
313
+ .rate_limits.five_hour.resets_at //
314
+ .quota."gemini-5h".reset_in_seconds //
315
+ .quota."3p-5h".reset_in_seconds //
223
316
  ""
224
317
  | tostring
225
318
  ),
226
319
  (
227
- .rate_limits.seven_day.used_percentage //
228
- (if .quota."gemini-weekly" != null then ((1.0 - .quota."gemini-weekly".remaining_fraction) * 100)
229
- elif .quota."3p-weekly" != null then ((1.0 - .quota."3p-weekly".remaining_fraction) * 100)
320
+ .rate_limits.seven_day.used_percentage //
321
+ (if .quota."gemini-weekly" != null then ((1.0 - .quota."gemini-weekly".remaining_fraction) * 100)
322
+ elif .quota."3p-weekly" != null then ((1.0 - .quota."3p-weekly".remaining_fraction) * 100)
230
323
  else "" end)
231
324
  | tostring
232
325
  ),
233
326
  (
234
- .rate_limits.seven_day.resets_at //
235
- .quota."gemini-weekly".reset_in_seconds //
236
- .quota."3p-weekly".reset_in_seconds //
327
+ .rate_limits.seven_day.resets_at //
328
+ .quota."gemini-weekly".reset_in_seconds //
329
+ .quota."3p-weekly".reset_in_seconds //
237
330
  ""
238
331
  | tostring
239
332
  ),
240
333
  (.context_window.total_input_tokens // "" | tostring),
241
334
  (.context_window.total_output_tokens // "" | tostring)
242
- ] | join("\u001f")
335
+ ] | join("")
243
336
  ' 2>/dev/null <<<"$payload"
244
337
  }
245
338
 
246
339
  IFS=$'\x1f' read -r cwd transcript model effort ctx five_used five_reset seven_used seven_reset payload_in payload_out < <(read_fields)
247
340
 
248
- segments=()
341
+ repo_plain=(); repo_color=()
249
342
 
250
343
  if [[ -n "$cwd" ]]; then
251
- segments+=("${C_CWD}$(shorten_home "$cwd")${RESET}")
344
+ p=$(shorten_home "$cwd")
345
+ repo_plain+=("$p"); repo_color+=("${C_CWD}${p}${RESET}")
252
346
  fi
253
347
 
254
- # Branch: must use git -C "$cwd", never PWD.
255
- branch=""
348
+ # One `status -b --porcelain` call yields branch, dirty counts and ahead/behind.
349
+ # Must use git -C "$cwd", never PWD.
350
+ branch=""; staged=0; modified=0; deleted=0; untracked=0; ahead=0; behind=0
256
351
  if [[ -n "$cwd" ]]; then
257
- branch=$(git -C "$cwd" rev-parse --abbrev-ref HEAD 2>/dev/null) || branch=""
258
- fi
259
- if [[ -n "$branch" ]]; then
260
- segments+=("${C_BRANCH}(${branch})${RESET}")
352
+ while IFS= read -r line; do
353
+ if [[ "$line" == '## '* ]]; then
354
+ head=${line#'## '}
355
+ if [[ "$head" == 'HEAD (no branch)' ]]; then
356
+ branch=$(git -C "$cwd" rev-parse --short HEAD 2>/dev/null)
357
+ [[ -n "$branch" ]] && branch="detached@${branch}"
358
+ else
359
+ branch=${head%%...*}
360
+ if [[ "$head" == *'[ahead '* ]]; then t=${head#*'[ahead '}; ahead=${t%%[,\]]*}; fi
361
+ if [[ "$head" == *'behind '* ]]; then t=${head#*'behind '}; behind=${t%%[,\]]*}; fi
362
+ fi
363
+ elif [[ "$line" == '??'* ]]; then
364
+ (( ++untracked ))
365
+ else
366
+ case "${line:0:1}" in
367
+ ' ') ;;
368
+ 'D') (( ++deleted )) ;;
369
+ *) (( ++staged )) ;;
370
+ esac
371
+ case "${line:1:1}" in
372
+ ' ') ;;
373
+ 'D') (( ++deleted )) ;;
374
+ *) (( ++modified )) ;;
375
+ esac
376
+ fi
377
+ done < <(git -C "$cwd" status -b --porcelain 2>/dev/null)
261
378
  fi
262
379
 
263
- if [[ -n "$model" ]]; then
264
- if [[ -n "$effort" ]]; then
265
- segments+=("${C_MODEL}${model} (${effort})${RESET}")
380
+ if [[ -n "$branch" ]]; then
381
+ repo_plain+=("$branch"); repo_color+=("${C_BRANCH}${branch}${RESET}")
382
+
383
+ if (( staged || modified || deleted || untracked )); then
384
+ dirty=""
385
+ (( staged )) && dirty+="+${staged} "
386
+ (( modified )) && dirty+="~${modified} "
387
+ (( deleted )) && dirty+="-${deleted} "
388
+ (( untracked )) && dirty+="?${untracked} "
389
+ dirty=${dirty% }
390
+ dirty_c="${GREEN}${dirty}${RESET}"
266
391
  else
267
- segments+=("${C_MODEL}${model}${RESET}")
392
+ dirty="clean"; dirty_c="${C_DIM}clean${RESET}"
393
+ fi
394
+ sync=""
395
+ (( ahead )) && sync+=" ↑${ahead}"
396
+ (( behind )) && sync+=" ↓${behind}"
397
+ repo_plain+=("${dirty}${sync}")
398
+ repo_color+=("${dirty_c}${C_DIM}${sync}${RESET}")
399
+
400
+ last=$(git -C "$cwd" log -1 --format='%h %s' 2>/dev/null)
401
+ if [[ -n "$last" ]]; then
402
+ last=$(truncate_to "$last" 80)
403
+ repo_plain+=("$last"); repo_color+=("${C_DIM}${last}${RESET}")
268
404
  fi
269
405
  fi
270
406
 
271
- if [[ -n "$ctx" ]]; then
272
- ctx_int=$(awk -v n="$ctx" 'BEGIN{printf "%d", (n+0.5)}')
273
- segments+=("${C_CTX}ctx ${ctx_int}%${RESET}")
407
+ sess_plain=(); sess_color=()
408
+
409
+ if [[ -n "$model" ]]; then
410
+ m="$model"
411
+ [[ -n "$effort" ]] && m="${model} (${effort})"
412
+ sess_plain+=("$m"); sess_color+=("${C_MODEL}${m}${RESET}")
274
413
  fi
275
414
 
276
415
  ti=0
@@ -288,7 +427,7 @@ if [[ -n "$transcript" ]]; then
288
427
  fi
289
428
 
290
429
  if [[ -n "$transcript" && -f "$transcript" ]]; then
291
- tok=$(jq -Rn '
430
+ tok=$(jq -Rrn '
292
431
  reduce (inputs | fromjson?) as $m ({i:0,o:0};
293
432
  ($m.message.usage // null) as $u
294
433
  | if $u == null then .
@@ -312,34 +451,82 @@ if (( ti == 0 && to == 0 )); then
312
451
  fi
313
452
  fi
314
453
 
454
+ tokens=""
315
455
  if (( ti > 0 || to > 0 )); then
316
- segments+=("${C_TOK}↑$(fmt_tokens "$ti") ↓$(fmt_tokens "$to")${RESET}")
456
+ tokens="↑$(fmt_tokens "$ti") ↓$(fmt_tokens "$to")"
457
+ fi
458
+
459
+ if [[ -n "$ctx" ]]; then
460
+ ctx_int=$(awk -v n="$ctx" 'BEGIN{printf "%d", (n+0.5)}')
461
+ c="ctx ${ctx_int}%"
462
+ c_col="${C_CTX}${c}${RESET}"
463
+ if [[ -n "$tokens" ]]; then
464
+ c+=" ${tokens}"; c_col+=" ${C_TOK}${tokens}${RESET}"
465
+ fi
466
+ sess_plain+=("$c"); sess_color+=("$c_col")
467
+ elif [[ -n "$tokens" ]]; then
468
+ sess_plain+=("$tokens"); sess_color+=("${C_TOK}${tokens}${RESET}")
317
469
  fi
318
470
 
319
471
  if [[ -n "$five_used" ]]; then
320
472
  fu=$(awk -v n="$five_used" 'BEGIN{printf "%d", (n+0.5)}')
321
473
  eta=$(fmt_eta "$five_reset")
322
- segments+=("$(quota_color "$fu")5h:${fu}%(${eta})${RESET}")
474
+ s="5h ${fu}%"; [[ -n "$eta" ]] && s+=" resets ${eta}"
475
+ sess_plain+=("$s"); sess_color+=("$(quota_color "$fu")${s}${RESET}")
323
476
  fi
324
477
  if [[ -n "$seven_used" ]]; then
325
478
  su=$(awk -v n="$seven_used" 'BEGIN{printf "%d", (n+0.5)}')
326
479
  eta=$(fmt_eta "$seven_reset")
327
- segments+=("$(quota_color "$su")7d:${su}%(${eta})${RESET}")
480
+ s="7d ${su}%"; [[ -n "$eta" ]] && s+=" resets ${eta}"
481
+ sess_plain+=("$s"); sess_color+=("$(quota_color "$su")${s}${RESET}")
328
482
  fi
329
483
 
330
- sep=" ${C_PIPE}|${RESET} "
331
- out=""
332
- for i in "${!segments[@]}"; do
333
- if (( i == 0 )); then out="${segments[$i]}"
334
- else out="${out}${sep}${segments[$i]}"
335
- fi
484
+ rows=${#repo_plain[@]}
485
+ (( ${#sess_plain[@]} > rows )) && rows=${#sess_plain[@]}
486
+ if (( rows == 0 )); then printf ''; exit 0; fi
487
+ for (( i = ${#repo_plain[@]}; i < rows; i++ )); do repo_plain+=(""); repo_color+=(""); done
488
+ for (( i = ${#sess_plain[@]}; i < rows; i++ )); do sess_plain+=(""); sess_color+=(""); done
489
+
490
+ # Width comes from the plain rows; the parallel colored rows carry ANSI escapes
491
+ # that would otherwise be counted as visible characters. Arrays are passed
492
+ # positionally because bash 3.2 (macOS) has no `local -n` nameref.
493
+ box_width() {
494
+ local title="$1"; shift
495
+ local w=$(( ${#title} + 2 )) s
496
+ for s in "$@"; do (( ${#s} > w )) && w=${#s}; done
497
+ printf '%d' "$w"
498
+ }
499
+ rw=$(box_width "repo" "${repo_plain[@]}")
500
+ sw=$(box_width "session" "${sess_plain[@]}")
501
+
502
+ box_top() {
503
+ local title="$1" w="$2"
504
+ printf '%s╭─ %s%s%s %s╮%s' "$C_BOX" "$C_TITLE" "$title" "$C_BOX" \
505
+ "$(repeat_char '─' $(( w - ${#title} - 1 )))" "$RESET"
506
+ }
507
+ box_bottom() {
508
+ printf '%s╰%s╯%s' "$C_BOX" "$(repeat_char '─' $(( $1 + 2 )))" "$RESET"
509
+ }
510
+ box_row() {
511
+ local colored="$1" w="$2" plain="$3"
512
+ printf '%s│%s %s%s %s│%s' "$C_BOX" "$RESET" "$colored" \
513
+ "$(repeat_char ' ' $(( w - ${#plain} )))" "$C_BOX" "$RESET"
514
+ }
515
+
516
+ out="$(box_top repo "$rw") $(box_top session "$sw")"
517
+ for (( i = 0; i < rows; i++ )); do
518
+ out+=$'\n'
519
+ out+="$(box_row "${repo_color[$i]}" "$rw" "${repo_plain[$i]}")"
520
+ out+=" $(box_row "${sess_color[$i]}" "$sw" "${sess_plain[$i]}")"
336
521
  done
522
+ out+=$'\n'"$(box_bottom "$rw") $(box_bottom "$sw")"
523
+
337
524
  printf '%s' "$out"
338
525
  ```
339
526
 
340
527
  ## Windows Implementation
341
528
 
342
- Write `statusline-command.ps1` to the targeted platform directory. Use `ConvertFrom-Json` (no Python dep). Read the JSONL transcript with `Get-Content -ReadCount 0` and `ConvertFrom-Json` per line inside try/catch. Use `[char]27` for ANSI escapes.
529
+ Write `statusline-command.ps1` to the targeted platform directory. Use `ConvertFrom-Json` (no Python dep). Read the JSONL transcript with `Get-Content -ReadCount 0` and `ConvertFrom-Json` per line inside try/catch. Use `[char]27` for ANSI escapes, and set UTF-8 output encoding so the box-drawing glyphs survive.
343
530
 
344
531
  `statusline-command.ps1`:
345
532
 
@@ -351,10 +538,12 @@ $ESC = [char]27
351
538
 
352
539
  $C_CWD = "$ESC[38;2;94;255;255m"
353
540
  $C_BRANCH = "$ESC[38;2;194;72;112m"
354
- $C_PIPE = "$ESC[0;37m"
541
+ $C_BOX = "$ESC[38;2;90;95;105m"
542
+ $C_TITLE = "$ESC[38;2;140;146;158m"
355
543
  $C_MODEL = "$ESC[38;2;232;148;64m"
356
544
  $C_CTX = "$ESC[38;2;0;154;251m"
357
545
  $C_TOK = "$ESC[38;2;147;123;218m"
546
+ $C_DIM = "$ESC[38;2;120;126;138m"
358
547
  $RESET = "$ESC[0m"
359
548
 
360
549
  $GREEN = "$ESC[38;2;78;186;101m"
@@ -362,6 +551,10 @@ $AMBER = "$ESC[38;2;222;142;62m"
362
551
  $ORANGE = "$ESC[38;2;234;88;12m"
363
552
  $REDORG = "$ESC[38;2;183;68;38m"
364
553
 
554
+ $TL = [char]0x256D; $TR = [char]0x256E; $BL = [char]0x2570; $BR = [char]0x256F
555
+ $HZ = [char]0x2500; $VT = [char]0x2502
556
+ $UP = [char]0x2191; $DN = [char]0x2193; $ELLIPSIS = [char]0x2026
557
+
365
558
  function QuotaColor([double]$used) {
366
559
  $remaining = 100 - $used
367
560
  if ($remaining -gt 60) { return $GREEN }
@@ -373,6 +566,7 @@ function QuotaColor([double]$used) {
373
566
  function FmtEta($resetsAt) {
374
567
  if ($null -eq $resetsAt -or $resetsAt -eq "") { return "" }
375
568
  $val = [int64]$resetsAt
569
+ # Claude Code sends an absolute epoch; Antigravity sends a duration.
376
570
  if ($val -lt 10000000) {
377
571
  $delta = $val
378
572
  } else {
@@ -393,23 +587,19 @@ function FmtTokens([int64]$n) {
393
587
  return ("{0:N1}M" -f ($n / 1000000.0))
394
588
  }
395
589
 
590
+ function TruncateTo($s, [int]$max) {
591
+ if ($s.Length -gt $max) { return $s.Substring(0, $max - 1) + $ELLIPSIS }
592
+ return $s
593
+ }
594
+
396
595
  function ShortenHome($p) {
397
596
  if (-not $p) { return "" }
398
- $home = $env:USERPROFILE
399
- if ($p -eq $home) { return "~" }
400
- if ($p.StartsWith("$home\")) { return "~" + $p.Substring($home.Length) }
597
+ $userHome = $env:USERPROFILE
598
+ if ($p -eq $userHome) { return "~" }
599
+ if ($p.StartsWith("$userHome\")) { return "~" + $p.Substring($userHome.Length) }
401
600
  return $p
402
601
  }
403
602
 
404
- function GetBranch($cwd) {
405
- if (-not $cwd) { return $null }
406
- try {
407
- $b = & git -C "$cwd" rev-parse --abbrev-ref HEAD 2>$null
408
- if ($LASTEXITCODE -eq 0 -and $b) { return $b.Trim() }
409
- } catch {}
410
- return $null
411
- }
412
-
413
603
  function SumTokens($path) {
414
604
  if (-not $path) { return $null }
415
605
  # Resolve path mismatch (if it has .gemini/antigravity/ but it actually is at .gemini/antigravity-cli/)
@@ -443,30 +633,84 @@ function SumTokens($path) {
443
633
  $raw = [Console]::In.ReadToEnd()
444
634
  try { $data = if ($raw.Trim()) { $raw | ConvertFrom-Json } else { @{} } } catch { $data = @{} }
445
635
 
446
- $segments = @()
636
+ $repoPlain = @(); $repoColor = @()
637
+ $sessPlain = @(); $sessColor = @()
447
638
 
448
639
  $cwd = $data.cwd
449
- if ($cwd) { $segments += "$C_CWD$(ShortenHome $cwd)$RESET" }
640
+ if ($cwd) {
641
+ $p = ShortenHome $cwd
642
+ $repoPlain += $p; $repoColor += "$C_CWD$p$RESET"
643
+ }
450
644
 
451
- $branch = GetBranch $cwd
452
- if ($branch) { $segments += "$C_BRANCH($branch)$RESET" }
645
+ # One `status -b --porcelain` call yields branch, dirty counts and ahead/behind.
646
+ # Must use git -C "$cwd", never PWD.
647
+ $branch = ""; $staged = 0; $modified = 0; $deleted = 0; $untracked = 0; $ahead = 0; $behind = 0
648
+ if ($cwd) {
649
+ $statusLines = & git -C "$cwd" status -b --porcelain 2>$null
650
+ if ($LASTEXITCODE -eq 0 -and $statusLines) {
651
+ foreach ($line in @($statusLines)) {
652
+ if ($line.StartsWith("## ")) {
653
+ $head = $line.Substring(3)
654
+ if ($head -eq "HEAD (no branch)") {
655
+ $sha = & git -C "$cwd" rev-parse --short HEAD 2>$null
656
+ if ($sha) { $branch = "detached@" + $sha.Trim() }
657
+ } else {
658
+ $branch = ($head -split '\.\.\.')[0]
659
+ if ($head -match '\[ahead (\d+)') { $ahead = [int]$Matches[1] }
660
+ if ($head -match 'behind (\d+)') { $behind = [int]$Matches[1] }
661
+ }
662
+ } elseif ($line.StartsWith("??")) {
663
+ $untracked++
664
+ } else {
665
+ $idx = $line.Substring(0, 1)
666
+ if ($idx -eq "D") { $deleted++ } elseif ($idx -ne " ") { $staged++ }
667
+ $wt = $line.Substring(1, 1)
668
+ if ($wt -eq "D") { $deleted++ } elseif ($wt -ne " ") { $modified++ }
669
+ }
670
+ }
671
+ }
672
+ }
673
+
674
+ if ($branch) {
675
+ $repoPlain += $branch; $repoColor += "$C_BRANCH$branch$RESET"
676
+
677
+ if ($staged -gt 0 -or $modified -gt 0 -or $deleted -gt 0 -or $untracked -gt 0) {
678
+ $parts = @()
679
+ if ($staged -gt 0) { $parts += "+$staged" }
680
+ if ($modified -gt 0) { $parts += "~$modified" }
681
+ if ($deleted -gt 0) { $parts += "-$deleted" }
682
+ if ($untracked -gt 0) { $parts += "?$untracked" }
683
+ $dirty = [string]::Join(" ", $parts)
684
+ $dirtyColored = "$GREEN$dirty$RESET"
685
+ } else {
686
+ $dirty = "clean"; $dirtyColored = "$C_DIM" + "clean$RESET"
687
+ }
688
+ $sync = ""
689
+ if ($ahead -gt 0) { $sync += " $UP$ahead" }
690
+ if ($behind -gt 0) { $sync += " $DN$behind" }
691
+ $repoPlain += "$dirty$sync"
692
+ $repoColor += "$dirtyColored$C_DIM$sync$RESET"
693
+
694
+ $last = & git -C "$cwd" log -1 --format='%h %s' 2>$null
695
+ if ($last) {
696
+ $last = TruncateTo $last.Trim() 80
697
+ $repoPlain += $last; $repoColor += "$C_DIM$last$RESET"
698
+ }
699
+ }
453
700
 
454
701
  $model = $data.model.display_name
455
702
  $effort = $data.effort.level
456
703
  if ($model) {
457
- if ($effort) { $segments += "$C_MODEL$model ($effort)$RESET" }
458
- else { $segments += "$C_MODEL$model$RESET" }
704
+ if ($effort) { $m = "$model ($effort)" } else { $m = "$model" }
705
+ $sessPlain += $m; $sessColor += "$C_MODEL$m$RESET"
459
706
  }
460
707
 
461
- $ctx = $data.context_window.used_percentage
462
- if ($null -ne $ctx) { $segments += "$C_CTX" + "ctx " + [int][math]::Round([double]$ctx) + "%$RESET" }
463
-
464
- $tokens = SumTokens $data.transcript_path
708
+ $tokenSums = SumTokens $data.transcript_path
465
709
  $ti = 0
466
710
  $to = 0
467
- if ($null -ne $tokens) {
468
- $ti = $tokens[0]
469
- $to = $tokens[1]
711
+ if ($null -ne $tokenSums) {
712
+ $ti = $tokenSums[0]
713
+ $to = $tokenSums[1]
470
714
  }
471
715
 
472
716
  # Fallback to payload context_window tokens if transcript sum is 0
@@ -477,8 +721,22 @@ if ($ti -eq 0 -and $to -eq 0) {
477
721
  }
478
722
  }
479
723
 
724
+ $tokens = ""
480
725
  if ($ti -gt 0 -or $to -gt 0) {
481
- $segments += "$C_TOK↑$(FmtTokens $ti) ↓$(FmtTokens $to)$RESET"
726
+ $tokens = "$UP" + (FmtTokens $ti) + " $DN" + (FmtTokens $to)
727
+ }
728
+
729
+ $ctx = $data.context_window.used_percentage
730
+ if ($null -ne $ctx) {
731
+ $c = "ctx " + [int][math]::Round([double]$ctx) + "%"
732
+ $cColored = "$C_CTX$c$RESET"
733
+ if ($tokens) {
734
+ $c += " $tokens"
735
+ $cColored += " $C_TOK$tokens$RESET"
736
+ }
737
+ $sessPlain += $c; $sessColor += $cColored
738
+ } elseif ($tokens) {
739
+ $sessPlain += $tokens; $sessColor += "$C_TOK$tokens$RESET"
482
740
  }
483
741
 
484
742
  $five_used = $null
@@ -509,14 +767,53 @@ if ($null -ne $data.rate_limits.seven_day.used_percentage) {
509
767
  }
510
768
 
511
769
  if ($null -ne $five_used) {
512
- $segments += "$(QuotaColor $five_used)5h:$([int][math]::Round($five_used))%($(FmtEta $five_reset))$RESET"
770
+ $s = "5h " + [int][math]::Round($five_used) + "%"
771
+ $eta = FmtEta $five_reset
772
+ if ($eta) { $s += " resets $eta" }
773
+ $sessPlain += $s; $sessColor += "$(QuotaColor $five_used)$s$RESET"
513
774
  }
514
775
  if ($null -ne $seven_used) {
515
- $segments += "$(QuotaColor $seven_used)7d:$([int][math]::Round($seven_used))%($(FmtEta $seven_reset))$RESET"
776
+ $s = "7d " + [int][math]::Round($seven_used) + "%"
777
+ $eta = FmtEta $seven_reset
778
+ if ($eta) { $s += " resets $eta" }
779
+ $sessPlain += $s; $sessColor += "$(QuotaColor $seven_used)$s$RESET"
780
+ }
781
+
782
+ $rows = [math]::Max($repoPlain.Count, $sessPlain.Count)
783
+ if ($rows -eq 0) { exit 0 }
784
+ while ($repoPlain.Count -lt $rows) { $repoPlain += ""; $repoColor += "" }
785
+ while ($sessPlain.Count -lt $rows) { $sessPlain += ""; $sessColor += "" }
786
+
787
+ # Width comes from the plain rows; the parallel colored rows carry ANSI escapes
788
+ # that would otherwise be counted as visible characters.
789
+ function BoxWidth($title, $plainRows) {
790
+ $w = $title.Length + 2
791
+ foreach ($s in $plainRows) { if ($s.Length -gt $w) { $w = $s.Length } }
792
+ return $w
793
+ }
794
+ $rw = BoxWidth "repo" $repoPlain
795
+ $sw = BoxWidth "session" $sessPlain
796
+
797
+ function BoxTop($title, [int]$w) {
798
+ $dashes = [string]$HZ * ($w - $title.Length - 1)
799
+ return "$C_BOX$TL$HZ $C_TITLE$title$C_BOX $dashes$TR$RESET"
800
+ }
801
+ function BoxBottom([int]$w) {
802
+ return "$C_BOX$BL" + ([string]$HZ * ($w + 2)) + "$BR$RESET"
516
803
  }
804
+ function BoxRow($colored, [int]$w, $plain) {
805
+ $pad = " " * ($w - $plain.Length)
806
+ return "$C_BOX$VT$RESET $colored$pad $C_BOX$VT$RESET"
807
+ }
808
+
809
+ $lines = @()
810
+ $lines += (BoxTop "repo" $rw) + " " + (BoxTop "session" $sw)
811
+ for ($i = 0; $i -lt $rows; $i++) {
812
+ $lines += (BoxRow $repoColor[$i] $rw $repoPlain[$i]) + " " + (BoxRow $sessColor[$i] $sw $sessPlain[$i])
813
+ }
814
+ $lines += (BoxBottom $rw) + " " + (BoxBottom $sw)
517
815
 
518
- $sep = " $C_PIPE|$RESET "
519
- [Console]::Out.Write([string]::Join($sep, $segments))
816
+ [Console]::Out.Write([string]::Join("`n", $lines))
520
817
  ```
521
818
 
522
819
  ## Settings Wiring
@@ -554,12 +851,16 @@ Wire into `~/.gemini/antigravity-cli/settings.json` (or `%USERPROFILE%\.gemini\a
554
851
 
555
852
  ## Smoke Test
556
853
 
557
- After installing, generate a fake JSON payload with 5h at 80% used, 7d at 50% used, both with future `resets_at`/`reset_in_seconds`, plus a `transcript_path` pointing at a small fixture JSONL containing two assistant messages with `usage` blocks. Pipe it into the script and confirm:
854
+ After installing, pipe a fake payload into the script and confirm:
558
855
 
559
- - 5h renders **orange**, 7d renders **amber**.
560
- - cwd, model, and context % are present.
561
- - Tokens segment renders `↑<sum> ↓<sum>` matching the fixture, in `#937bda`, as its own pipe-separated segment immediately after `ctx`.
562
- - Omitting `transcript_path` hides the tokens segment cleanly (no stray separator).
856
+ - **Every rendered line has the same character count** — this is the alignment check, and it fails first when width is measured off a colored string or the locale is not UTF-8.
857
+ - Both boxes show the same number of rows, framed top and bottom on the same lines.
858
+ - Inside a repo the repo box shows 4 rows: full cwd, branch, working-tree state, last commit.
859
+ - Staging a deletion (`git rm`) renders `-N`, not `+N`.
860
+ - 5h renders **orange** at 80% used, 7d renders **amber** at 50% used.
861
+ - The tokens sum matches the fixture and sits on the `ctx` row.
862
+ - Omitting `transcript_path` leaves the `ctx` row as a bare `ctx N%` — no `↑0 ↓0`, no stray spacing.
863
+ - Outside a repo the repo box collapses to the cwd row alone and the session box pads with blank rows.
563
864
 
564
865
  ### Linux/macOS smoke test
565
866
 
@@ -567,13 +868,31 @@ After installing, generate a fake JSON payload with 5h at 80% used, 7d at 50% us
567
868
  mkdir -p /tmp/statusline-smoke
568
869
  cat >/tmp/statusline-smoke/transcript.jsonl <<'EOF'
569
870
  {"type":"assistant","message":{"role":"assistant","usage":{"input_tokens":100,"cache_creation_input_tokens":200,"cache_read_input_tokens":300,"output_tokens":50}}}
871
+ not json at all - must be skipped, not fatal
570
872
  {"type":"assistant","message":{"role":"assistant","usage":{"input_tokens":10,"cache_creation_input_tokens":20,"cache_read_input_tokens":30,"output_tokens":5}}}
571
873
  EOF
572
874
  FUTURE=$(($(date +%s) + 7200))
573
875
  FUTURE7=$(($(date +%s) + 3*86400))
574
- printf '{"cwd":"%s","transcript_path":"/tmp/statusline-smoke/transcript.jsonl","model":{"display_name":"Opus 4.7"},"effort":{"level":"medium"},"context_window":{"used_percentage":42},"rate_limits":{"five_hour":{"used_percentage":80,"resets_at":%d},"seven_day":{"used_percentage":50,"resets_at":%d}}}' "$HOME" "$FUTURE" "$FUTURE7" | bash ~/.claude/statusline-command.sh; echo
876
+
877
+ # Claude Code payload
878
+ printf '{"cwd":"%s","transcript_path":"/tmp/statusline-smoke/transcript.jsonl","model":{"display_name":"Opus 4.7"},"effort":{"level":"medium"},"context_window":{"used_percentage":42},"rate_limits":{"five_hour":{"used_percentage":80,"resets_at":%d},"seven_day":{"used_percentage":50,"resets_at":%d}}}' "$PWD" "$FUTURE" "$FUTURE7" | bash ~/.claude/statusline-command.sh; echo
879
+
880
+ # Antigravity payload - exercises the quota/reset_in_seconds and token fallbacks
881
+ printf '{"cwd":"%s","model":{"display_name":"Gemini 3.5 Flash"},"context_window":{"used_percentage":6.7,"total_input_tokens":70419,"total_output_tokens":16667},"quota":{"gemini-5h":{"remaining_fraction":0.87,"reset_in_seconds":2515},"gemini-weekly":{"remaining_fraction":0.97,"reset_in_seconds":589315}}}' "$PWD" | bash ~/.claude/statusline-command.sh; echo
575
882
  ```
576
883
 
884
+ Assert equal line widths mechanically:
885
+
886
+ ```bash
887
+ printf '{"cwd":"%s","transcript_path":"/tmp/statusline-smoke/transcript.jsonl","model":{"display_name":"Opus 4.7"},"context_window":{"used_percentage":42}}' "$PWD" \
888
+ | bash ~/.claude/statusline-command.sh \
889
+ | perl -CSD -pe 's/\e\[[0-9;]*m//g' \
890
+ | perl -CSD -ne 'chomp; print length($_), "\n"' \
891
+ | sort -u
892
+ ```
893
+
894
+ That must print exactly one number. Re-run the same pipeline wrapped in `env -i PATH="$PATH" HOME="$HOME" bash -c '...'` to confirm the locale guard holds with no `LANG` set.
895
+
577
896
  ### Windows smoke test (PowerShell)
578
897
 
579
898
  ```powershell
@@ -586,7 +905,7 @@ New-Item -ItemType Directory -Force -Path $tmp | Out-Null
586
905
  $future = [int64](Get-Date -UFormat %s) + 7200
587
906
  $future7 = [int64](Get-Date -UFormat %s) + 3*86400
588
907
  $payload = @{
589
- cwd = $env:USERPROFILE
908
+ cwd = $PWD.Path
590
909
  transcript_path = "$tmp\transcript.jsonl"
591
910
  model = @{ display_name = "Opus 4.7" }
592
911
  effort = @{ level = "medium" }
@@ -596,23 +915,27 @@ $payload = @{
596
915
  seven_day = @{ used_percentage = 50; resets_at = $future7 }
597
916
  }
598
917
  } | ConvertTo-Json -Depth 5 -Compress
599
- $payload | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.claude\statusline-command.ps1"
918
+ $out = $payload | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.claude\statusline-command.ps1"
919
+ $out
920
+ ($out -split "`n" | ForEach-Object { ($_ -replace "$([char]27)\[[0-9;]*m", "").Length } | Sort-Object -Unique)
600
921
  ```
601
922
 
923
+ The last line must print exactly one number.
924
+
602
925
  ## Color Preview One-Liner
603
926
 
604
- After install, output a one-liner the user can paste to preview all four quota tiers, with both 5h and 7d on each line, separated by ` | `, one tier per line.
927
+ After install, output a one-liner the user can paste to preview all four quota tiers, one tier per line, in the row format the session box uses.
605
928
 
606
929
  ### Bash form
607
930
 
608
931
  ```bash
609
- printf '\033[38;2;78;186;101m5h:20%%(2h30m)\033[0m \033[0;37m|\033[0m \033[38;2;78;186;101m7d:25%%(3d12h)\033[0m\n\033[38;2;222;142;62m5h:55%%(2h30m)\033[0m \033[0;37m|\033[0m \033[38;2;222;142;62m7d:45%%(3d12h)\033[0m\n\033[38;2;234;88;12m5h:80%%(1h15m)\033[0m \033[0;37m|\033[0m \033[38;2;234;88;12m7d:75%%(2d3h)\033[0m\n\033[38;2;183;68;38m5h:95%%(0h30m)\033[0m \033[0;37m|\033[0m \033[38;2;183;68;38m7d:92%%(1d2h)\033[0m\n'
932
+ printf '\033[38;2;78;186;101m5h 20%% resets 2h 30m\033[0m\n\033[38;2;222;142;62m5h 55%% resets 2h 30m\033[0m\n\033[38;2;234;88;12m5h 80%% resets 1h 15m\033[0m\n\033[38;2;183;68;38m5h 95%% resets 30m\033[0m\n'
610
933
  ```
611
934
 
612
935
  ### PowerShell form
613
936
 
614
937
  ```powershell
615
- $e=[char]27; Write-Host "$e[38;2;78;186;101m5h:20%(2h30m)$e[0m $e[0;37m|$e[0m $e[38;2;78;186;101m7d:25%(3d12h)$e[0m`n$e[38;2;222;142;62m5h:55%(2h30m)$e[0m $e[0;37m|$e[0m $e[38;2;222;142;62m7d:45%(3d12h)$e[0m`n$e[38;2;234;88;12m5h:80%(1h15m)$e[0m $e[0;37m|$e[0m $e[38;2;234;88;12m7d:75%(2d3h)$e[0m`n$e[38;2;183;68;38m5h:95%(0h30m)$e[0m $e[0;37m|$e[0m $e[38;2;183;68;38m7d:92%(1d2h)$e[0m"
938
+ $e=[char]27; Write-Host "$e[38;2;78;186;101m5h 20% resets 2h 30m$e[0m`n$e[38;2;222;142;62m5h 55% resets 2h 30m$e[0m`n$e[38;2;234;88;12m5h 80% resets 1h 15m$e[0m`n$e[38;2;183;68;38m5h 95% resets 30m$e[0m"
616
939
  ```
617
940
 
618
941
  ## Other Rules
@@ -620,5 +943,6 @@ $e=[char]27; Write-Host "$e[38;2;78;186;101m5h:20%(2h30m)$e[0m $e[0;37m|$e[0m $e
620
943
  - Do not add features beyond this spec.
621
944
  - Do not auto-commit any changes.
622
945
  - Do not add Co-Authored-By trailers.
623
- - Read git branch with `git -C "$cwd" rev-parse --abbrev-ref HEAD`, never from `PWD`.
946
+ - Read git state with `git -C "$cwd" status -b --porcelain`, never from `PWD`.
947
+ - Never measure padding from a string containing ANSI escapes.
624
948
  - When merging into existing `settings.json`, preserve all other keys; only set/replace `statusLine`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kurokeita/add-skill",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "CLI to install AI agent skills to various platforms",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,47 +0,0 @@
1
- ---
2
- name: serena-file-processing
3
- description: Master instructions for using Serena MCP tools for code navigation, search, navigation, and editing. Includes rules for terminal usage and memory management.
4
- ---
5
-
6
- # Serena File Processing Skill
7
-
8
- This skill defines the **authoritative rules** for using Serena tools (`view_file`, `replace_file_content`, `grep_search`, etc.) and interactions with other tools in Antigravity.
9
-
10
- ## Core Rules
11
-
12
- ### 1. Terminal Execution
13
-
14
- - ❌ NEVER use `mcp_serena_execute_shell_command`.
15
- - ✅ ALWAYS use `run_command` for terminal operations in Antigravity.
16
-
17
- ## Workflow Best Practices
18
-
19
- ### 2. Code Navigation
20
-
21
- - **Symbolic First**: Use `view_file_outline` to understand file structure before reading content.
22
- - **Search Smart**: Use `grep_search` to find usages. Constraint searches to relevant paths.
23
- - **Jumping**: Use `view_code_item` to inspect specific functions/classes without reading the whole file.
24
-
25
- ### 3. Code Editing
26
-
27
- - **Pattern Matching**: When using `replace_file_content` or `multi_replace_file_content`, ensure `TargetContent` is unique.
28
- - **Context**: Use `view_file` around the target area first to ensure you have the exact string for replacement.
29
-
30
- ### 4. Memory Management
31
-
32
- - **Limit Output**: When reading large files, read in chunks or use `view_child_nodes`.
33
- - **Close Context**: You don't need to explicitly "close" files, but don't re-read files unnecessarily.
34
-
35
- ## Integration with Other Skills
36
-
37
- - **Layering**: Use this skill as the foundational layer for file operations.
38
- - **Specifics**: Higher-level skills (like `bliksund-pr-reviewer`) define *what* to look for; this skill defines *how* to look for it reliably.
39
-
40
- ## Troubleshooting
41
-
42
- ### 5. Connection Issues
43
-
44
- If you encounter errors connecting to the Serena MCP server or tools are unavailable:
45
-
46
- 1. **Check Process Status**: Ask the user to verify if the Serena process has started.
47
- 2. **Check Workspace Config**: Ask the user to confirm if the Serena configuration has been initiated for the current workspace.