@kurokeita/add-skill 2.0.0 → 2.1.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.
@@ -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.1.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.