ctxline-claude 0.0.7 → 1.1.0
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/package.json +2 -2
- package/statusline.js +49 -8
package/README.md
CHANGED
|
@@ -103,11 +103,15 @@ Remove-Item "$env:USERPROFILE\.claude\cache\usage-cache.json" -ErrorAction Silen
|
|
|
103
103
|
| **Context** | Visual bar of context-window usage |
|
|
104
104
|
| **Current** | Live 5-hour session limit + reset countdown (subscription users) |
|
|
105
105
|
| **Weekly** | Weekly usage allowance + time until the weekly reset (subscription users) |
|
|
106
|
+
| **Cost** | Running session cost in USD (e.g. `$0.42`) |
|
|
106
107
|
| **Task** | The in-progress todo, when there is one |
|
|
107
108
|
|
|
108
109
|
> [!NOTE]
|
|
109
110
|
> Usage bars change color automatically as you approach your limits.
|
|
110
111
|
|
|
112
|
+
> [!NOTE]
|
|
113
|
+
> **Responsive.** On a narrow terminal the line wraps to two — directory, model, and context on the first line; usage, cost, and task on the second. Wide terminals stay on a single line. (Auto-sizing needs Claude Code v2.1.153+.)
|
|
114
|
+
|
|
111
115
|
## How it works
|
|
112
116
|
|
|
113
117
|
- **Source** — context comes from Claude Code's session data. Usage bars are read straight from the `rate_limits` field Claude Code pipes in (no network), falling back to `https://api.anthropic.com/api/oauth/usage` (the same `/usage` data — 5-hour and weekly limits) when that field isn't present yet. API-key users skip usage entirely.
|
|
@@ -125,6 +129,13 @@ Yes — the same 5-hour and weekly limits. It reads them from the session data C
|
|
|
125
129
|
|
|
126
130
|
</details>
|
|
127
131
|
|
|
132
|
+
<details>
|
|
133
|
+
<summary>Is the session cost my actual bill?</summary>
|
|
134
|
+
|
|
135
|
+
It's the cost Claude Code computes for the session (tokens × per-model API pricing), read straight from the session data. For subscription (Pro/Max) users it's the *equivalent* pay-as-you-go API cost — useful as a gauge of session weight, but not what you're billed (you pay the flat subscription). It's an estimate, accurate to the extent your Claude Code pricing tables are current.
|
|
136
|
+
|
|
137
|
+
</details>
|
|
138
|
+
|
|
128
139
|
<details>
|
|
129
140
|
<summary>Does it work with API keys?</summary>
|
|
130
141
|
|
|
@@ -135,7 +146,7 @@ Yes. The statusline automatically detects subscription vs API-key usage.
|
|
|
135
146
|
<details>
|
|
136
147
|
<summary>Can it break Claude Code?</summary>
|
|
137
148
|
|
|
138
|
-
No. All failures are handled silently and the statusline always renders.
|
|
149
|
+
No. [Statuslines are a built-in Claude Code feature](https://code.claude.com/docs/en/statusline) — this provides the command Claude Code runs. All failures are handled silently and the statusline always renders.
|
|
139
150
|
|
|
140
151
|
</details>
|
|
141
152
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ctxline-claude",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A customizable statusline for Claude Code that tracks context usage and session limits",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ctxline-claude": "bin/install.js"
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
],
|
|
29
29
|
"author": "Mithun Wijayasiri",
|
|
30
30
|
"license": "MIT",
|
|
31
|
-
"homepage": "https://
|
|
31
|
+
"homepage": "https://ctxline.js.org",
|
|
32
32
|
"bugs": {
|
|
33
33
|
"url": "https://github.com/MithunWijayasiri/ctxline-claude/issues"
|
|
34
34
|
},
|
package/statusline.js
CHANGED
|
@@ -19,6 +19,14 @@ const BAR_WIDTH = 6;
|
|
|
19
19
|
// Tail-truncation keeps the start (ticket IDs like "TAMA5-32796" live there) visible.
|
|
20
20
|
const MAX_BRANCH_LEN = 24;
|
|
21
21
|
|
|
22
|
+
// Separator between segments on a rendered line.
|
|
23
|
+
const SEGMENT_SEP = ' │ ';
|
|
24
|
+
|
|
25
|
+
// Cells reserved at the terminal edge when deciding to wrap to a second line.
|
|
26
|
+
// 0 = use the full COLUMNS; bump it if Claude Code reserves columns and the line
|
|
27
|
+
// truncates a char or two before wrapping.
|
|
28
|
+
const WIDTH_MARGIN = 0;
|
|
29
|
+
|
|
22
30
|
// Cache configuration
|
|
23
31
|
const CACHE_DIR = path.join(os.homedir(), '.claude', 'cache');
|
|
24
32
|
const USAGE_CACHE_FILE = path.join(CACHE_DIR, 'usage-cache.json');
|
|
@@ -359,6 +367,15 @@ function getUsageWithCache(callback) {
|
|
|
359
367
|
});
|
|
360
368
|
}
|
|
361
369
|
|
|
370
|
+
// Session cost from stdin `cost.total_cost_usd` (USD float, computed client-side by
|
|
371
|
+
// Claude Code as tokens × per-model API pricing). Pure stdin — no network/cache.
|
|
372
|
+
// Returns "$0.00" rendered dim, or '' when absent/non-finite so the segment is omitted.
|
|
373
|
+
function getCostSegment(data) {
|
|
374
|
+
const usd = data?.cost?.total_cost_usd;
|
|
375
|
+
if (!Number.isFinite(usd)) return '';
|
|
376
|
+
return `${colors.dim}$${usd.toFixed(2)}${colors.reset}`;
|
|
377
|
+
}
|
|
378
|
+
|
|
362
379
|
function getCurrentTask(sessionId) {
|
|
363
380
|
if (!sessionId) return '';
|
|
364
381
|
|
|
@@ -383,6 +400,25 @@ function getCurrentTask(sessionId) {
|
|
|
383
400
|
return '';
|
|
384
401
|
}
|
|
385
402
|
|
|
403
|
+
// Visible (printable) width of a segment string: strip ANSI color codes, count code points.
|
|
404
|
+
function visibleWidth(str) {
|
|
405
|
+
return [...str.replace(/\x1b\[[0-9;]*m/g, '')].length;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Responsive layout: one line when it fits the terminal, else line1 (identity + context)
|
|
409
|
+
// on top and line2 (usage/cost/task) below. Splits only when COLUMNS is known (Claude Code
|
|
410
|
+
// v2.1.153+) and the single line overflows — unknown width or an empty line2 stays single,
|
|
411
|
+
// so there is no regression on older clients or wide terminals.
|
|
412
|
+
function layout(line1Parts, line2Parts) {
|
|
413
|
+
const single = [...line1Parts, ...line2Parts].join(SEGMENT_SEP);
|
|
414
|
+
if (line2Parts.length === 0) return single;
|
|
415
|
+
const cols = parseInt(process.env.COLUMNS, 10);
|
|
416
|
+
if (Number.isFinite(cols) && cols > 0 && visibleWidth(single) > cols - WIDTH_MARGIN) {
|
|
417
|
+
return line1Parts.join(SEGMENT_SEP) + '\n' + line2Parts.join(SEGMENT_SEP);
|
|
418
|
+
}
|
|
419
|
+
return single;
|
|
420
|
+
}
|
|
421
|
+
|
|
386
422
|
// Main
|
|
387
423
|
function outputStatus(data, usage) {
|
|
388
424
|
try {
|
|
@@ -395,17 +431,22 @@ function outputStatus(data, usage) {
|
|
|
395
431
|
const remaining = data?.context_window?.remaining_percentage;
|
|
396
432
|
|
|
397
433
|
const contextBar = getContextBar(remaining);
|
|
434
|
+
const cost = getCostSegment(data);
|
|
398
435
|
const task = getCurrentTask(sessionId);
|
|
399
|
-
const parts = [];
|
|
400
|
-
parts.push(branch ? `${dirname} ${colors.dim}⎇ ${branch}${colors.reset}` : dirname);
|
|
401
|
-
parts.push(effort ? `${model}${getEffortColor(effort)} · ${effort}${colors.reset}` : model);
|
|
402
|
-
parts.push(contextBar);
|
|
403
436
|
|
|
404
|
-
|
|
405
|
-
|
|
437
|
+
// line1 = identity + context (always); line2 = usage/cost/task (wrap target).
|
|
438
|
+
const line1 = [];
|
|
439
|
+
line1.push(branch ? `${dirname} ${colors.dim}⎇ ${branch}${colors.reset}` : dirname);
|
|
440
|
+
line1.push(effort ? `${model}${getEffortColor(effort)} · ${effort}${colors.reset}` : model);
|
|
441
|
+
line1.push(contextBar);
|
|
442
|
+
|
|
443
|
+
const line2 = [];
|
|
444
|
+
if (usage?.current) line2.push(usage.current);
|
|
445
|
+
if (usage?.weekly) line2.push(usage.weekly);
|
|
446
|
+
if (cost) line2.push(cost);
|
|
447
|
+
if (task) line2.push(`${colors.dim}${task}${colors.reset}`);
|
|
406
448
|
|
|
407
|
-
|
|
408
|
-
process.stdout.write(parts.join(' \u2502 '));
|
|
449
|
+
process.stdout.write(layout(line1, line2));
|
|
409
450
|
} catch (e) {
|
|
410
451
|
process.stdout.write('Status unavailable');
|
|
411
452
|
}
|