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 +1 -1
- package/bin/statusline.sh +1 -13
- package/lib/core.sh +51 -132
- package/lib/json-parser.sh +137 -58
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
|
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.
|
|
9
|
-
#
|
|
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
|
-
|
|
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.
|
|
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
|
-
# ──
|
|
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
|
-
# ──
|
|
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
|
-
# ──
|
|
93
|
+
# ── 6. Initialize cache ──
|
|
99
94
|
_sl_cache_init
|
|
100
95
|
|
|
101
|
-
# ──
|
|
96
|
+
# ── 7. Map parsed JSON vars to display vars ──
|
|
102
97
|
|
|
103
|
-
# ---
|
|
104
|
-
SL_CWD
|
|
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
|
-
# ---
|
|
116
|
-
SL_MODEL_DISPLAY
|
|
117
|
-
SL_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
|
-
# ---
|
|
134
|
-
ctx_size
|
|
135
|
-
cur_input
|
|
136
|
-
cur_output
|
|
137
|
-
cur_cache_create
|
|
138
|
-
cur_cache_read
|
|
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=$(
|
|
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=$(
|
|
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
|
|
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" ]
|
|
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
|
-
# ---
|
|
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
|
-
# ---
|
|
235
|
-
cost_raw
|
|
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
|
-
# ---
|
|
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
|
-
|
|
251
|
-
|
|
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
|
-
# ---
|
|
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
|
-
# ---
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
385
|
-
|
|
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
|
-
# ──
|
|
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
|
-
# ──
|
|
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
|
package/lib/json-parser.sh
CHANGED
|
@@ -1,71 +1,150 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# skill-statusline v2 — JSON parser (no jq,
|
|
3
|
-
#
|
|
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
|
-
# ──
|
|
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
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
19
|
+
# Top-level strings
|
|
20
|
+
extract_str(s, "cwd")
|
|
21
|
+
extract_str(s, "version")
|
|
22
|
+
extract_str(s, "session_id")
|
|
11
23
|
|
|
12
|
-
#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}
|
|
24
|
+
# model object
|
|
25
|
+
extract_nested_str(s, "model", "id")
|
|
26
|
+
extract_nested_str(s, "model", "display_name")
|
|
16
27
|
|
|
17
|
-
#
|
|
28
|
+
# workspace object
|
|
29
|
+
extract_nested_str(s, "workspace", "current_dir")
|
|
30
|
+
extract_nested_str(s, "workspace", "project_dir")
|
|
18
31
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
#
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
#
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
69
|
-
|
|
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.
|
|
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",
|