skill-statusline 2.2.0 → 2.2.1

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.
package/bin/cli.js CHANGED
@@ -9,7 +9,7 @@ const readline = require('readline');
9
9
  const args = process.argv.slice(2);
10
10
  const command = args[0];
11
11
  const subcommand = args[1];
12
- const VERSION = '2.2.0';
12
+ const VERSION = '2.2.1';
13
13
 
14
14
  const PKG_DIR = path.resolve(__dirname, '..');
15
15
  const HOME = os.homedir();
package/bin/statusline.sh CHANGED
@@ -12,20 +12,12 @@ fi
12
12
  # ── v1 fallback (inline legacy script) ──
13
13
  # This runs if only statusline-command.sh was copied without the statusline/ directory
14
14
 
15
- # Safety: kill script after 3 seconds to prevent hangs
16
- _sl_cleanup() { exit 0; }
17
- trap '_sl_cleanup' ALRM 2>/dev/null
18
- ( sleep 3 && kill -ALRM $$ 2>/dev/null ) &
19
- _SL_WD_PID=$!
20
-
21
15
  # Read stdin with protection against missing pipe
22
16
  if [ -t 0 ]; then
23
- kill "$_SL_WD_PID" 2>/dev/null; wait "$_SL_WD_PID" 2>/dev/null
24
17
  exit 0
25
18
  fi
26
- input=$(timeout 2 cat 2>/dev/null || cat 2>/dev/null)
19
+ input=$(timeout 2 cat 2>/dev/null)
27
20
  if [ -z "$input" ]; then
28
- kill "$_SL_WD_PID" 2>/dev/null; wait "$_SL_WD_PID" 2>/dev/null
29
21
  exit 0
30
22
  fi
31
23
 
@@ -133,7 +125,3 @@ printf ' '; rpad "${PINK}Skill:${RST} ${PINK}${skill_label}${RST}" "$C1"; printf
133
125
  printf ' '; rpad "${PURPLE}Model:${RST} ${PURPLE}${BOLD}${model_full}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${CYAN}Dir:${RST} ${CYAN}${dir_label}${RST}"
134
126
  printf ' '; rpad "${YELLOW}Tokens:${RST} ${YELLOW}${token_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${GREEN}Cost:${RST} ${GREEN}${cost_label}${RST}"
135
127
  printf ' '; printf '%b' "${CTX_CLR}Context:${RST} ${ctx_bar}"
136
-
137
- # Cleanup watchdog
138
- kill "$_SL_WD_PID" 2>/dev/null
139
- wait "$_SL_WD_PID" 2>/dev/null
package/lib/core.sh CHANGED
@@ -1,37 +1,32 @@
1
1
  #!/usr/bin/env bash
2
- # skill-statusline v2.0 — Core engine
2
+ # skill-statusline v2 — Core engine
3
3
  # Reads Claude Code JSON from stdin, computes all fields, renders via layout
4
4
 
5
5
  STATUSLINE_DIR="${HOME}/.claude/statusline"
6
6
  CONFIG_FILE="${HOME}/.claude/statusline-config.json"
7
7
 
8
- # ── 0. Global safety: kill entire script if it takes too long ──
9
- # Claude Code calls this on every refresh; a hang here freezes the terminal
10
- _sl_cleanup() { exit 0; }
11
- trap '_sl_cleanup' ALRM 2>/dev/null
12
- ( sleep 3 && kill -ALRM $$ 2>/dev/null ) &
13
- _SL_WATCHDOG_PID=$!
14
-
15
- # ── 0b. Read stdin JSON (with timeout) ──
16
- # If stdin isn't piped properly, `cat` hangs forever — use read with timeout
17
- input=""
8
+ # ── 0. Read stdin JSON first (before anything else) ──
9
+ # If stdin isn't piped properly, `cat` hangs forever guard against that
18
10
  if [ -t 0 ]; then
19
- # stdin is a terminal (not piped) — no data to read, output nothing
20
- kill "$_SL_WATCHDOG_PID" 2>/dev/null; wait "$_SL_WATCHDOG_PID" 2>/dev/null
21
11
  exit 0
22
12
  fi
23
- # Read all of stdin but bail after 2 seconds
24
- input=$(timeout 2 cat 2>/dev/null || cat 2>/dev/null)
13
+ input=$(timeout 2 cat 2>/dev/null)
25
14
  if [ -z "$input" ]; then
26
- kill "$_SL_WATCHDOG_PID" 2>/dev/null; wait "$_SL_WATCHDOG_PID" 2>/dev/null
27
15
  exit 0
28
16
  fi
29
17
 
18
+ # Note: no global watchdog — individual timeouts on stdin (2s), git (2s),
19
+ # and tput (1s) protect against hangs without background process issues
20
+
30
21
  # ── 1. Source modules ──
31
22
  source "${STATUSLINE_DIR}/json-parser.sh"
32
23
  source "${STATUSLINE_DIR}/helpers.sh"
33
24
 
34
- # ── 2. Load config ──
25
+ # ── 2. Parse ALL JSON in one awk pass ──
26
+ # This sets SL_J_* variables — avoids spawning 100+ subshells
27
+ sl_parse_json
28
+
29
+ # ── 3. Load config (using simple grep — much faster than multiple json calls) ──
35
30
  active_theme="default"
36
31
  active_layout="standard"
37
32
  cfg_warn_threshold=85
@@ -62,7 +57,7 @@ fi
62
57
  [ -n "$STATUSLINE_THEME_OVERRIDE" ] && active_theme="$STATUSLINE_THEME_OVERRIDE"
63
58
  [ -n "$STATUSLINE_LAYOUT_OVERRIDE" ] && active_layout="$STATUSLINE_LAYOUT_OVERRIDE"
64
59
 
65
- # ── 3. Source theme ──
60
+ # ── 4. Source theme ──
66
61
  theme_file="${STATUSLINE_DIR}/themes/${active_theme}.sh"
67
62
  if [ -f "$theme_file" ]; then
68
63
  source "$theme_file"
@@ -70,7 +65,7 @@ else
70
65
  source "${STATUSLINE_DIR}/themes/default.sh"
71
66
  fi
72
67
 
73
- # ── 4. Terminal width detection (with timeout) ──
68
+ # ── 5. Terminal width detection (with timeout) ──
74
69
  SL_TERM_WIDTH=${COLUMNS:-0}
75
70
  if [ "$SL_TERM_WIDTH" -eq 0 ] 2>/dev/null; then
76
71
  _tw=$(timeout 1 tput cols 2>/dev/null || echo "")
@@ -95,14 +90,13 @@ elif [ "$SL_TERM_WIDTH" -lt 70 ]; then
95
90
  BAR_WIDTH=20
96
91
  fi
97
92
 
98
- # ── 5. Initialize cache ──
93
+ # ── 6. Initialize cache ──
99
94
  _sl_cache_init
100
95
 
101
- # ── 6. Parse ALL JSON fields ──
96
+ # ── 7. Map parsed JSON vars to display vars ──
102
97
 
103
- # --- 6a. Directory ---
104
- SL_CWD=$(json_nested_val "workspace" "current_dir")
105
- [ -z "$SL_CWD" ] && SL_CWD=$(json_val "cwd")
98
+ # --- Directory ---
99
+ SL_CWD="${SL_J_workspace_current_dir:-$SL_J_cwd}"
106
100
  if [ -z "$SL_CWD" ]; then
107
101
  SL_DIR="~"
108
102
  clean_cwd=""
@@ -112,14 +106,9 @@ else
112
106
  [ -z "$SL_DIR" ] && SL_DIR="~"
113
107
  fi
114
108
 
115
- # --- 6b. Model ---
116
- SL_MODEL_DISPLAY=$(json_nested_val "model" "display_name")
117
- SL_MODEL_ID=$(json_nested_val "model" "id")
118
- # Flat fallback
119
- [ -z "$SL_MODEL_DISPLAY" ] && SL_MODEL_DISPLAY=$(json_val "display_name")
120
- [ -z "$SL_MODEL_ID" ] && SL_MODEL_ID=$(json_val "id")
121
- [ -z "$SL_MODEL_DISPLAY" ] && SL_MODEL_DISPLAY="unknown"
122
-
109
+ # --- Model ---
110
+ SL_MODEL_DISPLAY="${SL_J_model_display_name:-unknown}"
111
+ SL_MODEL_ID="${SL_J_model_id}"
123
112
  model_ver=""
124
113
  if [ -n "$SL_MODEL_ID" ]; then
125
114
  model_ver=$(echo "$SL_MODEL_ID" | sed -n 's/.*-\([0-9]*\)-\([0-9]*\)$/\1.\2/p')
@@ -130,37 +119,29 @@ else
130
119
  SL_MODEL="$SL_MODEL_DISPLAY"
131
120
  fi
132
121
 
133
- # --- 6c. Context — ACCURATE computation from current_usage ---
134
- ctx_size=$(json_nested "context_window" "context_window_size")
135
- cur_input=$(json_deep "context_window" "current_usage" "input_tokens")
136
- cur_output=$(json_deep "context_window" "current_usage" "output_tokens")
137
- cur_cache_create=$(json_deep "context_window" "current_usage" "cache_creation_input_tokens")
138
- cur_cache_read=$(json_deep "context_window" "current_usage" "cache_read_input_tokens")
139
-
140
- [ -z "$ctx_size" ] && ctx_size=200000
141
- [ -z "$cur_input" ] && cur_input=0
142
- [ -z "$cur_output" ] && cur_output=0
143
- [ -z "$cur_cache_create" ] && cur_cache_create=0
144
- [ -z "$cur_cache_read" ] && cur_cache_read=0
122
+ # --- Context — ACCURATE computation from current_usage ---
123
+ ctx_size="${SL_J_context_window_context_window_size:-200000}"
124
+ cur_input="${SL_J_context_window_current_usage_input_tokens:-0}"
125
+ cur_output="${SL_J_context_window_current_usage_output_tokens:-0}"
126
+ cur_cache_create="${SL_J_context_window_current_usage_cache_creation_input_tokens:-0}"
127
+ cur_cache_read="${SL_J_context_window_current_usage_cache_read_input_tokens:-0}"
145
128
 
146
129
  # Claude's formula: input + cache_creation + cache_read (output excluded from context %)
147
- ctx_used=$(awk -v a="$cur_input" -v b="$cur_cache_create" -v c="$cur_cache_read" \
148
- 'BEGIN { printf "%d", a + b + c }')
130
+ ctx_used=$(( cur_input + cur_cache_create + cur_cache_read ))
149
131
 
150
132
  # Self-calculated percentage
151
133
  calc_pct=0
152
- if [ "$cur_input" -gt 0 ] 2>/dev/null; then
153
- calc_pct=$(awk -v used="$ctx_used" -v total="$ctx_size" \
154
- 'BEGIN { if (total > 0) printf "%d", (used * 100) / total; else print 0 }')
134
+ if [ "$cur_input" -gt 0 ] 2>/dev/null && [ "$ctx_size" -gt 0 ] 2>/dev/null; then
135
+ calc_pct=$(( ctx_used * 100 / ctx_size ))
155
136
  fi
156
137
 
157
138
  # Reported percentage as fallback
158
- reported_pct=$(json_nested "context_window" "used_percentage")
139
+ reported_pct="${SL_J_context_window_used_percentage}"
159
140
 
160
141
  # Use self-calculated if we have current_usage data, else fallback
161
142
  if [ "$cur_input" -gt 0 ] 2>/dev/null; then
162
143
  SL_CTX_PCT="$calc_pct"
163
- elif [ -n "$reported_pct" ] && [ "$reported_pct" != "null" ]; then
144
+ elif [ -n "$reported_pct" ]; then
164
145
  SL_CTX_PCT=$(echo "$reported_pct" | cut -d. -f1)
165
146
  else
166
147
  SL_CTX_PCT=0
@@ -197,7 +178,7 @@ elif [ "$SL_CTX_PCT" -ge "$cfg_warn_threshold" ] 2>/dev/null; then
197
178
  SL_COMPACT_WARNING=" ${CLR_CTX_HIGH}${SL_CTX_REMAINING}% left${CLR_RST}"
198
179
  fi
199
180
 
200
- # --- 6d. GitHub (with caching + timeouts) ---
181
+ # --- GitHub (with caching + timeouts) ---
201
182
  SL_BRANCH="no-git"
202
183
  SL_GIT_DIRTY=""
203
184
  SL_GITHUB=""
@@ -231,34 +212,24 @@ else
231
212
  SL_GITHUB="$SL_BRANCH"
232
213
  fi
233
214
 
234
- # --- 6e. Cost ---
235
- cost_raw=$(json_nested "cost" "total_cost_usd")
236
- [ -z "$cost_raw" ] && cost_raw=$(json_num "total_cost_usd")
215
+ # --- Cost ---
216
+ cost_raw="${SL_J_cost_total_cost_usd:-0}"
237
217
  if [ -z "$cost_raw" ] || [ "$cost_raw" = "0" ]; then
238
218
  SL_COST='$0.00'
239
219
  else
240
220
  SL_COST=$(awk -v c="$cost_raw" 'BEGIN { if (c < 0.01) printf "$%.4f", c; else printf "$%.2f", c }')
241
221
  fi
242
222
 
243
- # --- 6f. Tokens (window vs cumulative) ---
244
- # Current window tokens (what's actually loaded — accurate)
245
- [ -z "$cur_input" ] && cur_input=0
246
- [ -z "$cur_output" ] && cur_output=0
223
+ # --- Tokens (window vs cumulative) ---
247
224
  SL_TOKENS_WIN_IN=$(fmt_tok "$cur_input")
248
225
  SL_TOKENS_WIN_OUT=$(fmt_tok "$cur_output")
249
226
 
250
- # Cumulative session tokens (grows forever, for reference)
251
- cum_input=$(json_nested "context_window" "total_input_tokens")
252
- cum_output=$(json_nested "context_window" "total_output_tokens")
253
- # Flat fallback
254
- [ -z "$cum_input" ] && cum_input=$(json_num "total_input_tokens")
255
- [ -z "$cum_output" ] && cum_output=$(json_num "total_output_tokens")
256
- [ -z "$cum_input" ] && cum_input=0
257
- [ -z "$cum_output" ] && cum_output=0
227
+ cum_input="${SL_J_context_window_total_input_tokens:-0}"
228
+ cum_output="${SL_J_context_window_total_output_tokens:-0}"
258
229
  SL_TOKENS_CUM_IN=$(fmt_tok "$cum_input")
259
230
  SL_TOKENS_CUM_OUT=$(fmt_tok "$cum_output")
260
231
 
261
- # --- 6g. Skill detection (with caching — longer TTL to avoid I/O thrashing) ---
232
+ # --- Skill detection (with caching) ---
262
233
  SL_SKILL="Idle"
263
234
 
264
235
  _detect_skill() {
@@ -269,7 +240,6 @@ _detect_skill() {
269
240
  proj_hash=$(echo "$search_path" | sed 's|^/\([a-zA-Z]\)/|\U\1--|; s|^[A-Z]:/|&|; s|:/|--|; s|/|-|g')
270
241
  proj_dir="$HOME/.claude/projects/${proj_hash}"
271
242
  if [ -d "$proj_dir" ]; then
272
- # Use ls with head to avoid sorting huge file lists
273
243
  tpath=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -1)
274
244
  [ -n "$tpath" ] && break
275
245
  fi
@@ -278,26 +248,11 @@ _detect_skill() {
278
248
 
279
249
  if [ -n "$tpath" ] && [ -f "$tpath" ]; then
280
250
  local last_tool
281
- # Only read the last 50 lines — enough to find the most recent tool_use
282
251
  last_tool=$(tail -50 "$tpath" 2>/dev/null | grep -o '"type":"tool_use","id":"[^"]*","name":"[^"]*"' | tail -1 | sed 's/.*"name":"\([^"]*\)".*/\1/')
283
252
 
284
253
  if [ -n "$last_tool" ]; then
285
254
  case "$last_tool" in
286
- Task)
287
- # Lightweight agent count — check last 50 lines only
288
- local agent_count
289
- agent_count=$(tail -50 "$tpath" 2>/dev/null | grep -c '"type":"tool_use","id":"[^"]*","name":"Task"')
290
- if [ "$agent_count" -gt 1 ]; then
291
- echo "${agent_count} Agents"
292
- else
293
- local agent_desc
294
- agent_desc=$(tail -50 "$tpath" 2>/dev/null | grep -o '"description":"[^"]*"' | tail -1 | sed 's/"description":"//;s/"$//')
295
- if [ -n "$agent_desc" ]; then
296
- echo "Agent($(echo "$agent_desc" | cut -c1-20))"
297
- else
298
- echo "Agent"
299
- fi
300
- fi ;;
255
+ Task) echo "Agent" ;;
301
256
  Read) echo "Read" ;;
302
257
  Write) echo "Write" ;;
303
258
  Edit) echo "Edit" ;;
@@ -313,10 +268,6 @@ _detect_skill() {
313
268
  ExitPlanMode) echo "Plan Ready" ;;
314
269
  TaskCreate) echo "Task Create" ;;
315
270
  TaskUpdate) echo "Task Update" ;;
316
- TaskGet) echo "Task Get" ;;
317
- TaskList) echo "Task List" ;;
318
- TaskStop) echo "Task Stop" ;;
319
- TaskOutput) echo "Task Output" ;;
320
271
  NotebookEdit) echo "Notebook" ;;
321
272
  *) echo "$last_tool" ;;
322
273
  esac
@@ -324,76 +275,47 @@ _detect_skill() {
324
275
  fi
325
276
  fi
326
277
 
327
- # Fallback: check .ccs/task.md
328
- local task_file="${cwd}/.ccs/task.md"
329
- if [ -f "$task_file" ]; then
330
- local last_skill
331
- last_skill=$(grep -oE '/ccs-[a-z]+' "$task_file" 2>/dev/null | tail -1)
332
- [ -n "$last_skill" ] && { echo "$last_skill"; return; }
333
- fi
334
-
335
278
  echo "Idle"
336
279
  }
337
280
 
338
281
  if [ -n "$clean_cwd" ]; then
339
- # Cache skill detection for 5 seconds (was 2s — too frequent for heavy I/O)
340
282
  SL_SKILL=$(cache_get "skill-label" "_detect_skill '$clean_cwd'" 5)
341
283
  fi
342
284
 
343
- # --- 6h. New fields ---
344
-
345
- # Session duration
346
- dur_ms=$(json_nested "cost" "total_duration_ms")
347
- [ -z "$dur_ms" ] && dur_ms=0
285
+ # --- Extra fields ---
286
+ dur_ms="${SL_J_cost_total_duration_ms:-0}"
348
287
  SL_DURATION=$(fmt_duration "$dur_ms")
349
288
 
350
- # Lines changed
351
- SL_LINES_ADDED=$(json_nested "cost" "total_lines_added")
352
- SL_LINES_REMOVED=$(json_nested "cost" "total_lines_removed")
353
- [ -z "$SL_LINES_ADDED" ] && SL_LINES_ADDED=0
354
- [ -z "$SL_LINES_REMOVED" ] && SL_LINES_REMOVED=0
289
+ SL_LINES_ADDED="${SL_J_cost_total_lines_added:-0}"
290
+ SL_LINES_REMOVED="${SL_J_cost_total_lines_removed:-0}"
355
291
 
356
- # API duration
357
- api_ms=$(json_nested "cost" "total_api_duration_ms")
358
- [ -z "$api_ms" ] && api_ms=0
292
+ api_ms="${SL_J_cost_total_api_duration_ms:-0}"
359
293
  SL_API_DURATION=$(fmt_duration "$api_ms")
360
294
 
361
- # Vim mode (absent when vim is off)
362
295
  SL_VIM_MODE=""
363
- if [ "$cfg_show_vim" = "true" ]; then
364
- SL_VIM_MODE=$(json_nested_val "vim" "mode")
365
- fi
296
+ [ "$cfg_show_vim" = "true" ] && SL_VIM_MODE="${SL_J_vim_mode}"
366
297
 
367
- # Agent name (absent when not in agent mode)
368
298
  SL_AGENT_NAME=""
369
- if [ "$cfg_show_agent" = "true" ]; then
370
- SL_AGENT_NAME=$(json_nested_val "agent" "name")
371
- fi
299
+ [ "$cfg_show_agent" = "true" ] && SL_AGENT_NAME="${SL_J_agent_name}"
372
300
 
373
- # Cache stats (formatted)
374
301
  SL_CACHE_CREATE=$(fmt_tok "$cur_cache_create")
375
302
  SL_CACHE_READ=$(fmt_tok "$cur_cache_read")
376
303
 
377
- # Burn rate (cost per minute)
378
304
  SL_BURN_RATE=""
379
305
  if [ "$cfg_show_burn_rate" = "true" ] && [ "$dur_ms" -gt 60000 ] 2>/dev/null; then
380
306
  SL_BURN_RATE=$(awk -v cost="$cost_raw" -v ms="$dur_ms" \
381
307
  'BEGIN { if (ms > 0 && cost+0 > 0) { rate = cost / (ms / 60000); printf "$%.2f/m", rate } }')
382
308
  fi
383
309
 
384
- # Exceeds 200k flag
385
- SL_EXCEEDS_200K=""
386
- [ -n "$(json_bool "exceeds_200k_tokens")" ] && SL_EXCEEDS_200K="true"
387
-
388
- # Version
389
- SL_VERSION=$(json_val "version")
310
+ SL_EXCEEDS_200K="${SL_J_exceeds_200k_tokens}"
311
+ SL_VERSION="${SL_J_version}"
390
312
 
391
- # ── 7. Dynamic column widths ──
313
+ # ── 8. Dynamic column widths ──
392
314
  SL_C1=$(( SL_TERM_WIDTH / 2 - 4 ))
393
315
  [ "$SL_C1" -lt 25 ] && SL_C1=25
394
316
  [ "$SL_C1" -gt 42 ] && SL_C1=42
395
317
 
396
- # ── 8. Source layout and render ──
318
+ # ── 9. Source layout and render ──
397
319
  layout_file="${STATUSLINE_DIR}/layouts/${active_layout}.sh"
398
320
  if [ -f "$layout_file" ]; then
399
321
  source "$layout_file"
@@ -403,6 +325,3 @@ fi
403
325
 
404
326
  render_layout
405
327
 
406
- # ── 9. Cleanup watchdog ──
407
- kill "$_SL_WATCHDOG_PID" 2>/dev/null
408
- wait "$_SL_WATCHDOG_PID" 2>/dev/null
@@ -1,71 +1,150 @@
1
1
  #!/usr/bin/env bash
2
- # skill-statusline v2 — JSON parser (no jq, pure grep/sed)
3
- # Handles both flat and nested Claude Code JSON structures
2
+ # skill-statusline v2 — JSON parser (no jq, single awk pass)
3
+ # Extracts ALL needed fields in one pass for speed on Windows/Git Bash
4
4
 
5
- # ── Flat parsers (v1 compat fallback) ──
5
+ # ── Bulk parser: extract all fields at once ──
6
+ # Sets SL_J_* variables for all known fields
7
+ # This avoids spawning 100+ subshells (grep|sed|head per field)
8
+ sl_parse_json() {
9
+ eval "$(echo "$input" | awk '
10
+ BEGIN { FS="" }
11
+ {
12
+ s = s $0
13
+ }
14
+ END {
15
+ # Helper: extract "key":value from a string
16
+ # For strings: "key":"value"
17
+ # For numbers: "key":number
6
18
 
7
- # Extract a quoted string value: json_val "key" → value
8
- json_val() {
9
- echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
10
- }
19
+ # Top-level strings
20
+ extract_str(s, "cwd")
21
+ extract_str(s, "version")
22
+ extract_str(s, "session_id")
11
23
 
12
- # Extract a numeric value: json_num "key" → number
13
- json_num() {
14
- echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
15
- }
24
+ # model object
25
+ extract_nested_str(s, "model", "id")
26
+ extract_nested_str(s, "model", "display_name")
16
27
 
17
- # ── Nested parsers (v2 — handles Claude Code's real JSON structure) ──
28
+ # workspace object
29
+ extract_nested_str(s, "workspace", "current_dir")
30
+ extract_nested_str(s, "workspace", "project_dir")
18
31
 
19
- # Extract numeric from single-nested object: json_nested "context_window" "used_percentage"
20
- json_nested() {
21
- local parent="$1" key="$2"
22
- local block
23
- block=$(echo "$input" | sed -n 's/.*"'"$parent"'"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p' | head -1)
24
- if [ -n "$block" ]; then
25
- echo "$block" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
26
- fi
27
- }
32
+ # cost object
33
+ extract_nested_num(s, "cost", "total_cost_usd")
34
+ extract_nested_num(s, "cost", "total_duration_ms")
35
+ extract_nested_num(s, "cost", "total_api_duration_ms")
36
+ extract_nested_num(s, "cost", "total_lines_added")
37
+ extract_nested_num(s, "cost", "total_lines_removed")
28
38
 
29
- # Extract string from single-nested object: json_nested_val "model" "display_name"
30
- json_nested_val() {
31
- local parent="$1" key="$2"
32
- local block
33
- block=$(echo "$input" | sed -n 's/.*"'"$parent"'"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p' | head -1)
34
- if [ -n "$block" ]; then
35
- echo "$block" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
36
- fi
37
- }
39
+ # context_window object
40
+ extract_nested_num(s, "context_window", "context_window_size")
41
+ extract_nested_num(s, "context_window", "used_percentage")
42
+ extract_nested_num(s, "context_window", "remaining_percentage")
43
+ extract_nested_num(s, "context_window", "total_input_tokens")
44
+ extract_nested_num(s, "context_window", "total_output_tokens")
45
+
46
+ # context_window.current_usage (double-nested)
47
+ extract_deep_num(s, "context_window", "current_usage", "input_tokens")
48
+ extract_deep_num(s, "context_window", "current_usage", "output_tokens")
49
+ extract_deep_num(s, "context_window", "current_usage", "cache_creation_input_tokens")
50
+ extract_deep_num(s, "context_window", "current_usage", "cache_read_input_tokens")
51
+
52
+ # vim object
53
+ extract_nested_str(s, "vim", "mode")
54
+
55
+ # agent object
56
+ extract_nested_str(s, "agent", "name")
38
57
 
39
- # Extract numeric from double-nested: json_deep "context_window" "current_usage" "input_tokens"
40
- # Handles: {"context_window":{..."current_usage":{"input_tokens":8500}...}}
41
- json_deep() {
42
- local p1="$1" p2="$2" key="$3"
43
- local outer inner
44
- # Get everything inside the outer object (greedy — captures nested braces)
45
- outer=$(echo "$input" | sed -n 's/.*"'"$p1"'"[[:space:]]*:[[:space:]]*{\(.*\)}/\1/p' | head -1)
46
- if [ -n "$outer" ]; then
47
- # Now extract the inner object
48
- inner=$(echo "$outer" | sed -n 's/.*"'"$p2"'"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p' | head -1)
49
- if [ -n "$inner" ]; then
50
- echo "$inner" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
51
- fi
52
- fi
58
+ # boolean
59
+ if (match(s, /"exceeds_200k_tokens"[ \t]*:[ \t]*true/)) {
60
+ print "SL_J_exceeds_200k_tokens=true"
61
+ }
62
+ }
63
+
64
+ function varname(parts, r, i) {
65
+ r = "SL_J"
66
+ for (i = 1; i <= length(parts); i++) {
67
+ r = r "_" parts[i]
68
+ }
69
+ return r
70
+ }
71
+
72
+ function extract_str(json, key, pat, val, pos, rest) {
73
+ pat = "\"" key "\"[ \t]*:[ \t]*\""
74
+ if (match(json, pat)) {
75
+ rest = substr(json, RSTART + RLENGTH)
76
+ if (match(rest, /^[^"]*/)) {
77
+ val = substr(rest, 1, RLENGTH)
78
+ gsub(/'\''/, "'\''\\'\'''\''", val)
79
+ print "SL_J_" key "='\''" val "'\''"
80
+ }
81
+ }
82
+ }
83
+
84
+ function extract_nested_str(json, parent, key, pat, block, rest) {
85
+ pat = "\"" parent "\"[ \t]*:[ \t]*\\{"
86
+ if (match(json, pat)) {
87
+ rest = substr(json, RSTART + RLENGTH)
88
+ # Find matching brace (simple: first })
89
+ if (match(rest, /[^}]*/)) {
90
+ block = substr(rest, 1, RLENGTH)
91
+ pat = "\"" key "\"[ \t]*:[ \t]*\""
92
+ if (match(block, pat)) {
93
+ rest = substr(block, RSTART + RLENGTH)
94
+ if (match(rest, /^[^"]*/)) {
95
+ val = substr(rest, 1, RLENGTH)
96
+ gsub(/'\''/, "'\''\\'\'''\''", val)
97
+ print "SL_J_" parent "_" key "='\''" val "'\''"
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+
104
+ function extract_nested_num(json, parent, key, pat, block, rest, val) {
105
+ pat = "\"" parent "\"[ \t]*:[ \t]*\\{"
106
+ if (match(json, pat)) {
107
+ rest = substr(json, RSTART + RLENGTH)
108
+ # For nested nums, search the full remainder (handles double-nested too)
109
+ block = rest
110
+ pat = "\"" key "\"[ \t]*:[ \t]*"
111
+ if (match(block, pat)) {
112
+ rest = substr(block, RSTART + RLENGTH)
113
+ if (match(rest, /^[0-9.]+/)) {
114
+ val = substr(rest, 1, RLENGTH)
115
+ print "SL_J_" parent "_" key "=" val
116
+ }
117
+ }
118
+ }
119
+ }
120
+
121
+ function extract_deep_num(json, p1, p2, key, pat, outer, inner, rest, val) {
122
+ pat = "\"" p1 "\"[ \t]*:[ \t]*\\{"
123
+ if (match(json, pat)) {
124
+ outer = substr(json, RSTART + RLENGTH)
125
+ pat = "\"" p2 "\"[ \t]*:[ \t]*\\{"
126
+ if (match(outer, pat)) {
127
+ inner = substr(outer, RSTART + RLENGTH)
128
+ pat = "\"" key "\"[ \t]*:[ \t]*"
129
+ if (match(inner, pat)) {
130
+ rest = substr(inner, RSTART + RLENGTH)
131
+ if (match(rest, /^[0-9.]+/)) {
132
+ val = substr(rest, 1, RLENGTH)
133
+ print "SL_J_" p1 "_" p2 "_" key "=" val
134
+ }
135
+ }
136
+ }
137
+ }
138
+ }
139
+ ')"
53
140
  }
54
141
 
55
- # Extract string from double-nested: json_deep_val "context_window" "current_usage" "mode"
56
- json_deep_val() {
57
- local p1="$1" p2="$2" key="$3"
58
- local outer inner
59
- outer=$(echo "$input" | sed -n 's/.*"'"$p1"'"[[:space:]]*:[[:space:]]*{\(.*\)}/\1/p' | head -1)
60
- if [ -n "$outer" ]; then
61
- inner=$(echo "$outer" | sed -n 's/.*"'"$p2"'"[[:space:]]*:[[:space:]]*{\([^}]*\)}.*/\1/p' | head -1)
62
- if [ -n "$inner" ]; then
63
- echo "$inner" | grep -o "\"$key\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
64
- fi
65
- fi
142
+ # ── Legacy single-field parsers (for v1 fallback only) ──
143
+
144
+ json_val() {
145
+ echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
66
146
  }
67
147
 
68
- # Extract boolean: json_bool "exceeds_200k_tokens" → "true" or ""
69
- json_bool() {
70
- echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*true" | head -1 | sed 's/.*:[[:space:]]*//'
148
+ json_num() {
149
+ echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
71
150
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-statusline",
3
- "version": "2.2.0",
3
+ "version": "2.2.1",
4
4
  "description": "Rich, themeable statusline for Claude Code — accurate context tracking, 5 themes, 3 layouts, token/cost/GitHub/skill display. Pure bash, zero deps.",
5
5
  "bin": {
6
6
  "skill-statusline": "bin/cli.js",