@alyibrahim/claude-statusline 1.6.1 → 1.6.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/package.json +6 -6
- package/scripts/config.js +11 -0
- package/scripts/history.js +26 -19
- package/scripts/setup.js +5 -5
- package/scripts/uninstall.js +2 -2
- package/statusline.js +10 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alyibrahim/claude-statusline",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.2",
|
|
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",
|
|
@@ -58,11 +58,11 @@
|
|
|
58
58
|
"open": "^10.1.0"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@alyibrahim/claude-statusline-linux-x64": "1.6.
|
|
62
|
-
"@alyibrahim/claude-statusline-linux-arm64": "1.6.
|
|
63
|
-
"@alyibrahim/claude-statusline-darwin-x64": "1.6.
|
|
64
|
-
"@alyibrahim/claude-statusline-darwin-arm64": "1.6.
|
|
65
|
-
"@alyibrahim/claude-statusline-win32-x64": "1.6.
|
|
61
|
+
"@alyibrahim/claude-statusline-linux-x64": "1.6.2",
|
|
62
|
+
"@alyibrahim/claude-statusline-linux-arm64": "1.6.2",
|
|
63
|
+
"@alyibrahim/claude-statusline-darwin-x64": "1.6.2",
|
|
64
|
+
"@alyibrahim/claude-statusline-darwin-arm64": "1.6.2",
|
|
65
|
+
"@alyibrahim/claude-statusline-win32-x64": "1.6.2"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"jest": "^29.0.0"
|
package/scripts/config.js
CHANGED
|
@@ -3,6 +3,10 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
|
|
6
|
+
function getHomeDir() {
|
|
7
|
+
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
8
|
+
}
|
|
9
|
+
|
|
6
10
|
function getClaudeConfigDir() {
|
|
7
11
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
8
12
|
return (configDir && configDir.trim())
|
|
@@ -10,6 +14,10 @@ function getClaudeConfigDir() {
|
|
|
10
14
|
: path.join(os.homedir(), '.claude');
|
|
11
15
|
}
|
|
12
16
|
|
|
17
|
+
function isPlainObject(value) {
|
|
18
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
13
21
|
function sanitizeSlug(s) {
|
|
14
22
|
return String(s || '')
|
|
15
23
|
.replace(/[^a-zA-Z0-9_-]/g, '-')
|
|
@@ -45,6 +53,7 @@ function getSettingsPath() {
|
|
|
45
53
|
|
|
46
54
|
function atomicWrite(filePath, obj) {
|
|
47
55
|
const tmpPath = filePath + '.tmp';
|
|
56
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
48
57
|
if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath);
|
|
49
58
|
fs.writeFileSync(tmpPath, JSON.stringify(obj, null, 2));
|
|
50
59
|
try {
|
|
@@ -76,7 +85,9 @@ module.exports = {
|
|
|
76
85
|
getSettingsPath,
|
|
77
86
|
atomicWrite,
|
|
78
87
|
resolveBinary,
|
|
88
|
+
getHomeDir,
|
|
79
89
|
getClaudeConfigDir,
|
|
90
|
+
isPlainObject,
|
|
80
91
|
getRealtimeTtySlug,
|
|
81
92
|
getRealtimePaths,
|
|
82
93
|
};
|
package/scripts/history.js
CHANGED
|
@@ -3,13 +3,22 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const { normalizeProjectSlug } = require('./slug-utils');
|
|
6
|
+
const { getHomeDir } = require('./config');
|
|
6
7
|
|
|
7
8
|
const JSONL_PATH = path.join(
|
|
8
|
-
|
|
9
|
+
getHomeDir(),
|
|
9
10
|
'.claude',
|
|
10
11
|
'statusline-history.jsonl'
|
|
11
12
|
);
|
|
12
13
|
|
|
14
|
+
function safeJsonParse(value, fallback = null) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(value);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
13
22
|
function now() {
|
|
14
23
|
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
15
24
|
}
|
|
@@ -60,11 +69,10 @@ function handleHookEnd() {
|
|
|
60
69
|
process.stdin.setEncoding('utf8');
|
|
61
70
|
process.stdin.on('data', chunk => input += chunk);
|
|
62
71
|
process.stdin.on('end', () => {
|
|
63
|
-
|
|
64
|
-
try { reason = JSON.parse(input).reason || 'unknown'; } catch (e) {}
|
|
72
|
+
const reason = safeJsonParse(input, {}).reason || 'unknown';
|
|
65
73
|
|
|
66
74
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
67
|
-
const home =
|
|
75
|
+
const home = getHomeDir();
|
|
68
76
|
const slug = normalizeProjectSlug(projectDir);
|
|
69
77
|
const projectsDir = path.join(home, '.claude', 'projects', slug);
|
|
70
78
|
|
|
@@ -86,21 +94,20 @@ function handleHookEnd() {
|
|
|
86
94
|
const lines = fs.readFileSync(newestFile, 'utf8').split('\n');
|
|
87
95
|
for (const line of lines) {
|
|
88
96
|
if (!line.trim()) continue;
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
} catch (e) {}
|
|
97
|
+
const entry = safeJsonParse(line);
|
|
98
|
+
if (!entry) continue;
|
|
99
|
+
if (entry.type === 'assistant' && entry.message?.usage) {
|
|
100
|
+
const u = entry.message.usage;
|
|
101
|
+
totalIn += (u.input_tokens || 0)
|
|
102
|
+
+ Math.round((u.cache_read_input_tokens || 0) * 0.1)
|
|
103
|
+
+ (u.cache_creation_input_tokens || 0);
|
|
104
|
+
totalOut += (u.output_tokens || 0);
|
|
105
|
+
if (!model) model = entry.message.model || '';
|
|
106
|
+
} else if (entry.type === 'cost') {
|
|
107
|
+
cost += (entry.cost_usd || 0);
|
|
108
|
+
} else if (entry.type === 'message_start' && !model) {
|
|
109
|
+
model = entry.message?.model || '';
|
|
110
|
+
}
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
} catch (e) {}
|
package/scripts/setup.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const config = require('./config');
|
|
5
|
-
const { getSettingsPath, atomicWrite } = config;
|
|
5
|
+
const { getSettingsPath, atomicWrite, isPlainObject } = config;
|
|
6
6
|
|
|
7
7
|
const UNSAFE_CHARS = /["`$!()\\]/;
|
|
8
8
|
const HOOK_MARKER = 'claude-statusline-owned-v1';
|
|
@@ -59,7 +59,7 @@ function setup({ force = false } = {}) {
|
|
|
59
59
|
} catch (e) {
|
|
60
60
|
return { ok: false, error: 'settings.json contains invalid JSON — fix manually then re-run.' };
|
|
61
61
|
}
|
|
62
|
-
if (!
|
|
62
|
+
if (!isPlainObject(settings)) {
|
|
63
63
|
return { ok: false, error: 'settings.json does not contain a JSON object — fix manually then re-run.' };
|
|
64
64
|
}
|
|
65
65
|
}
|
|
@@ -183,7 +183,7 @@ function toggleHistory(enable) {
|
|
|
183
183
|
} catch (e) {
|
|
184
184
|
return { ok: false, error: 'settings.json contains invalid JSON — fix manually then re-run.' };
|
|
185
185
|
}
|
|
186
|
-
if (!
|
|
186
|
+
if (!isPlainObject(settings)) {
|
|
187
187
|
return { ok: false, error: 'settings.json does not contain a JSON object — fix manually then re-run.' };
|
|
188
188
|
}
|
|
189
189
|
}
|
|
@@ -215,7 +215,7 @@ function getDashboardMode() {
|
|
|
215
215
|
} catch (e) {
|
|
216
216
|
return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
|
|
217
217
|
}
|
|
218
|
-
if (!
|
|
218
|
+
if (!isPlainObject(settings)) {
|
|
219
219
|
return { ok: false, error: 'settings.json does not contain a JSON object - fix manually then re-run.' };
|
|
220
220
|
}
|
|
221
221
|
|
|
@@ -236,7 +236,7 @@ function setDashboardMode(mode) {
|
|
|
236
236
|
} catch (e) {
|
|
237
237
|
return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
|
|
238
238
|
}
|
|
239
|
-
if (!
|
|
239
|
+
if (!isPlainObject(settings)) {
|
|
240
240
|
return { ok: false, error: 'settings.json does not contain a JSON object - fix manually then re-run.' };
|
|
241
241
|
}
|
|
242
242
|
}
|
package/scripts/uninstall.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const fs = require('fs');
|
|
3
|
-
const { getSettingsPath, atomicWrite } = require('./config');
|
|
3
|
+
const { getSettingsPath, atomicWrite, isPlainObject } = require('./config');
|
|
4
4
|
|
|
5
5
|
function uninstall() {
|
|
6
6
|
const settingsPath = getSettingsPath();
|
|
@@ -12,7 +12,7 @@ function uninstall() {
|
|
|
12
12
|
} catch (e) {
|
|
13
13
|
return { ok: false, error: 'settings.json contains invalid JSON — cannot safely modify.' };
|
|
14
14
|
}
|
|
15
|
-
if (!
|
|
15
|
+
if (!isPlainObject(settings)) {
|
|
16
16
|
return { ok: false, error: 'settings.json does not contain a JSON object — cannot safely modify.' };
|
|
17
17
|
}
|
|
18
18
|
|
package/statusline.js
CHANGED
|
@@ -4,45 +4,34 @@
|
|
|
4
4
|
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
-
const os = require('os');
|
|
8
7
|
const { execSync, execFileSync } = require('child_process');
|
|
9
8
|
const { normalizeProjectSlug } = require('./scripts/slug-utils');
|
|
9
|
+
const {
|
|
10
|
+
getHomeDir,
|
|
11
|
+
getRealtimePaths,
|
|
12
|
+
atomicWrite: atomicWriteJson,
|
|
13
|
+
} = require('./scripts/config');
|
|
10
14
|
|
|
11
15
|
function realtimeEnabled() {
|
|
12
16
|
const v = process.env.CLAUDE_STATUSLINE_REALTIME;
|
|
13
17
|
return v === '1' || v === 'true' || v === 'TRUE';
|
|
14
18
|
}
|
|
15
19
|
|
|
16
|
-
function realtimeTtySlug() {
|
|
17
|
-
const raw = (process.env.CLAUDE_STATUSLINE_TTY || process.env.TERM_SESSION_ID || `pid-${process.pid}`).trim();
|
|
18
|
-
return raw.replace(/[^a-zA-Z0-9_-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function atomicWrite(filePath, obj) {
|
|
22
|
-
const tmp = `${filePath}.tmp`;
|
|
23
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
24
|
-
fs.writeFileSync(tmp, JSON.stringify(obj));
|
|
25
|
-
fs.renameSync(tmp, filePath);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
20
|
function emitRealtimeEvent(eventType, payload) {
|
|
29
21
|
if (!realtimeEnabled()) return;
|
|
30
22
|
try {
|
|
31
|
-
const claudeDir
|
|
32
|
-
const ttySlug = realtimeTtySlug();
|
|
23
|
+
const { claudeDir, ttySlug, statePath, registryPath, socketPath } = getRealtimePaths();
|
|
33
24
|
const now = Date.now();
|
|
34
|
-
const statePath = path.join(claudeDir, `statusline-state-${ttySlug}.json`);
|
|
35
|
-
const registryPath = path.join(claudeDir, `statusline-renderer-${ttySlug}.json`);
|
|
36
25
|
|
|
37
|
-
|
|
26
|
+
atomicWriteJson(registryPath, {
|
|
38
27
|
version: 1,
|
|
39
28
|
pid: process.pid,
|
|
40
29
|
tty_slug: ttySlug,
|
|
41
30
|
heartbeat_at_ms: now,
|
|
42
|
-
socket_path:
|
|
31
|
+
socket_path: socketPath,
|
|
43
32
|
});
|
|
44
33
|
|
|
45
|
-
|
|
34
|
+
atomicWriteJson(statePath, {
|
|
46
35
|
version: 1,
|
|
47
36
|
event_type: eventType,
|
|
48
37
|
tty_slug: ttySlug,
|
|
@@ -261,7 +250,7 @@ process.stdin.on('end', () => {
|
|
|
261
250
|
// Current task from todos
|
|
262
251
|
let task = '';
|
|
263
252
|
let activeAgents = 0;
|
|
264
|
-
const homeDir =
|
|
253
|
+
const homeDir = getHomeDir();
|
|
265
254
|
const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(homeDir, '.claude');
|
|
266
255
|
const todosDir = path.join(claudeDir, 'todos');
|
|
267
256
|
if (session && fs.existsSync(todosDir)) {
|