skill-statusline 1.0.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/LICENSE +21 -0
- package/README.md +68 -0
- package/bin/cli.js +187 -0
- package/bin/statusline.sh +253 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thinqmesh Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Claude Code Statusline
|
|
2
|
+
|
|
3
|
+
A rich, customizable statusline for Claude Code with colored legends, context progress bar, GitHub info, token tracking, and active skill display.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Skill: Edit │ GitHub: User/Repo/master+~
|
|
9
|
+
Model: Opus 4.6 │ Dir: Downloads/Project
|
|
10
|
+
Tokens: 25k + 12k = 37k │ Cost: $1.23
|
|
11
|
+
Context: ████████████████████░░░░░░░░░░░░░░░░░░░░ 50%
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npx skill-statusline install
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Or install globally:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g skill-statusline
|
|
24
|
+
ccsl install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Then restart Claude Code.
|
|
28
|
+
|
|
29
|
+
## Uninstall
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ccsl uninstall
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Fields
|
|
36
|
+
|
|
37
|
+
| Field | Color | Description |
|
|
38
|
+
|---------|------------------|-------------|
|
|
39
|
+
| Skill | Pink | Last tool used (Read, Write, Edit, Terminal, Agent, etc.) |
|
|
40
|
+
| Model | Purple | Active model name and version |
|
|
41
|
+
| GitHub | White | username/repo/branch with +~ dirty indicators |
|
|
42
|
+
| Dir | Cyan | Last 3 segments of working directory |
|
|
43
|
+
| Tokens | Yellow | Input + Output = Total (e.g., 25k + 12k = 37k) |
|
|
44
|
+
| Cost | Green | Session cost in USD |
|
|
45
|
+
| Context | White/Orange/Red | 40-char progress bar (white ≤40%, orange 41-75%, red >75%) |
|
|
46
|
+
|
|
47
|
+
## Requirements
|
|
48
|
+
|
|
49
|
+
- Bash shell (Git Bash on Windows, or any Unix shell)
|
|
50
|
+
- Git (for GitHub field)
|
|
51
|
+
- Works on Windows, macOS, Linux
|
|
52
|
+
|
|
53
|
+
## What it installs
|
|
54
|
+
|
|
55
|
+
- `~/.claude/statusline-command.sh` — the statusline script
|
|
56
|
+
- Updates `~/.claude/settings.json` — adds `statusLine` config
|
|
57
|
+
|
|
58
|
+
## Part of the Thinqmesh Skills Ecosystem
|
|
59
|
+
|
|
60
|
+
This statusline is also bundled with [codebase-context-skill](https://www.npmjs.com/package/codebase-context-skill) — context engineering middleware for Claude Code with 23 slash commands, 6 agents, and session persistence.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
npx codebase-context-skill init
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT — [Thinqmesh Technologies](https://thinqmesh.com)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const command = args[0];
|
|
9
|
+
const VERSION = '1.0.0';
|
|
10
|
+
|
|
11
|
+
const PKG_DIR = path.resolve(__dirname, '..');
|
|
12
|
+
const HOME = os.homedir();
|
|
13
|
+
|
|
14
|
+
// Terminal colors
|
|
15
|
+
const R = '\x1b[0m';
|
|
16
|
+
const B = '\x1b[1m';
|
|
17
|
+
const D = '\x1b[2m';
|
|
18
|
+
const GRN = '\x1b[32m';
|
|
19
|
+
const YLW = '\x1b[33m';
|
|
20
|
+
const CYN = '\x1b[36m';
|
|
21
|
+
const WHT = '\x1b[97m';
|
|
22
|
+
const PURPLE = '\x1b[38;2;168;85;247m';
|
|
23
|
+
const PINK = '\x1b[38;2;236;72;153m';
|
|
24
|
+
const TEAL = '\x1b[38;2;6;182;212m';
|
|
25
|
+
const GRAY = '\x1b[38;2;90;90;99m';
|
|
26
|
+
|
|
27
|
+
function log(msg) { console.log(msg); }
|
|
28
|
+
function success(msg) { log(` ${GRAY}\u2502${R} ${GRN}\u2713${R} ${msg}`); }
|
|
29
|
+
function warn(msg) { log(` ${GRAY}\u2502${R} ${YLW}\u26A0${R} ${msg}`); }
|
|
30
|
+
function info(msg) { log(` ${GRAY}\u2502${R} ${CYN}\u2139${R} ${msg}`); }
|
|
31
|
+
function bar(msg) { log(` ${GRAY}\u2502${R} ${D}${msg}${R}`); }
|
|
32
|
+
function blank() { log(` ${GRAY}\u2502${R}`); }
|
|
33
|
+
|
|
34
|
+
function header() {
|
|
35
|
+
log('');
|
|
36
|
+
log(` ${GRAY}\u250C${''.padEnd(58, '\u2500')}\u2510${R}`);
|
|
37
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
38
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}${B}\u2588\u2588\u2588${R} ${PINK}${B}\u2588\u2588\u2588${R} ${WHT}${B}skill-statusline${R} ${D}v${VERSION}${R} ${GRAY}\u2502${R}`);
|
|
39
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}\u2588${R} ${PINK}\u2588${R} ${PURPLE}\u2588${R} ${D}Rich statusline for Claude Code${R} ${GRAY}\u2502${R}`);
|
|
40
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}${B}\u2588\u2588\u2588${R} ${PINK}${B}\u2588\u2588\u2588${R} ${GRAY}\u2502${R}`);
|
|
41
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
42
|
+
log(` ${GRAY}\u2502${R} ${TEAL}Thinqmesh Technologies${R} ${GRAY}\u2502${R}`);
|
|
43
|
+
log(` ${GRAY}\u2502${R} ${GRAY}skills.thinqmesh.com${R} ${GRAY}\u2502${R}`);
|
|
44
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
45
|
+
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function footer() {
|
|
49
|
+
log(` ${GRAY}\u2502${R}`);
|
|
50
|
+
log(` ${GRAY}\u2514${''.padEnd(58, '\u2500')}\u2518${R}`);
|
|
51
|
+
log('');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function install() {
|
|
55
|
+
header();
|
|
56
|
+
blank();
|
|
57
|
+
info(`${B}Installing statusline${R} to ~/.claude/`);
|
|
58
|
+
blank();
|
|
59
|
+
|
|
60
|
+
const claudeDir = path.join(HOME, '.claude');
|
|
61
|
+
if (!fs.existsSync(claudeDir)) fs.mkdirSync(claudeDir, { recursive: true });
|
|
62
|
+
|
|
63
|
+
// Copy statusline script
|
|
64
|
+
const slSrc = path.join(PKG_DIR, 'bin', 'statusline.sh');
|
|
65
|
+
const slDest = path.join(claudeDir, 'statusline-command.sh');
|
|
66
|
+
fs.copyFileSync(slSrc, slDest);
|
|
67
|
+
success(`${B}statusline-command.sh${R} copied to ~/.claude/`);
|
|
68
|
+
|
|
69
|
+
// Merge into ~/.claude/settings.json
|
|
70
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
71
|
+
let settings = {};
|
|
72
|
+
if (fs.existsSync(settingsPath)) {
|
|
73
|
+
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
|
|
74
|
+
}
|
|
75
|
+
if (!settings.statusLine) {
|
|
76
|
+
settings.statusLine = {
|
|
77
|
+
type: 'command',
|
|
78
|
+
command: 'bash ~/.claude/statusline-command.sh'
|
|
79
|
+
};
|
|
80
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
81
|
+
success(`${B}statusLine${R} config added to ~/.claude/settings.json`);
|
|
82
|
+
} else {
|
|
83
|
+
success(`statusLine already configured in settings.json`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
blank();
|
|
87
|
+
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
88
|
+
blank();
|
|
89
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}Ready.${R} Restart Claude Code to see the statusline.`);
|
|
90
|
+
blank();
|
|
91
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Layout:${R}`);
|
|
92
|
+
blank();
|
|
93
|
+
log(` ${GRAY}\u2502${R} ${PINK}Skill:${R} Edit ${GRAY}\u2502${R} ${WHT}GitHub:${R} User/Repo/main`);
|
|
94
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Model:${R} Opus 4.6 ${GRAY}\u2502${R} ${CYN}Dir:${R} Downloads/Project`);
|
|
95
|
+
log(` ${GRAY}\u2502${R} ${YLW}Tokens:${R} 25k + 12k = 37k ${GRAY}\u2502${R} ${GRN}Cost:${R} $1.23`);
|
|
96
|
+
log(` ${GRAY}\u2502${R} ${WHT}Context:${R} ${GRN}\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588${R}${D}\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591${R} 50%`);
|
|
97
|
+
blank();
|
|
98
|
+
bar(`${R}${D}Script:${R} ${CYN}~/.claude/statusline-command.sh${R}`);
|
|
99
|
+
bar(`${R}${D}Settings:${R} ${CYN}~/.claude/settings.json${R}`);
|
|
100
|
+
blank();
|
|
101
|
+
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
102
|
+
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
103
|
+
|
|
104
|
+
footer();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function uninstall() {
|
|
108
|
+
header();
|
|
109
|
+
blank();
|
|
110
|
+
info(`${B}Uninstalling statusline${R}`);
|
|
111
|
+
blank();
|
|
112
|
+
|
|
113
|
+
const claudeDir = path.join(HOME, '.claude');
|
|
114
|
+
|
|
115
|
+
// Remove script
|
|
116
|
+
const slDest = path.join(claudeDir, 'statusline-command.sh');
|
|
117
|
+
if (fs.existsSync(slDest)) {
|
|
118
|
+
fs.unlinkSync(slDest);
|
|
119
|
+
success(`Removed ~/.claude/statusline-command.sh`);
|
|
120
|
+
} else {
|
|
121
|
+
warn(`statusline-command.sh not found`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Remove from settings.json
|
|
125
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
126
|
+
if (fs.existsSync(settingsPath)) {
|
|
127
|
+
try {
|
|
128
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
129
|
+
if (settings.statusLine) {
|
|
130
|
+
delete settings.statusLine;
|
|
131
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
132
|
+
success(`Removed statusLine from settings.json`);
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
warn(`Could not parse settings.json`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
blank();
|
|
140
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}Done.${R} Restart Claude Code to apply.`);
|
|
141
|
+
|
|
142
|
+
footer();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function showHelp() {
|
|
146
|
+
header();
|
|
147
|
+
blank();
|
|
148
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Usage:${R}`);
|
|
149
|
+
blank();
|
|
150
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl install${R} Install statusline to ~/.claude/`);
|
|
151
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl uninstall${R} Remove statusline`);
|
|
152
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl help${R} Show this help`);
|
|
153
|
+
blank();
|
|
154
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}What it shows:${R}`);
|
|
155
|
+
blank();
|
|
156
|
+
log(` ${GRAY}\u2502${R} ${PINK}Skill${R} Last tool used (Read, Write, Terminal...)`);
|
|
157
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Model${R} Active model name and version`);
|
|
158
|
+
log(` ${GRAY}\u2502${R} ${WHT}GitHub${R} username/repo/branch with dirty indicators`);
|
|
159
|
+
log(` ${GRAY}\u2502${R} ${CYN}Dir${R} Last 3 segments of working directory`);
|
|
160
|
+
log(` ${GRAY}\u2502${R} ${YLW}Tokens${R} Input + Output = Total`);
|
|
161
|
+
log(` ${GRAY}\u2502${R} ${GRN}Cost${R} Session cost in USD`);
|
|
162
|
+
log(` ${GRAY}\u2502${R} ${WHT}Context${R} 40-char progress bar with color thresholds`);
|
|
163
|
+
blank();
|
|
164
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Quick install:${R}`);
|
|
165
|
+
blank();
|
|
166
|
+
log(` ${GRAY}\u2502${R} ${CYN}npx skill-statusline install${R}`);
|
|
167
|
+
blank();
|
|
168
|
+
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
169
|
+
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
170
|
+
|
|
171
|
+
footer();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Main
|
|
175
|
+
if (command === 'install' || command === 'init') {
|
|
176
|
+
install();
|
|
177
|
+
} else if (command === 'uninstall' || command === 'remove') {
|
|
178
|
+
uninstall();
|
|
179
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
180
|
+
showHelp();
|
|
181
|
+
} else {
|
|
182
|
+
if (command) {
|
|
183
|
+
log('');
|
|
184
|
+
warn(`Unknown command: ${command}`);
|
|
185
|
+
}
|
|
186
|
+
showHelp();
|
|
187
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Claude Code status line — 4-row grid, no jq, cross-platform
|
|
3
|
+
# Row 1: Skill │ GitHub
|
|
4
|
+
# Row 2: Model │ Dir
|
|
5
|
+
# Row 3: Tokens │ Cost
|
|
6
|
+
# Row 4: Context (wide progress bar)
|
|
7
|
+
|
|
8
|
+
input=$(cat)
|
|
9
|
+
|
|
10
|
+
# --- Helpers ---
|
|
11
|
+
json_val() {
|
|
12
|
+
echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" | head -1 | sed 's/.*:.*"\(.*\)"/\1/'
|
|
13
|
+
}
|
|
14
|
+
json_num() {
|
|
15
|
+
echo "$input" | grep -o "\"$1\"[[:space:]]*:[[:space:]]*[0-9.]*" | head -1 | sed 's/.*:[[:space:]]*//'
|
|
16
|
+
}
|
|
17
|
+
# Convert any path to forward slashes (safe on all OS)
|
|
18
|
+
to_fwd() {
|
|
19
|
+
echo "$1" | tr '\\' '/' | sed 's|//\+|/|g'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Right-pad a colored string to a visible width
|
|
23
|
+
rpad() {
|
|
24
|
+
local str="$1" w="$2"
|
|
25
|
+
local plain
|
|
26
|
+
plain=$(printf '%b' "$str" | sed $'s/\033\\[[0-9;]*m//g')
|
|
27
|
+
local vlen=${#plain}
|
|
28
|
+
local need=$(( w - vlen ))
|
|
29
|
+
printf '%b' "$str"
|
|
30
|
+
[ "$need" -gt 0 ] && printf "%${need}s" ""
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# --- Colors ---
|
|
34
|
+
RST='\033[0m'
|
|
35
|
+
BOLD='\033[1m'
|
|
36
|
+
CYAN='\033[38;2;6;182;212m'
|
|
37
|
+
PURPLE='\033[38;2;168;85;247m'
|
|
38
|
+
GREEN='\033[38;2;34;197;94m'
|
|
39
|
+
YELLOW='\033[38;2;245;158;11m'
|
|
40
|
+
RED='\033[38;2;239;68;68m'
|
|
41
|
+
ORANGE='\033[38;2;251;146;60m'
|
|
42
|
+
WHITE='\033[38;2;228;228;231m'
|
|
43
|
+
PINK='\033[38;2;236;72;153m'
|
|
44
|
+
BLUE='\033[38;2;99;102;241m'
|
|
45
|
+
TEAL='\033[38;2;20;184;166m'
|
|
46
|
+
SEP='\033[38;2;55;55;62m'
|
|
47
|
+
DIM_BAR='\033[38;2;40;40;45m'
|
|
48
|
+
|
|
49
|
+
# ── 1. Directory ──
|
|
50
|
+
cwd=$(json_val "current_dir")
|
|
51
|
+
[ -z "$cwd" ] && cwd=$(json_val "cwd")
|
|
52
|
+
if [ -z "$cwd" ]; then
|
|
53
|
+
dir_label="~"
|
|
54
|
+
clean_cwd=""
|
|
55
|
+
else
|
|
56
|
+
clean_cwd=$(to_fwd "$cwd")
|
|
57
|
+
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}')
|
|
58
|
+
[ -z "$dir_label" ] && dir_label="~"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# ── 2. Model ──
|
|
62
|
+
model_display=$(json_val "display_name")
|
|
63
|
+
model_id=$(json_val "id")
|
|
64
|
+
[ -z "$model_display" ] && model_display="unknown"
|
|
65
|
+
model_ver=""
|
|
66
|
+
if [ -n "$model_id" ]; then
|
|
67
|
+
model_ver=$(echo "$model_id" | sed -n 's/.*-\([0-9]*\)-\([0-9]*\)$/\1.\2/p')
|
|
68
|
+
fi
|
|
69
|
+
if [ -n "$model_ver" ] && ! echo "$model_display" | grep -q '[0-9]'; then
|
|
70
|
+
model_full="${model_display} ${model_ver}"
|
|
71
|
+
else
|
|
72
|
+
model_full="$model_display"
|
|
73
|
+
fi
|
|
74
|
+
|
|
75
|
+
# ── 3. Context bar (wide: 40 chars) ──
|
|
76
|
+
pct=$(json_num "used_percentage")
|
|
77
|
+
[ -z "$pct" ] && pct="0"
|
|
78
|
+
pct=$(echo "$pct" | cut -d. -f1)
|
|
79
|
+
if [ "$pct" -gt 75 ] 2>/dev/null; then
|
|
80
|
+
CTX_CLR="$RED"
|
|
81
|
+
elif [ "$pct" -gt 40 ] 2>/dev/null; then
|
|
82
|
+
CTX_CLR="$ORANGE"
|
|
83
|
+
else
|
|
84
|
+
CTX_CLR="$WHITE"
|
|
85
|
+
fi
|
|
86
|
+
BAR_WIDTH=40
|
|
87
|
+
filled=$(( pct * BAR_WIDTH / 100 ))
|
|
88
|
+
[ "$filled" -gt "$BAR_WIDTH" ] && filled=$BAR_WIDTH
|
|
89
|
+
empty=$(( BAR_WIDTH - filled ))
|
|
90
|
+
bar_filled=""; bar_empty=""
|
|
91
|
+
i=0; while [ $i -lt $filled ]; do bar_filled="${bar_filled}█"; i=$((i+1)); done
|
|
92
|
+
i=0; while [ $i -lt $empty ]; do bar_empty="${bar_empty}░"; i=$((i+1)); done
|
|
93
|
+
ctx_bar="${CTX_CLR}${bar_filled}${RST}${DIM_BAR}${bar_empty}${RST} ${CTX_CLR}${pct}%${RST}"
|
|
94
|
+
|
|
95
|
+
# ── 4. GitHub: user/repo/branch + dirty ──
|
|
96
|
+
branch="no-git"
|
|
97
|
+
gh_user=""
|
|
98
|
+
gh_repo=""
|
|
99
|
+
git_dirty=""
|
|
100
|
+
if [ -n "$clean_cwd" ]; then
|
|
101
|
+
branch=$(git --no-optional-locks -C "$clean_cwd" symbolic-ref --short HEAD 2>/dev/null)
|
|
102
|
+
[ -z "$branch" ] && branch=$(git --no-optional-locks -C "$clean_cwd" rev-parse --short HEAD 2>/dev/null)
|
|
103
|
+
if [ -n "$branch" ]; then
|
|
104
|
+
remote_url=$(git --no-optional-locks -C "$clean_cwd" remote get-url origin 2>/dev/null)
|
|
105
|
+
if [ -n "$remote_url" ]; then
|
|
106
|
+
gh_user=$(echo "$remote_url" | sed 's|.*github\.com[:/]\([^/]*\)/.*|\1|')
|
|
107
|
+
[ "$gh_user" = "$remote_url" ] && gh_user=""
|
|
108
|
+
gh_repo=$(echo "$remote_url" | sed 's|.*/\([^/]*\)\.git$|\1|; s|.*/\([^/]*\)$|\1|')
|
|
109
|
+
[ "$gh_repo" = "$remote_url" ] && gh_repo=""
|
|
110
|
+
fi
|
|
111
|
+
git --no-optional-locks -C "$clean_cwd" diff --cached --quiet 2>/dev/null || git_dirty="${GREEN}+${RST}"
|
|
112
|
+
git --no-optional-locks -C "$clean_cwd" diff --quiet 2>/dev/null || git_dirty="${git_dirty}${YELLOW}~${RST}"
|
|
113
|
+
fi
|
|
114
|
+
[ -z "$branch" ] && branch="no-git"
|
|
115
|
+
fi
|
|
116
|
+
if [ -n "$gh_repo" ]; then
|
|
117
|
+
gh_label="${gh_user}/${gh_repo}/${branch}"
|
|
118
|
+
else
|
|
119
|
+
gh_label="$branch"
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
# ── 5. Cost (green, price only) ──
|
|
123
|
+
cost_raw=$(json_num "total_cost_usd")
|
|
124
|
+
if [ -z "$cost_raw" ] || [ "$cost_raw" = "0" ]; then
|
|
125
|
+
cost_label='$0.00'
|
|
126
|
+
else
|
|
127
|
+
cost_label=$(awk -v c="$cost_raw" 'BEGIN { if (c < 0.01) printf "$%.4f", c; else printf "$%.2f", c }')
|
|
128
|
+
fi
|
|
129
|
+
|
|
130
|
+
# ── 5b. Tokens (input + output = total) ──
|
|
131
|
+
total_in=$(json_num "total_input_tokens")
|
|
132
|
+
total_out=$(json_num "total_output_tokens")
|
|
133
|
+
[ -z "$total_in" ] && total_in="0"
|
|
134
|
+
[ -z "$total_out" ] && total_out="0"
|
|
135
|
+
fmt_tok() {
|
|
136
|
+
awk -v t="$1" 'BEGIN {
|
|
137
|
+
if (t >= 1000000) printf "%.1fM", t/1000000
|
|
138
|
+
else if (t >= 1000) printf "%.0fk", t/1000
|
|
139
|
+
else printf "%d", t
|
|
140
|
+
}'
|
|
141
|
+
}
|
|
142
|
+
tok_in=$(fmt_tok "$total_in")
|
|
143
|
+
tok_out=$(fmt_tok "$total_out")
|
|
144
|
+
tok_total=$(awk -v i="$total_in" -v o="$total_out" 'BEGIN { printf "%d", i + o }')
|
|
145
|
+
tok_total_fmt=$(fmt_tok "$tok_total")
|
|
146
|
+
token_label="${tok_in} + ${tok_out} = ${tok_total_fmt}"
|
|
147
|
+
|
|
148
|
+
# ── 6. Skill (live tool detection from transcript) ──
|
|
149
|
+
skill_label="Idle"
|
|
150
|
+
tpath=""
|
|
151
|
+
|
|
152
|
+
# Find the most recent transcript from ~/.claude/projects/<hash>/*.jsonl
|
|
153
|
+
# Walks up parent directories if exact match not found (handles subfolders)
|
|
154
|
+
if [ -n "$clean_cwd" ]; then
|
|
155
|
+
search_path="$clean_cwd"
|
|
156
|
+
while [ -n "$search_path" ] && [ "$search_path" != "/" ]; do
|
|
157
|
+
proj_hash=$(echo "$search_path" | sed 's|^/\([a-zA-Z]\)/|\U\1--|; s|^[A-Z]:/|&|; s|:/|--|; s|/|-|g')
|
|
158
|
+
proj_dir="$HOME/.claude/projects/${proj_hash}"
|
|
159
|
+
if [ -d "$proj_dir" ]; then
|
|
160
|
+
tpath=$(ls -t "$proj_dir"/*.jsonl 2>/dev/null | head -1)
|
|
161
|
+
[ -n "$tpath" ] && break
|
|
162
|
+
fi
|
|
163
|
+
# Go up one directory
|
|
164
|
+
search_path=$(echo "$search_path" | sed 's|/[^/]*$||')
|
|
165
|
+
done
|
|
166
|
+
fi
|
|
167
|
+
|
|
168
|
+
if [ -n "$tpath" ] && [ -f "$tpath" ]; then
|
|
169
|
+
# Read recent transcript lines
|
|
170
|
+
recent_block=$(tail -200 "$tpath" 2>/dev/null)
|
|
171
|
+
|
|
172
|
+
# Get the LAST tool used — this is what the user sees
|
|
173
|
+
last_tool=$(echo "$recent_block" | grep -o '"type":"tool_use","id":"[^"]*","name":"[^"]*"' | tail -1 | sed 's/.*"name":"\([^"]*\)".*/\1/')
|
|
174
|
+
|
|
175
|
+
if [ -n "$last_tool" ]; then
|
|
176
|
+
case "$last_tool" in
|
|
177
|
+
Task)
|
|
178
|
+
# Last tool is Task (agent) — count how many in recent block
|
|
179
|
+
agent_count=$(echo "$recent_block" | grep -c '"type":"tool_use","id":"[^"]*","name":"Task"')
|
|
180
|
+
if [ "$agent_count" -gt 1 ]; then
|
|
181
|
+
skill_label="${agent_count} Agents"
|
|
182
|
+
else
|
|
183
|
+
agent_desc=$(echo "$recent_block" | grep -o '"description":"[^"]*"' | tail -1 | sed 's/"description":"//;s/"$//')
|
|
184
|
+
if [ -n "$agent_desc" ]; then
|
|
185
|
+
skill_label="Agent($(echo "$agent_desc" | cut -c1-20))"
|
|
186
|
+
else
|
|
187
|
+
skill_label="Agent"
|
|
188
|
+
fi
|
|
189
|
+
fi
|
|
190
|
+
;;
|
|
191
|
+
Read) skill_label="Read" ;;
|
|
192
|
+
Write) skill_label="Write" ;;
|
|
193
|
+
Edit) skill_label="Edit" ;;
|
|
194
|
+
MultiEdit) skill_label="Multi Edit" ;;
|
|
195
|
+
Glob) skill_label="Search(Files)" ;;
|
|
196
|
+
Grep) skill_label="Search(Content)" ;;
|
|
197
|
+
Bash) skill_label="Terminal" ;;
|
|
198
|
+
WebSearch) skill_label="Web Search" ;;
|
|
199
|
+
WebFetch) skill_label="Web Fetch" ;;
|
|
200
|
+
Skill) skill_label="Skill" ;;
|
|
201
|
+
AskUserQuestion) skill_label="Asking..." ;;
|
|
202
|
+
EnterPlanMode) skill_label="Planning" ;;
|
|
203
|
+
ExitPlanMode) skill_label="Plan Ready" ;;
|
|
204
|
+
TaskCreate) skill_label="Task Create" ;;
|
|
205
|
+
TaskUpdate) skill_label="Task Update" ;;
|
|
206
|
+
TaskGet) skill_label="Task Get" ;;
|
|
207
|
+
TaskList) skill_label="Task List" ;;
|
|
208
|
+
TaskStop) skill_label="Task Stop" ;;
|
|
209
|
+
TaskOutput) skill_label="Task Output" ;;
|
|
210
|
+
NotebookEdit) skill_label="Notebook" ;;
|
|
211
|
+
*) skill_label="$last_tool" ;;
|
|
212
|
+
esac
|
|
213
|
+
fi
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
# Fallback: check .ccs/task.md for last slash command
|
|
217
|
+
if [ "$skill_label" = "Idle" ] && [ -n "$clean_cwd" ]; then
|
|
218
|
+
task_file="${clean_cwd}/.ccs/task.md"
|
|
219
|
+
if [ -f "$task_file" ]; then
|
|
220
|
+
last_skill=$(grep -oE '/ccs-[a-z]+' "$task_file" 2>/dev/null | tail -1)
|
|
221
|
+
[ -n "$last_skill" ] && skill_label="$last_skill"
|
|
222
|
+
fi
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# ── Column widths ──
|
|
226
|
+
C1=38 # Col 1
|
|
227
|
+
C2=30 # Col 2
|
|
228
|
+
|
|
229
|
+
# ── Separator ──
|
|
230
|
+
S=$(printf '%b' " ${SEP}│${RST} ")
|
|
231
|
+
|
|
232
|
+
# ── Assemble: 4 rows ──
|
|
233
|
+
# Row 1: Skill │ GitHub
|
|
234
|
+
printf ' '
|
|
235
|
+
rpad "${PINK}Skill:${RST} ${PINK}${skill_label}${RST}" "$C1"
|
|
236
|
+
printf '%b' "$S"
|
|
237
|
+
printf '%b\n' "${WHITE}GitHub:${RST} ${WHITE}${gh_label}${RST}${git_dirty}"
|
|
238
|
+
|
|
239
|
+
# Row 2: Model │ Dir
|
|
240
|
+
printf ' '
|
|
241
|
+
rpad "${PURPLE}Model:${RST} ${PURPLE}${BOLD}${model_full}${RST}" "$C1"
|
|
242
|
+
printf '%b' "$S"
|
|
243
|
+
printf '%b\n' "${CYAN}Dir:${RST} ${CYAN}${dir_label}${RST}"
|
|
244
|
+
|
|
245
|
+
# Row 3: Tokens │ Cost
|
|
246
|
+
printf ' '
|
|
247
|
+
rpad "${YELLOW}Tokens:${RST} ${YELLOW}${token_label}${RST}" "$C1"
|
|
248
|
+
printf '%b' "$S"
|
|
249
|
+
printf '%b\n' "${GREEN}Cost:${RST} ${GREEN}${cost_label}${RST}"
|
|
250
|
+
|
|
251
|
+
# Row 4: Context (wide bar, full width)
|
|
252
|
+
printf ' '
|
|
253
|
+
printf '%b' "${CTX_CLR}Context:${RST} ${ctx_bar}"
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "skill-statusline",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Rich, customizable statusline for Claude Code — model, tokens, cost, context bar, GitHub info, and active skill display.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"skill-statusline": "bin/cli.js",
|
|
7
|
+
"ccsl": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude-code",
|
|
16
|
+
"statusline",
|
|
17
|
+
"status-bar",
|
|
18
|
+
"cli",
|
|
19
|
+
"claude",
|
|
20
|
+
"anthropic",
|
|
21
|
+
"ai-coding",
|
|
22
|
+
"developer-tools",
|
|
23
|
+
"terminal",
|
|
24
|
+
"prompt"
|
|
25
|
+
],
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Anit Chaudhary",
|
|
28
|
+
"url": "https://github.com/AnitChaudhry"
|
|
29
|
+
},
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/AnitChaudhry/claude-code-statusline.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://skills.thinqmesh.com",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/AnitChaudhry/claude-code-statusline/issues"
|
|
38
|
+
},
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=16"
|
|
41
|
+
}
|
|
42
|
+
}
|