ctxline-claude 0.0.6 → 1.0.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.
Files changed (3) hide show
  1. package/README.md +15 -7
  2. package/package.json +4 -4
  3. package/statusline.js +36 -30
package/README.md CHANGED
@@ -12,10 +12,10 @@
12
12
  <img src="https://img.shields.io/npm/dm/ctxline-claude" alt="npm downloads">
13
13
  </a>
14
14
  <a href="LICENSE">
15
- <img src="https://img.shields.io/github/license/MithunWijayasiri/claudecode-statusline" alt="license">
15
+ <img src="https://img.shields.io/github/license/MithunWijayasiri/ctxline-claude" alt="license">
16
16
  </a>
17
- <a href="https://github.com/MithunWijayasiri/claudecode-statusline/stargazers">
18
- <img src="https://img.shields.io/github/stars/MithunWijayasiri/claudecode-statusline" alt="stars">
17
+ <a href="https://github.com/MithunWijayasiri/ctxline-claude/stargazers">
18
+ <img src="https://img.shields.io/github/stars/MithunWijayasiri/ctxline-claude" alt="stars">
19
19
  </a>
20
20
  </p>
21
21
 
@@ -43,8 +43,8 @@ Then restart Claude Code or start a new session. That's it.
43
43
  **Clone & run the installer:**
44
44
 
45
45
  ```bash
46
- git clone https://github.com/MithunWijayasiri/claudecode-statusline.git
47
- cd claudecode-statusline
46
+ git clone https://github.com/MithunWijayasiri/ctxline-claude.git
47
+ cd ctxline-claude
48
48
  ./install.sh # macOS / Linux
49
49
  ./install.ps1 # Windows (PowerShell)
50
50
  ```
@@ -52,7 +52,7 @@ cd claudecode-statusline
52
52
  **Manual:** download the script, then point `~/.claude/settings.json` at it.
53
53
 
54
54
  ```bash
55
- curl -o ~/.claude/hooks/statusline.js https://raw.githubusercontent.com/MithunWijayasiri/claudecode-statusline/main/statusline.js
55
+ curl -o ~/.claude/hooks/statusline.js https://raw.githubusercontent.com/MithunWijayasiri/ctxline-claude/main/statusline.js
56
56
  chmod +x ~/.claude/hooks/statusline.js
57
57
  ```
58
58
 
@@ -103,6 +103,7 @@ 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]
@@ -125,6 +126,13 @@ Yes — the same 5-hour and weekly limits. It reads them from the session data C
125
126
 
126
127
  </details>
127
128
 
129
+ <details>
130
+ <summary>Is the session cost my actual bill?</summary>
131
+
132
+ 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.
133
+
134
+ </details>
135
+
128
136
  <details>
129
137
  <summary>Does it work with API keys?</summary>
130
138
 
@@ -135,7 +143,7 @@ Yes. The statusline automatically detects subscription vs API-key usage.
135
143
  <details>
136
144
  <summary>Can it break Claude Code?</summary>
137
145
 
138
- No. All failures are handled silently and the statusline always renders.
146
+ 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
147
 
140
148
  </details>
141
149
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ctxline-claude",
3
- "version": "0.0.6",
3
+ "version": "1.0.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,12 +28,12 @@
28
28
  ],
29
29
  "author": "Mithun Wijayasiri",
30
30
  "license": "MIT",
31
- "homepage": "https://github.com/MithunWijayasiri/claudecode-statusline#readme",
31
+ "homepage": "https://ctxline.js.org",
32
32
  "bugs": {
33
- "url": "https://github.com/MithunWijayasiri/claudecode-statusline/issues"
33
+ "url": "https://github.com/MithunWijayasiri/ctxline-claude/issues"
34
34
  },
35
35
  "repository": {
36
36
  "type": "git",
37
- "url": "https://github.com/MithunWijayasiri/claudecode-statusline.git"
37
+ "url": "https://github.com/MithunWijayasiri/ctxline-claude.git"
38
38
  }
39
39
  }
package/statusline.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // Claude Code Enhanced Statusline
3
3
  // Shows: directory | model | context usage | current (5-hour) + weekly usage | current task
4
4
  // Auto-detects API key vs subscription usage
5
- // https://github.com/MithunWijayasiri/claudecode-statusline
5
+ // https://github.com/MithunWijayasiri/ctxline-claude
6
6
 
7
7
  const fs = require('fs');
8
8
  const path = require('path');
@@ -109,23 +109,21 @@ function getContextBar(remaining) {
109
109
  const filled = Math.round((used / 100) * BAR_WIDTH);
110
110
  const bar = '\u2588'.repeat(filled) + '\u2591'.repeat(BAR_WIDTH - filled);
111
111
 
112
- let coloredBar;
113
- if (used < 50) {
114
- coloredBar = `${colors.green}${bar} ${used}%${colors.reset}`;
115
- } else if (used < 65) {
116
- coloredBar = `${colors.yellow}${bar} ${used}%${colors.reset}`;
117
- } else if (used < 80) {
118
- coloredBar = `${colors.orange}${bar} ${used}%${colors.reset}`;
119
- } else {
120
- coloredBar = `${colors.blink}${colors.red}${bar} ${used}%${colors.reset}`;
121
- }
112
+ // Context color: green <50 / yellow <65 / orange <80 / blink-red >=80.
113
+ let color;
114
+ if (used < 50) color = colors.green;
115
+ else if (used < 65) color = colors.yellow;
116
+ else if (used < 80) color = colors.orange;
117
+ else color = colors.blink + colors.red;
122
118
 
123
- return coloredBar;
119
+ // Compact label form: "C<used> <bar>" (e.g. "C45 ███░░░"), colored as a whole.
120
+ return `${color}C${used} ${bar}${colors.reset}`;
124
121
  }
125
122
 
126
- // Render the usage bar from raw data. Called on every read (live or cached) so the
127
- // reset countdown is always recomputed from resetsAt rather than frozen at fetch time.
128
- function buildUsageBar(percentage, resetsAt) {
123
+ // Render a compact usage segment from raw data: "<label><pct> <countdown>"
124
+ // (e.g. "H81 2h21m") no bar. Called on every read (live or cached) so the reset
125
+ // countdown is always recomputed from resetsAt rather than frozen at fetch time.
126
+ function buildUsageBar(label, percentage, resetsAt) {
129
127
  let timeStr = '';
130
128
  if (resetsAt) {
131
129
  const diffMins = Math.max(0, Math.floor((new Date(resetsAt) - new Date()) / 60000));
@@ -137,21 +135,18 @@ function buildUsageBar(percentage, resetsAt) {
137
135
  else timeStr = `${mins}m`;
138
136
  }
139
137
 
140
- const filledWidth = Math.max(0, Math.min(BAR_WIDTH, Math.round((percentage / 100) * BAR_WIDTH)));
141
- const filled = '█'.repeat(filledWidth);
142
- const empty = '░'.repeat(BAR_WIDTH - filledWidth);
143
138
  const color = getUsageColor(percentage);
144
- const timePart = timeStr ? `${colors.dim} (${timeStr})${colors.reset}` : '';
139
+ const timePart = timeStr ? `${colors.dim} ${timeStr}${colors.reset}` : '';
145
140
 
146
- return `${color}${filled}${empty} ${percentage}%${colors.reset}${timePart}`;
141
+ return `${color}${label}${percentage}${colors.reset}${timePart}`;
147
142
  }
148
143
 
149
- // Build both usage bars from raw entries. Each entry is { percentage, resetsAt } or
150
- // null/absent. Returns { current, weekly } where each is a rendered bar string or null.
144
+ // Build both usage segments from raw entries. Each entry is { percentage, resetsAt } or
145
+ // null/absent. Returns { current, weekly } where each is a rendered segment string or null.
151
146
  function buildUsageBars(fiveHour, weekly) {
152
147
  return {
153
- current: fiveHour ? buildUsageBar(fiveHour.percentage, fiveHour.resetsAt) : null,
154
- weekly: weekly ? buildUsageBar(weekly.percentage, weekly.resetsAt) : null
148
+ current: fiveHour ? buildUsageBar('H', fiveHour.percentage, fiveHour.resetsAt) : null,
149
+ weekly: weekly ? buildUsageBar('W', weekly.percentage, weekly.resetsAt) : null
155
150
  };
156
151
  }
157
152
 
@@ -364,6 +359,15 @@ function getUsageWithCache(callback) {
364
359
  });
365
360
  }
366
361
 
362
+ // Session cost from stdin `cost.total_cost_usd` (USD float, computed client-side by
363
+ // Claude Code as tokens × per-model API pricing). Pure stdin — no network/cache.
364
+ // Returns "$0.00" rendered dim, or '' when absent/non-finite so the segment is omitted.
365
+ function getCostSegment(data) {
366
+ const usd = data?.cost?.total_cost_usd;
367
+ if (!Number.isFinite(usd)) return '';
368
+ return `${colors.dim}$${usd.toFixed(2)}${colors.reset}`;
369
+ }
370
+
367
371
  function getCurrentTask(sessionId) {
368
372
  if (!sessionId) return '';
369
373
 
@@ -400,15 +404,17 @@ function outputStatus(data, usage) {
400
404
  const remaining = data?.context_window?.remaining_percentage;
401
405
 
402
406
  const contextBar = getContextBar(remaining);
407
+ const cost = getCostSegment(data);
403
408
  const task = getCurrentTask(sessionId);
404
409
  const parts = [];
405
410
  parts.push(branch ? `${dirname} ${colors.dim}⎇ ${branch}${colors.reset}` : dirname);
406
411
  parts.push(effort ? `${model}${getEffortColor(effort)} · ${effort}${colors.reset}` : model);
407
- parts.push(`CTX ${contextBar}`);
412
+ parts.push(contextBar);
408
413
 
409
- if (usage?.current) parts.push(`5h ${usage.current}`);
410
- if (usage?.weekly) parts.push(`7d ${usage.weekly}`);
414
+ if (usage?.current) parts.push(usage.current);
415
+ if (usage?.weekly) parts.push(usage.weekly);
411
416
 
417
+ if (cost) parts.push(cost);
412
418
  if (task) parts.push(`${colors.dim}${task}${colors.reset}`);
413
419
  process.stdout.write(parts.join(' \u2502 '));
414
420
  } catch (e) {
@@ -418,9 +424,9 @@ function outputStatus(data, usage) {
418
424
 
419
425
  function outputFallback(usage) {
420
426
  const contextBar = getContextBar(undefined);
421
- const parts = ['~', 'Claude', `CTX ${contextBar}`];
422
- if (usage?.current) parts.push(`5h ${usage.current}`);
423
- if (usage?.weekly) parts.push(`7d ${usage.weekly}`);
427
+ const parts = ['~', 'Claude', contextBar];
428
+ if (usage?.current) parts.push(usage.current);
429
+ if (usage?.weekly) parts.push(usage.weekly);
424
430
  process.stdout.write(parts.join(' \u2502 '));
425
431
  }
426
432