@alyibrahim/claude-statusline 1.6.0 → 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/README.md +1 -1
- package/package.json +6 -6
- package/scripts/check-version-alignment.js +6 -1
- package/scripts/config.js +11 -0
- package/scripts/history.js +27 -20
- package/scripts/plugin-autosetup.js +38 -23
- package/scripts/postinstall.js +11 -0
- package/scripts/setup.js +13 -1
- package/scripts/uninstall.js +4 -1
- package/statusline.js +10 -21
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ Done. The statusline configures itself automatically. Restart Claude Code to see
|
|
|
34
34
|
|
|
35
35
|
> If auto-setup didn't run: `claude-statusline setup`
|
|
36
36
|
|
|
37
|
-
**Plugin install (Claude Code marketplace):** Search for `claude-statusline` in the Claude Code plugin browser. The plugin auto-configures
|
|
37
|
+
**Plugin install (Claude Code marketplace):** Search for `claude-statusline` in the Claude Code plugin browser. The plugin auto-configures during installation. To get the native Rust binary after a plugin install, run:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
40
|
claude-statusline download-binary
|
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.
|
|
62
|
-
"@alyibrahim/claude-statusline-linux-arm64": "1.
|
|
63
|
-
"@alyibrahim/claude-statusline-darwin-x64": "1.
|
|
64
|
-
"@alyibrahim/claude-statusline-darwin-arm64": "1.
|
|
65
|
-
"@alyibrahim/claude-statusline-win32-x64": "1.
|
|
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"
|
|
@@ -15,7 +15,7 @@ function readCargoVersion(cargoTomlPath) {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function main() {
|
|
18
|
-
const root = path.resolve(__dirname, '..');
|
|
18
|
+
const root = process.env.CHECK_VERSIONS_ROOT || path.resolve(__dirname, '..');
|
|
19
19
|
const packageJsonPath = path.join(root, 'package.json');
|
|
20
20
|
const packageLockPath = path.join(root, 'package-lock.json');
|
|
21
21
|
const cargoTomlPath = path.join(root, 'Cargo.toml');
|
|
@@ -56,6 +56,11 @@ function main() {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
for (const [name, version] of Object.entries(optionalDeps)) {
|
|
59
|
+
// Each platform package must be pinned to the root version — they are published together.
|
|
60
|
+
if (version !== expected) {
|
|
61
|
+
errors.push(`optionalDependencies[${name}] is pinned to ${version}, expected root version ${expected}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
59
64
|
const lockRootVersion = lock.packages && lock.packages[''] && lock.packages[''].optionalDependencies
|
|
60
65
|
? lock.packages[''].optionalDependencies[name]
|
|
61
66
|
: undefined;
|
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
|
@@ -2,15 +2,23 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
|
-
const open = require('open');
|
|
6
5
|
const { normalizeProjectSlug } = require('./slug-utils');
|
|
6
|
+
const { getHomeDir } = require('./config');
|
|
7
7
|
|
|
8
8
|
const JSONL_PATH = path.join(
|
|
9
|
-
|
|
9
|
+
getHomeDir(),
|
|
10
10
|
'.claude',
|
|
11
11
|
'statusline-history.jsonl'
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
+
function safeJsonParse(value, fallback = null) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(value);
|
|
17
|
+
} catch (e) {
|
|
18
|
+
return fallback;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
function now() {
|
|
15
23
|
return new Date().toISOString().replace('T', ' ').slice(0, 19);
|
|
16
24
|
}
|
|
@@ -61,11 +69,10 @@ function handleHookEnd() {
|
|
|
61
69
|
process.stdin.setEncoding('utf8');
|
|
62
70
|
process.stdin.on('data', chunk => input += chunk);
|
|
63
71
|
process.stdin.on('end', () => {
|
|
64
|
-
|
|
65
|
-
try { reason = JSON.parse(input).reason || 'unknown'; } catch (e) {}
|
|
72
|
+
const reason = safeJsonParse(input, {}).reason || 'unknown';
|
|
66
73
|
|
|
67
74
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
68
|
-
const home =
|
|
75
|
+
const home = getHomeDir();
|
|
69
76
|
const slug = normalizeProjectSlug(projectDir);
|
|
70
77
|
const projectsDir = path.join(home, '.claude', 'projects', slug);
|
|
71
78
|
|
|
@@ -87,21 +94,20 @@ function handleHookEnd() {
|
|
|
87
94
|
const lines = fs.readFileSync(newestFile, 'utf8').split('\n');
|
|
88
95
|
for (const line of lines) {
|
|
89
96
|
if (!line.trim()) continue;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
} 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
|
+
}
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
} catch (e) {}
|
|
@@ -165,6 +171,7 @@ async function handleHistory() {
|
|
|
165
171
|
const tempPath = path.join(os.tmpdir(), 'claude-statusline-dashboard.html');
|
|
166
172
|
fs.writeFileSync(tempPath, html);
|
|
167
173
|
try {
|
|
174
|
+
const open = require('open');
|
|
168
175
|
await open.default(tempPath);
|
|
169
176
|
console.log(`Dashboard opened: ${tempPath}`);
|
|
170
177
|
} catch (e) {
|
|
@@ -2,34 +2,49 @@
|
|
|
2
2
|
const fs = require('fs');
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
|
-
const { atomicWrite } = require('./config');
|
|
5
|
+
const { atomicWrite, resolveBinary } = require('./config');
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!pluginRoot)
|
|
7
|
+
function pluginAutoSetup(pluginRoot = process.env.CLAUDE_PLUGIN_ROOT) {
|
|
8
|
+
// Only meaningful in plugin context.
|
|
9
|
+
if (!pluginRoot) return { ok: true, configured: false };
|
|
10
10
|
|
|
11
|
-
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
12
|
-
const settingsPath = path.join(configDir, 'settings.json');
|
|
11
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude');
|
|
12
|
+
const settingsPath = path.join(configDir, 'settings.json');
|
|
13
13
|
|
|
14
|
-
let settings = {};
|
|
15
|
-
try {
|
|
16
|
-
|
|
17
|
-
} catch (e) {}
|
|
14
|
+
let settings = {};
|
|
15
|
+
try {
|
|
16
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
17
|
+
} catch (e) {}
|
|
18
18
|
|
|
19
|
-
// Already configured
|
|
20
|
-
if (settings.statusLine)
|
|
19
|
+
// Already configured; leave user config untouched.
|
|
20
|
+
if (settings.statusLine) return { ok: true, configured: false };
|
|
21
21
|
|
|
22
|
-
const script = path.join(pluginRoot, 'statusline.js');
|
|
23
|
-
|
|
22
|
+
const script = path.join(pluginRoot, 'statusline.js');
|
|
23
|
+
const binaryPath = resolveBinary();
|
|
24
|
+
if (!binaryPath && !fs.existsSync(script)) return { ok: true, configured: false };
|
|
24
25
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
26
|
+
const command = binaryPath
|
|
27
|
+
? `"${binaryPath}"`
|
|
28
|
+
: `"${process.execPath}" "${script}"`;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
30
|
+
settings.statusLine = {
|
|
31
|
+
type: 'command',
|
|
32
|
+
command,
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
|
|
37
|
+
atomicWrite(settingsPath, settings);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return { ok: true, configured: false }; // Non-fatal.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return { ok: true, configured: true };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = { pluginAutoSetup };
|
|
46
|
+
|
|
47
|
+
if (require.main === module) {
|
|
48
|
+
pluginAutoSetup();
|
|
49
|
+
process.exit(0); // never fail plugin startup
|
|
35
50
|
}
|
package/scripts/postinstall.js
CHANGED
|
@@ -3,8 +3,19 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const { setup } = require('./setup');
|
|
6
|
+
const { pluginAutoSetup } = require('./plugin-autosetup');
|
|
6
7
|
const config = require('./config');
|
|
7
8
|
try {
|
|
9
|
+
// Plugin installs run npm scripts in plugin context; configure statusLine once at install time.
|
|
10
|
+
if (process.env.CLAUDE_PLUGIN_ROOT) {
|
|
11
|
+
pluginAutoSetup(process.env.CLAUDE_PLUGIN_ROOT);
|
|
12
|
+
const pluginBinaryPath = config.resolveBinary();
|
|
13
|
+
if (pluginBinaryPath && process.platform !== 'win32') {
|
|
14
|
+
try { fs.chmodSync(pluginBinaryPath, 0o755); } catch (e) {}
|
|
15
|
+
}
|
|
16
|
+
process.exit(0);
|
|
17
|
+
}
|
|
18
|
+
|
|
8
19
|
const result = setup({ force: false });
|
|
9
20
|
if (result.settingsPath === null) process.exit(0); // non-global install, skip silently
|
|
10
21
|
|
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,6 +59,9 @@ 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 (!isPlainObject(settings)) {
|
|
63
|
+
return { ok: false, error: 'settings.json does not contain a JSON object — fix manually then re-run.' };
|
|
64
|
+
}
|
|
62
65
|
}
|
|
63
66
|
|
|
64
67
|
const binaryPath = config.resolveBinary();
|
|
@@ -180,6 +183,9 @@ function toggleHistory(enable) {
|
|
|
180
183
|
} catch (e) {
|
|
181
184
|
return { ok: false, error: 'settings.json contains invalid JSON — fix manually then re-run.' };
|
|
182
185
|
}
|
|
186
|
+
if (!isPlainObject(settings)) {
|
|
187
|
+
return { ok: false, error: 'settings.json does not contain a JSON object — fix manually then re-run.' };
|
|
188
|
+
}
|
|
183
189
|
}
|
|
184
190
|
|
|
185
191
|
try {
|
|
@@ -209,6 +215,9 @@ function getDashboardMode() {
|
|
|
209
215
|
} catch (e) {
|
|
210
216
|
return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
|
|
211
217
|
}
|
|
218
|
+
if (!isPlainObject(settings)) {
|
|
219
|
+
return { ok: false, error: 'settings.json does not contain a JSON object - fix manually then re-run.' };
|
|
220
|
+
}
|
|
212
221
|
|
|
213
222
|
const mode = settings.dashboardMode === 'terminal' ? 'terminal' : 'web';
|
|
214
223
|
return { ok: true, settingsPath, mode };
|
|
@@ -227,6 +236,9 @@ function setDashboardMode(mode) {
|
|
|
227
236
|
} catch (e) {
|
|
228
237
|
return { ok: false, error: 'settings.json contains invalid JSON - fix manually then re-run.' };
|
|
229
238
|
}
|
|
239
|
+
if (!isPlainObject(settings)) {
|
|
240
|
+
return { ok: false, error: 'settings.json does not contain a JSON object - fix manually then re-run.' };
|
|
241
|
+
}
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
settings.dashboardMode = mode;
|
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,6 +12,9 @@ function uninstall() {
|
|
|
12
12
|
} catch (e) {
|
|
13
13
|
return { ok: false, error: 'settings.json contains invalid JSON — cannot safely modify.' };
|
|
14
14
|
}
|
|
15
|
+
if (!isPlainObject(settings)) {
|
|
16
|
+
return { ok: false, error: 'settings.json does not contain a JSON object — cannot safely modify.' };
|
|
17
|
+
}
|
|
15
18
|
|
|
16
19
|
if (settings.statusLine) {
|
|
17
20
|
delete settings.statusLine;
|
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)) {
|