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