@alyibrahim/claude-statusline 1.4.6 → 1.5.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/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  <div align="center">
2
2
 
3
- <img src=".github/assets/logo.svg" alt="claude-statusline" width="600">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/AlyIbrahim1/claude-statusline/main/.github/assets/logo-dark.svg">
5
+ <img src="https://raw.githubusercontent.com/AlyIbrahim1/claude-statusline/main/.github/assets/logo-light.svg" alt="claude-statusline" width="600">
6
+ </picture>
4
7
 
5
8
  [![CI](https://github.com/AlyIbrahim1/claude-statusline/actions/workflows/ci.yml/badge.svg)](https://github.com/AlyIbrahim1/claude-statusline/actions/workflows/ci.yml)
6
9
  [![npm](https://img.shields.io/npm/v/@alyibrahim/claude-statusline)](https://www.npmjs.com/package/@alyibrahim/claude-statusline)
@@ -60,11 +63,9 @@ Done. The statusline configures itself automatically. Restart Claude Code to see
60
63
 
61
64
  ## Session History
62
65
 
63
- </div>
64
-
65
- Track token usage, cost, and duration across every Claude Code session with a built-in analytics dashboard.
66
+ Track token usage, cost, and duration across every Claude Code session. Choose between a **terminal-native TUI** or a **browser dashboard** — your preference is saved automatically.
66
67
 
67
- ![history dashboard](https://raw.githubusercontent.com/AlyIbrahim1/claude-statusline/main/.github/assets/history-dashboard.png)
68
+ ![history dashboard](assets/dashboard-preview.png)
68
69
 
69
70
  Session history is **enabled by default** on setup. Each session records:
70
71
 
@@ -77,15 +78,63 @@ Session history is **enabled by default** on setup. Each session records:
77
78
  | Duration | Session length in seconds |
78
79
  | Exit reason | How the session ended |
79
80
 
81
+ </div>
82
+
80
83
  **Commands:**
81
84
 
82
85
  ```bash
83
- claude-statusline history # Open the analytics dashboard
84
- claude-statusline enable-history # Enable session tracking
85
- claude-statusline disable-history # Disable session tracking
86
+ claude-statusline history # Open dashboard in saved mode (default: web)
87
+ claude-statusline history --mode terminal # Switch to terminal TUI (persisted)
88
+ claude-statusline history --mode web # Switch to browser dashboard (persisted)
89
+ claude-statusline enable-history # Enable session tracking
90
+ claude-statusline disable-history # Disable session tracking
91
+ ```
92
+
93
+ Data is stored at `~/.claude/statusline-history.jsonl`.
94
+
95
+ ### Terminal TUI
96
+
97
+ `--mode terminal` opens an interactive full-screen dashboard directly in your terminal — no browser required. Requires the native Rust binary (falls back to web dashboard with a warning if unavailable).
98
+
99
+ ```
100
+ ┌─ claude-statusline history ──────────────────────────────────────────────┐
101
+ │ Sessions: 42 Tokens: 8.3M Cost: $12.47 │
102
+ └──────────────────────────────────────────────────────────────────────────┘
103
+ ┌──────────────────────────────────────────────────────────────────────────┐
104
+ │ Filter: [All Projects ▾] │
105
+ └──────────────────────────────────────────────────────────────────────────┘
106
+ ┌──────────────────────────────────────────────────────────────────────────┐
107
+ │ Project Model Started Dur Tok Cost│
108
+ │ │
109
+ │> my-web-app claude-sonnet-4-6 2026-04-02 14: 32m 842k $2.14│
110
+ │ cli-tool claude-opus-4-6 2026-04-01 09: 1h4m 1.2M $5.60│
111
+ │ data-pipeline claude-haiku-4-5 2026-03-31 17: 18m 220k $0.44│
112
+ │ my-web-app claude-sonnet-4-6 2026-03-30 11: 45m 990k $2.89│
113
+ │ api-server claude-sonnet-4-6 2026-03-29 08: 2h1m 2.1M $8.33│
114
+ │ │
115
+ └──────────────────────────────────────────────────────────────────────────┘
116
+ ┌──────────────────────────────────────────────────────────────────────────┐
117
+ │ up/down or j/k: move f: filter Enter: apply Esc: close q: quit │
118
+ └──────────────────────────────────────────────────────────────────────────┘
119
+ ```
120
+
121
+ Press `f` to open the project filter popup:
122
+
123
+ ```
124
+ ┌─ Filter by Project ──────────┐
125
+ │ ██ All Projects ██ │
126
+ │ api-server │
127
+ │ cli-tool │
128
+ │ data-pipeline │
129
+ │ my-web-app │
130
+ └──────────────────────────────┘
86
131
  ```
87
132
 
88
- Data is stored at `~/.claude/statusline-history.jsonl`. The dashboard opens in your browser and supports project filtering and light/dark theme toggle.
133
+ Rows are color-coded by exit reason: green = normal, yellow = interrupted, orange = pending.
134
+
135
+ ### Web Dashboard
136
+
137
+ `--mode web` (the default) opens a browser-based dashboard with project filtering and light/dark theme toggle.
89
138
 
90
139
  ---
91
140
 
@@ -117,7 +166,7 @@ See [PLATFORMS.md](PLATFORMS.md) for the full compatibility guide, per-platform
117
166
  | Subagent counter | Counts active agents from todos dir | — |
118
167
  | Session tokens | Real-time via JSONL offset cache, split I/O (`X↓ Y↑`) | Stale stdin snapshot or none |
119
168
  | Session commits | Tracks git commits made this session | — |
120
- | Session history | Analytics dashboard with per-project filtering, zero dependencies | — |
169
+ | Session history | Terminal TUI + browser dashboard, per-project filtering, zero dependencies | — |
121
170
 
122
171
  ---
123
172
 
package/bin/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
- const { setup, toggleHistory } = require('../scripts/setup');
3
+ const { setup, toggleHistory, getDashboardMode, setDashboardMode } = require('../scripts/setup');
4
4
  const { uninstall } = require('../scripts/uninstall');
5
5
  const config = require('../scripts/config');
6
6
  const { getSettingsPath } = config;
@@ -13,11 +13,92 @@ claude-statusline <command>
13
13
  Commands:
14
14
  setup Configure ~/.claude/settings.json to use this statusline
15
15
  uninstall Remove this statusline from ~/.claude/settings.json
16
- enable-history Enable tracking session analytics to SQLite (default on setup)
16
+ enable-history Enable tracking session analytics to JSONL (default on setup)
17
17
  disable-history Remove history tracking hooks from Claude settings
18
18
  history Open the session analytics dashboard
19
+ --mode web|terminal (persist dashboard mode preference)
19
20
  `.trim();
20
21
 
22
+ const TERMINAL_FALLBACK_WARNING = [
23
+ '[claude-statusline] terminal mode requires the native binary.',
24
+ 'Falling back to web dashboard. To install the binary, run:',
25
+ ' npm install -g @alyibrahim/claude-statusline'
26
+ ].join('\n');
27
+
28
+ function parseHistoryMode(args) {
29
+ let mode;
30
+ for (let i = 0; i < args.length; i++) {
31
+ const arg = args[i];
32
+ if (arg === '--mode') {
33
+ const value = args[i + 1];
34
+ if (!value) {
35
+ return { ok: false, error: 'Missing value for --mode. Expected "web" or "terminal".' };
36
+ }
37
+ mode = value;
38
+ i++;
39
+ continue;
40
+ }
41
+ if (arg.startsWith('--mode=')) {
42
+ mode = arg.slice('--mode='.length);
43
+ continue;
44
+ }
45
+ return { ok: false, error: `Unknown history option: ${arg}` };
46
+ }
47
+
48
+ if (mode !== undefined && mode !== 'web' && mode !== 'terminal') {
49
+ return { ok: false, error: `Invalid mode: ${mode}. Expected "web" or "terminal".` };
50
+ }
51
+
52
+ return { ok: true, mode };
53
+ }
54
+
55
+ function runHistory() {
56
+ const parseResult = parseHistoryMode(process.argv.slice(3));
57
+ if (!parseResult.ok) {
58
+ console.error('Error:', parseResult.error);
59
+ process.exit(1);
60
+ }
61
+
62
+ let mode;
63
+ if (parseResult.mode) {
64
+ const saved = setDashboardMode(parseResult.mode);
65
+ if (!saved.ok) {
66
+ console.error('Error:', saved.error);
67
+ process.exit(1);
68
+ }
69
+ mode = parseResult.mode;
70
+ } else {
71
+ const saved = getDashboardMode();
72
+ if (!saved.ok) {
73
+ console.error('Error:', saved.error);
74
+ process.exit(1);
75
+ }
76
+ mode = saved.mode;
77
+ }
78
+
79
+ const binaryPath = config.resolveBinary();
80
+ const scriptPath = path.resolve(__dirname, '../statusline.js');
81
+
82
+ if (mode === 'terminal') {
83
+ if (binaryPath) {
84
+ const child = spawnSync(binaryPath, ['history', '--terminal'], { stdio: 'inherit' });
85
+ process.exit(child.status || 0);
86
+ }
87
+
88
+ console.error(TERMINAL_FALLBACK_WARNING);
89
+ const child = spawnSync(process.execPath, [scriptPath, 'history'], { stdio: 'inherit' });
90
+ process.exit(child.status || 0);
91
+ }
92
+
93
+ if (binaryPath) {
94
+ const child = spawnSync(binaryPath, ['history'], { stdio: 'inherit' });
95
+ process.exit(child.status || 0);
96
+ }
97
+
98
+ const child = spawnSync(process.execPath, [scriptPath, 'history'], { stdio: 'inherit' });
99
+ process.exit(child.status || 0);
100
+ }
101
+
21
102
  const cmd = process.argv[2];
22
103
 
23
104
  if (cmd === 'setup') {
@@ -52,7 +133,7 @@ if (cmd === 'setup') {
52
133
  }
53
134
  console.log(`✓ History tracking disabled from ${result.settingsPath}`);
54
135
 
55
- } else if (cmd === 'hook' || cmd === 'history') {
136
+ } else if (cmd === 'hook') {
56
137
  const binaryPath = config.resolveBinary();
57
138
  const scriptPath = path.resolve(__dirname, '../statusline.js');
58
139
 
@@ -66,6 +147,9 @@ if (cmd === 'setup') {
66
147
  process.exit(child.status || 0);
67
148
  }
68
149
 
150
+ } else if (cmd === 'history') {
151
+ runHistory();
152
+
69
153
  } else if (cmd === undefined) {
70
154
  console.log(USAGE);
71
155
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alyibrahim/claude-statusline",
3
- "version": "1.4.6",
3
+ "version": "1.5.0",
4
4
  "description": "Rich statusline for Claude Code — model, context bar, real-time token tracking, git branch, rate limits, and session stats. Rust binary, ~5ms startup.",
5
5
  "keywords": [
6
6
  "claude",
@@ -56,11 +56,11 @@
56
56
  "open": "^10.1.0"
57
57
  },
58
58
  "optionalDependencies": {
59
- "@alyibrahim/claude-statusline-linux-x64": "1.4.0",
60
- "@alyibrahim/claude-statusline-linux-arm64": "1.4.0",
61
- "@alyibrahim/claude-statusline-darwin-x64": "1.4.0",
62
- "@alyibrahim/claude-statusline-darwin-arm64": "1.4.0",
63
- "@alyibrahim/claude-statusline-win32-x64": "1.4.0"
59
+ "@alyibrahim/claude-statusline-linux-x64": "1.5.0",
60
+ "@alyibrahim/claude-statusline-linux-arm64": "1.5.0",
61
+ "@alyibrahim/claude-statusline-darwin-x64": "1.5.0",
62
+ "@alyibrahim/claude-statusline-darwin-arm64": "1.5.0",
63
+ "@alyibrahim/claude-statusline-win32-x64": "1.5.0"
64
64
  },
65
65
  "devDependencies": {
66
66
  "jest": "^29.0.0"
package/scripts/setup.js CHANGED
@@ -62,9 +62,9 @@ function updateHooks(settings, command, enable) {
62
62
  // Remove any existing statusline hook entries (both old and new format)
63
63
  settings.hooks[hookName] = settings.hooks[hookName].filter(h => {
64
64
  if (h.hooks) {
65
- return !h.hooks.some(inner => inner.command && (inner.command.includes('hook start') || inner.command.includes('hook end')));
65
+ return !h.hooks.some(inner => inner.command && (inner.command.endsWith(' hook start') || inner.command.endsWith(' hook end')));
66
66
  }
67
- return !(h.command && (h.command.includes('hook start') || h.command.includes('hook end')));
67
+ return !(h.command && (h.command.endsWith(' hook start') || h.command.endsWith(' hook end')));
68
68
  });
69
69
  if (enable) {
70
70
  settings.hooks[hookName].push({ matcher: '', hooks: [{ type: 'command', command: cmdString }] });
@@ -109,4 +109,47 @@ function toggleHistory(enable) {
109
109
  return { ok: true, settingsPath };
110
110
  }
111
111
 
112
- module.exports = { setup, toggleHistory, updateHooks };
112
+ function getDashboardMode() {
113
+ const settingsPath = getSettingsPath();
114
+ if (!fs.existsSync(settingsPath)) {
115
+ return { ok: true, settingsPath, mode: 'web' };
116
+ }
117
+
118
+ let settings = {};
119
+ try {
120
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
121
+ } catch (e) {
122
+ return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
123
+ }
124
+
125
+ const mode = settings.dashboardMode === 'terminal' ? 'terminal' : 'web';
126
+ return { ok: true, settingsPath, mode };
127
+ }
128
+
129
+ function setDashboardMode(mode) {
130
+ if (mode !== 'web' && mode !== 'terminal') {
131
+ return { ok: false, error: 'Invalid mode. Expected "web" or "terminal".' };
132
+ }
133
+
134
+ const settingsPath = getSettingsPath();
135
+ let settings = {};
136
+ if (fs.existsSync(settingsPath)) {
137
+ try {
138
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
139
+ } catch (e) {
140
+ return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
141
+ }
142
+ }
143
+
144
+ settings.dashboardMode = mode;
145
+
146
+ try {
147
+ atomicWrite(settingsPath, settings);
148
+ } catch (err) {
149
+ return { ok: false, error: err.message };
150
+ }
151
+
152
+ return { ok: true, settingsPath, mode };
153
+ }
154
+
155
+ module.exports = { setup, toggleHistory, updateHooks, getDashboardMode, setDashboardMode };