ctxline-claude 1.0.0 → 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 +3 -0
- package/package.json +1 -1
- package/statusline.js +39 -9
package/README.md
CHANGED
|
@@ -109,6 +109,9 @@ Remove-Item "$env:USERPROFILE\.claude\cache\usage-cache.json" -ErrorAction Silen
|
|
|
109
109
|
> [!NOTE]
|
|
110
110
|
> Usage bars change color automatically as you approach your limits.
|
|
111
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
|
+
|
|
112
115
|
## How it works
|
|
113
116
|
|
|
114
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.
|
package/package.json
CHANGED
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');
|
|
@@ -392,6 +400,25 @@ function getCurrentTask(sessionId) {
|
|
|
392
400
|
return '';
|
|
393
401
|
}
|
|
394
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
|
+
|
|
395
422
|
// Main
|
|
396
423
|
function outputStatus(data, usage) {
|
|
397
424
|
try {
|
|
@@ -406,17 +433,20 @@ function outputStatus(data, usage) {
|
|
|
406
433
|
const contextBar = getContextBar(remaining);
|
|
407
434
|
const cost = getCostSegment(data);
|
|
408
435
|
const task = getCurrentTask(sessionId);
|
|
409
|
-
const parts = [];
|
|
410
|
-
parts.push(branch ? `${dirname} ${colors.dim}⎇ ${branch}${colors.reset}` : dirname);
|
|
411
|
-
parts.push(effort ? `${model}${getEffortColor(effort)} · ${effort}${colors.reset}` : model);
|
|
412
|
-
parts.push(contextBar);
|
|
413
436
|
|
|
414
|
-
|
|
415
|
-
|
|
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}`);
|
|
416
448
|
|
|
417
|
-
|
|
418
|
-
if (task) parts.push(`${colors.dim}${task}${colors.reset}`);
|
|
419
|
-
process.stdout.write(parts.join(' \u2502 '));
|
|
449
|
+
process.stdout.write(layout(line1, line2));
|
|
420
450
|
} catch (e) {
|
|
421
451
|
process.stdout.write('Status unavailable');
|
|
422
452
|
}
|