@alyibrahim/claude-statusline 1.4.7 → 1.5.1
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 +70 -8
- package/bin/cli.js +86 -2
- package/package.json +6 -6
- package/scripts/postinstall.js +9 -0
- package/scripts/preuninstall.js +14 -1
- package/scripts/setup.js +44 -1
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<
|
|
4
|
-
<
|
|
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>
|
|
5
7
|
|
|
6
8
|
[](https://github.com/AlyIbrahim1/claude-statusline/actions/workflows/ci.yml)
|
|
7
9
|
[](https://www.npmjs.com/package/@alyibrahim/claude-statusline)
|
|
@@ -61,7 +63,7 @@ Done. The statusline configures itself automatically. Restart Claude Code to see
|
|
|
61
63
|
|
|
62
64
|
## Session History
|
|
63
65
|
|
|
64
|
-
Track token usage, cost, and duration across every Claude Code session
|
|
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.
|
|
65
67
|
|
|
66
68
|

|
|
67
69
|
|
|
@@ -81,12 +83,70 @@ Session history is **enabled by default** on setup. Each session records:
|
|
|
81
83
|
**Commands:**
|
|
82
84
|
|
|
83
85
|
```bash
|
|
84
|
-
claude-statusline history
|
|
85
|
-
claude-statusline
|
|
86
|
-
claude-statusline
|
|
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
|
|
87
91
|
```
|
|
88
92
|
|
|
89
|
-
Data is stored at `~/.claude/statusline-history.jsonl`.
|
|
93
|
+
Data is stored at `~/.claude/statusline-history.jsonl`.
|
|
94
|
+
|
|
95
|
+
### Claude Code slash commands
|
|
96
|
+
|
|
97
|
+
History commands are also available directly inside Claude Code as slash commands:
|
|
98
|
+
|
|
99
|
+
- `/history`
|
|
100
|
+
- `/history-enable`
|
|
101
|
+
- `/history-disable`
|
|
102
|
+
- `/history-mode <web|terminal>`
|
|
103
|
+
|
|
104
|
+
Project contributors get these from the repo at `.claude/commands/`.
|
|
105
|
+
Global npm installs copy them to `~/.claude/commands/` automatically.
|
|
106
|
+
|
|
107
|
+
### Terminal TUI
|
|
108
|
+
|
|
109
|
+
`--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).
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
┌─ claude-statusline history ──────────────────────────────────────────────┐
|
|
113
|
+
│ Sessions: 42 Tokens: 8.3M Cost: $12.47 │
|
|
114
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
115
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
116
|
+
│ Filter: [All Projects ▾] │
|
|
117
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
118
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
119
|
+
│ Project Model Started Dur Tok Cost│
|
|
120
|
+
│ │
|
|
121
|
+
│> my-web-app claude-sonnet-4-6 2026-04-02 14: 32m 842k $2.14│
|
|
122
|
+
│ cli-tool claude-opus-4-6 2026-04-01 09: 1h4m 1.2M $5.60│
|
|
123
|
+
│ data-pipeline claude-haiku-4-5 2026-03-31 17: 18m 220k $0.44│
|
|
124
|
+
│ my-web-app claude-sonnet-4-6 2026-03-30 11: 45m 990k $2.89│
|
|
125
|
+
│ api-server claude-sonnet-4-6 2026-03-29 08: 2h1m 2.1M $8.33│
|
|
126
|
+
│ │
|
|
127
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
128
|
+
┌──────────────────────────────────────────────────────────────────────────┐
|
|
129
|
+
│ up/down or j/k: move f: filter Enter: apply Esc: close q: quit │
|
|
130
|
+
└──────────────────────────────────────────────────────────────────────────┘
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Press `f` to open the project filter popup:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
┌─ Filter by Project ──────────┐
|
|
137
|
+
│ ██ All Projects ██ │
|
|
138
|
+
│ api-server │
|
|
139
|
+
│ cli-tool │
|
|
140
|
+
│ data-pipeline │
|
|
141
|
+
│ my-web-app │
|
|
142
|
+
└──────────────────────────────┘
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Rows are color-coded by exit reason: green = normal, yellow = interrupted, orange = pending.
|
|
146
|
+
|
|
147
|
+
### Web Dashboard
|
|
148
|
+
|
|
149
|
+
`--mode web` (the default) opens a browser-based dashboard with project filtering and light/dark theme toggle.
|
|
90
150
|
|
|
91
151
|
---
|
|
92
152
|
|
|
@@ -118,7 +178,7 @@ See [PLATFORMS.md](PLATFORMS.md) for the full compatibility guide, per-platform
|
|
|
118
178
|
| Subagent counter | Counts active agents from todos dir | — |
|
|
119
179
|
| Session tokens | Real-time via JSONL offset cache, split I/O (`X↓ Y↑`) | Stale stdin snapshot or none |
|
|
120
180
|
| Session commits | Tracks git commits made this session | — |
|
|
121
|
-
| Session history |
|
|
181
|
+
| Session history | Terminal TUI + browser dashboard, per-project filtering, zero dependencies | — |
|
|
122
182
|
|
|
123
183
|
---
|
|
124
184
|
|
|
@@ -146,6 +206,8 @@ npm uninstall -g @alyibrahim/claude-statusline
|
|
|
146
206
|
|
|
147
207
|
> Always run `claude-statusline uninstall` first — it removes the `statusLine` entry from `~/.claude/settings.json` before the files are deleted.
|
|
148
208
|
|
|
209
|
+
`npm uninstall -g @alyibrahim/claude-statusline` also removes the four history slash command files installed by this package from `~/.claude/commands/`, without touching other custom commands.
|
|
210
|
+
|
|
149
211
|
---
|
|
150
212
|
|
|
151
213
|
<div align="center">
|
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;
|
|
@@ -16,8 +16,89 @@ Commands:
|
|
|
16
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'
|
|
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.
|
|
3
|
+
"version": "1.5.1",
|
|
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.
|
|
60
|
-
"@alyibrahim/claude-statusline-linux-arm64": "1.
|
|
61
|
-
"@alyibrahim/claude-statusline-darwin-x64": "1.
|
|
62
|
-
"@alyibrahim/claude-statusline-darwin-arm64": "1.
|
|
63
|
-
"@alyibrahim/claude-statusline-win32-x64": "1.
|
|
59
|
+
"@alyibrahim/claude-statusline-linux-x64": "1.5.1",
|
|
60
|
+
"@alyibrahim/claude-statusline-linux-arm64": "1.5.1",
|
|
61
|
+
"@alyibrahim/claude-statusline-darwin-x64": "1.5.1",
|
|
62
|
+
"@alyibrahim/claude-statusline-darwin-arm64": "1.5.1",
|
|
63
|
+
"@alyibrahim/claude-statusline-win32-x64": "1.5.1"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
66
|
"jest": "^29.0.0"
|
package/scripts/postinstall.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
4
5
|
const { setup } = require('./setup');
|
|
5
6
|
const config = require('./config');
|
|
6
7
|
try {
|
|
7
8
|
const result = setup({ force: false });
|
|
8
9
|
if (result.settingsPath === null) process.exit(0); // non-global install, skip silently
|
|
10
|
+
|
|
11
|
+
const sourceDir = path.join(__dirname, '..', '.claude', 'commands');
|
|
12
|
+
const commandsDir = path.join(path.dirname(result.settingsPath), 'commands');
|
|
13
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
14
|
+
for (const f of fs.readdirSync(sourceDir)) {
|
|
15
|
+
fs.copyFileSync(path.join(sourceDir, f), path.join(commandsDir, f));
|
|
16
|
+
}
|
|
17
|
+
|
|
9
18
|
if (!result.ok) {
|
|
10
19
|
console.warn('\n⚠ claude-statusline: auto-setup failed:', result.error);
|
|
11
20
|
console.warn(' Run manually: claude-statusline setup\n');
|
package/scripts/preuninstall.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { getSettingsPath } = require('./config');
|
|
3
6
|
const { uninstall } = require('./uninstall');
|
|
4
|
-
|
|
7
|
+
|
|
8
|
+
const FILES = ['history.md', 'history-enable.md', 'history-disable.md', 'history-mode.md'];
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const commandsDir = path.join(path.dirname(getSettingsPath()), 'commands');
|
|
12
|
+
for (const f of FILES) {
|
|
13
|
+
const dest = path.join(commandsDir, f);
|
|
14
|
+
if (fs.existsSync(dest)) fs.unlinkSync(dest);
|
|
15
|
+
}
|
|
16
|
+
uninstall();
|
|
17
|
+
} catch (e) {} // fully silent — best-effort cleanup
|
|
5
18
|
process.exit(0);
|
package/scripts/setup.js
CHANGED
|
@@ -109,4 +109,47 @@ function toggleHistory(enable) {
|
|
|
109
109
|
return { ok: true, settingsPath };
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
|
|
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 };
|