claude-contextline 1.3.2 → 2.0.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 +6 -37
- package/dist/index.js +52 -189
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
# claude-contextline
|
|
2
2
|
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
<img width="677" height="38" alt="image" src="https://github.com/user-attachments/assets/070d99fe-290d-4897-88b0-04247bf27866" />
|
|
6
|
-
|
|
3
|
+
A two-line statusline for Claude Code showing context window usage, model, git
|
|
4
|
+
branch, and working directory.
|
|
7
5
|
|
|
8
6
|
## Usage
|
|
9
7
|
|
|
@@ -18,49 +16,20 @@ Add to your Claude Code settings (`~/.claude/settings.json`):
|
|
|
18
16
|
}
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
## Features
|
|
22
|
-
|
|
23
|
-
- **Directory** - Shows current project/directory name
|
|
24
|
-
- **Git** - Shows branch name with dirty indicator (●)
|
|
25
|
-
- **Model** - Shows active Claude model
|
|
26
|
-
- **Context** - Shows context window usage percentage
|
|
27
|
-
|
|
28
19
|
## Display
|
|
29
20
|
|
|
30
21
|
```
|
|
31
|
-
|
|
22
|
+
[█████░░░░░] 42% Opus 4.6
|
|
23
|
+
(main) myproject
|
|
32
24
|
```
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- **Normal** (<80%): Sky blue text on dark background
|
|
37
|
-
- **Warning** (≥80%): White text on orange background
|
|
38
|
-
- **Critical** (≥100%): White text on red background
|
|
39
|
-
|
|
40
|
-
## Options
|
|
41
|
-
|
|
42
|
-
- `--theme <name>` - Use a named color theme (e.g. `--theme fulcrum` for purple-accented Fulcrum branding)
|
|
43
|
-
- `--no-arrows` - Disable powerline arrow separators
|
|
44
|
-
|
|
45
|
-
```json
|
|
46
|
-
{
|
|
47
|
-
"statusLine": {
|
|
48
|
-
"type": "command",
|
|
49
|
-
"command": "npx claude-contextline --theme fulcrum"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
```
|
|
26
|
+
- **Line 1**: Context window battery bar (blue) with usage percentage, model name (red)
|
|
27
|
+
- **Line 2**: Git branch (red), working directory (blue) aligned below the model
|
|
53
28
|
|
|
54
29
|
## Requirements
|
|
55
30
|
|
|
56
31
|
- Node.js 18+
|
|
57
|
-
- A terminal with powerline font support (for arrow glyphs)
|
|
58
|
-
|
|
59
|
-
## Credits
|
|
60
|
-
|
|
61
|
-
Styling based on [claude-limitline](https://github.com/tylergraydev/claude-limitline).
|
|
62
32
|
|
|
63
33
|
## License
|
|
64
34
|
|
|
65
35
|
MIT
|
|
66
|
-
|
package/dist/index.js
CHANGED
|
@@ -43,9 +43,8 @@ function getEnvironmentInfo(hookData) {
|
|
|
43
43
|
return {
|
|
44
44
|
directory: getDirectoryName(cwd),
|
|
45
45
|
gitBranch: getGitBranch(cwd),
|
|
46
|
-
gitDirty: isGitDirty(cwd),
|
|
47
46
|
model: getModelName(hookData),
|
|
48
|
-
|
|
47
|
+
usedPercentage: getUsedPercentage(hookData)
|
|
49
48
|
};
|
|
50
49
|
}
|
|
51
50
|
function getDirectoryName(cwd) {
|
|
@@ -57,220 +56,84 @@ function getGitBranch(cwd) {
|
|
|
57
56
|
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
58
57
|
cwd,
|
|
59
58
|
encoding: "utf8",
|
|
60
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
59
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
60
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: "0" }
|
|
61
61
|
}).trim();
|
|
62
62
|
if (branch === "HEAD") {
|
|
63
|
-
return
|
|
64
|
-
cwd,
|
|
65
|
-
encoding: "utf8",
|
|
66
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
67
|
-
}).trim();
|
|
63
|
+
return null;
|
|
68
64
|
}
|
|
69
65
|
return branch;
|
|
70
66
|
} catch {
|
|
71
67
|
return null;
|
|
72
68
|
}
|
|
73
69
|
}
|
|
74
|
-
function isGitDirty(cwd) {
|
|
75
|
-
try {
|
|
76
|
-
const status = execSync("git status --porcelain", {
|
|
77
|
-
cwd,
|
|
78
|
-
encoding: "utf8",
|
|
79
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
80
|
-
});
|
|
81
|
-
return status.trim().length > 0;
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
70
|
function getModelName(hookData) {
|
|
87
71
|
const displayName = hookData.model?.display_name || "Claude";
|
|
88
72
|
return displayName.replace(/^Claude\s+/, "");
|
|
89
73
|
}
|
|
90
|
-
function
|
|
74
|
+
function getUsedPercentage(hookData) {
|
|
91
75
|
const ctx = hookData.context_window;
|
|
92
|
-
if (!ctx
|
|
93
|
-
|
|
76
|
+
if (!ctx) return null;
|
|
77
|
+
if (ctx.used_percentage != null) {
|
|
78
|
+
return Math.floor(ctx.used_percentage);
|
|
94
79
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
// src/utils/constants.ts
|
|
101
|
-
var SYMBOLS = {
|
|
102
|
-
arrow: "\uE0B0",
|
|
103
|
-
// Powerline right arrow
|
|
104
|
-
branch: "\uE0A0",
|
|
105
|
-
// Git branch icon
|
|
106
|
-
model: "\u2731",
|
|
107
|
-
// Heavy asterisk ✱
|
|
108
|
-
context: "\u25EB",
|
|
109
|
-
// White square with vertical bisecting line ◫
|
|
110
|
-
dirty: "\u25CF"
|
|
111
|
-
// Dirty indicator
|
|
112
|
-
};
|
|
113
|
-
var TEXT_SYMBOLS = {
|
|
114
|
-
arrow: "",
|
|
115
|
-
branch: "",
|
|
116
|
-
model: "",
|
|
117
|
-
context: "",
|
|
118
|
-
dirty: "*"
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
// src/themes/index.ts
|
|
122
|
-
var darkTheme = {
|
|
123
|
-
directory: { bg: "#8b4513", fg: "#ffffff" },
|
|
124
|
-
// Brown, white
|
|
125
|
-
git: { bg: "#404040", fg: "#ffffff" },
|
|
126
|
-
// Dark gray, white
|
|
127
|
-
model: { bg: "#2d2d2d", fg: "#ffffff" },
|
|
128
|
-
// Very dark gray, white
|
|
129
|
-
context: { bg: "#2a2a2a", fg: "#87ceeb" },
|
|
130
|
-
// Nearly black, sky blue
|
|
131
|
-
warning: { bg: "#d75f00", fg: "#ffffff" },
|
|
132
|
-
// Orange, white (80%+)
|
|
133
|
-
critical: { bg: "#af0000", fg: "#ffffff" }
|
|
134
|
-
// Red, white (100%+)
|
|
135
|
-
};
|
|
136
|
-
var fulcrumTheme = {
|
|
137
|
-
directory: { bg: "#f90013", fg: "#ffffff" },
|
|
138
|
-
// Destructive red, white
|
|
139
|
-
git: { bg: "#121212", fg: "#0064f4" },
|
|
140
|
-
// Secondary, accent blue
|
|
141
|
-
model: { bg: "#090909", fg: "#f84331" },
|
|
142
|
-
// Card, warning red-orange
|
|
143
|
-
context: { bg: "#161616", fg: "#0064f4" },
|
|
144
|
-
// Muted, accent blue
|
|
145
|
-
warning: { bg: "#f84331", fg: "#ffffff" },
|
|
146
|
-
// Warning red-orange, white (80%+)
|
|
147
|
-
critical: { bg: "#0064f4", fg: "#ffffff" }
|
|
148
|
-
// Accent blue, white (100%+)
|
|
149
|
-
};
|
|
150
|
-
function getTheme(name) {
|
|
151
|
-
if (name === "fulcrum") return fulcrumTheme;
|
|
152
|
-
return darkTheme;
|
|
153
|
-
}
|
|
154
|
-
function hexToAnsi256(hex) {
|
|
155
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
156
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
157
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
158
|
-
if (r === g && g === b) {
|
|
159
|
-
if (r < 8) return 16;
|
|
160
|
-
if (r > 248) return 231;
|
|
161
|
-
return Math.round((r - 8) / 247 * 24) + 232;
|
|
80
|
+
if (ctx.current_usage && ctx.context_window_size) {
|
|
81
|
+
const usage = ctx.current_usage;
|
|
82
|
+
const totalTokens = (usage.input_tokens || 0) + (usage.cache_creation_input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
|
83
|
+
return Math.floor(totalTokens / ctx.context_window_size * 100);
|
|
162
84
|
}
|
|
163
|
-
|
|
164
|
-
const gi = Math.round(g / 255 * 5);
|
|
165
|
-
const bi = Math.round(b / 255 * 5);
|
|
166
|
-
return 16 + 36 * ri + 6 * gi + bi;
|
|
167
|
-
}
|
|
168
|
-
var ansi = {
|
|
169
|
-
fg: (hex) => `\x1B[38;5;${hexToAnsi256(hex)}m`,
|
|
170
|
-
bg: (hex) => `\x1B[48;5;${hexToAnsi256(hex)}m`,
|
|
171
|
-
reset: "\x1B[0m"
|
|
172
|
-
};
|
|
173
|
-
function getContextColors(percent, theme) {
|
|
174
|
-
if (percent >= 100) {
|
|
175
|
-
return theme.critical;
|
|
176
|
-
} else if (percent >= 80) {
|
|
177
|
-
return theme.warning;
|
|
178
|
-
}
|
|
179
|
-
return theme.context;
|
|
85
|
+
return null;
|
|
180
86
|
}
|
|
181
87
|
|
|
182
88
|
// src/renderer.ts
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
89
|
+
var ESC = "\x1B";
|
|
90
|
+
var RESET = `${ESC}[0m`;
|
|
91
|
+
var BLUE = `${ESC}[38;5;69m`;
|
|
92
|
+
var RED = `${ESC}[38;5;196m`;
|
|
93
|
+
var GRAY = `${ESC}[38;5;243m`;
|
|
94
|
+
var BAR_WIDTH = 10;
|
|
95
|
+
var FILLED_CHAR = "\u2588";
|
|
96
|
+
var EMPTY_CHAR = "\u2591";
|
|
97
|
+
function render(envInfo) {
|
|
98
|
+
let out = "";
|
|
99
|
+
let modelCol = 0;
|
|
100
|
+
if (envInfo.usedPercentage != null) {
|
|
101
|
+
const pct = Math.max(0, envInfo.usedPercentage);
|
|
102
|
+
const pctStr = String(pct);
|
|
103
|
+
const nFilled = Math.min(
|
|
104
|
+
Math.floor(pct * BAR_WIDTH / 100),
|
|
105
|
+
BAR_WIDTH
|
|
106
|
+
);
|
|
107
|
+
const nEmpty = BAR_WIDTH - nFilled;
|
|
108
|
+
const filled = FILLED_CHAR.repeat(nFilled);
|
|
109
|
+
const empty = EMPTY_CHAR.repeat(nEmpty);
|
|
110
|
+
out += `${BLUE}[${filled}${GRAY}${empty}${BLUE}] ${pctStr}%`;
|
|
111
|
+
modelCol = 16 + pctStr.length;
|
|
204
112
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
*/
|
|
208
|
-
render(envInfo) {
|
|
209
|
-
const segments = this.buildSegments(envInfo);
|
|
210
|
-
if (segments.length === 0) {
|
|
211
|
-
return "";
|
|
212
|
-
}
|
|
213
|
-
return this.renderPowerline(segments);
|
|
113
|
+
if (out.length > 0) {
|
|
114
|
+
out += " ";
|
|
214
115
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
if (envInfo.gitBranch) {
|
|
225
|
-
const dirty = envInfo.gitDirty ? ` ${this.symbols.dirty}` : "";
|
|
226
|
-
segments.push({
|
|
227
|
-
text: ` ${this.symbols.branch} ${envInfo.gitBranch}${dirty} `,
|
|
228
|
-
colors: this.theme.git
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
segments.push({
|
|
232
|
-
text: ` ${this.symbols.model} ${envInfo.model} `,
|
|
233
|
-
colors: this.theme.model
|
|
234
|
-
});
|
|
235
|
-
const contextColors = getContextColors(envInfo.contextPercent, this.theme);
|
|
236
|
-
segments.push({
|
|
237
|
-
text: ` ${this.symbols.context} ${envInfo.contextPercent}% `,
|
|
238
|
-
colors: contextColors
|
|
239
|
-
});
|
|
240
|
-
return segments;
|
|
116
|
+
out += `${RED}${envInfo.model}`;
|
|
117
|
+
out += "\n";
|
|
118
|
+
if (envInfo.gitBranch) {
|
|
119
|
+
const branchText = `(${envInfo.gitBranch})`;
|
|
120
|
+
out += `${RED}${branchText}`;
|
|
121
|
+
const gap = Math.max(2, modelCol - branchText.length);
|
|
122
|
+
out += " ".repeat(gap);
|
|
123
|
+
} else {
|
|
124
|
+
out += " ".repeat(modelCol);
|
|
241
125
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
let output = "";
|
|
247
|
-
for (let i = 0; i < segments.length; i++) {
|
|
248
|
-
const seg = segments[i];
|
|
249
|
-
const nextColors = i < segments.length - 1 ? segments[i + 1].colors : null;
|
|
250
|
-
output += ansi.bg(seg.colors.bg) + ansi.fg(seg.colors.fg) + seg.text;
|
|
251
|
-
output += ansi.reset;
|
|
252
|
-
if (this.noArrows) {
|
|
253
|
-
} else if (nextColors) {
|
|
254
|
-
output += ansi.fg(seg.colors.bg) + ansi.bg(nextColors.bg) + this.symbols.arrow;
|
|
255
|
-
} else {
|
|
256
|
-
output += ansi.fg(seg.colors.bg) + this.symbols.arrow;
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
output += ansi.reset;
|
|
260
|
-
return output;
|
|
261
|
-
}
|
|
262
|
-
};
|
|
126
|
+
out += `${BLUE}${envInfo.directory}`;
|
|
127
|
+
out += RESET;
|
|
128
|
+
return out;
|
|
129
|
+
}
|
|
263
130
|
|
|
264
131
|
// src/index.ts
|
|
265
132
|
async function main() {
|
|
266
133
|
try {
|
|
267
|
-
const noArrows = process.argv.includes("--no-arrows");
|
|
268
|
-
const themeIndex = process.argv.indexOf("--theme");
|
|
269
|
-
const themeName = themeIndex !== -1 ? process.argv[themeIndex + 1] : void 0;
|
|
270
134
|
const hookData = await readHookData();
|
|
271
135
|
const envInfo = getEnvironmentInfo(hookData);
|
|
272
|
-
const
|
|
273
|
-
const output = renderer.render(envInfo);
|
|
136
|
+
const output = render(envInfo);
|
|
274
137
|
process.stdout.write(output);
|
|
275
138
|
} catch {
|
|
276
139
|
process.exit(0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-contextline",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Two-line statusline for Claude Code showing context window usage",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
"claude",
|
|
26
26
|
"claude-code",
|
|
27
27
|
"statusline",
|
|
28
|
-
"powerline",
|
|
29
28
|
"context-window",
|
|
30
29
|
"cli"
|
|
31
30
|
],
|