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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/statusline.js +59 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxline-claude",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
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"
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.floor((used / 100) * BAR_WIDTH);
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}`);