skill-statusline 2.4.0 → 2.4.2

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/README.md CHANGED
@@ -124,17 +124,17 @@ Stored in `~/.claude/statusline-config.json`:
124
124
 
125
125
  ## Architecture
126
126
 
127
- Two rendering engines the installer picks the right one for your platform:
127
+ Single entry point (`~/.claude/statusline-command.sh`) on all platforms. On Windows, it auto-delegates to the Node.js renderer:
128
128
 
129
- - **Windows**: Node.js renderer (`statusline-node.js`) single process, no subprocess spawning, ~30-50ms
130
- - **macOS/Linux**: Bash engine (`core.sh`) pure bash, zero dependencies, <50ms with caching
129
+ - **Windows**: `statusline-command.sh` detects Git Bash/MSYS2 exec `node statusline-node.js` (~30-50ms)
130
+ - **macOS/Linux**: `statusline-command.sh` exec `bash core.sh` (pure bash, <50ms with caching)
131
131
 
132
132
  Git Bash on Windows has ~50-100ms overhead *per subprocess spawn* (awk, sed, grep, git, date). A bash statusline spawning 10-20 subprocesses = 500-1000ms. The Node.js renderer eliminates this by doing everything in a single process.
133
133
 
134
134
  ```
135
135
  ~/.claude/
136
- statusline-command.sh # Bash entry point (macOS/Linux)
137
- statusline-node.js # Node.js renderer (Windows)
136
+ statusline-command.sh # Entry point (all platforms)
137
+ statusline-node.js # Node.js renderer (auto-used on Windows)
138
138
  statusline/
139
139
  core.sh # Bash engine: parse JSON, compute fields, render
140
140
  json-parser.sh # Nested JSON extraction (no jq)
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.4.0';
12
+ const VERSION = '2.4.2';
13
13
 
14
14
  const PKG_DIR = path.resolve(__dirname, '..');
15
15
  const HOME = os.homedir();
@@ -110,16 +110,26 @@ function writeSettings(settings) {
110
110
  // instead of the built-in statusline-setup agent (which fails on Windows
111
111
  // and conflicts with our setup).
112
112
 
113
- const CLAUDE_MD_SECTION = `${CLAUDE_MD_START}
113
+ function getClaudeMdSection() {
114
+ const isWin = process.platform === 'win32';
115
+ const howItWorks = isWin
116
+ ? `### How It Works
117
+ - \`~/.claude/settings.json\` → runs \`~/.claude/statusline-command.sh\`
118
+ - \`~/.claude/statusline-command.sh\` → detects Windows, delegates to Node.js renderer
119
+ - \`~/.claude/statusline-node.js\` → Node.js renderer (fast on Windows, no bash subprocess overhead)
120
+ - \`~/.claude/statusline-config.json\` → user preferences (theme, layout, options)`
121
+ : `### How It Works
122
+ - \`~/.claude/settings.json\` → runs \`~/.claude/statusline-command.sh\`
123
+ - \`~/.claude/statusline-command.sh\` → entry point, delegates to v2 bash engine
124
+ - \`~/.claude/statusline/core.sh\` → v2 engine (themes, layouts, accurate context tracking)
125
+ - \`~/.claude/statusline-config.json\` → user preferences (theme, layout, options)`;
126
+
127
+ return `${CLAUDE_MD_START}
114
128
  ## Statusline — Managed by skill-statusline v2
115
129
 
116
130
  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
131
 
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)
132
+ ${howItWorks}
123
133
 
124
134
  ### When the user asks about statusline configuration, use these CLI commands:
125
135
 
@@ -157,6 +167,7 @@ Users can also use these slash commands inside Claude Code:
157
167
  - All statusline changes are made via \`ccsl\` CLI commands (run in terminal) or \`/sls-*\` slash commands
158
168
  - Changes take effect on next Claude Code restart (or next statusline refresh for config changes)
159
169
  ${CLAUDE_MD_END}`;
170
+ }
160
171
 
161
172
  function installClaudeMd() {
162
173
  let content = '';
@@ -171,7 +182,7 @@ function installClaudeMd() {
171
182
  }
172
183
  }
173
184
  // Append our section
174
- content = content ? content + '\n\n' + CLAUDE_MD_SECTION + '\n' : CLAUDE_MD_SECTION + '\n';
185
+ content = content ? content + '\n\n' + getClaudeMdSection() + '\n' : getClaudeMdSection() + '\n';
175
186
  fs.writeFileSync(CLAUDE_MD_PATH, content);
176
187
  }
177
188
 
@@ -351,8 +362,13 @@ async function install() {
351
362
  }
352
363
 
353
364
  // Install files
365
+ const isWin = process.platform === 'win32';
354
366
  installFiles();
355
- success(`${B}statusline/${R} directory installed to ~/.claude/`);
367
+ if (isWin) {
368
+ success(`${B}statusline-node.js${R} renderer installed to ~/.claude/`);
369
+ } else {
370
+ success(`${B}statusline/${R} engine installed to ~/.claude/`);
371
+ }
356
372
 
357
373
  // Write config
358
374
  if (!config.options) config.options = {};
@@ -360,28 +376,20 @@ async function install() {
360
376
  success(`Config: theme=${CYN}${config.theme}${R}, layout=${CYN}${config.layout}${R}`);
361
377
 
362
378
  // Update settings.json
379
+ // Use ~/.claude/statusline-command.sh on ALL platforms
380
+ // Claude Code on Windows runs commands through Git Bash, which resolves ~
381
+ // The bash script detects Windows and delegates to Node.js renderer automatically
363
382
  const settings = readSettings();
364
- const isWin = process.platform === 'win32';
365
383
  const prevCmd = settings.statusLine?.command || '';
384
+ const expectedCmd = '~/.claude/statusline-command.sh';
366
385
  const needsUpdate = !settings.statusLine
367
- || prevCmd === 'bash ~/.claude/statusline-command.sh'
368
- || (isWin && prevCmd.includes('bash.exe'))
369
- || (isWin && prevCmd.includes('\\\\'));
386
+ || prevCmd !== expectedCmd;
370
387
  if (needsUpdate) {
371
- let cmd;
372
- if (isWin) {
373
- // Windows: use Node.js renderer directly — avoids Git Bash/MSYS2 overhead
374
- // (~50-100ms per subprocess spawn in Git Bash vs single Node.js process)
375
- const nodeScript = path.join(CLAUDE_DIR, 'statusline-node.js');
376
- cmd = `node "${nodeScript.replace(/\\/g, '/')}"`;
377
- } else {
378
- cmd = 'bash ~/.claude/statusline-command.sh';
379
- }
380
- settings.statusLine = { type: 'command', command: cmd };
388
+ settings.statusLine = { type: 'command', command: expectedCmd };
381
389
  writeSettings(settings);
382
390
  success(`${B}statusLine${R} config added to settings.json`);
383
391
  if (isWin) {
384
- info(`Using Node.js renderer (fast on Windows)`);
392
+ info(`Windows: auto-delegates to Node.js renderer (fast)`);
385
393
  }
386
394
  } else {
387
395
  success(`statusLine already configured in settings.json`);
@@ -422,10 +430,10 @@ async function install() {
422
430
  }
423
431
 
424
432
  blank();
433
+ bar(`Script: ${R}${CYN}~/.claude/statusline-command.sh${R}`);
425
434
  if (isWin) {
426
- bar(`Renderer: ${R}${CYN}~/.claude/statusline-node.js${R} ${D}(Node.js)${R}`);
435
+ bar(`Renderer: ${R}${CYN}~/.claude/statusline-node.js${R} ${D}(Node.js — auto on Windows)${R}`);
427
436
  } else {
428
- bar(`Script: ${R}${CYN}~/.claude/statusline-command.sh${R}`);
429
437
  bar(`Engine: ${R}${CYN}~/.claude/statusline/core.sh${R}`);
430
438
  }
431
439
  bar(`Config: ${R}${CYN}~/.claude/statusline-config.json${R}`);
package/bin/statusline.sh CHANGED
@@ -1,8 +1,18 @@
1
1
  #!/usr/bin/env bash
2
- # skill-statusline v2.0 — Entry point
3
- # Delegates to modular v2 engine if installed, falls back to v1 inline
2
+ # skill-statusline v2.4 — Entry point
3
+ # On Windows: delegates to Node.js renderer (avoids Git Bash subprocess overhead)
4
+ # On Unix: delegates to modular v2 bash engine
4
5
 
5
6
  STATUSLINE_DIR="${HOME}/.claude/statusline"
7
+ NODE_RENDERER="${HOME}/.claude/statusline-node.js"
8
+
9
+ # Windows detection: MSYS, MINGW, or CYGWIN environment (Git Bash)
10
+ if [[ "$OSTYPE" == msys* ]] || [[ "$OSTYPE" == mingw* ]] || [[ "$OSTYPE" == cygwin* ]] || [[ -n "$MSYSTEM" ]]; then
11
+ # Use Node.js renderer — single process, no subprocess overhead
12
+ if [ -f "$NODE_RENDERER" ] && command -v node &>/dev/null; then
13
+ exec node "$NODE_RENDERER"
14
+ fi
15
+ fi
6
16
 
7
17
  if [ -f "${STATUSLINE_DIR}/core.sh" ]; then
8
18
  # v2: modular engine with themes, layouts, accurate context
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skill-statusline",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Rich, themeable statusline for Claude Code — accurate context tracking, 5 themes, 3 layouts, token/cost/GitHub/skill display. Node.js renderer on Windows (no bash overhead), pure bash on Unix.",
5
5
  "bin": {
6
6
  "skill-statusline": "bin/cli.js",
package/bin/cli.js.bak DELETED
@@ -1,837 +0,0 @@
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
- }