@alyibrahim/claude-statusline 1.5.3 → 1.5.4
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 +12 -1
- package/bin/cli.js +1 -1
- package/hooks/hooks.json +2 -2
- package/hooks/plugin-setup.json +1 -1
- package/package.json +8 -7
- package/scripts/check-version-alignment.js +84 -0
- package/scripts/history.js +2 -1
- package/scripts/setup.js +15 -2
- package/scripts/slug-utils.js +7 -0
- package/statusline.js +2 -1
package/README.md
CHANGED
|
@@ -34,6 +34,13 @@ 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 on first session start. To get the native Rust binary after a plugin install, run:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude-statusline download-binary
|
|
41
|
+
claude-statusline setup
|
|
42
|
+
```
|
|
43
|
+
|
|
37
44
|
---
|
|
38
45
|
|
|
39
46
|
<div align="center">
|
|
@@ -95,15 +102,17 @@ Data is stored at `~/.claude/statusline-history.jsonl`.
|
|
|
95
102
|
|
|
96
103
|
### Claude Code slash commands
|
|
97
104
|
|
|
98
|
-
|
|
105
|
+
Commands are also available directly inside Claude Code as slash commands:
|
|
99
106
|
|
|
100
107
|
- `/history`
|
|
101
108
|
- `/history-enable`
|
|
102
109
|
- `/history-disable`
|
|
103
110
|
- `/history-mode <web|terminal>`
|
|
111
|
+
- `/download-binary`
|
|
104
112
|
|
|
105
113
|
Project contributors get these from the repo at `.claude/commands/`.
|
|
106
114
|
Global npm installs copy them to `~/.claude/commands/` automatically.
|
|
115
|
+
Plugin installs include them via the plugin's `commands/` directory.
|
|
107
116
|
|
|
108
117
|
### Terminal TUI
|
|
109
118
|
|
|
@@ -159,6 +168,8 @@ Rows are color-coded by exit reason: green = normal, yellow = interrupted, orang
|
|
|
159
168
|
|
|
160
169
|
Pre-built Rust binaries are available for **Linux x64/arm64, macOS x64/arm64, and Windows x64**. All Linux distributions (Ubuntu, Arch, Fedora, etc.) are supported. Any other platform falls back to the JS implementation automatically — no action needed.
|
|
161
170
|
|
|
171
|
+
Plugin installs skip `npm install`, so the binary is not downloaded automatically. Run `claude-statusline download-binary` once to get it.
|
|
172
|
+
|
|
162
173
|
See [PLATFORMS.md](PLATFORMS.md) for the full compatibility guide, per-platform install instructions, and feature availability table.
|
|
163
174
|
|
|
164
175
|
---
|
package/bin/cli.js
CHANGED
|
@@ -23,7 +23,7 @@ Commands:
|
|
|
23
23
|
const TERMINAL_FALLBACK_WARNING = [
|
|
24
24
|
'[claude-statusline] terminal mode requires the native binary.',
|
|
25
25
|
'Falling back to web dashboard. To install the binary, run:',
|
|
26
|
-
'
|
|
26
|
+
' claude-statusline download-binary'
|
|
27
27
|
].join('\n');
|
|
28
28
|
|
|
29
29
|
function parseHistoryMode(args) {
|
package/hooks/hooks.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"hooks": [
|
|
7
7
|
{
|
|
8
8
|
"type": "command",
|
|
9
|
-
"command": "${CLAUDE_NODE_EXEC} \"${CLAUDE_PLUGIN_ROOT}/statusline.js\" hook start"
|
|
9
|
+
"command": "${CLAUDE_NODE_EXEC} \"${CLAUDE_PLUGIN_ROOT}/statusline.js\" hook start --marker=${HOOK_MARKER}"
|
|
10
10
|
}
|
|
11
11
|
]
|
|
12
12
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"hooks": [
|
|
18
18
|
{
|
|
19
19
|
"type": "command",
|
|
20
|
-
"command": "${CLAUDE_NODE_EXEC} \"${CLAUDE_PLUGIN_ROOT}/statusline.js\" hook end"
|
|
20
|
+
"command": "${CLAUDE_NODE_EXEC} \"${CLAUDE_PLUGIN_ROOT}/statusline.js\" hook end --marker=${HOOK_MARKER}"
|
|
21
21
|
}
|
|
22
22
|
]
|
|
23
23
|
}
|
package/hooks/plugin-setup.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alyibrahim/claude-statusline",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.4",
|
|
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",
|
|
@@ -37,7 +37,8 @@
|
|
|
37
37
|
"scripts": {
|
|
38
38
|
"postinstall": "node scripts/postinstall.js",
|
|
39
39
|
"preuninstall": "node scripts/preuninstall.js",
|
|
40
|
-
"test": "jest"
|
|
40
|
+
"test": "jest",
|
|
41
|
+
"check-versions": "node scripts/check-version-alignment.js"
|
|
41
42
|
},
|
|
42
43
|
"jest": {
|
|
43
44
|
"testPathIgnorePatterns": [
|
|
@@ -57,11 +58,11 @@
|
|
|
57
58
|
"open": "^10.1.0"
|
|
58
59
|
},
|
|
59
60
|
"optionalDependencies": {
|
|
60
|
-
"@alyibrahim/claude-statusline-linux-x64": "1.5.
|
|
61
|
-
"@alyibrahim/claude-statusline-linux-arm64": "1.5.
|
|
62
|
-
"@alyibrahim/claude-statusline-darwin-x64": "1.5.
|
|
63
|
-
"@alyibrahim/claude-statusline-darwin-arm64": "1.5.
|
|
64
|
-
"@alyibrahim/claude-statusline-win32-x64": "1.5.
|
|
61
|
+
"@alyibrahim/claude-statusline-linux-x64": "1.5.4",
|
|
62
|
+
"@alyibrahim/claude-statusline-linux-arm64": "1.5.4",
|
|
63
|
+
"@alyibrahim/claude-statusline-darwin-x64": "1.5.4",
|
|
64
|
+
"@alyibrahim/claude-statusline-darwin-arm64": "1.5.4",
|
|
65
|
+
"@alyibrahim/claude-statusline-win32-x64": "1.5.4"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
67
68
|
"jest": "^29.0.0"
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
function readJson(filePath) {
|
|
8
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function readCargoVersion(cargoTomlPath) {
|
|
12
|
+
const content = fs.readFileSync(cargoTomlPath, 'utf8');
|
|
13
|
+
const match = content.match(/^version\s*=\s*"([^"]+)"/m);
|
|
14
|
+
return match ? match[1] : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function main() {
|
|
18
|
+
const root = path.resolve(__dirname, '..');
|
|
19
|
+
const packageJsonPath = path.join(root, 'package.json');
|
|
20
|
+
const packageLockPath = path.join(root, 'package-lock.json');
|
|
21
|
+
const cargoTomlPath = path.join(root, 'Cargo.toml');
|
|
22
|
+
const pluginJsonPath = path.join(root, '.claude-plugin', 'plugin.json');
|
|
23
|
+
const marketplaceJsonPath = path.join(root, '.claude-plugin', 'marketplace.json');
|
|
24
|
+
|
|
25
|
+
const rootPkg = readJson(packageJsonPath);
|
|
26
|
+
const lock = readJson(packageLockPath);
|
|
27
|
+
const cargoVersion = readCargoVersion(cargoTomlPath);
|
|
28
|
+
const pluginJson = readJson(pluginJsonPath);
|
|
29
|
+
const marketplaceJson = readJson(marketplaceJsonPath);
|
|
30
|
+
const optionalDeps = rootPkg.optionalDependencies || {};
|
|
31
|
+
|
|
32
|
+
const errors = [];
|
|
33
|
+
const expected = rootPkg.version;
|
|
34
|
+
|
|
35
|
+
if (!expected) {
|
|
36
|
+
errors.push('package.json is missing a version');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!cargoVersion) {
|
|
40
|
+
errors.push('Cargo.toml is missing a version');
|
|
41
|
+
} else if (cargoVersion !== expected) {
|
|
42
|
+
errors.push(`Cargo.toml version ${cargoVersion} does not match package.json version ${expected}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (!pluginJson.version) {
|
|
46
|
+
errors.push('.claude-plugin/plugin.json is missing a version');
|
|
47
|
+
} else if (pluginJson.version !== expected) {
|
|
48
|
+
errors.push(`.claude-plugin/plugin.json version ${pluginJson.version} does not match package.json version ${expected}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const marketplaceVersion = marketplaceJson.plugins && marketplaceJson.plugins[0] && marketplaceJson.plugins[0].version;
|
|
52
|
+
if (!marketplaceVersion) {
|
|
53
|
+
errors.push('.claude-plugin/marketplace.json is missing plugins[0].version');
|
|
54
|
+
} else if (marketplaceVersion !== expected) {
|
|
55
|
+
errors.push(`.claude-plugin/marketplace.json plugins[0].version ${marketplaceVersion} does not match package.json version ${expected}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
for (const [name, version] of Object.entries(optionalDeps)) {
|
|
59
|
+
const lockRootVersion = lock.packages && lock.packages[''] && lock.packages[''].optionalDependencies
|
|
60
|
+
? lock.packages[''].optionalDependencies[name]
|
|
61
|
+
: undefined;
|
|
62
|
+
if (lockRootVersion !== version) {
|
|
63
|
+
errors.push(`package-lock.json root optionalDependencies[${name}] is ${lockRootVersion}, expected ${version}`);
|
|
64
|
+
}
|
|
65
|
+
// node_modules entries are only resolved once the package exists on npm.
|
|
66
|
+
// Pre-publish they contain only { optional: true } — skip the version check in that case.
|
|
67
|
+
const lockPkg = lock.packages && lock.packages[`node_modules/${name}`];
|
|
68
|
+
if (lockPkg && lockPkg.version !== undefined && lockPkg.version !== version) {
|
|
69
|
+
errors.push(`package-lock.json node_modules entry for ${name} is ${lockPkg.version}, expected ${version}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (errors.length > 0) {
|
|
74
|
+
console.error('[check-versions] Failed version alignment checks:');
|
|
75
|
+
for (const err of errors) {
|
|
76
|
+
console.error(`- ${err}`);
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
console.log(`[check-versions] OK (version ${expected})`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
package/scripts/history.js
CHANGED
|
@@ -3,6 +3,7 @@ const fs = require('fs');
|
|
|
3
3
|
const path = require('path');
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const open = require('open');
|
|
6
|
+
const { normalizeProjectSlug } = require('./slug-utils');
|
|
6
7
|
|
|
7
8
|
const JSONL_PATH = path.join(
|
|
8
9
|
process.env.HOME || process.env.USERPROFILE || os.homedir(),
|
|
@@ -65,7 +66,7 @@ function handleHookEnd() {
|
|
|
65
66
|
|
|
66
67
|
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
67
68
|
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
68
|
-
const slug = projectDir
|
|
69
|
+
const slug = normalizeProjectSlug(projectDir);
|
|
69
70
|
const projectsDir = path.join(home, '.claude', 'projects', slug);
|
|
70
71
|
|
|
71
72
|
// Read session stats from the most recently modified JSONL in the project dir
|
package/scripts/setup.js
CHANGED
|
@@ -5,6 +5,7 @@ const config = require('./config');
|
|
|
5
5
|
const { getSettingsPath, atomicWrite } = config;
|
|
6
6
|
|
|
7
7
|
const UNSAFE_CHARS = /["`$!()\\]/;
|
|
8
|
+
const HOOK_MARKER = 'claude-statusline-owned-v1';
|
|
8
9
|
|
|
9
10
|
function buildNodeExecCommand() {
|
|
10
11
|
return `"${process.execPath}"`;
|
|
@@ -108,6 +109,7 @@ function updateHooks(settings, enable, { nodeExecCommand = buildNodeExecCommand(
|
|
|
108
109
|
const resolved = resolveHooksFromFile(f, {
|
|
109
110
|
CLAUDE_PLUGIN_ROOT: escapedRoot,
|
|
110
111
|
CLAUDE_NODE_EXEC: escapedNodeExec,
|
|
112
|
+
HOOK_MARKER,
|
|
111
113
|
});
|
|
112
114
|
for (const entries of Object.values(resolved)) {
|
|
113
115
|
for (const entry of entries) {
|
|
@@ -122,6 +124,7 @@ function updateHooks(settings, enable, { nodeExecCommand = buildNodeExecCommand(
|
|
|
122
124
|
const resolvedHooks = resolveHooksFromFile(f, {
|
|
123
125
|
CLAUDE_PLUGIN_ROOT: escapedRoot,
|
|
124
126
|
CLAUDE_NODE_EXEC: escapedNodeExec,
|
|
127
|
+
HOOK_MARKER,
|
|
125
128
|
});
|
|
126
129
|
|
|
127
130
|
for (const [event, entries] of Object.entries(resolvedHooks)) {
|
|
@@ -133,11 +136,21 @@ function updateHooks(settings, enable, { nodeExecCommand = buildNodeExecCommand(
|
|
|
133
136
|
const hasOwnedMarker = /claude-statusline|CLAUDE_PLUGIN_ROOT/i.test(cmd);
|
|
134
137
|
return hasScript && hasOwnedMarker;
|
|
135
138
|
};
|
|
139
|
+
const hasOwnedMarker = cmd => cmd && cmd.includes(`--marker=${HOOK_MARKER}`);
|
|
140
|
+
const isLegacyStatuslineHook = cmd => {
|
|
141
|
+
if (!cmd) return false;
|
|
142
|
+
const isHookSuffix = cmd.endsWith(' hook start') || cmd.endsWith(' hook end');
|
|
143
|
+
if (!isHookSuffix) return false;
|
|
144
|
+
// Keep backward compatibility with older commands while avoiding broad suffix-only matches.
|
|
145
|
+
return /(?:^|\s)(?:statusline|claude-statusline)(?:\s|$)/i.test(cmd);
|
|
146
|
+
};
|
|
136
147
|
const isOurs = inner => inner.command && (
|
|
137
|
-
//
|
|
138
|
-
|
|
148
|
+
// Marker match — canonical ownership check for new installs.
|
|
149
|
+
hasOwnedMarker(inner.command) ||
|
|
139
150
|
// Exact match — catches current hooks including plugin-setup entries
|
|
140
151
|
ourCommands.has(inner.command) ||
|
|
152
|
+
// Backward-compatible statusline suffix match for older package versions.
|
|
153
|
+
isLegacyStatuslineHook(inner.command) ||
|
|
141
154
|
// Legacy autosetup fallback — catches prior install roots
|
|
142
155
|
isLegacyAutosetup(inner.command)
|
|
143
156
|
);
|
package/statusline.js
CHANGED
|
@@ -6,6 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const path = require('path');
|
|
7
7
|
const os = require('os');
|
|
8
8
|
const { execSync, execFileSync } = require('child_process');
|
|
9
|
+
const { normalizeProjectSlug } = require('./scripts/slug-utils');
|
|
9
10
|
|
|
10
11
|
const cmd = process.argv[2];
|
|
11
12
|
if (cmd === 'history') {
|
|
@@ -30,7 +31,7 @@ if (cmd === 'history') {
|
|
|
30
31
|
// Returns { totalIn, totalOut } or null on any error.
|
|
31
32
|
function readSessionTokens(claudeDir, session, absDir) {
|
|
32
33
|
if (!session) return null;
|
|
33
|
-
const slug = absDir
|
|
34
|
+
const slug = normalizeProjectSlug(absDir);
|
|
34
35
|
const jsonlPath = path.join(claudeDir, 'projects', slug, `${session}.jsonl`);
|
|
35
36
|
const cachePath = path.join(claudeDir, `statusline-tokcache-${session}.json`);
|
|
36
37
|
try {
|