skill-statusline 2.1.1 → 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.
package/bin/statusline.sh CHANGED
@@ -1,121 +1,139 @@
1
- #!/usr/bin/env bash
2
- # skill-statusline v2.0 — Entry point
3
- # Delegates to modular v2 engine if installed, falls back to v1 inline
4
-
5
- STATUSLINE_DIR="${HOME}/.claude/statusline"
6
-
7
- if [ -f "${STATUSLINE_DIR}/core.sh" ]; then
8
- # v2: modular engine with themes, layouts, accurate context
9
- exec bash "${STATUSLINE_DIR}/core.sh"
10
- fi
11
-
12
- # ── v1 fallback (inline legacy script) ──
13
- # This runs if only statusline-command.sh was copied without the statusline/ directory
14
-
15
- input=$(cat)
16
-
17
- json_val() {
18
- echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
19
- }
20
- json_num() {
21
- echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
22
- }
23
- to_fwd() {
24
- echo "$1" | tr '\\' '/' | sed 's|//\+|/|g'
25
- }
26
- rpad() {
27
- local str="$1" w="$2"
28
- local plain
29
- plain=$(printf '%b' "$str" | sed $'s/\033\\[[0-9;]*m//g')
30
- local vlen=${#plain}
31
- local need=$(( w - vlen ))
32
- printf '%b' "$str"
33
- [ "$need" -gt 0 ] && printf "%${need}s" ""
34
- }
35
-
36
- RST='\033[0m'; BOLD='\033[1m'
37
- CYAN='\033[38;2;6;182;212m'; PURPLE='\033[38;2;168;85;247m'
38
- GREEN='\033[38;2;34;197;94m'; YELLOW='\033[38;2;245;158;11m'
39
- RED='\033[38;2;239;68;68m'; ORANGE='\033[38;2;251;146;60m'
40
- WHITE='\033[38;2;228;228;231m'; PINK='\033[38;2;236;72;153m'
41
- SEP_C='\033[38;2;55;55;62m'; DIM_BAR='\033[38;2;40;40;45m'
42
-
43
- cwd=$(json_val "current_dir")
44
- [ -z "$cwd" ] && cwd=$(json_val "cwd")
45
- if [ -z "$cwd" ]; then dir_label="~"; clean_cwd=""
46
- else
47
- clean_cwd=$(to_fwd "$cwd")
48
- dir_label=$(echo "$clean_cwd" | awk -F'/' '{if(NF>3) print $(NF-2)"/"$(NF-1)"/"$NF; else if(NF>2) print $(NF-1)"/"$NF; else print $0}')
49
- [ -z "$dir_label" ] && dir_label="~"
50
- fi
51
-
52
- model_display=$(json_val "display_name"); model_id=$(json_val "id")
53
- [ -z "$model_display" ] && model_display="unknown"
54
- model_ver=""; [ -n "$model_id" ] && model_ver=$(echo "$model_id" | sed -n 's/.*-\([0-9]*\)-\([0-9]*\)$/\1.\2/p')
55
- if [ -n "$model_ver" ] && ! echo "$model_display" | grep -q '[0-9]'; then model_full="${model_display} ${model_ver}"; else model_full="$model_display"; fi
56
-
57
- pct=$(json_num "used_percentage"); [ -z "$pct" ] && pct="0"; pct=$(echo "$pct" | cut -d. -f1)
58
- if [ "$pct" -gt 75 ] 2>/dev/null; then CTX_CLR="$RED"
59
- elif [ "$pct" -gt 40 ] 2>/dev/null; then CTX_CLR="$ORANGE"
60
- else CTX_CLR="$WHITE"; fi
61
- BAR_WIDTH=40; filled=$(( pct * BAR_WIDTH / 100 )); [ "$filled" -gt "$BAR_WIDTH" ] && filled=$BAR_WIDTH
62
- empty=$(( BAR_WIDTH - filled )); bar_filled=""; bar_empty=""
63
- i=0; while [ $i -lt $filled ]; do bar_filled="${bar_filled}█"; i=$((i+1)); done
64
- i=0; while [ $i -lt $empty ]; do bar_empty="${bar_empty}░"; i=$((i+1)); done
65
- ctx_bar="${CTX_CLR}${bar_filled}${RST}${DIM_BAR}${bar_empty}${RST} ${CTX_CLR}${pct}%${RST}"
66
-
67
- branch="no-git"; gh_user=""; gh_repo=""; git_dirty=""
68
- if [ -n "$clean_cwd" ]; then
69
- branch=$(git --no-optional-locks -C "$clean_cwd" symbolic-ref --short HEAD 2>/dev/null)
70
- [ -z "$branch" ] && branch=$(git --no-optional-locks -C "$clean_cwd" rev-parse --short HEAD 2>/dev/null)
71
- if [ -n "$branch" ]; then
72
- remote_url=$(git --no-optional-locks -C "$clean_cwd" remote get-url origin 2>/dev/null)
73
- if [ -n "$remote_url" ]; then
74
- gh_user=$(echo "$remote_url" | sed 's|.*github\.com[:/]\([^/]*\)/.*|\1|'); [ "$gh_user" = "$remote_url" ] && gh_user=""
75
- gh_repo=$(echo "$remote_url" | sed 's|.*/\([^/]*\)\.git$|\1|; s|.*/\([^/]*\)$|\1|'); [ "$gh_repo" = "$remote_url" ] && gh_repo=""
76
- fi
77
- git --no-optional-locks -C "$clean_cwd" diff --cached --quiet 2>/dev/null || git_dirty="${GREEN}+${RST}"
78
- git --no-optional-locks -C "$clean_cwd" diff --quiet 2>/dev/null || git_dirty="${git_dirty}${YELLOW}~${RST}"
79
- fi
80
- [ -z "$branch" ] && branch="no-git"
81
- fi
82
- [ -n "$gh_repo" ] && gh_label="${gh_user}/${gh_repo}/${branch}" || gh_label="$branch"
83
-
84
- cost_raw=$(json_num "total_cost_usd")
85
- if [ -z "$cost_raw" ] || [ "$cost_raw" = "0" ]; then cost_label='$0.00'
86
- else cost_label=$(awk -v c="$cost_raw" 'BEGIN { if (c < 0.01) printf "$%.4f", c; else printf "$%.2f", c }'); fi
87
-
88
- total_in=$(json_num "total_input_tokens"); total_out=$(json_num "total_output_tokens")
89
- [ -z "$total_in" ] && total_in="0"; [ -z "$total_out" ] && total_out="0"
90
- fmt_tok() { awk -v t="$1" 'BEGIN { if (t >= 1000000) printf "%.1fM", t/1000000; else if (t >= 1000) printf "%.0fk", t/1000; else printf "%d", t }'; }
91
- tok_in=$(fmt_tok "$total_in"); tok_out=$(fmt_tok "$total_out")
92
- tok_total=$(awk -v i="$total_in" -v o="$total_out" 'BEGIN { printf "%d", i + o }')
93
- token_label="${tok_in} + ${tok_out} = $(fmt_tok "$tok_total")"
94
-
95
- skill_label="Idle"
96
- if [ -n "$clean_cwd" ]; then
97
- search_path="$clean_cwd"
98
- while [ -n "$search_path" ] && [ "$search_path" != "/" ]; do
99
- proj_hash=$(echo "$search_path" | sed 's|^/\([a-zA-Z]\)/|\U\1--|; s|^[A-Z]:/|&|; s|:/|--|; s|/|-|g')
100
- proj_dir="$HOME/.claude/projects/${proj_hash}"
101
- if [ -d "$proj_dir" ]; then tpath=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -1); [ -n "$tpath" ] && break; fi
102
- search_path=$(echo "$search_path" | sed 's|/[^/]*$||')
103
- done
104
- if [ -n "$tpath" ] && [ -f "$tpath" ]; then
105
- recent_block=$(tail -200 "$tpath" 2>/dev/null)
106
- last_tool=$(echo "$recent_block" | grep -o '"type":"tool_use","id":"[^"]*","name":"[^"]*"' | tail -1 | sed 's/.*"name":"\([^"]*\)".*/\1/')
107
- if [ -n "$last_tool" ]; then
108
- case "$last_tool" in
109
- Task) skill_label="Agent" ;; Read) skill_label="Read" ;; Write) skill_label="Write" ;;
110
- Edit) skill_label="Edit" ;; Glob) skill_label="Search(Files)" ;; Grep) skill_label="Search(Content)" ;;
111
- Bash) skill_label="Terminal" ;; WebSearch) skill_label="Web Search" ;; *) skill_label="$last_tool" ;;
112
- esac
113
- fi
114
- fi
115
- fi
116
-
117
- C1=38; S=$(printf '%b' " ${SEP_C}│${RST} ")
118
- printf ' '; rpad "${PINK}Skill:${RST} ${PINK}${skill_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${WHITE}GitHub:${RST} ${WHITE}${gh_label}${RST}${git_dirty}"
119
- printf ' '; rpad "${PURPLE}Model:${RST} ${PURPLE}${BOLD}${model_full}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${CYAN}Dir:${RST} ${CYAN}${dir_label}${RST}"
120
- printf ' '; rpad "${YELLOW}Tokens:${RST} ${YELLOW}${token_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${GREEN}Cost:${RST} ${GREEN}${cost_label}${RST}"
121
- printf ' '; printf '%b' "${CTX_CLR}Context:${RST} ${ctx_bar}"
1
+ #!/usr/bin/env bash
2
+ # skill-statusline v2.0 — Entry point
3
+ # Delegates to modular v2 engine if installed, falls back to v1 inline
4
+
5
+ STATUSLINE_DIR="${HOME}/.claude/statusline"
6
+
7
+ if [ -f "${STATUSLINE_DIR}/core.sh" ]; then
8
+ # v2: modular engine with themes, layouts, accurate context
9
+ exec bash "${STATUSLINE_DIR}/core.sh"
10
+ fi
11
+
12
+ # ── v1 fallback (inline legacy script) ──
13
+ # This runs if only statusline-command.sh was copied without the statusline/ directory
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
+ # Read stdin with protection against missing pipe
22
+ if [ -t 0 ]; then
23
+ kill "$_SL_WD_PID" 2>/dev/null; wait "$_SL_WD_PID" 2>/dev/null
24
+ exit 0
25
+ fi
26
+ input=$(timeout 2 cat 2>/dev/null || cat 2>/dev/null)
27
+ if [ -z "$input" ]; then
28
+ kill "$_SL_WD_PID" 2>/dev/null; wait "$_SL_WD_PID" 2>/dev/null
29
+ exit 0
30
+ fi
31
+
32
+ json_val() {
33
+ echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
34
+ }
35
+ json_num() {
36
+ echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
37
+ }
38
+ to_fwd() {
39
+ echo "$1" | tr '\\' '/' | sed 's|//\+|/|g'
40
+ }
41
+ rpad() {
42
+ local str="$1" w="$2"
43
+ local plain
44
+ plain=$(printf '%b' "$str" | sed $'s/\033\\[[0-9;]*m//g')
45
+ local vlen=${#plain}
46
+ local need=$(( w - vlen ))
47
+ printf '%b' "$str"
48
+ [ "$need" -gt 0 ] && printf "%${need}s" ""
49
+ }
50
+
51
+ RST='\033[0m'; BOLD='\033[1m'
52
+ CYAN='\033[38;2;6;182;212m'; PURPLE='\033[38;2;168;85;247m'
53
+ GREEN='\033[38;2;34;197;94m'; YELLOW='\033[38;2;245;158;11m'
54
+ RED='\033[38;2;239;68;68m'; ORANGE='\033[38;2;251;146;60m'
55
+ WHITE='\033[38;2;228;228;231m'; PINK='\033[38;2;236;72;153m'
56
+ SEP_C='\033[38;2;55;55;62m'; DIM_BAR='\033[38;2;40;40;45m'
57
+
58
+ cwd=$(json_val "current_dir")
59
+ [ -z "$cwd" ] && cwd=$(json_val "cwd")
60
+ if [ -z "$cwd" ]; then dir_label="~"; clean_cwd=""
61
+ else
62
+ clean_cwd=$(to_fwd "$cwd")
63
+ dir_label=$(echo "$clean_cwd" | awk -F'/' '{if(NF>3) print $(NF-2)"/"$(NF-1)"/"$NF; else if(NF>2) print $(NF-1)"/"$NF; else print $0}')
64
+ [ -z "$dir_label" ] && dir_label="~"
65
+ fi
66
+
67
+ model_display=$(json_val "display_name"); model_id=$(json_val "id")
68
+ [ -z "$model_display" ] && model_display="unknown"
69
+ model_ver=""; [ -n "$model_id" ] && model_ver=$(echo "$model_id" | sed -n 's/.*-\([0-9]*\)-\([0-9]*\)$/\1.\2/p')
70
+ if [ -n "$model_ver" ] && ! echo "$model_display" | grep -q '[0-9]'; then model_full="${model_display} ${model_ver}"; else model_full="$model_display"; fi
71
+
72
+ pct=$(json_num "used_percentage"); [ -z "$pct" ] && pct="0"; pct=$(echo "$pct" | cut -d. -f1)
73
+ if [ "$pct" -gt 75 ] 2>/dev/null; then CTX_CLR="$RED"
74
+ elif [ "$pct" -gt 40 ] 2>/dev/null; then CTX_CLR="$ORANGE"
75
+ else CTX_CLR="$WHITE"; fi
76
+ BAR_WIDTH=40; filled=$(( pct * BAR_WIDTH / 100 )); [ "$filled" -gt "$BAR_WIDTH" ] && filled=$BAR_WIDTH
77
+ empty=$(( BAR_WIDTH - filled )); bar_filled=""; bar_empty=""
78
+ i=0; while [ $i -lt $filled ]; do bar_filled="${bar_filled}"; i=$((i+1)); done
79
+ i=0; while [ $i -lt $empty ]; do bar_empty="${bar_empty}░"; i=$((i+1)); done
80
+ ctx_bar="${CTX_CLR}${bar_filled}${RST}${DIM_BAR}${bar_empty}${RST} ${CTX_CLR}${pct}%${RST}"
81
+
82
+ branch="no-git"; gh_user=""; gh_repo=""; git_dirty=""
83
+ if [ -n "$clean_cwd" ]; then
84
+ branch=$(timeout 2 git --no-optional-locks -C "$clean_cwd" symbolic-ref --short HEAD 2>/dev/null)
85
+ [ -z "$branch" ] && branch=$(timeout 2 git --no-optional-locks -C "$clean_cwd" rev-parse --short HEAD 2>/dev/null)
86
+ if [ -n "$branch" ]; then
87
+ remote_url=$(timeout 2 git --no-optional-locks -C "$clean_cwd" remote get-url origin 2>/dev/null)
88
+ if [ -n "$remote_url" ]; then
89
+ gh_user=$(echo "$remote_url" | sed 's|.*github\.com[:/]\([^/]*\)/.*|\1|'); [ "$gh_user" = "$remote_url" ] && gh_user=""
90
+ gh_repo=$(echo "$remote_url" | sed 's|.*/\([^/]*\)\.git$|\1|; s|.*/\([^/]*\)$|\1|'); [ "$gh_repo" = "$remote_url" ] && gh_repo=""
91
+ fi
92
+ timeout 2 git --no-optional-locks -C "$clean_cwd" diff --cached --quiet 2>/dev/null || git_dirty="${GREEN}+${RST}"
93
+ timeout 2 git --no-optional-locks -C "$clean_cwd" diff --quiet 2>/dev/null || git_dirty="${git_dirty}${YELLOW}~${RST}"
94
+ fi
95
+ [ -z "$branch" ] && branch="no-git"
96
+ fi
97
+ [ -n "$gh_repo" ] && gh_label="${gh_user}/${gh_repo}/${branch}" || gh_label="$branch"
98
+
99
+ cost_raw=$(json_num "total_cost_usd")
100
+ if [ -z "$cost_raw" ] || [ "$cost_raw" = "0" ]; then cost_label='$0.00'
101
+ else cost_label=$(awk -v c="$cost_raw" 'BEGIN { if (c < 0.01) printf "$%.4f", c; else printf "$%.2f", c }'); fi
102
+
103
+ total_in=$(json_num "total_input_tokens"); total_out=$(json_num "total_output_tokens")
104
+ [ -z "$total_in" ] && total_in="0"; [ -z "$total_out" ] && total_out="0"
105
+ fmt_tok() { awk -v t="$1" 'BEGIN { if (t >= 1000000) printf "%.1fM", t/1000000; else if (t >= 1000) printf "%.0fk", t/1000; else printf "%d", t }'; }
106
+ tok_in=$(fmt_tok "$total_in"); tok_out=$(fmt_tok "$total_out")
107
+ tok_total=$(awk -v i="$total_in" -v o="$total_out" 'BEGIN { printf "%d", i + o }')
108
+ token_label="${tok_in} + ${tok_out} = $(fmt_tok "$tok_total")"
109
+
110
+ skill_label="Idle"
111
+ if [ -n "$clean_cwd" ]; then
112
+ search_path="$clean_cwd"
113
+ while [ -n "$search_path" ] && [ "$search_path" != "/" ]; do
114
+ proj_hash=$(echo "$search_path" | sed 's|^/\([a-zA-Z]\)/|\U\1--|; s|^[A-Z]:/|&|; s|:/|--|; s|/|-|g')
115
+ proj_dir="$HOME/.claude/projects/${proj_hash}"
116
+ if [ -d "$proj_dir" ]; then tpath=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -1); [ -n "$tpath" ] && break; fi
117
+ search_path=$(echo "$search_path" | sed 's|/[^/]*$||')
118
+ done
119
+ if [ -n "$tpath" ] && [ -f "$tpath" ]; then
120
+ last_tool=$(tail -50 "$tpath" 2>/dev/null | grep -o '"type":"tool_use","id":"[^"]*","name":"[^"]*"' | tail -1 | sed 's/.*"name":"\([^"]*\)".*/\1/')
121
+ if [ -n "$last_tool" ]; then
122
+ case "$last_tool" in
123
+ Task) skill_label="Agent" ;; Read) skill_label="Read" ;; Write) skill_label="Write" ;;
124
+ Edit) skill_label="Edit" ;; Glob) skill_label="Search(Files)" ;; Grep) skill_label="Search(Content)" ;;
125
+ Bash) skill_label="Terminal" ;; WebSearch) skill_label="Web Search" ;; *) skill_label="$last_tool" ;;
126
+ esac
127
+ fi
128
+ fi
129
+ fi
130
+
131
+ C1=38; S=$(printf '%b' " ${SEP_C}│${RST} ")
132
+ printf ' '; rpad "${PINK}Skill:${RST} ${PINK}${skill_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${WHITE}GitHub:${RST} ${WHITE}${gh_label}${RST}${git_dirty}"
133
+ 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
+ printf ' '; rpad "${YELLOW}Tokens:${RST} ${YELLOW}${token_label}${RST}" "$C1"; printf '%b' "$S"; printf '%b\n' "${GREEN}Cost:${RST} ${GREEN}${cost_label}${RST}"
135
+ 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