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/cli.js +929 -929
- package/bin/cli.js.bak +837 -0
- package/bin/statusline.sh +139 -121
- package/lib/core.sh +408 -382
- package/lib/helpers.sh +119 -81
- package/package.json +52 -52
package/bin/cli.js
CHANGED
|
@@ -1,929 +1,929 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
|
-
const os = require('os');
|
|
6
|
-
const { execSync } = require('child_process');
|
|
7
|
-
const readline = require('readline');
|
|
8
|
-
|
|
9
|
-
const args = process.argv.slice(2);
|
|
10
|
-
const command = args[0];
|
|
11
|
-
const subcommand = args[1];
|
|
12
|
-
const VERSION = '2.
|
|
13
|
-
|
|
14
|
-
const PKG_DIR = path.resolve(__dirname, '..');
|
|
15
|
-
const HOME = os.homedir();
|
|
16
|
-
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
17
|
-
const SL_DIR = path.join(CLAUDE_DIR, 'statusline');
|
|
18
|
-
const CONFIG_PATH = path.join(CLAUDE_DIR, 'statusline-config.json');
|
|
19
|
-
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
20
|
-
const SCRIPT_DEST = path.join(CLAUDE_DIR, 'statusline-command.sh');
|
|
21
|
-
|
|
22
|
-
const CLAUDE_MD_PATH = path.join(CLAUDE_DIR, 'CLAUDE.md');
|
|
23
|
-
const COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands');
|
|
24
|
-
const THEMES = ['default', 'nord', 'tokyo-night', 'catppuccin', 'gruvbox'];
|
|
25
|
-
const LAYOUTS = ['compact', 'standard', 'full'];
|
|
26
|
-
const SLS_COMMANDS = ['sls-theme', 'sls-layout', 'sls-preview', 'sls-config', 'sls-doctor', 'sls-help'];
|
|
27
|
-
|
|
28
|
-
// Marker for our managed section in CLAUDE.md
|
|
29
|
-
const CLAUDE_MD_START = '<!-- skill-statusline:start -->';
|
|
30
|
-
const CLAUDE_MD_END = '<!-- skill-statusline:end -->';
|
|
31
|
-
|
|
32
|
-
// Terminal colors
|
|
33
|
-
const R = '\x1b[0m';
|
|
34
|
-
const B = '\x1b[1m';
|
|
35
|
-
const D = '\x1b[2m';
|
|
36
|
-
const GRN = '\x1b[32m';
|
|
37
|
-
const YLW = '\x1b[33m';
|
|
38
|
-
const RED = '\x1b[31m';
|
|
39
|
-
const CYN = '\x1b[36m';
|
|
40
|
-
const WHT = '\x1b[97m';
|
|
41
|
-
const PURPLE = '\x1b[38;2;168;85;247m';
|
|
42
|
-
const PINK = '\x1b[38;2;236;72;153m';
|
|
43
|
-
const TEAL = '\x1b[38;2;6;182;212m';
|
|
44
|
-
const GRAY = '\x1b[38;2;90;90;99m';
|
|
45
|
-
const ORANGE = '\x1b[38;2;251;146;60m';
|
|
46
|
-
|
|
47
|
-
function log(msg) { console.log(msg); }
|
|
48
|
-
function success(msg) { log(` ${GRAY}\u2502${R} ${GRN}\u2713${R} ${msg}`); }
|
|
49
|
-
function warn(msg) { log(` ${GRAY}\u2502${R} ${YLW}\u26A0${R} ${msg}`); }
|
|
50
|
-
function fail(msg) { log(` ${GRAY}\u2502${R} ${RED}\u2717${R} ${msg}`); }
|
|
51
|
-
function info(msg) { log(` ${GRAY}\u2502${R} ${CYN}\u2139${R} ${msg}`); }
|
|
52
|
-
function bar(msg) { log(` ${GRAY}\u2502${R} ${D}${msg}${R}`); }
|
|
53
|
-
function blank() { log(` ${GRAY}\u2502${R}`); }
|
|
54
|
-
|
|
55
|
-
function header() {
|
|
56
|
-
log('');
|
|
57
|
-
log(` ${GRAY}\u250C${''.padEnd(58, '\u2500')}\u2510${R}`);
|
|
58
|
-
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
59
|
-
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}`);
|
|
60
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}\u2588${R} ${PINK}\u2588${R} ${PURPLE}\u2588${R} ${D}Rich statusline for Claude Code${R} ${GRAY}\u2502${R}`);
|
|
61
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}${B}\u2588\u2588\u2588${R} ${PINK}${B}\u2588\u2588\u2588${R} ${GRAY}\u2502${R}`);
|
|
62
|
-
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
63
|
-
log(` ${GRAY}\u2502${R} ${TEAL}Thinqmesh Technologies${R} ${GRAY}\u2502${R}`);
|
|
64
|
-
log(` ${GRAY}\u2502${R} ${GRAY}skills.thinqmesh.com${R} ${GRAY}\u2502${R}`);
|
|
65
|
-
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
66
|
-
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
function footer() {
|
|
70
|
-
log(` ${GRAY}\u2502${R}`);
|
|
71
|
-
log(` ${GRAY}\u2514${''.padEnd(58, '\u2500')}\u2518${R}`);
|
|
72
|
-
log('');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function divider() {
|
|
76
|
-
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ── Config helpers ──
|
|
80
|
-
|
|
81
|
-
function readConfig() {
|
|
82
|
-
try {
|
|
83
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
84
|
-
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
85
|
-
}
|
|
86
|
-
} catch (e) {}
|
|
87
|
-
return { version: 2, theme: 'default', layout: 'standard', options: {} };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function writeConfig(config) {
|
|
91
|
-
config.version = 2;
|
|
92
|
-
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function readSettings() {
|
|
96
|
-
try {
|
|
97
|
-
if (fs.existsSync(SETTINGS_PATH)) {
|
|
98
|
-
return JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {}
|
|
101
|
-
return {};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function writeSettings(settings) {
|
|
105
|
-
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// ── CLAUDE.md management ──
|
|
109
|
-
// Adds instructions to ~/.claude/CLAUDE.md so Claude Code knows to use ccsl
|
|
110
|
-
// instead of the built-in statusline-setup agent (which fails on Windows
|
|
111
|
-
// and conflicts with our setup).
|
|
112
|
-
|
|
113
|
-
const CLAUDE_MD_SECTION = `${CLAUDE_MD_START}
|
|
114
|
-
## Statusline — Managed by skill-statusline v2
|
|
115
|
-
|
|
116
|
-
The Claude Code statusline is fully configured and managed by the \`skill-statusline\` npm package. Do NOT use the built-in \`statusline-setup\` agent for any statusline configuration — it is not compatible with this setup.
|
|
117
|
-
|
|
118
|
-
### How It Works
|
|
119
|
-
- \`~/.claude/settings.json\` → points to \`bash ~/.claude/statusline-command.sh\`
|
|
120
|
-
- \`~/.claude/statusline-command.sh\` → entry point, delegates to v2 engine
|
|
121
|
-
- \`~/.claude/statusline/core.sh\` → v2 engine (themes, layouts, accurate context tracking)
|
|
122
|
-
- \`~/.claude/statusline-config.json\` → user preferences (theme, layout, options)
|
|
123
|
-
|
|
124
|
-
### When the user asks about statusline configuration, use these CLI commands:
|
|
125
|
-
|
|
126
|
-
| Action | Command |
|
|
127
|
-
|--------|---------|
|
|
128
|
-
| Change theme | \`ccsl theme set <name>\` |
|
|
129
|
-
| Change layout | \`ccsl layout set <name>\` |
|
|
130
|
-
| List themes | \`ccsl theme\` |
|
|
131
|
-
| List layouts | \`ccsl layout\` |
|
|
132
|
-
| Preview | \`ccsl preview\` |
|
|
133
|
-
| Preview with overrides | \`ccsl preview --theme <name> --layout <name>\` |
|
|
134
|
-
| Show config | \`ccsl config\` |
|
|
135
|
-
| Set config option | \`ccsl config set <key> <value>\` |
|
|
136
|
-
| Diagnostics | \`ccsl doctor\` |
|
|
137
|
-
| Update scripts | \`ccsl update\` |
|
|
138
|
-
| Reinstall | \`ccsl install\` |
|
|
139
|
-
| Uninstall | \`ccsl uninstall\` |
|
|
140
|
-
|
|
141
|
-
**Available themes:** default, nord, tokyo-night, catppuccin, gruvbox
|
|
142
|
-
**Available layouts:** compact (2 rows), standard (4 rows), full (6 rows)
|
|
143
|
-
**Config options:** compaction_warning_threshold, bar_width, cache_ttl_seconds, show_burn_rate, show_vim_mode, show_agent_name
|
|
144
|
-
|
|
145
|
-
### Slash Commands (installed in ~/.claude/commands/)
|
|
146
|
-
|
|
147
|
-
Users can also use these slash commands inside Claude Code:
|
|
148
|
-
- \`/sls-theme [name]\` — List or set theme
|
|
149
|
-
- \`/sls-layout [name]\` — List or set layout
|
|
150
|
-
- \`/sls-preview [theme] [layout]\` — Preview with sample data
|
|
151
|
-
- \`/sls-config [key] [value]\` — Show or set config options
|
|
152
|
-
- \`/sls-doctor\` — Run diagnostics
|
|
153
|
-
- \`/sls-help\` — Show all commands
|
|
154
|
-
|
|
155
|
-
### IMPORTANT
|
|
156
|
-
- NEVER spawn the \`statusline-setup\` agent — it will fail on Windows and conflict with this setup
|
|
157
|
-
- All statusline changes are made via \`ccsl\` CLI commands (run in terminal) or \`/sls-*\` slash commands
|
|
158
|
-
- Changes take effect on next Claude Code restart (or next statusline refresh for config changes)
|
|
159
|
-
${CLAUDE_MD_END}`;
|
|
160
|
-
|
|
161
|
-
function installClaudeMd() {
|
|
162
|
-
let content = '';
|
|
163
|
-
if (fs.existsSync(CLAUDE_MD_PATH)) {
|
|
164
|
-
content = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
165
|
-
// Remove existing section if present
|
|
166
|
-
const startIdx = content.indexOf(CLAUDE_MD_START);
|
|
167
|
-
const endIdx = content.indexOf(CLAUDE_MD_END);
|
|
168
|
-
if (startIdx !== -1 && endIdx !== -1) {
|
|
169
|
-
content = content.substring(0, startIdx) + content.substring(endIdx + CLAUDE_MD_END.length);
|
|
170
|
-
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
// Append our section
|
|
174
|
-
content = content ? content + '\n\n' + CLAUDE_MD_SECTION + '\n' : CLAUDE_MD_SECTION + '\n';
|
|
175
|
-
fs.writeFileSync(CLAUDE_MD_PATH, content);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function uninstallClaudeMd() {
|
|
179
|
-
if (!fs.existsSync(CLAUDE_MD_PATH)) return false;
|
|
180
|
-
let content = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
181
|
-
const startIdx = content.indexOf(CLAUDE_MD_START);
|
|
182
|
-
const endIdx = content.indexOf(CLAUDE_MD_END);
|
|
183
|
-
if (startIdx === -1 || endIdx === -1) return false;
|
|
184
|
-
content = content.substring(0, startIdx) + content.substring(endIdx + CLAUDE_MD_END.length);
|
|
185
|
-
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
|
186
|
-
if (content) {
|
|
187
|
-
fs.writeFileSync(CLAUDE_MD_PATH, content + '\n');
|
|
188
|
-
} else {
|
|
189
|
-
// File is empty after removing our section — delete it
|
|
190
|
-
fs.unlinkSync(CLAUDE_MD_PATH);
|
|
191
|
-
}
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// ── Slash commands management ──
|
|
196
|
-
|
|
197
|
-
function installCommands() {
|
|
198
|
-
ensureDir(COMMANDS_DIR);
|
|
199
|
-
const cmdSrc = path.join(PKG_DIR, 'commands');
|
|
200
|
-
if (!fs.existsSync(cmdSrc)) return 0;
|
|
201
|
-
let count = 0;
|
|
202
|
-
for (const cmd of SLS_COMMANDS) {
|
|
203
|
-
const src = path.join(cmdSrc, `${cmd}.md`);
|
|
204
|
-
const dest = path.join(COMMANDS_DIR, `${cmd}.md`);
|
|
205
|
-
if (fs.existsSync(src)) {
|
|
206
|
-
fs.copyFileSync(src, dest);
|
|
207
|
-
count++;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
return count;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
function uninstallCommands() {
|
|
214
|
-
let count = 0;
|
|
215
|
-
for (const cmd of SLS_COMMANDS) {
|
|
216
|
-
const f = path.join(COMMANDS_DIR, `${cmd}.md`);
|
|
217
|
-
if (fs.existsSync(f)) {
|
|
218
|
-
fs.unlinkSync(f);
|
|
219
|
-
count++;
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// Remove commands dir if empty and we created it
|
|
223
|
-
try {
|
|
224
|
-
if (fs.existsSync(COMMANDS_DIR) && fs.readdirSync(COMMANDS_DIR).length === 0) {
|
|
225
|
-
fs.rmdirSync(COMMANDS_DIR);
|
|
226
|
-
}
|
|
227
|
-
} catch (e) {}
|
|
228
|
-
return count;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ── File copy helpers ──
|
|
232
|
-
|
|
233
|
-
function ensureDir(dir) {
|
|
234
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function copyDir(src, dest) {
|
|
238
|
-
ensureDir(dest);
|
|
239
|
-
for (const entry of fs.readdirSync(src)) {
|
|
240
|
-
const srcPath = path.join(src, entry);
|
|
241
|
-
const destPath = path.join(dest, entry);
|
|
242
|
-
if (fs.statSync(srcPath).isDirectory()) {
|
|
243
|
-
copyDir(srcPath, destPath);
|
|
244
|
-
} else {
|
|
245
|
-
fs.copyFileSync(srcPath, destPath);
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
function installFiles() {
|
|
251
|
-
ensureDir(CLAUDE_DIR);
|
|
252
|
-
ensureDir(SL_DIR);
|
|
253
|
-
|
|
254
|
-
// Copy lib/ → ~/.claude/statusline/
|
|
255
|
-
const libSrc = path.join(PKG_DIR, 'lib');
|
|
256
|
-
if (fs.existsSync(libSrc)) {
|
|
257
|
-
for (const f of fs.readdirSync(libSrc)) {
|
|
258
|
-
fs.copyFileSync(path.join(libSrc, f), path.join(SL_DIR, f));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Copy themes/ → ~/.claude/statusline/themes/
|
|
263
|
-
const themesSrc = path.join(PKG_DIR, 'themes');
|
|
264
|
-
if (fs.existsSync(themesSrc)) {
|
|
265
|
-
copyDir(themesSrc, path.join(SL_DIR, 'themes'));
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Copy layouts/ → ~/.claude/statusline/layouts/
|
|
269
|
-
const layoutsSrc = path.join(PKG_DIR, 'layouts');
|
|
270
|
-
if (fs.existsSync(layoutsSrc)) {
|
|
271
|
-
copyDir(layoutsSrc, path.join(SL_DIR, 'layouts'));
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Copy entry point
|
|
275
|
-
const slSrc = path.join(PKG_DIR, 'bin', 'statusline.sh');
|
|
276
|
-
fs.copyFileSync(slSrc, SCRIPT_DEST);
|
|
277
|
-
|
|
278
|
-
return true;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// ── Interactive prompt ──
|
|
282
|
-
|
|
283
|
-
function ask(rl, question) {
|
|
284
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
async function chooseFromList(rl, label, items, current) {
|
|
288
|
-
blank();
|
|
289
|
-
info(`${B}${label}${R}`);
|
|
290
|
-
blank();
|
|
291
|
-
items.forEach((item, i) => {
|
|
292
|
-
const marker = item === current ? ` ${GRN}(current)${R}` : '';
|
|
293
|
-
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${item}${marker}`);
|
|
294
|
-
});
|
|
295
|
-
blank();
|
|
296
|
-
const answer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
297
|
-
const idx = parseInt(answer, 10) - 1;
|
|
298
|
-
if (idx >= 0 && idx < items.length) return items[idx];
|
|
299
|
-
return current || items[0];
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
// ── Commands ──
|
|
303
|
-
|
|
304
|
-
async function install() {
|
|
305
|
-
const isQuick = args.includes('--quick');
|
|
306
|
-
const config = readConfig();
|
|
307
|
-
|
|
308
|
-
header();
|
|
309
|
-
|
|
310
|
-
if (isQuick) {
|
|
311
|
-
blank();
|
|
312
|
-
info(`${B}Quick install${R} — using defaults`);
|
|
313
|
-
blank();
|
|
314
|
-
} else {
|
|
315
|
-
// Interactive wizard
|
|
316
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
317
|
-
|
|
318
|
-
const themeNames = ['Default (classic purple/pink/cyan)', 'Nord (arctic, blue-tinted)', 'Tokyo Night (vibrant neon)', 'Catppuccin (warm pastels)', 'Gruvbox (retro groovy)'];
|
|
319
|
-
blank();
|
|
320
|
-
info(`${B}Choose a theme:${R}`);
|
|
321
|
-
blank();
|
|
322
|
-
themeNames.forEach((name, i) => {
|
|
323
|
-
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${name}`);
|
|
324
|
-
});
|
|
325
|
-
blank();
|
|
326
|
-
const tAnswer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
327
|
-
const tIdx = parseInt(tAnswer, 10) - 1;
|
|
328
|
-
if (tIdx >= 0 && tIdx < THEMES.length) config.theme = THEMES[tIdx];
|
|
329
|
-
|
|
330
|
-
const layoutNames = ['Compact (2 rows \u2014 minimal)', 'Standard (4 rows \u2014 balanced)', 'Full (6 rows \u2014 everything)'];
|
|
331
|
-
blank();
|
|
332
|
-
info(`${B}Choose a layout:${R}`);
|
|
333
|
-
blank();
|
|
334
|
-
layoutNames.forEach((name, i) => {
|
|
335
|
-
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${name}`);
|
|
336
|
-
});
|
|
337
|
-
blank();
|
|
338
|
-
const lAnswer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
339
|
-
const lIdx = parseInt(lAnswer, 10) - 1;
|
|
340
|
-
if (lIdx >= 0 && lIdx < LAYOUTS.length) config.layout = LAYOUTS[lIdx];
|
|
341
|
-
|
|
342
|
-
rl.close();
|
|
343
|
-
blank();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// Install files
|
|
347
|
-
installFiles();
|
|
348
|
-
success(`${B}statusline/${R} directory installed to ~/.claude/`);
|
|
349
|
-
|
|
350
|
-
// Write config
|
|
351
|
-
if (!config.options) config.options = {};
|
|
352
|
-
writeConfig(config);
|
|
353
|
-
success(`Config: theme=${CYN}${config.theme}${R}, layout=${CYN}${config.layout}${R}`);
|
|
354
|
-
|
|
355
|
-
// Update settings.json
|
|
356
|
-
const settings = readSettings();
|
|
357
|
-
if (!settings.statusLine) {
|
|
358
|
-
settings.statusLine = {
|
|
359
|
-
type: 'command',
|
|
360
|
-
command: 'bash ~/.claude/statusline-command.sh'
|
|
361
|
-
};
|
|
362
|
-
writeSettings(settings);
|
|
363
|
-
success(`${B}statusLine${R} config added to settings.json`);
|
|
364
|
-
} else {
|
|
365
|
-
success(`statusLine already configured in settings.json`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Add CLAUDE.md instructions (prevents built-in statusline-setup agent)
|
|
369
|
-
installClaudeMd();
|
|
370
|
-
success(`CLAUDE.md updated (statusline agent redirect)`);
|
|
371
|
-
|
|
372
|
-
// Install slash commands to ~/.claude/commands/
|
|
373
|
-
const cmdCount = installCommands();
|
|
374
|
-
if (cmdCount > 0) {
|
|
375
|
-
success(`${B}${cmdCount} slash commands${R} installed (/sls-theme, /sls-layout, ...)`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
divider();
|
|
379
|
-
blank();
|
|
380
|
-
log(` ${GRAY}\u2502${R} ${GRN}${B}Ready.${R} Restart Claude Code to see the statusline.`);
|
|
381
|
-
blank();
|
|
382
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}Layout: ${config.layout}${R} ${WHT}${B}Theme: ${config.theme}${R}`);
|
|
383
|
-
blank();
|
|
384
|
-
|
|
385
|
-
if (config.layout === 'compact') {
|
|
386
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}Opus 4.6${R} ${GRAY}\u2502${R} ${TEAL}Downloads/Project${R} ${GRAY}\u2502${R} ${WHT}47%${R} ${GRN}$1.23${R}`);
|
|
387
|
-
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%`);
|
|
388
|
-
} else if (config.layout === 'full') {
|
|
389
|
-
log(` ${GRAY}\u2502${R} ${PINK}Skill:${R} Edit ${GRAY}\u2502${R} ${WHT}GitHub:${R} User/Repo/main`);
|
|
390
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}Model:${R} Opus 4.6 ${GRAY}\u2502${R} ${TEAL}Dir:${R} Downloads/Project`);
|
|
391
|
-
log(` ${GRAY}\u2502${R} ${YLW}Window:${R} 8.5k + 1.2k ${GRAY}\u2502${R} ${GRN}Cost:${R} $1.23`);
|
|
392
|
-
log(` ${GRAY}\u2502${R} ${YLW}Session:${R} ${D}25k + 12k${R} ${GRAY}\u2502${R} ${D}+156/-23 12m34s${R}`);
|
|
393
|
-
log(` ${GRAY}\u2502${R} ${CYN}Cache:${R} ${D}W:5k R:2k${R} ${GRAY}\u2502${R} ${TEAL}NORMAL${R} ${CYN}@reviewer${R}`);
|
|
394
|
-
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%`);
|
|
395
|
-
} else {
|
|
396
|
-
log(` ${GRAY}\u2502${R} ${PINK}Skill:${R} Edit ${GRAY}\u2502${R} ${WHT}GitHub:${R} User/Repo/main`);
|
|
397
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}Model:${R} Opus 4.6 ${GRAY}\u2502${R} ${TEAL}Dir:${R} Downloads/Project`);
|
|
398
|
-
log(` ${GRAY}\u2502${R} ${YLW}Tokens:${R} 8.5k + 1.2k ${GRAY}\u2502${R} ${GRN}Cost:${R} $1.23`);
|
|
399
|
-
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%`);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
blank();
|
|
403
|
-
bar(`Script: ${R}${CYN}~/.claude/statusline-command.sh${R}`);
|
|
404
|
-
bar(`Engine: ${R}${CYN}~/.claude/statusline/core.sh${R}`);
|
|
405
|
-
bar(`Config: ${R}${CYN}~/.claude/statusline-config.json${R}`);
|
|
406
|
-
bar(`Settings: ${R}${CYN}~/.claude/settings.json${R}`);
|
|
407
|
-
blank();
|
|
408
|
-
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
409
|
-
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
410
|
-
|
|
411
|
-
footer();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function uninstall() {
|
|
415
|
-
header();
|
|
416
|
-
blank();
|
|
417
|
-
info(`${B}Uninstalling statusline${R}`);
|
|
418
|
-
blank();
|
|
419
|
-
|
|
420
|
-
// Remove statusline directory
|
|
421
|
-
if (fs.existsSync(SL_DIR)) {
|
|
422
|
-
fs.rmSync(SL_DIR, { recursive: true });
|
|
423
|
-
success(`Removed ~/.claude/statusline/`);
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Remove script
|
|
427
|
-
if (fs.existsSync(SCRIPT_DEST)) {
|
|
428
|
-
fs.unlinkSync(SCRIPT_DEST);
|
|
429
|
-
success(`Removed ~/.claude/statusline-command.sh`);
|
|
430
|
-
} else {
|
|
431
|
-
warn(`statusline-command.sh not found`);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
// Remove config
|
|
435
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
436
|
-
fs.unlinkSync(CONFIG_PATH);
|
|
437
|
-
success(`Removed ~/.claude/statusline-config.json`);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
// Remove from settings.json
|
|
441
|
-
if (fs.existsSync(SETTINGS_PATH)) {
|
|
442
|
-
try {
|
|
443
|
-
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
444
|
-
if (settings.statusLine) {
|
|
445
|
-
delete settings.statusLine;
|
|
446
|
-
writeSettings(settings);
|
|
447
|
-
success(`Removed statusLine from settings.json`);
|
|
448
|
-
}
|
|
449
|
-
} catch (e) {
|
|
450
|
-
warn(`Could not parse settings.json`);
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Remove CLAUDE.md section
|
|
455
|
-
if (uninstallClaudeMd()) {
|
|
456
|
-
success(`Removed statusline section from CLAUDE.md`);
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Remove slash commands
|
|
460
|
-
const cmdRemoved = uninstallCommands();
|
|
461
|
-
if (cmdRemoved > 0) {
|
|
462
|
-
success(`Removed ${cmdRemoved} slash commands`);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
blank();
|
|
466
|
-
log(` ${GRAY}\u2502${R} ${GRN}${B}Done.${R} Restart Claude Code to apply.`);
|
|
467
|
-
|
|
468
|
-
footer();
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
function update() {
|
|
472
|
-
header();
|
|
473
|
-
blank();
|
|
474
|
-
info(`${B}Updating statusline scripts${R} (preserving config)`);
|
|
475
|
-
blank();
|
|
476
|
-
|
|
477
|
-
installFiles();
|
|
478
|
-
success(`Scripts updated to v${VERSION}`);
|
|
479
|
-
|
|
480
|
-
const config = readConfig();
|
|
481
|
-
success(`Config preserved: theme=${CYN}${config.theme}${R}, layout=${CYN}${config.layout}${R}`);
|
|
482
|
-
|
|
483
|
-
// Refresh CLAUDE.md instructions
|
|
484
|
-
installClaudeMd();
|
|
485
|
-
success(`CLAUDE.md refreshed`);
|
|
486
|
-
|
|
487
|
-
// Refresh slash commands
|
|
488
|
-
const cmdCount = installCommands();
|
|
489
|
-
if (cmdCount > 0) {
|
|
490
|
-
success(`${cmdCount} slash commands refreshed`);
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
blank();
|
|
494
|
-
log(` ${GRAY}\u2502${R} ${GRN}${B}Done.${R} Restart Claude Code to apply.`);
|
|
495
|
-
|
|
496
|
-
footer();
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
function preview() {
|
|
500
|
-
const themeName = args.includes('--theme') ? args[args.indexOf('--theme') + 1] : null;
|
|
501
|
-
const layoutName = args.includes('--layout') ? args[args.indexOf('--layout') + 1] : null;
|
|
502
|
-
|
|
503
|
-
const sampleJson = JSON.stringify({
|
|
504
|
-
cwd: process.cwd(),
|
|
505
|
-
session_id: 'preview-session',
|
|
506
|
-
version: '2.0.0',
|
|
507
|
-
model: { id: 'claude-opus-4-6', display_name: 'Opus' },
|
|
508
|
-
workspace: { current_dir: process.cwd(), project_dir: process.cwd() },
|
|
509
|
-
cost: { total_cost_usd: 1.23, total_duration_ms: 754000, total_api_duration_ms: 23400, total_lines_added: 156, total_lines_removed: 23 },
|
|
510
|
-
context_window: {
|
|
511
|
-
total_input_tokens: 125234, total_output_tokens: 34521,
|
|
512
|
-
context_window_size: 200000, used_percentage: 47, remaining_percentage: 53,
|
|
513
|
-
current_usage: { input_tokens: 85000, output_tokens: 12000, cache_creation_input_tokens: 5000, cache_read_input_tokens: 2000 }
|
|
514
|
-
},
|
|
515
|
-
vim: { mode: 'NORMAL' },
|
|
516
|
-
agent: { name: 'code-reviewer' }
|
|
517
|
-
});
|
|
518
|
-
|
|
519
|
-
// Check if v2 engine is installed
|
|
520
|
-
const coreFile = path.join(SL_DIR, 'core.sh');
|
|
521
|
-
let scriptPath;
|
|
522
|
-
if (fs.existsSync(coreFile)) {
|
|
523
|
-
scriptPath = coreFile;
|
|
524
|
-
} else if (fs.existsSync(SCRIPT_DEST)) {
|
|
525
|
-
scriptPath = SCRIPT_DEST;
|
|
526
|
-
} else {
|
|
527
|
-
// Use the package's own script
|
|
528
|
-
scriptPath = path.join(PKG_DIR, 'lib', 'core.sh');
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
const env = { ...process.env };
|
|
532
|
-
if (themeName) env.STATUSLINE_THEME_OVERRIDE = themeName;
|
|
533
|
-
if (layoutName) env.STATUSLINE_LAYOUT_OVERRIDE = layoutName;
|
|
534
|
-
|
|
535
|
-
// For preview with package's own files, set STATUSLINE_DIR
|
|
536
|
-
if (!fs.existsSync(path.join(SL_DIR, 'core.sh'))) {
|
|
537
|
-
// Point to package's own lib directory structure
|
|
538
|
-
env.HOME = PKG_DIR;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
try {
|
|
542
|
-
const escaped = sampleJson.replace(/'/g, "'\\''");
|
|
543
|
-
const result = execSync(`printf '%s' '${escaped}' | bash "${scriptPath.replace(/\\/g, '/')}"`, {
|
|
544
|
-
encoding: 'utf8',
|
|
545
|
-
env,
|
|
546
|
-
timeout: 5000
|
|
547
|
-
});
|
|
548
|
-
log('');
|
|
549
|
-
log(result);
|
|
550
|
-
log('');
|
|
551
|
-
} catch (e) {
|
|
552
|
-
warn(`Preview failed: ${e.message}`);
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
function themeCmd() {
|
|
557
|
-
const config = readConfig();
|
|
558
|
-
|
|
559
|
-
if (subcommand === 'set') {
|
|
560
|
-
const name = args[2];
|
|
561
|
-
if (!name || !THEMES.includes(name)) {
|
|
562
|
-
header();
|
|
563
|
-
blank();
|
|
564
|
-
fail(`Unknown theme: ${name || '(none)'}`);
|
|
565
|
-
blank();
|
|
566
|
-
info(`Available: ${THEMES.join(', ')}`);
|
|
567
|
-
footer();
|
|
568
|
-
process.exit(1);
|
|
569
|
-
}
|
|
570
|
-
config.theme = name;
|
|
571
|
-
writeConfig(config);
|
|
572
|
-
header();
|
|
573
|
-
blank();
|
|
574
|
-
success(`Theme set to ${CYN}${B}${name}${R}`);
|
|
575
|
-
blank();
|
|
576
|
-
log(` ${GRAY}\u2502${R} Restart Claude Code to apply.`);
|
|
577
|
-
footer();
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// List themes
|
|
582
|
-
header();
|
|
583
|
-
blank();
|
|
584
|
-
info(`${B}Themes${R}`);
|
|
585
|
-
blank();
|
|
586
|
-
THEMES.forEach(t => {
|
|
587
|
-
const marker = t === config.theme ? ` ${GRN}\u2190 current${R}` : '';
|
|
588
|
-
log(` ${GRAY}\u2502${R} ${CYN}${t}${R}${marker}`);
|
|
589
|
-
});
|
|
590
|
-
blank();
|
|
591
|
-
bar(`Set theme: ${R}${CYN}ccsl theme set <name>${R}`);
|
|
592
|
-
bar(`Preview: ${R}${CYN}ccsl preview --theme <name>${R}`);
|
|
593
|
-
footer();
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function layoutCmd() {
|
|
597
|
-
const config = readConfig();
|
|
598
|
-
|
|
599
|
-
if (subcommand === 'set') {
|
|
600
|
-
const name = args[2];
|
|
601
|
-
if (!name || !LAYOUTS.includes(name)) {
|
|
602
|
-
header();
|
|
603
|
-
blank();
|
|
604
|
-
fail(`Unknown layout: ${name || '(none)'}`);
|
|
605
|
-
blank();
|
|
606
|
-
info(`Available: ${LAYOUTS.join(', ')}`);
|
|
607
|
-
footer();
|
|
608
|
-
process.exit(1);
|
|
609
|
-
}
|
|
610
|
-
config.layout = name;
|
|
611
|
-
writeConfig(config);
|
|
612
|
-
header();
|
|
613
|
-
blank();
|
|
614
|
-
success(`Layout set to ${CYN}${B}${name}${R}`);
|
|
615
|
-
blank();
|
|
616
|
-
log(` ${GRAY}\u2502${R} Restart Claude Code to apply.`);
|
|
617
|
-
footer();
|
|
618
|
-
return;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// List layouts
|
|
622
|
-
header();
|
|
623
|
-
blank();
|
|
624
|
-
info(`${B}Layouts${R}`);
|
|
625
|
-
blank();
|
|
626
|
-
const descriptions = { compact: '2 rows \u2014 minimal', standard: '4 rows \u2014 balanced', full: '6 rows \u2014 everything' };
|
|
627
|
-
LAYOUTS.forEach(l => {
|
|
628
|
-
const marker = l === config.layout ? ` ${GRN}\u2190 current${R}` : '';
|
|
629
|
-
log(` ${GRAY}\u2502${R} ${CYN}${l}${R} ${D}(${descriptions[l]})${R}${marker}`);
|
|
630
|
-
});
|
|
631
|
-
blank();
|
|
632
|
-
bar(`Set layout: ${R}${CYN}ccsl layout set <name>${R}`);
|
|
633
|
-
bar(`Preview: ${R}${CYN}ccsl preview --layout <name>${R}`);
|
|
634
|
-
footer();
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
function configCmd() {
|
|
638
|
-
const config = readConfig();
|
|
639
|
-
|
|
640
|
-
if (subcommand === 'set') {
|
|
641
|
-
const key = args[2];
|
|
642
|
-
const value = args[3];
|
|
643
|
-
if (!key || value === undefined) {
|
|
644
|
-
header();
|
|
645
|
-
blank();
|
|
646
|
-
fail(`Usage: ccsl config set <key> <value>`);
|
|
647
|
-
blank();
|
|
648
|
-
info(`Keys: compaction_warning_threshold, bar_width, cache_ttl_seconds,`);
|
|
649
|
-
info(` show_burn_rate, show_vim_mode, show_agent_name`);
|
|
650
|
-
footer();
|
|
651
|
-
process.exit(1);
|
|
652
|
-
}
|
|
653
|
-
if (!config.options) config.options = {};
|
|
654
|
-
// Parse booleans and numbers
|
|
655
|
-
if (value === 'true') config.options[key] = true;
|
|
656
|
-
else if (value === 'false') config.options[key] = false;
|
|
657
|
-
else if (!isNaN(value)) config.options[key] = Number(value);
|
|
658
|
-
else config.options[key] = value;
|
|
659
|
-
|
|
660
|
-
writeConfig(config);
|
|
661
|
-
header();
|
|
662
|
-
blank();
|
|
663
|
-
success(`Set ${CYN}${key}${R} = ${CYN}${value}${R}`);
|
|
664
|
-
footer();
|
|
665
|
-
return;
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
// Show config
|
|
669
|
-
header();
|
|
670
|
-
blank();
|
|
671
|
-
info(`${B}Current configuration${R}`);
|
|
672
|
-
blank();
|
|
673
|
-
log(` ${GRAY}\u2502${R} ${WHT}Theme:${R} ${CYN}${config.theme}${R}`);
|
|
674
|
-
log(` ${GRAY}\u2502${R} ${WHT}Layout:${R} ${CYN}${config.layout}${R}`);
|
|
675
|
-
if (config.options && Object.keys(config.options).length > 0) {
|
|
676
|
-
blank();
|
|
677
|
-
info(`${B}Options${R}`);
|
|
678
|
-
blank();
|
|
679
|
-
for (const [k, v] of Object.entries(config.options)) {
|
|
680
|
-
log(` ${GRAY}\u2502${R} ${D}${k}:${R} ${CYN}${v}${R}`);
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
blank();
|
|
684
|
-
bar(`File: ${R}${CYN}~/.claude/statusline-config.json${R}`);
|
|
685
|
-
footer();
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function doctor() {
|
|
689
|
-
header();
|
|
690
|
-
blank();
|
|
691
|
-
info(`${B}Diagnostic check${R}`);
|
|
692
|
-
blank();
|
|
693
|
-
|
|
694
|
-
let issues = 0;
|
|
695
|
-
|
|
696
|
-
// 1. Bash
|
|
697
|
-
try {
|
|
698
|
-
const bashVer = execSync('bash --version 2>&1', { encoding: 'utf8' }).split('\n')[0];
|
|
699
|
-
success(`bash: ${D}${bashVer.substring(0, 60)}${R}`);
|
|
700
|
-
} catch (e) {
|
|
701
|
-
fail(`bash not found`);
|
|
702
|
-
issues++;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// 2. Git
|
|
706
|
-
try {
|
|
707
|
-
execSync('git --version', { encoding: 'utf8' });
|
|
708
|
-
success(`git available`);
|
|
709
|
-
} catch (e) {
|
|
710
|
-
warn(`git not found (GitHub field will show "no-git")`);
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// 3. settings.json
|
|
714
|
-
if (fs.existsSync(SETTINGS_PATH)) {
|
|
715
|
-
try {
|
|
716
|
-
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
717
|
-
if (settings.statusLine && settings.statusLine.command) {
|
|
718
|
-
success(`settings.json has statusLine config`);
|
|
719
|
-
} else {
|
|
720
|
-
fail(`settings.json missing statusLine entry`);
|
|
721
|
-
issues++;
|
|
722
|
-
}
|
|
723
|
-
} catch (e) {
|
|
724
|
-
fail(`settings.json is invalid JSON`);
|
|
725
|
-
issues++;
|
|
726
|
-
}
|
|
727
|
-
} else {
|
|
728
|
-
fail(`~/.claude/settings.json not found`);
|
|
729
|
-
issues++;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// 4. Entry point script
|
|
733
|
-
if (fs.existsSync(SCRIPT_DEST)) {
|
|
734
|
-
success(`statusline-command.sh exists`);
|
|
735
|
-
} else {
|
|
736
|
-
fail(`~/.claude/statusline-command.sh not found`);
|
|
737
|
-
issues++;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// 5. v2 engine
|
|
741
|
-
const coreFile = path.join(SL_DIR, 'core.sh');
|
|
742
|
-
if (fs.existsSync(coreFile)) {
|
|
743
|
-
success(`v2 engine installed (statusline/core.sh)`);
|
|
744
|
-
|
|
745
|
-
// Check theme file
|
|
746
|
-
const config = readConfig();
|
|
747
|
-
const themeFile = path.join(SL_DIR, 'themes', `${config.theme}.sh`);
|
|
748
|
-
if (fs.existsSync(themeFile)) {
|
|
749
|
-
success(`Theme "${config.theme}" found`);
|
|
750
|
-
} else {
|
|
751
|
-
fail(`Theme "${config.theme}" not found at ${themeFile}`);
|
|
752
|
-
issues++;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
// Check layout file
|
|
756
|
-
const layoutFile = path.join(SL_DIR, 'layouts', `${config.layout}.sh`);
|
|
757
|
-
if (fs.existsSync(layoutFile)) {
|
|
758
|
-
success(`Layout "${config.layout}" found`);
|
|
759
|
-
} else {
|
|
760
|
-
fail(`Layout "${config.layout}" not found at ${layoutFile}`);
|
|
761
|
-
issues++;
|
|
762
|
-
}
|
|
763
|
-
} else {
|
|
764
|
-
warn(`v2 engine not installed (running v1 fallback)`);
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
// 6. CLAUDE.md agent redirect
|
|
768
|
-
if (fs.existsSync(CLAUDE_MD_PATH)) {
|
|
769
|
-
const mdContent = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
770
|
-
if (mdContent.includes(CLAUDE_MD_START)) {
|
|
771
|
-
success(`CLAUDE.md has statusline agent redirect`);
|
|
772
|
-
} else {
|
|
773
|
-
warn(`CLAUDE.md exists but missing statusline section`);
|
|
774
|
-
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add it`);
|
|
775
|
-
}
|
|
776
|
-
} else {
|
|
777
|
-
warn(`No ~/.claude/CLAUDE.md (built-in statusline agent may interfere)`);
|
|
778
|
-
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to fix`);
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// 7. Slash commands
|
|
782
|
-
if (fs.existsSync(COMMANDS_DIR)) {
|
|
783
|
-
const installed = SLS_COMMANDS.filter(c => fs.existsSync(path.join(COMMANDS_DIR, `${c}.md`)));
|
|
784
|
-
if (installed.length === SLS_COMMANDS.length) {
|
|
785
|
-
success(`All ${SLS_COMMANDS.length} slash commands installed`);
|
|
786
|
-
} else if (installed.length > 0) {
|
|
787
|
-
warn(`${installed.length}/${SLS_COMMANDS.length} slash commands installed`);
|
|
788
|
-
info(`Run ${CYN}ccsl update${R} to install missing commands`);
|
|
789
|
-
} else {
|
|
790
|
-
warn(`No slash commands found in ~/.claude/commands/`);
|
|
791
|
-
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add them`);
|
|
792
|
-
}
|
|
793
|
-
} else {
|
|
794
|
-
warn(`~/.claude/commands/ not found (slash commands not installed)`);
|
|
795
|
-
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add them`);
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// 8. Config file
|
|
799
|
-
if (fs.existsSync(CONFIG_PATH)) {
|
|
800
|
-
try {
|
|
801
|
-
JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
802
|
-
success(`statusline-config.json is valid`);
|
|
803
|
-
} catch (e) {
|
|
804
|
-
fail(`statusline-config.json is invalid JSON`);
|
|
805
|
-
issues++;
|
|
806
|
-
}
|
|
807
|
-
} else {
|
|
808
|
-
warn(`No config file (using defaults)`);
|
|
809
|
-
}
|
|
810
|
-
|
|
811
|
-
// 9. Performance benchmark
|
|
812
|
-
blank();
|
|
813
|
-
info(`${B}Performance benchmark${R}`);
|
|
814
|
-
blank();
|
|
815
|
-
try {
|
|
816
|
-
const sampleJson = '{"model":{"id":"claude-opus-4-6","display_name":"Opus"},"workspace":{"current_dir":"/tmp"},"cost":{"total_cost_usd":0.5},"context_window":{"context_window_size":200000,"used_percentage":50,"current_usage":{"input_tokens":90000,"output_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}';
|
|
817
|
-
const target = fs.existsSync(coreFile) ? coreFile : SCRIPT_DEST;
|
|
818
|
-
if (target && fs.existsSync(target)) {
|
|
819
|
-
const start = Date.now();
|
|
820
|
-
execSync(`printf '%s' '${sampleJson}' | bash "${target.replace(/\\/g, '/')}"`, {
|
|
821
|
-
encoding: 'utf8',
|
|
822
|
-
timeout: 10000
|
|
823
|
-
});
|
|
824
|
-
const elapsed = Date.now() - start;
|
|
825
|
-
const color = elapsed < 50 ? GRN : elapsed < 100 ? YLW : RED;
|
|
826
|
-
const label = elapsed < 50 ? 'excellent' : elapsed < 100 ? 'good' : 'slow';
|
|
827
|
-
log(` ${GRAY}\u2502${R} ${color}\u25CF${R} Execution: ${color}${B}${elapsed}ms${R} (${label}, target: <50ms)`);
|
|
828
|
-
}
|
|
829
|
-
} catch (e) {
|
|
830
|
-
fail(`Benchmark failed: ${e.message.substring(0, 50)}`);
|
|
831
|
-
issues++;
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
blank();
|
|
835
|
-
if (issues === 0) {
|
|
836
|
-
log(` ${GRAY}\u2502${R} ${GRN}${B}All checks passed.${R}`);
|
|
837
|
-
} else {
|
|
838
|
-
log(` ${GRAY}\u2502${R} ${RED}${B}${issues} issue(s) found.${R} Run ${CYN}ccsl install${R} to fix.`);
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
footer();
|
|
842
|
-
}
|
|
843
|
-
|
|
844
|
-
function showVersion() {
|
|
845
|
-
log(`skill-statusline v${VERSION}`);
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
function showHelp() {
|
|
849
|
-
header();
|
|
850
|
-
blank();
|
|
851
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}Commands:${R}`);
|
|
852
|
-
blank();
|
|
853
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl install${R} Install with theme/layout wizard`);
|
|
854
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl install --quick${R} Install with defaults`);
|
|
855
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl uninstall${R} Remove statusline`);
|
|
856
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl update${R} Update scripts (keep config)`);
|
|
857
|
-
blank();
|
|
858
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl theme${R} List themes`);
|
|
859
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl theme set <name>${R} Set active theme`);
|
|
860
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl layout${R} List layouts`);
|
|
861
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl layout set <name>${R} Set active layout`);
|
|
862
|
-
blank();
|
|
863
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview${R} Preview with sample data`);
|
|
864
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview --theme x${R} Preview a specific theme`);
|
|
865
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview --layout x${R} Preview a specific layout`);
|
|
866
|
-
blank();
|
|
867
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl config${R} Show current config`);
|
|
868
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl config set k v${R} Set config option`);
|
|
869
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl doctor${R} Run diagnostics`);
|
|
870
|
-
log(` ${GRAY}\u2502${R} ${CYN}ccsl version${R} Show version`);
|
|
871
|
-
blank();
|
|
872
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}Slash Commands${R} ${D}(inside Claude Code):${R}`);
|
|
873
|
-
blank();
|
|
874
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-theme${R} List or set theme`);
|
|
875
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-layout${R} List or set layout`);
|
|
876
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-preview${R} Preview with sample data`);
|
|
877
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-config${R} Show or set config options`);
|
|
878
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-doctor${R} Run diagnostics`);
|
|
879
|
-
log(` ${GRAY}\u2502${R} ${PINK}/sls-help${R} Show all commands`);
|
|
880
|
-
blank();
|
|
881
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}Themes:${R} ${THEMES.join(', ')}`);
|
|
882
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}Layouts:${R} ${LAYOUTS.join(', ')}`);
|
|
883
|
-
blank();
|
|
884
|
-
log(` ${GRAY}\u2502${R} ${WHT}${B}What it shows:${R}`);
|
|
885
|
-
blank();
|
|
886
|
-
log(` ${GRAY}\u2502${R} ${PINK}Skill${R} Last tool used (Read, Write, Terminal, Agent...)`);
|
|
887
|
-
log(` ${GRAY}\u2502${R} ${PURPLE}Model${R} Active model name and version`);
|
|
888
|
-
log(` ${GRAY}\u2502${R} ${WHT}GitHub${R} user/repo/branch with dirty indicators`);
|
|
889
|
-
log(` ${GRAY}\u2502${R} ${TEAL}Dir${R} Last 3 segments of working directory`);
|
|
890
|
-
log(` ${GRAY}\u2502${R} ${YLW}Tokens${R} Current window: input + output`);
|
|
891
|
-
log(` ${GRAY}\u2502${R} ${GRN}Cost${R} Session cost in USD`);
|
|
892
|
-
log(` ${GRAY}\u2502${R} ${WHT}Context${R} Accurate progress bar with compaction warning`);
|
|
893
|
-
log(` ${GRAY}\u2502${R} ${D}+ Session tokens, duration, lines, cache, vim, agent (full layout)${R}`);
|
|
894
|
-
blank();
|
|
895
|
-
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
896
|
-
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
897
|
-
|
|
898
|
-
footer();
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// ── Main ──
|
|
902
|
-
|
|
903
|
-
if (command === 'install' || command === 'init') {
|
|
904
|
-
install();
|
|
905
|
-
} else if (command === 'uninstall' || command === 'remove') {
|
|
906
|
-
uninstall();
|
|
907
|
-
} else if (command === 'update' || command === 'upgrade') {
|
|
908
|
-
update();
|
|
909
|
-
} else if (command === 'preview') {
|
|
910
|
-
preview();
|
|
911
|
-
} else if (command === 'theme') {
|
|
912
|
-
themeCmd();
|
|
913
|
-
} else if (command === 'layout') {
|
|
914
|
-
layoutCmd();
|
|
915
|
-
} else if (command === 'config') {
|
|
916
|
-
configCmd();
|
|
917
|
-
} else if (command === 'doctor' || command === 'check') {
|
|
918
|
-
doctor();
|
|
919
|
-
} else if (command === 'version' || command === '--version' || command === '-v') {
|
|
920
|
-
showVersion();
|
|
921
|
-
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
922
|
-
showHelp();
|
|
923
|
-
} else {
|
|
924
|
-
if (command) {
|
|
925
|
-
log('');
|
|
926
|
-
warn(`Unknown command: ${command}`);
|
|
927
|
-
}
|
|
928
|
-
showHelp();
|
|
929
|
-
}
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const readline = require('readline');
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0];
|
|
11
|
+
const subcommand = args[1];
|
|
12
|
+
const VERSION = '2.2.0';
|
|
13
|
+
|
|
14
|
+
const PKG_DIR = path.resolve(__dirname, '..');
|
|
15
|
+
const HOME = os.homedir();
|
|
16
|
+
const CLAUDE_DIR = path.join(HOME, '.claude');
|
|
17
|
+
const SL_DIR = path.join(CLAUDE_DIR, 'statusline');
|
|
18
|
+
const CONFIG_PATH = path.join(CLAUDE_DIR, 'statusline-config.json');
|
|
19
|
+
const SETTINGS_PATH = path.join(CLAUDE_DIR, 'settings.json');
|
|
20
|
+
const SCRIPT_DEST = path.join(CLAUDE_DIR, 'statusline-command.sh');
|
|
21
|
+
|
|
22
|
+
const CLAUDE_MD_PATH = path.join(CLAUDE_DIR, 'CLAUDE.md');
|
|
23
|
+
const COMMANDS_DIR = path.join(CLAUDE_DIR, 'commands');
|
|
24
|
+
const THEMES = ['default', 'nord', 'tokyo-night', 'catppuccin', 'gruvbox'];
|
|
25
|
+
const LAYOUTS = ['compact', 'standard', 'full'];
|
|
26
|
+
const SLS_COMMANDS = ['sls-theme', 'sls-layout', 'sls-preview', 'sls-config', 'sls-doctor', 'sls-help'];
|
|
27
|
+
|
|
28
|
+
// Marker for our managed section in CLAUDE.md
|
|
29
|
+
const CLAUDE_MD_START = '<!-- skill-statusline:start -->';
|
|
30
|
+
const CLAUDE_MD_END = '<!-- skill-statusline:end -->';
|
|
31
|
+
|
|
32
|
+
// Terminal colors
|
|
33
|
+
const R = '\x1b[0m';
|
|
34
|
+
const B = '\x1b[1m';
|
|
35
|
+
const D = '\x1b[2m';
|
|
36
|
+
const GRN = '\x1b[32m';
|
|
37
|
+
const YLW = '\x1b[33m';
|
|
38
|
+
const RED = '\x1b[31m';
|
|
39
|
+
const CYN = '\x1b[36m';
|
|
40
|
+
const WHT = '\x1b[97m';
|
|
41
|
+
const PURPLE = '\x1b[38;2;168;85;247m';
|
|
42
|
+
const PINK = '\x1b[38;2;236;72;153m';
|
|
43
|
+
const TEAL = '\x1b[38;2;6;182;212m';
|
|
44
|
+
const GRAY = '\x1b[38;2;90;90;99m';
|
|
45
|
+
const ORANGE = '\x1b[38;2;251;146;60m';
|
|
46
|
+
|
|
47
|
+
function log(msg) { console.log(msg); }
|
|
48
|
+
function success(msg) { log(` ${GRAY}\u2502${R} ${GRN}\u2713${R} ${msg}`); }
|
|
49
|
+
function warn(msg) { log(` ${GRAY}\u2502${R} ${YLW}\u26A0${R} ${msg}`); }
|
|
50
|
+
function fail(msg) { log(` ${GRAY}\u2502${R} ${RED}\u2717${R} ${msg}`); }
|
|
51
|
+
function info(msg) { log(` ${GRAY}\u2502${R} ${CYN}\u2139${R} ${msg}`); }
|
|
52
|
+
function bar(msg) { log(` ${GRAY}\u2502${R} ${D}${msg}${R}`); }
|
|
53
|
+
function blank() { log(` ${GRAY}\u2502${R}`); }
|
|
54
|
+
|
|
55
|
+
function header() {
|
|
56
|
+
log('');
|
|
57
|
+
log(` ${GRAY}\u250C${''.padEnd(58, '\u2500')}\u2510${R}`);
|
|
58
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
59
|
+
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}`);
|
|
60
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}\u2588${R} ${PINK}\u2588${R} ${PURPLE}\u2588${R} ${D}Rich statusline for Claude Code${R} ${GRAY}\u2502${R}`);
|
|
61
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}${B}\u2588\u2588\u2588${R} ${PINK}${B}\u2588\u2588\u2588${R} ${GRAY}\u2502${R}`);
|
|
62
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
63
|
+
log(` ${GRAY}\u2502${R} ${TEAL}Thinqmesh Technologies${R} ${GRAY}\u2502${R}`);
|
|
64
|
+
log(` ${GRAY}\u2502${R} ${GRAY}skills.thinqmesh.com${R} ${GRAY}\u2502${R}`);
|
|
65
|
+
log(` ${GRAY}\u2502${R} ${GRAY}\u2502${R}`);
|
|
66
|
+
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function footer() {
|
|
70
|
+
log(` ${GRAY}\u2502${R}`);
|
|
71
|
+
log(` ${GRAY}\u2514${''.padEnd(58, '\u2500')}\u2518${R}`);
|
|
72
|
+
log('');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function divider() {
|
|
76
|
+
log(` ${GRAY}\u251C${''.padEnd(58, '\u2500')}\u2524${R}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ── Config helpers ──
|
|
80
|
+
|
|
81
|
+
function readConfig() {
|
|
82
|
+
try {
|
|
83
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
84
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {}
|
|
87
|
+
return { version: 2, theme: 'default', layout: 'standard', options: {} };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function writeConfig(config) {
|
|
91
|
+
config.version = 2;
|
|
92
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function readSettings() {
|
|
96
|
+
try {
|
|
97
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
98
|
+
return JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {}
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeSettings(settings) {
|
|
105
|
+
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(settings, null, 2) + '\n');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ── CLAUDE.md management ──
|
|
109
|
+
// Adds instructions to ~/.claude/CLAUDE.md so Claude Code knows to use ccsl
|
|
110
|
+
// instead of the built-in statusline-setup agent (which fails on Windows
|
|
111
|
+
// and conflicts with our setup).
|
|
112
|
+
|
|
113
|
+
const CLAUDE_MD_SECTION = `${CLAUDE_MD_START}
|
|
114
|
+
## Statusline — Managed by skill-statusline v2
|
|
115
|
+
|
|
116
|
+
The Claude Code statusline is fully configured and managed by the \`skill-statusline\` npm package. Do NOT use the built-in \`statusline-setup\` agent for any statusline configuration — it is not compatible with this setup.
|
|
117
|
+
|
|
118
|
+
### How It Works
|
|
119
|
+
- \`~/.claude/settings.json\` → points to \`bash ~/.claude/statusline-command.sh\`
|
|
120
|
+
- \`~/.claude/statusline-command.sh\` → entry point, delegates to v2 engine
|
|
121
|
+
- \`~/.claude/statusline/core.sh\` → v2 engine (themes, layouts, accurate context tracking)
|
|
122
|
+
- \`~/.claude/statusline-config.json\` → user preferences (theme, layout, options)
|
|
123
|
+
|
|
124
|
+
### When the user asks about statusline configuration, use these CLI commands:
|
|
125
|
+
|
|
126
|
+
| Action | Command |
|
|
127
|
+
|--------|---------|
|
|
128
|
+
| Change theme | \`ccsl theme set <name>\` |
|
|
129
|
+
| Change layout | \`ccsl layout set <name>\` |
|
|
130
|
+
| List themes | \`ccsl theme\` |
|
|
131
|
+
| List layouts | \`ccsl layout\` |
|
|
132
|
+
| Preview | \`ccsl preview\` |
|
|
133
|
+
| Preview with overrides | \`ccsl preview --theme <name> --layout <name>\` |
|
|
134
|
+
| Show config | \`ccsl config\` |
|
|
135
|
+
| Set config option | \`ccsl config set <key> <value>\` |
|
|
136
|
+
| Diagnostics | \`ccsl doctor\` |
|
|
137
|
+
| Update scripts | \`ccsl update\` |
|
|
138
|
+
| Reinstall | \`ccsl install\` |
|
|
139
|
+
| Uninstall | \`ccsl uninstall\` |
|
|
140
|
+
|
|
141
|
+
**Available themes:** default, nord, tokyo-night, catppuccin, gruvbox
|
|
142
|
+
**Available layouts:** compact (2 rows), standard (4 rows), full (6 rows)
|
|
143
|
+
**Config options:** compaction_warning_threshold, bar_width, cache_ttl_seconds, show_burn_rate, show_vim_mode, show_agent_name
|
|
144
|
+
|
|
145
|
+
### Slash Commands (installed in ~/.claude/commands/)
|
|
146
|
+
|
|
147
|
+
Users can also use these slash commands inside Claude Code:
|
|
148
|
+
- \`/sls-theme [name]\` — List or set theme
|
|
149
|
+
- \`/sls-layout [name]\` — List or set layout
|
|
150
|
+
- \`/sls-preview [theme] [layout]\` — Preview with sample data
|
|
151
|
+
- \`/sls-config [key] [value]\` — Show or set config options
|
|
152
|
+
- \`/sls-doctor\` — Run diagnostics
|
|
153
|
+
- \`/sls-help\` — Show all commands
|
|
154
|
+
|
|
155
|
+
### IMPORTANT
|
|
156
|
+
- NEVER spawn the \`statusline-setup\` agent — it will fail on Windows and conflict with this setup
|
|
157
|
+
- All statusline changes are made via \`ccsl\` CLI commands (run in terminal) or \`/sls-*\` slash commands
|
|
158
|
+
- Changes take effect on next Claude Code restart (or next statusline refresh for config changes)
|
|
159
|
+
${CLAUDE_MD_END}`;
|
|
160
|
+
|
|
161
|
+
function installClaudeMd() {
|
|
162
|
+
let content = '';
|
|
163
|
+
if (fs.existsSync(CLAUDE_MD_PATH)) {
|
|
164
|
+
content = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
165
|
+
// Remove existing section if present
|
|
166
|
+
const startIdx = content.indexOf(CLAUDE_MD_START);
|
|
167
|
+
const endIdx = content.indexOf(CLAUDE_MD_END);
|
|
168
|
+
if (startIdx !== -1 && endIdx !== -1) {
|
|
169
|
+
content = content.substring(0, startIdx) + content.substring(endIdx + CLAUDE_MD_END.length);
|
|
170
|
+
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Append our section
|
|
174
|
+
content = content ? content + '\n\n' + CLAUDE_MD_SECTION + '\n' : CLAUDE_MD_SECTION + '\n';
|
|
175
|
+
fs.writeFileSync(CLAUDE_MD_PATH, content);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function uninstallClaudeMd() {
|
|
179
|
+
if (!fs.existsSync(CLAUDE_MD_PATH)) return false;
|
|
180
|
+
let content = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
181
|
+
const startIdx = content.indexOf(CLAUDE_MD_START);
|
|
182
|
+
const endIdx = content.indexOf(CLAUDE_MD_END);
|
|
183
|
+
if (startIdx === -1 || endIdx === -1) return false;
|
|
184
|
+
content = content.substring(0, startIdx) + content.substring(endIdx + CLAUDE_MD_END.length);
|
|
185
|
+
content = content.replace(/\n{3,}/g, '\n\n').trim();
|
|
186
|
+
if (content) {
|
|
187
|
+
fs.writeFileSync(CLAUDE_MD_PATH, content + '\n');
|
|
188
|
+
} else {
|
|
189
|
+
// File is empty after removing our section — delete it
|
|
190
|
+
fs.unlinkSync(CLAUDE_MD_PATH);
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ── Slash commands management ──
|
|
196
|
+
|
|
197
|
+
function installCommands() {
|
|
198
|
+
ensureDir(COMMANDS_DIR);
|
|
199
|
+
const cmdSrc = path.join(PKG_DIR, 'commands');
|
|
200
|
+
if (!fs.existsSync(cmdSrc)) return 0;
|
|
201
|
+
let count = 0;
|
|
202
|
+
for (const cmd of SLS_COMMANDS) {
|
|
203
|
+
const src = path.join(cmdSrc, `${cmd}.md`);
|
|
204
|
+
const dest = path.join(COMMANDS_DIR, `${cmd}.md`);
|
|
205
|
+
if (fs.existsSync(src)) {
|
|
206
|
+
fs.copyFileSync(src, dest);
|
|
207
|
+
count++;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return count;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function uninstallCommands() {
|
|
214
|
+
let count = 0;
|
|
215
|
+
for (const cmd of SLS_COMMANDS) {
|
|
216
|
+
const f = path.join(COMMANDS_DIR, `${cmd}.md`);
|
|
217
|
+
if (fs.existsSync(f)) {
|
|
218
|
+
fs.unlinkSync(f);
|
|
219
|
+
count++;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
// Remove commands dir if empty and we created it
|
|
223
|
+
try {
|
|
224
|
+
if (fs.existsSync(COMMANDS_DIR) && fs.readdirSync(COMMANDS_DIR).length === 0) {
|
|
225
|
+
fs.rmdirSync(COMMANDS_DIR);
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {}
|
|
228
|
+
return count;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ── File copy helpers ──
|
|
232
|
+
|
|
233
|
+
function ensureDir(dir) {
|
|
234
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function copyDir(src, dest) {
|
|
238
|
+
ensureDir(dest);
|
|
239
|
+
for (const entry of fs.readdirSync(src)) {
|
|
240
|
+
const srcPath = path.join(src, entry);
|
|
241
|
+
const destPath = path.join(dest, entry);
|
|
242
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
243
|
+
copyDir(srcPath, destPath);
|
|
244
|
+
} else {
|
|
245
|
+
fs.copyFileSync(srcPath, destPath);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function installFiles() {
|
|
251
|
+
ensureDir(CLAUDE_DIR);
|
|
252
|
+
ensureDir(SL_DIR);
|
|
253
|
+
|
|
254
|
+
// Copy lib/ → ~/.claude/statusline/
|
|
255
|
+
const libSrc = path.join(PKG_DIR, 'lib');
|
|
256
|
+
if (fs.existsSync(libSrc)) {
|
|
257
|
+
for (const f of fs.readdirSync(libSrc)) {
|
|
258
|
+
fs.copyFileSync(path.join(libSrc, f), path.join(SL_DIR, f));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Copy themes/ → ~/.claude/statusline/themes/
|
|
263
|
+
const themesSrc = path.join(PKG_DIR, 'themes');
|
|
264
|
+
if (fs.existsSync(themesSrc)) {
|
|
265
|
+
copyDir(themesSrc, path.join(SL_DIR, 'themes'));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Copy layouts/ → ~/.claude/statusline/layouts/
|
|
269
|
+
const layoutsSrc = path.join(PKG_DIR, 'layouts');
|
|
270
|
+
if (fs.existsSync(layoutsSrc)) {
|
|
271
|
+
copyDir(layoutsSrc, path.join(SL_DIR, 'layouts'));
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Copy entry point
|
|
275
|
+
const slSrc = path.join(PKG_DIR, 'bin', 'statusline.sh');
|
|
276
|
+
fs.copyFileSync(slSrc, SCRIPT_DEST);
|
|
277
|
+
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ── Interactive prompt ──
|
|
282
|
+
|
|
283
|
+
function ask(rl, question) {
|
|
284
|
+
return new Promise(resolve => rl.question(question, resolve));
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function chooseFromList(rl, label, items, current) {
|
|
288
|
+
blank();
|
|
289
|
+
info(`${B}${label}${R}`);
|
|
290
|
+
blank();
|
|
291
|
+
items.forEach((item, i) => {
|
|
292
|
+
const marker = item === current ? ` ${GRN}(current)${R}` : '';
|
|
293
|
+
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${item}${marker}`);
|
|
294
|
+
});
|
|
295
|
+
blank();
|
|
296
|
+
const answer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
297
|
+
const idx = parseInt(answer, 10) - 1;
|
|
298
|
+
if (idx >= 0 && idx < items.length) return items[idx];
|
|
299
|
+
return current || items[0];
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// ── Commands ──
|
|
303
|
+
|
|
304
|
+
async function install() {
|
|
305
|
+
const isQuick = args.includes('--quick');
|
|
306
|
+
const config = readConfig();
|
|
307
|
+
|
|
308
|
+
header();
|
|
309
|
+
|
|
310
|
+
if (isQuick) {
|
|
311
|
+
blank();
|
|
312
|
+
info(`${B}Quick install${R} — using defaults`);
|
|
313
|
+
blank();
|
|
314
|
+
} else {
|
|
315
|
+
// Interactive wizard
|
|
316
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
317
|
+
|
|
318
|
+
const themeNames = ['Default (classic purple/pink/cyan)', 'Nord (arctic, blue-tinted)', 'Tokyo Night (vibrant neon)', 'Catppuccin (warm pastels)', 'Gruvbox (retro groovy)'];
|
|
319
|
+
blank();
|
|
320
|
+
info(`${B}Choose a theme:${R}`);
|
|
321
|
+
blank();
|
|
322
|
+
themeNames.forEach((name, i) => {
|
|
323
|
+
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${name}`);
|
|
324
|
+
});
|
|
325
|
+
blank();
|
|
326
|
+
const tAnswer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
327
|
+
const tIdx = parseInt(tAnswer, 10) - 1;
|
|
328
|
+
if (tIdx >= 0 && tIdx < THEMES.length) config.theme = THEMES[tIdx];
|
|
329
|
+
|
|
330
|
+
const layoutNames = ['Compact (2 rows \u2014 minimal)', 'Standard (4 rows \u2014 balanced)', 'Full (6 rows \u2014 everything)'];
|
|
331
|
+
blank();
|
|
332
|
+
info(`${B}Choose a layout:${R}`);
|
|
333
|
+
blank();
|
|
334
|
+
layoutNames.forEach((name, i) => {
|
|
335
|
+
log(` ${GRAY}\u2502${R} ${CYN}[${i + 1}]${R} ${name}`);
|
|
336
|
+
});
|
|
337
|
+
blank();
|
|
338
|
+
const lAnswer = await ask(rl, ` ${GRAY}\u2502${R} > `);
|
|
339
|
+
const lIdx = parseInt(lAnswer, 10) - 1;
|
|
340
|
+
if (lIdx >= 0 && lIdx < LAYOUTS.length) config.layout = LAYOUTS[lIdx];
|
|
341
|
+
|
|
342
|
+
rl.close();
|
|
343
|
+
blank();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Install files
|
|
347
|
+
installFiles();
|
|
348
|
+
success(`${B}statusline/${R} directory installed to ~/.claude/`);
|
|
349
|
+
|
|
350
|
+
// Write config
|
|
351
|
+
if (!config.options) config.options = {};
|
|
352
|
+
writeConfig(config);
|
|
353
|
+
success(`Config: theme=${CYN}${config.theme}${R}, layout=${CYN}${config.layout}${R}`);
|
|
354
|
+
|
|
355
|
+
// Update settings.json
|
|
356
|
+
const settings = readSettings();
|
|
357
|
+
if (!settings.statusLine) {
|
|
358
|
+
settings.statusLine = {
|
|
359
|
+
type: 'command',
|
|
360
|
+
command: 'bash ~/.claude/statusline-command.sh'
|
|
361
|
+
};
|
|
362
|
+
writeSettings(settings);
|
|
363
|
+
success(`${B}statusLine${R} config added to settings.json`);
|
|
364
|
+
} else {
|
|
365
|
+
success(`statusLine already configured in settings.json`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Add CLAUDE.md instructions (prevents built-in statusline-setup agent)
|
|
369
|
+
installClaudeMd();
|
|
370
|
+
success(`CLAUDE.md updated (statusline agent redirect)`);
|
|
371
|
+
|
|
372
|
+
// Install slash commands to ~/.claude/commands/
|
|
373
|
+
const cmdCount = installCommands();
|
|
374
|
+
if (cmdCount > 0) {
|
|
375
|
+
success(`${B}${cmdCount} slash commands${R} installed (/sls-theme, /sls-layout, ...)`);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
divider();
|
|
379
|
+
blank();
|
|
380
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}Ready.${R} Restart Claude Code to see the statusline.`);
|
|
381
|
+
blank();
|
|
382
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Layout: ${config.layout}${R} ${WHT}${B}Theme: ${config.theme}${R}`);
|
|
383
|
+
blank();
|
|
384
|
+
|
|
385
|
+
if (config.layout === 'compact') {
|
|
386
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Opus 4.6${R} ${GRAY}\u2502${R} ${TEAL}Downloads/Project${R} ${GRAY}\u2502${R} ${WHT}47%${R} ${GRN}$1.23${R}`);
|
|
387
|
+
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%`);
|
|
388
|
+
} else if (config.layout === 'full') {
|
|
389
|
+
log(` ${GRAY}\u2502${R} ${PINK}Skill:${R} Edit ${GRAY}\u2502${R} ${WHT}GitHub:${R} User/Repo/main`);
|
|
390
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Model:${R} Opus 4.6 ${GRAY}\u2502${R} ${TEAL}Dir:${R} Downloads/Project`);
|
|
391
|
+
log(` ${GRAY}\u2502${R} ${YLW}Window:${R} 8.5k + 1.2k ${GRAY}\u2502${R} ${GRN}Cost:${R} $1.23`);
|
|
392
|
+
log(` ${GRAY}\u2502${R} ${YLW}Session:${R} ${D}25k + 12k${R} ${GRAY}\u2502${R} ${D}+156/-23 12m34s${R}`);
|
|
393
|
+
log(` ${GRAY}\u2502${R} ${CYN}Cache:${R} ${D}W:5k R:2k${R} ${GRAY}\u2502${R} ${TEAL}NORMAL${R} ${CYN}@reviewer${R}`);
|
|
394
|
+
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%`);
|
|
395
|
+
} else {
|
|
396
|
+
log(` ${GRAY}\u2502${R} ${PINK}Skill:${R} Edit ${GRAY}\u2502${R} ${WHT}GitHub:${R} User/Repo/main`);
|
|
397
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Model:${R} Opus 4.6 ${GRAY}\u2502${R} ${TEAL}Dir:${R} Downloads/Project`);
|
|
398
|
+
log(` ${GRAY}\u2502${R} ${YLW}Tokens:${R} 8.5k + 1.2k ${GRAY}\u2502${R} ${GRN}Cost:${R} $1.23`);
|
|
399
|
+
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%`);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
blank();
|
|
403
|
+
bar(`Script: ${R}${CYN}~/.claude/statusline-command.sh${R}`);
|
|
404
|
+
bar(`Engine: ${R}${CYN}~/.claude/statusline/core.sh${R}`);
|
|
405
|
+
bar(`Config: ${R}${CYN}~/.claude/statusline-config.json${R}`);
|
|
406
|
+
bar(`Settings: ${R}${CYN}~/.claude/settings.json${R}`);
|
|
407
|
+
blank();
|
|
408
|
+
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
409
|
+
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
410
|
+
|
|
411
|
+
footer();
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function uninstall() {
|
|
415
|
+
header();
|
|
416
|
+
blank();
|
|
417
|
+
info(`${B}Uninstalling statusline${R}`);
|
|
418
|
+
blank();
|
|
419
|
+
|
|
420
|
+
// Remove statusline directory
|
|
421
|
+
if (fs.existsSync(SL_DIR)) {
|
|
422
|
+
fs.rmSync(SL_DIR, { recursive: true });
|
|
423
|
+
success(`Removed ~/.claude/statusline/`);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Remove script
|
|
427
|
+
if (fs.existsSync(SCRIPT_DEST)) {
|
|
428
|
+
fs.unlinkSync(SCRIPT_DEST);
|
|
429
|
+
success(`Removed ~/.claude/statusline-command.sh`);
|
|
430
|
+
} else {
|
|
431
|
+
warn(`statusline-command.sh not found`);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Remove config
|
|
435
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
436
|
+
fs.unlinkSync(CONFIG_PATH);
|
|
437
|
+
success(`Removed ~/.claude/statusline-config.json`);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Remove from settings.json
|
|
441
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
442
|
+
try {
|
|
443
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
444
|
+
if (settings.statusLine) {
|
|
445
|
+
delete settings.statusLine;
|
|
446
|
+
writeSettings(settings);
|
|
447
|
+
success(`Removed statusLine from settings.json`);
|
|
448
|
+
}
|
|
449
|
+
} catch (e) {
|
|
450
|
+
warn(`Could not parse settings.json`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Remove CLAUDE.md section
|
|
455
|
+
if (uninstallClaudeMd()) {
|
|
456
|
+
success(`Removed statusline section from CLAUDE.md`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Remove slash commands
|
|
460
|
+
const cmdRemoved = uninstallCommands();
|
|
461
|
+
if (cmdRemoved > 0) {
|
|
462
|
+
success(`Removed ${cmdRemoved} slash commands`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
blank();
|
|
466
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}Done.${R} Restart Claude Code to apply.`);
|
|
467
|
+
|
|
468
|
+
footer();
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function update() {
|
|
472
|
+
header();
|
|
473
|
+
blank();
|
|
474
|
+
info(`${B}Updating statusline scripts${R} (preserving config)`);
|
|
475
|
+
blank();
|
|
476
|
+
|
|
477
|
+
installFiles();
|
|
478
|
+
success(`Scripts updated to v${VERSION}`);
|
|
479
|
+
|
|
480
|
+
const config = readConfig();
|
|
481
|
+
success(`Config preserved: theme=${CYN}${config.theme}${R}, layout=${CYN}${config.layout}${R}`);
|
|
482
|
+
|
|
483
|
+
// Refresh CLAUDE.md instructions
|
|
484
|
+
installClaudeMd();
|
|
485
|
+
success(`CLAUDE.md refreshed`);
|
|
486
|
+
|
|
487
|
+
// Refresh slash commands
|
|
488
|
+
const cmdCount = installCommands();
|
|
489
|
+
if (cmdCount > 0) {
|
|
490
|
+
success(`${cmdCount} slash commands refreshed`);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
blank();
|
|
494
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}Done.${R} Restart Claude Code to apply.`);
|
|
495
|
+
|
|
496
|
+
footer();
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function preview() {
|
|
500
|
+
const themeName = args.includes('--theme') ? args[args.indexOf('--theme') + 1] : null;
|
|
501
|
+
const layoutName = args.includes('--layout') ? args[args.indexOf('--layout') + 1] : null;
|
|
502
|
+
|
|
503
|
+
const sampleJson = JSON.stringify({
|
|
504
|
+
cwd: process.cwd(),
|
|
505
|
+
session_id: 'preview-session',
|
|
506
|
+
version: '2.0.0',
|
|
507
|
+
model: { id: 'claude-opus-4-6', display_name: 'Opus' },
|
|
508
|
+
workspace: { current_dir: process.cwd(), project_dir: process.cwd() },
|
|
509
|
+
cost: { total_cost_usd: 1.23, total_duration_ms: 754000, total_api_duration_ms: 23400, total_lines_added: 156, total_lines_removed: 23 },
|
|
510
|
+
context_window: {
|
|
511
|
+
total_input_tokens: 125234, total_output_tokens: 34521,
|
|
512
|
+
context_window_size: 200000, used_percentage: 47, remaining_percentage: 53,
|
|
513
|
+
current_usage: { input_tokens: 85000, output_tokens: 12000, cache_creation_input_tokens: 5000, cache_read_input_tokens: 2000 }
|
|
514
|
+
},
|
|
515
|
+
vim: { mode: 'NORMAL' },
|
|
516
|
+
agent: { name: 'code-reviewer' }
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// Check if v2 engine is installed
|
|
520
|
+
const coreFile = path.join(SL_DIR, 'core.sh');
|
|
521
|
+
let scriptPath;
|
|
522
|
+
if (fs.existsSync(coreFile)) {
|
|
523
|
+
scriptPath = coreFile;
|
|
524
|
+
} else if (fs.existsSync(SCRIPT_DEST)) {
|
|
525
|
+
scriptPath = SCRIPT_DEST;
|
|
526
|
+
} else {
|
|
527
|
+
// Use the package's own script
|
|
528
|
+
scriptPath = path.join(PKG_DIR, 'lib', 'core.sh');
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
const env = { ...process.env };
|
|
532
|
+
if (themeName) env.STATUSLINE_THEME_OVERRIDE = themeName;
|
|
533
|
+
if (layoutName) env.STATUSLINE_LAYOUT_OVERRIDE = layoutName;
|
|
534
|
+
|
|
535
|
+
// For preview with package's own files, set STATUSLINE_DIR
|
|
536
|
+
if (!fs.existsSync(path.join(SL_DIR, 'core.sh'))) {
|
|
537
|
+
// Point to package's own lib directory structure
|
|
538
|
+
env.HOME = PKG_DIR;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
const escaped = sampleJson.replace(/'/g, "'\\''");
|
|
543
|
+
const result = execSync(`printf '%s' '${escaped}' | bash "${scriptPath.replace(/\\/g, '/')}"`, {
|
|
544
|
+
encoding: 'utf8',
|
|
545
|
+
env,
|
|
546
|
+
timeout: 5000
|
|
547
|
+
});
|
|
548
|
+
log('');
|
|
549
|
+
log(result);
|
|
550
|
+
log('');
|
|
551
|
+
} catch (e) {
|
|
552
|
+
warn(`Preview failed: ${e.message}`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function themeCmd() {
|
|
557
|
+
const config = readConfig();
|
|
558
|
+
|
|
559
|
+
if (subcommand === 'set') {
|
|
560
|
+
const name = args[2];
|
|
561
|
+
if (!name || !THEMES.includes(name)) {
|
|
562
|
+
header();
|
|
563
|
+
blank();
|
|
564
|
+
fail(`Unknown theme: ${name || '(none)'}`);
|
|
565
|
+
blank();
|
|
566
|
+
info(`Available: ${THEMES.join(', ')}`);
|
|
567
|
+
footer();
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
config.theme = name;
|
|
571
|
+
writeConfig(config);
|
|
572
|
+
header();
|
|
573
|
+
blank();
|
|
574
|
+
success(`Theme set to ${CYN}${B}${name}${R}`);
|
|
575
|
+
blank();
|
|
576
|
+
log(` ${GRAY}\u2502${R} Restart Claude Code to apply.`);
|
|
577
|
+
footer();
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// List themes
|
|
582
|
+
header();
|
|
583
|
+
blank();
|
|
584
|
+
info(`${B}Themes${R}`);
|
|
585
|
+
blank();
|
|
586
|
+
THEMES.forEach(t => {
|
|
587
|
+
const marker = t === config.theme ? ` ${GRN}\u2190 current${R}` : '';
|
|
588
|
+
log(` ${GRAY}\u2502${R} ${CYN}${t}${R}${marker}`);
|
|
589
|
+
});
|
|
590
|
+
blank();
|
|
591
|
+
bar(`Set theme: ${R}${CYN}ccsl theme set <name>${R}`);
|
|
592
|
+
bar(`Preview: ${R}${CYN}ccsl preview --theme <name>${R}`);
|
|
593
|
+
footer();
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function layoutCmd() {
|
|
597
|
+
const config = readConfig();
|
|
598
|
+
|
|
599
|
+
if (subcommand === 'set') {
|
|
600
|
+
const name = args[2];
|
|
601
|
+
if (!name || !LAYOUTS.includes(name)) {
|
|
602
|
+
header();
|
|
603
|
+
blank();
|
|
604
|
+
fail(`Unknown layout: ${name || '(none)'}`);
|
|
605
|
+
blank();
|
|
606
|
+
info(`Available: ${LAYOUTS.join(', ')}`);
|
|
607
|
+
footer();
|
|
608
|
+
process.exit(1);
|
|
609
|
+
}
|
|
610
|
+
config.layout = name;
|
|
611
|
+
writeConfig(config);
|
|
612
|
+
header();
|
|
613
|
+
blank();
|
|
614
|
+
success(`Layout set to ${CYN}${B}${name}${R}`);
|
|
615
|
+
blank();
|
|
616
|
+
log(` ${GRAY}\u2502${R} Restart Claude Code to apply.`);
|
|
617
|
+
footer();
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// List layouts
|
|
622
|
+
header();
|
|
623
|
+
blank();
|
|
624
|
+
info(`${B}Layouts${R}`);
|
|
625
|
+
blank();
|
|
626
|
+
const descriptions = { compact: '2 rows \u2014 minimal', standard: '4 rows \u2014 balanced', full: '6 rows \u2014 everything' };
|
|
627
|
+
LAYOUTS.forEach(l => {
|
|
628
|
+
const marker = l === config.layout ? ` ${GRN}\u2190 current${R}` : '';
|
|
629
|
+
log(` ${GRAY}\u2502${R} ${CYN}${l}${R} ${D}(${descriptions[l]})${R}${marker}`);
|
|
630
|
+
});
|
|
631
|
+
blank();
|
|
632
|
+
bar(`Set layout: ${R}${CYN}ccsl layout set <name>${R}`);
|
|
633
|
+
bar(`Preview: ${R}${CYN}ccsl preview --layout <name>${R}`);
|
|
634
|
+
footer();
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function configCmd() {
|
|
638
|
+
const config = readConfig();
|
|
639
|
+
|
|
640
|
+
if (subcommand === 'set') {
|
|
641
|
+
const key = args[2];
|
|
642
|
+
const value = args[3];
|
|
643
|
+
if (!key || value === undefined) {
|
|
644
|
+
header();
|
|
645
|
+
blank();
|
|
646
|
+
fail(`Usage: ccsl config set <key> <value>`);
|
|
647
|
+
blank();
|
|
648
|
+
info(`Keys: compaction_warning_threshold, bar_width, cache_ttl_seconds,`);
|
|
649
|
+
info(` show_burn_rate, show_vim_mode, show_agent_name`);
|
|
650
|
+
footer();
|
|
651
|
+
process.exit(1);
|
|
652
|
+
}
|
|
653
|
+
if (!config.options) config.options = {};
|
|
654
|
+
// Parse booleans and numbers
|
|
655
|
+
if (value === 'true') config.options[key] = true;
|
|
656
|
+
else if (value === 'false') config.options[key] = false;
|
|
657
|
+
else if (!isNaN(value)) config.options[key] = Number(value);
|
|
658
|
+
else config.options[key] = value;
|
|
659
|
+
|
|
660
|
+
writeConfig(config);
|
|
661
|
+
header();
|
|
662
|
+
blank();
|
|
663
|
+
success(`Set ${CYN}${key}${R} = ${CYN}${value}${R}`);
|
|
664
|
+
footer();
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// Show config
|
|
669
|
+
header();
|
|
670
|
+
blank();
|
|
671
|
+
info(`${B}Current configuration${R}`);
|
|
672
|
+
blank();
|
|
673
|
+
log(` ${GRAY}\u2502${R} ${WHT}Theme:${R} ${CYN}${config.theme}${R}`);
|
|
674
|
+
log(` ${GRAY}\u2502${R} ${WHT}Layout:${R} ${CYN}${config.layout}${R}`);
|
|
675
|
+
if (config.options && Object.keys(config.options).length > 0) {
|
|
676
|
+
blank();
|
|
677
|
+
info(`${B}Options${R}`);
|
|
678
|
+
blank();
|
|
679
|
+
for (const [k, v] of Object.entries(config.options)) {
|
|
680
|
+
log(` ${GRAY}\u2502${R} ${D}${k}:${R} ${CYN}${v}${R}`);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
blank();
|
|
684
|
+
bar(`File: ${R}${CYN}~/.claude/statusline-config.json${R}`);
|
|
685
|
+
footer();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function doctor() {
|
|
689
|
+
header();
|
|
690
|
+
blank();
|
|
691
|
+
info(`${B}Diagnostic check${R}`);
|
|
692
|
+
blank();
|
|
693
|
+
|
|
694
|
+
let issues = 0;
|
|
695
|
+
|
|
696
|
+
// 1. Bash
|
|
697
|
+
try {
|
|
698
|
+
const bashVer = execSync('bash --version 2>&1', { encoding: 'utf8' }).split('\n')[0];
|
|
699
|
+
success(`bash: ${D}${bashVer.substring(0, 60)}${R}`);
|
|
700
|
+
} catch (e) {
|
|
701
|
+
fail(`bash not found`);
|
|
702
|
+
issues++;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// 2. Git
|
|
706
|
+
try {
|
|
707
|
+
execSync('git --version', { encoding: 'utf8' });
|
|
708
|
+
success(`git available`);
|
|
709
|
+
} catch (e) {
|
|
710
|
+
warn(`git not found (GitHub field will show "no-git")`);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// 3. settings.json
|
|
714
|
+
if (fs.existsSync(SETTINGS_PATH)) {
|
|
715
|
+
try {
|
|
716
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_PATH, 'utf8'));
|
|
717
|
+
if (settings.statusLine && settings.statusLine.command) {
|
|
718
|
+
success(`settings.json has statusLine config`);
|
|
719
|
+
} else {
|
|
720
|
+
fail(`settings.json missing statusLine entry`);
|
|
721
|
+
issues++;
|
|
722
|
+
}
|
|
723
|
+
} catch (e) {
|
|
724
|
+
fail(`settings.json is invalid JSON`);
|
|
725
|
+
issues++;
|
|
726
|
+
}
|
|
727
|
+
} else {
|
|
728
|
+
fail(`~/.claude/settings.json not found`);
|
|
729
|
+
issues++;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 4. Entry point script
|
|
733
|
+
if (fs.existsSync(SCRIPT_DEST)) {
|
|
734
|
+
success(`statusline-command.sh exists`);
|
|
735
|
+
} else {
|
|
736
|
+
fail(`~/.claude/statusline-command.sh not found`);
|
|
737
|
+
issues++;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// 5. v2 engine
|
|
741
|
+
const coreFile = path.join(SL_DIR, 'core.sh');
|
|
742
|
+
if (fs.existsSync(coreFile)) {
|
|
743
|
+
success(`v2 engine installed (statusline/core.sh)`);
|
|
744
|
+
|
|
745
|
+
// Check theme file
|
|
746
|
+
const config = readConfig();
|
|
747
|
+
const themeFile = path.join(SL_DIR, 'themes', `${config.theme}.sh`);
|
|
748
|
+
if (fs.existsSync(themeFile)) {
|
|
749
|
+
success(`Theme "${config.theme}" found`);
|
|
750
|
+
} else {
|
|
751
|
+
fail(`Theme "${config.theme}" not found at ${themeFile}`);
|
|
752
|
+
issues++;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Check layout file
|
|
756
|
+
const layoutFile = path.join(SL_DIR, 'layouts', `${config.layout}.sh`);
|
|
757
|
+
if (fs.existsSync(layoutFile)) {
|
|
758
|
+
success(`Layout "${config.layout}" found`);
|
|
759
|
+
} else {
|
|
760
|
+
fail(`Layout "${config.layout}" not found at ${layoutFile}`);
|
|
761
|
+
issues++;
|
|
762
|
+
}
|
|
763
|
+
} else {
|
|
764
|
+
warn(`v2 engine not installed (running v1 fallback)`);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// 6. CLAUDE.md agent redirect
|
|
768
|
+
if (fs.existsSync(CLAUDE_MD_PATH)) {
|
|
769
|
+
const mdContent = fs.readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
770
|
+
if (mdContent.includes(CLAUDE_MD_START)) {
|
|
771
|
+
success(`CLAUDE.md has statusline agent redirect`);
|
|
772
|
+
} else {
|
|
773
|
+
warn(`CLAUDE.md exists but missing statusline section`);
|
|
774
|
+
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add it`);
|
|
775
|
+
}
|
|
776
|
+
} else {
|
|
777
|
+
warn(`No ~/.claude/CLAUDE.md (built-in statusline agent may interfere)`);
|
|
778
|
+
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to fix`);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
// 7. Slash commands
|
|
782
|
+
if (fs.existsSync(COMMANDS_DIR)) {
|
|
783
|
+
const installed = SLS_COMMANDS.filter(c => fs.existsSync(path.join(COMMANDS_DIR, `${c}.md`)));
|
|
784
|
+
if (installed.length === SLS_COMMANDS.length) {
|
|
785
|
+
success(`All ${SLS_COMMANDS.length} slash commands installed`);
|
|
786
|
+
} else if (installed.length > 0) {
|
|
787
|
+
warn(`${installed.length}/${SLS_COMMANDS.length} slash commands installed`);
|
|
788
|
+
info(`Run ${CYN}ccsl update${R} to install missing commands`);
|
|
789
|
+
} else {
|
|
790
|
+
warn(`No slash commands found in ~/.claude/commands/`);
|
|
791
|
+
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add them`);
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
warn(`~/.claude/commands/ not found (slash commands not installed)`);
|
|
795
|
+
info(`Run ${CYN}ccsl install${R} or ${CYN}ccsl update${R} to add them`);
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
// 8. Config file
|
|
799
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
800
|
+
try {
|
|
801
|
+
JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
802
|
+
success(`statusline-config.json is valid`);
|
|
803
|
+
} catch (e) {
|
|
804
|
+
fail(`statusline-config.json is invalid JSON`);
|
|
805
|
+
issues++;
|
|
806
|
+
}
|
|
807
|
+
} else {
|
|
808
|
+
warn(`No config file (using defaults)`);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// 9. Performance benchmark
|
|
812
|
+
blank();
|
|
813
|
+
info(`${B}Performance benchmark${R}`);
|
|
814
|
+
blank();
|
|
815
|
+
try {
|
|
816
|
+
const sampleJson = '{"model":{"id":"claude-opus-4-6","display_name":"Opus"},"workspace":{"current_dir":"/tmp"},"cost":{"total_cost_usd":0.5},"context_window":{"context_window_size":200000,"used_percentage":50,"current_usage":{"input_tokens":90000,"output_tokens":10000,"cache_creation_input_tokens":0,"cache_read_input_tokens":0}}}';
|
|
817
|
+
const target = fs.existsSync(coreFile) ? coreFile : SCRIPT_DEST;
|
|
818
|
+
if (target && fs.existsSync(target)) {
|
|
819
|
+
const start = Date.now();
|
|
820
|
+
execSync(`printf '%s' '${sampleJson}' | bash "${target.replace(/\\/g, '/')}"`, {
|
|
821
|
+
encoding: 'utf8',
|
|
822
|
+
timeout: 10000
|
|
823
|
+
});
|
|
824
|
+
const elapsed = Date.now() - start;
|
|
825
|
+
const color = elapsed < 50 ? GRN : elapsed < 100 ? YLW : RED;
|
|
826
|
+
const label = elapsed < 50 ? 'excellent' : elapsed < 100 ? 'good' : 'slow';
|
|
827
|
+
log(` ${GRAY}\u2502${R} ${color}\u25CF${R} Execution: ${color}${B}${elapsed}ms${R} (${label}, target: <50ms)`);
|
|
828
|
+
}
|
|
829
|
+
} catch (e) {
|
|
830
|
+
fail(`Benchmark failed: ${e.message.substring(0, 50)}`);
|
|
831
|
+
issues++;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
blank();
|
|
835
|
+
if (issues === 0) {
|
|
836
|
+
log(` ${GRAY}\u2502${R} ${GRN}${B}All checks passed.${R}`);
|
|
837
|
+
} else {
|
|
838
|
+
log(` ${GRAY}\u2502${R} ${RED}${B}${issues} issue(s) found.${R} Run ${CYN}ccsl install${R} to fix.`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
footer();
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function showVersion() {
|
|
845
|
+
log(`skill-statusline v${VERSION}`);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
function showHelp() {
|
|
849
|
+
header();
|
|
850
|
+
blank();
|
|
851
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Commands:${R}`);
|
|
852
|
+
blank();
|
|
853
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl install${R} Install with theme/layout wizard`);
|
|
854
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl install --quick${R} Install with defaults`);
|
|
855
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl uninstall${R} Remove statusline`);
|
|
856
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl update${R} Update scripts (keep config)`);
|
|
857
|
+
blank();
|
|
858
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl theme${R} List themes`);
|
|
859
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl theme set <name>${R} Set active theme`);
|
|
860
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl layout${R} List layouts`);
|
|
861
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl layout set <name>${R} Set active layout`);
|
|
862
|
+
blank();
|
|
863
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview${R} Preview with sample data`);
|
|
864
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview --theme x${R} Preview a specific theme`);
|
|
865
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl preview --layout x${R} Preview a specific layout`);
|
|
866
|
+
blank();
|
|
867
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl config${R} Show current config`);
|
|
868
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl config set k v${R} Set config option`);
|
|
869
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl doctor${R} Run diagnostics`);
|
|
870
|
+
log(` ${GRAY}\u2502${R} ${CYN}ccsl version${R} Show version`);
|
|
871
|
+
blank();
|
|
872
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Slash Commands${R} ${D}(inside Claude Code):${R}`);
|
|
873
|
+
blank();
|
|
874
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-theme${R} List or set theme`);
|
|
875
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-layout${R} List or set layout`);
|
|
876
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-preview${R} Preview with sample data`);
|
|
877
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-config${R} Show or set config options`);
|
|
878
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-doctor${R} Run diagnostics`);
|
|
879
|
+
log(` ${GRAY}\u2502${R} ${PINK}/sls-help${R} Show all commands`);
|
|
880
|
+
blank();
|
|
881
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Themes:${R} ${THEMES.join(', ')}`);
|
|
882
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}Layouts:${R} ${LAYOUTS.join(', ')}`);
|
|
883
|
+
blank();
|
|
884
|
+
log(` ${GRAY}\u2502${R} ${WHT}${B}What it shows:${R}`);
|
|
885
|
+
blank();
|
|
886
|
+
log(` ${GRAY}\u2502${R} ${PINK}Skill${R} Last tool used (Read, Write, Terminal, Agent...)`);
|
|
887
|
+
log(` ${GRAY}\u2502${R} ${PURPLE}Model${R} Active model name and version`);
|
|
888
|
+
log(` ${GRAY}\u2502${R} ${WHT}GitHub${R} user/repo/branch with dirty indicators`);
|
|
889
|
+
log(` ${GRAY}\u2502${R} ${TEAL}Dir${R} Last 3 segments of working directory`);
|
|
890
|
+
log(` ${GRAY}\u2502${R} ${YLW}Tokens${R} Current window: input + output`);
|
|
891
|
+
log(` ${GRAY}\u2502${R} ${GRN}Cost${R} Session cost in USD`);
|
|
892
|
+
log(` ${GRAY}\u2502${R} ${WHT}Context${R} Accurate progress bar with compaction warning`);
|
|
893
|
+
log(` ${GRAY}\u2502${R} ${D}+ Session tokens, duration, lines, cache, vim, agent (full layout)${R}`);
|
|
894
|
+
blank();
|
|
895
|
+
bar(`Docs ${R}${TEAL}https://skills.thinqmesh.com${R}`);
|
|
896
|
+
bar(`GitHub ${R}${PURPLE}https://github.com/AnitChaudhry/skill-statusline${R}`);
|
|
897
|
+
|
|
898
|
+
footer();
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// ── Main ──
|
|
902
|
+
|
|
903
|
+
if (command === 'install' || command === 'init') {
|
|
904
|
+
install();
|
|
905
|
+
} else if (command === 'uninstall' || command === 'remove') {
|
|
906
|
+
uninstall();
|
|
907
|
+
} else if (command === 'update' || command === 'upgrade') {
|
|
908
|
+
update();
|
|
909
|
+
} else if (command === 'preview') {
|
|
910
|
+
preview();
|
|
911
|
+
} else if (command === 'theme') {
|
|
912
|
+
themeCmd();
|
|
913
|
+
} else if (command === 'layout') {
|
|
914
|
+
layoutCmd();
|
|
915
|
+
} else if (command === 'config') {
|
|
916
|
+
configCmd();
|
|
917
|
+
} else if (command === 'doctor' || command === 'check') {
|
|
918
|
+
doctor();
|
|
919
|
+
} else if (command === 'version' || command === '--version' || command === '-v') {
|
|
920
|
+
showVersion();
|
|
921
|
+
} else if (command === 'help' || command === '--help' || command === '-h') {
|
|
922
|
+
showHelp();
|
|
923
|
+
} else {
|
|
924
|
+
if (command) {
|
|
925
|
+
log('');
|
|
926
|
+
warn(`Unknown command: ${command}`);
|
|
927
|
+
}
|
|
928
|
+
showHelp();
|
|
929
|
+
}
|