ctxline-claude 0.0.2 → 0.0.5
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/package.json +1 -1
- package/statusline.js +59 -3
package/package.json
CHANGED
package/statusline.js
CHANGED
|
@@ -15,6 +15,10 @@ const IS_API_KEY = !!process.env.ANTHROPIC_API_KEY;
|
|
|
15
15
|
// Shared width (cells) for all progress bars: context, current, weekly.
|
|
16
16
|
const BAR_WIDTH = 6;
|
|
17
17
|
|
|
18
|
+
// Max characters shown for the git branch; longer names are tail-truncated with "…".
|
|
19
|
+
// Tail-truncation keeps the start (ticket IDs like "TAMA5-32796" live there) visible.
|
|
20
|
+
const MAX_BRANCH_LEN = 24;
|
|
21
|
+
|
|
18
22
|
// Cache configuration
|
|
19
23
|
const CACHE_DIR = path.join(os.homedir(), '.claude', 'cache');
|
|
20
24
|
const USAGE_CACHE_FILE = path.join(CACHE_DIR, 'usage-cache.json');
|
|
@@ -33,9 +37,20 @@ const colors = {
|
|
|
33
37
|
yellow: '\x1b[33m',
|
|
34
38
|
orange: '\x1b[38;5;208m',
|
|
35
39
|
red: '\x1b[31m',
|
|
40
|
+
purple: '\x1b[38;5;135m',
|
|
36
41
|
blink: '\x1b[5m'
|
|
37
42
|
};
|
|
38
43
|
|
|
44
|
+
// Color for the thinking-effort indicator. Levels rank low < medium < high < xhigh < max
|
|
45
|
+
// < ultracode; only the top two are highlighted — "max" red, "ultracode" purple. Every
|
|
46
|
+
// other level (including xhigh) renders dim like the rest of the metadata.
|
|
47
|
+
function getEffortColor(level) {
|
|
48
|
+
const lvl = String(level).toLowerCase();
|
|
49
|
+
if (lvl === 'max') return colors.red;
|
|
50
|
+
if (lvl === 'ultracode') return colors.purple;
|
|
51
|
+
return colors.dim;
|
|
52
|
+
}
|
|
53
|
+
|
|
39
54
|
function getUsageColor(percentage) {
|
|
40
55
|
if (percentage < 50) return colors.green;
|
|
41
56
|
if (percentage < 75) return colors.yellow;
|
|
@@ -48,11 +63,50 @@ function shortenModel(name) {
|
|
|
48
63
|
return name.replace(/\s+context\)/i, ')');
|
|
49
64
|
}
|
|
50
65
|
|
|
66
|
+
// Tail-truncate an over-long branch name, preserving the leading ticket ID.
|
|
67
|
+
function truncateBranch(name) {
|
|
68
|
+
return name.length > MAX_BRANCH_LEN ? name.slice(0, MAX_BRANCH_LEN - 1) + '…' : name;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Resolve the current git branch by reading .git/HEAD directly (no `git` subprocess —
|
|
72
|
+
// keeps the render fast and dependency-free). Walks up from `dir` to find the repo,
|
|
73
|
+
// handles worktrees (.git as a file) and detached HEAD (short sha). Best-effort: '' on any failure.
|
|
74
|
+
function getGitBranch(dir) {
|
|
75
|
+
try {
|
|
76
|
+
let cur = dir;
|
|
77
|
+
let gitPath = '';
|
|
78
|
+
for (let i = 0; i < 50 && cur; i++) {
|
|
79
|
+
const candidate = path.join(cur, '.git');
|
|
80
|
+
if (fs.existsSync(candidate)) { gitPath = candidate; break; }
|
|
81
|
+
const parent = path.dirname(cur);
|
|
82
|
+
if (parent === cur) break; // reached filesystem root
|
|
83
|
+
cur = parent;
|
|
84
|
+
}
|
|
85
|
+
if (!gitPath) return '';
|
|
86
|
+
|
|
87
|
+
let gitDir = gitPath;
|
|
88
|
+
if (fs.statSync(gitPath).isFile()) {
|
|
89
|
+
// Worktree/submodule: ".git" is a file like "gitdir: /path/to/.git/worktrees/x".
|
|
90
|
+
const m = fs.readFileSync(gitPath, 'utf8').match(/gitdir:\s*(.+)/);
|
|
91
|
+
if (!m) return '';
|
|
92
|
+
gitDir = path.resolve(path.dirname(gitPath), m[1].trim());
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const head = fs.readFileSync(path.join(gitDir, 'HEAD'), 'utf8').trim();
|
|
96
|
+
const ref = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
97
|
+
if (ref) return truncateBranch(ref[1]);
|
|
98
|
+
if (/^[0-9a-f]{7,40}$/i.test(head)) return head.slice(0, 7); // detached HEAD -> short sha
|
|
99
|
+
return '';
|
|
100
|
+
} catch (e) {
|
|
101
|
+
return '';
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
51
105
|
function getContextBar(remaining) {
|
|
52
106
|
const effectiveRemaining = remaining ?? 100;
|
|
53
107
|
const used = Math.max(0, Math.min(100, 100 - Math.round(effectiveRemaining)));
|
|
54
108
|
|
|
55
|
-
const filled = Math.
|
|
109
|
+
const filled = Math.round((used / 100) * BAR_WIDTH);
|
|
56
110
|
const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(BAR_WIDTH - filled);
|
|
57
111
|
|
|
58
112
|
let coloredBar;
|
|
@@ -309,14 +363,16 @@ function outputStatus(data, usage) {
|
|
|
309
363
|
const model = shortenModel(data?.model?.display_name || 'Claude');
|
|
310
364
|
const dir = data?.workspace?.current_dir || process.cwd();
|
|
311
365
|
const dirname = path.basename(dir);
|
|
366
|
+
const branch = getGitBranch(dir);
|
|
367
|
+
const effort = data?.effort?.level || '';
|
|
312
368
|
const sessionId = data?.session_id || '';
|
|
313
369
|
const remaining = data?.context_window?.remaining_percentage;
|
|
314
370
|
|
|
315
371
|
const contextBar = getContextBar(remaining);
|
|
316
372
|
const task = getCurrentTask(sessionId);
|
|
317
373
|
const parts = [];
|
|
318
|
-
parts.push(dirname);
|
|
319
|
-
parts.push(model);
|
|
374
|
+
parts.push(branch ? `${dirname} ${colors.dim}⎇ ${branch}${colors.reset}` : dirname);
|
|
375
|
+
parts.push(effort ? `${model}${getEffortColor(effort)} · ${effort}${colors.reset}` : model);
|
|
320
376
|
parts.push(`CTX ${contextBar}`);
|
|
321
377
|
|
|
322
378
|
if (usage?.current) parts.push(`5h ${usage.current}`);
|