ccstatusline-usage 2.0.45 → 2.1.1

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 CHANGED
@@ -33,12 +33,20 @@ This fork adds API-based usage widgets beyond the upstream:
33
33
  - **Reset Timer** - Time until 5-hour session window resets
34
34
  - **Context Window Display** - Visual bar showing context usage
35
35
  - **Two-line Layout** - Session info on line 1, context on line 2
36
+ - **Narrow Terminal Detection** - Auto-compact rendering on terminals < 80 cols via tmux pane width detection
36
37
 
37
38
  ### Enhanced Status Line Preview
38
39
 
40
+ **Desktop (full width):**
39
41
  ```
40
42
  Session: [████░░░░░░░░░░░] 27.0% | Weekly: [████████████░░░] 86.0% | 1:56 hr | Extra: $41.24/$47.00 | Model: Opus 4.6 | Session ID: 714aa815-8a...
41
- Context: [███████░░░░░░░░] 103k/200k (51%)
43
+ Context: [███████░░░░░░░░] 103k/200k (51%)
44
+ ```
45
+
46
+ **Narrow terminal (< 80 cols, e.g. mobile SSH via tmux):**
47
+ ```
48
+ S: [██░░] 51.0% | W: [████] 92.0% | 0:00 hr
49
+ C: [██░░] 100k/200k
42
50
  ```
43
51
 
44
52
  These widgets are enabled by default. Just install and run!
@@ -65,17 +73,27 @@ These widgets are enabled by default. Just install and run!
65
73
 
66
74
  ## 🆕 Recent Updates
67
75
 
68
- ### [v2.0.44](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.0.44) - Upstream sync + extra usage
76
+ ### [v2.1.1](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.1.1) - Automatic mobile/narrow terminal detection
69
77
 
70
- - Merged 101 upstream commits (line reorder, CLAUDE_CONFIG_DIR support, Windows fixes, and more)
71
- - Extra usage spending display in Reset Timer widget (e.g. `Extra: €41.24/€47.00`)
72
- - Auto-detect currency from timezone (Europe/* = €, else $)
78
+ - **Automatic compact rendering** on terminals < 80 columns via tmux pane width detection:
79
+ ```
80
+ S: [██░░] 51.0% | W: [████] 92.0% | 0:00 hr
81
+ C: [██░░] 100k/200k
82
+ ```
83
+ - Widgets hidden on narrow terminals: Model, Session Clock, Session ID
84
+ - Progress bars shrink from 15 to 4 characters, labels abbreviated (Session → S, Weekly → W, Context → C)
85
+ - Orphaned separators automatically removed when adjacent widgets are hidden
86
+ - FlexMode `-40` override on narrow terminals (no auto-compact sidebar)
87
+ - Works with any tmux-based remote access setup (SSH clients, mobile terminals, etc.)
88
+
89
+ ### [v2.0.46](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.0.46) - Upstream sync
73
90
 
74
- ### [v2.0.43](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.0.43) - Extra usage spending display
91
+ - Merged 101 upstream commits: line reorder move mode, Claude Session ID widget, `CLAUDE_CONFIG_DIR` support, Windows fixes, Block Timer fixes, model string/object handling fix, and more
75
92
 
76
- - Show extra usage spending in Reset Timer widget when weekly limit is reached (e.g. `Extra: €41.24/€47.00`)
93
+ ### [v2.0.45](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.0.45) - Extra usage spending display
94
+
95
+ - Extra usage spending display in Reset Timer widget (e.g. `Extra: €41.24/€47.00`)
77
96
  - Auto-detect currency from timezone (Europe/* = €, else $)
78
- - Updated README release notes
79
97
 
80
98
  ### [v2.0.42](https://github.com/pcvelz/ccstatusline-usage/releases/tag/v2.0.42) - Fix reset timer API field
81
99
 
@@ -152,6 +170,8 @@ These widgets are enabled by default. Just install and run!
152
170
 
153
171
  ---
154
172
 
173
+ </div>
174
+
155
175
  ## ✨ Features
156
176
 
157
177
  - **📊 Real-time Metrics** - Display model name, git branch, token usage, session duration, block timer, and more
@@ -164,6 +184,7 @@ These widgets are enabled by default. Just install and run!
164
184
  - **⚙️ Global Options** - Apply consistent formatting across all widgets (padding, separators, bold, background)
165
185
  - **🚀 Cross-platform** - Works seamlessly with both Bun and Node.js
166
186
  - **🔧 Flexible Configuration** - Supports custom Claude Code config directory via `CLAUDE_CONFIG_DIR` environment variable
187
+ - **📱 Narrow Terminal Detection** - Auto-compact rendering on terminals < 80 columns via tmux pane width detection
167
188
  - **📏 Smart Width Detection** - Automatically adapts to terminal width with flex separators
168
189
  - **⚡ Zero Config** - Sensible defaults that work out of the box
169
190
 
@@ -656,6 +677,7 @@ jq 'del(.statusLine)' ~/.claude/settings.json > /tmp/cs.json && cat /tmp/cs.json
656
677
 
657
678
  ---
658
679
 
680
+
659
681
  ## 📄 License
660
682
 
661
683
  [MIT](LICENSE) © Matthew Breedlove
@@ -672,6 +694,7 @@ jq 'del(.statusLine)' ~/.claude/settings.json > /tmp/cs.json && cat /tmp/cs.json
672
694
 
673
695
  - GitHub: [@pcvelz](https://github.com/pcvelz)
674
696
 
697
+
675
698
  ---
676
699
 
677
700
  ## 🔗 Related Projects
@@ -51450,7 +51450,8 @@ import { execSync as execSync3 } from "child_process";
51450
51450
  import * as fs5 from "fs";
51451
51451
  import * as path4 from "path";
51452
51452
  var __dirname = "/Users/peter/Documents/Code/ccstatusline-usage/src/utils";
51453
- var PACKAGE_VERSION = "2.0.45";
51453
+ var MOBILE_THRESHOLD = 80;
51454
+ var PACKAGE_VERSION = "2.1.1";
51454
51455
  function getPackageVersion() {
51455
51456
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51456
51457
  return PACKAGE_VERSION;
@@ -51530,6 +51531,60 @@ function canDetectTerminalWidth() {
51530
51531
  return false;
51531
51532
  }
51532
51533
  }
51534
+ function parseTmuxClients() {
51535
+ try {
51536
+ const output = execSync3("tmux list-clients -F '#{client_width} #{client_height} #{client_tty}'", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 2000 }).trim();
51537
+ if (!output)
51538
+ return [];
51539
+ const clients = [];
51540
+ for (const line of output.split(`
51541
+ `)) {
51542
+ const parts = line.trim().split(" ");
51543
+ if (parts.length >= 3) {
51544
+ const width = parseInt(parts[0] ?? "", 10);
51545
+ const height = parseInt(parts[1] ?? "", 10);
51546
+ const tty2 = parts.slice(2).join(" ");
51547
+ if (!isNaN(width) && !isNaN(height) && tty2) {
51548
+ clients.push({ width, height, tty: tty2 });
51549
+ }
51550
+ }
51551
+ }
51552
+ return clients;
51553
+ } catch {
51554
+ return [];
51555
+ }
51556
+ }
51557
+ function getTmuxPaneWidth() {
51558
+ try {
51559
+ const output = execSync3("tmux display-message -p '#{pane_width}'", { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], timeout: 2000 }).trim();
51560
+ const parsed = parseInt(output, 10);
51561
+ if (!isNaN(parsed) && parsed > 0)
51562
+ return parsed;
51563
+ } catch {}
51564
+ return null;
51565
+ }
51566
+ function detectTerminalEnvironment() {
51567
+ const inTmux = Boolean(process.env.TMUX);
51568
+ let terminalWidth = getTerminalWidth();
51569
+ let smallestClientWidth = null;
51570
+ let tmuxClients = [];
51571
+ if (inTmux) {
51572
+ const paneWidth = getTmuxPaneWidth();
51573
+ if (paneWidth !== null)
51574
+ terminalWidth = paneWidth;
51575
+ tmuxClients = parseTmuxClients();
51576
+ if (tmuxClients.length > 0)
51577
+ smallestClientWidth = Math.min(...tmuxClients.map((c) => c.width));
51578
+ }
51579
+ const isMobile = terminalWidth !== null && terminalWidth < MOBILE_THRESHOLD;
51580
+ return {
51581
+ inTmux,
51582
+ tmuxClients,
51583
+ terminalWidth,
51584
+ isMobile,
51585
+ smallestClientWidth
51586
+ };
51587
+ }
51533
51588
 
51534
51589
  // node_modules/ink-select-input/build/Indicator.js
51535
51590
  var import_react27 = __toESM(require_react(), 1);
@@ -52297,6 +52352,8 @@ class ModelWidget {
52297
52352
  if (context.isPreview) {
52298
52353
  return item.rawValue ? "Claude" : "Model: Claude";
52299
52354
  }
52355
+ if (context.terminalEnv?.isMobile)
52356
+ return null;
52300
52357
  const model = context.data?.model;
52301
52358
  const modelDisplayName = typeof model === "string" ? model : model?.display_name ?? model?.id;
52302
52359
  if (modelDisplayName) {
@@ -52644,6 +52701,8 @@ function renderPowerlineStatusLine(widgets, settings, context, lineIndex = 0, gl
52644
52701
  } else if (flexMode === "full-until-compact") {
52645
52702
  terminalWidth = detectedWidth - 6;
52646
52703
  }
52704
+ } else if (context.terminalEnv?.isMobile) {
52705
+ terminalWidth = detectedWidth - 6;
52647
52706
  } else {
52648
52707
  if (flexMode === "full") {
52649
52708
  terminalWidth = detectedWidth - 6;
@@ -53018,6 +53077,8 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
53018
53077
  } else if (flexMode === "full-until-compact") {
53019
53078
  terminalWidth = detectedWidth - 6;
53020
53079
  }
53080
+ } else if (context.terminalEnv?.isMobile) {
53081
+ terminalWidth = detectedWidth - 6;
53021
53082
  } else {
53022
53083
  if (flexMode === "full") {
53023
53084
  terminalWidth = detectedWidth - 6;
@@ -53053,6 +53114,18 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
53053
53114
  }
53054
53115
  if (!hasContentBefore)
53055
53116
  continue;
53117
+ let hasContentAfter = false;
53118
+ for (let j = i + 1;j < widgets.length; j++) {
53119
+ const nextWidget = widgets[j];
53120
+ if (nextWidget && nextWidget.type !== "separator" && nextWidget.type !== "flex-separator") {
53121
+ if (preRenderedWidgets[j]?.content) {
53122
+ hasContentAfter = true;
53123
+ break;
53124
+ }
53125
+ }
53126
+ }
53127
+ if (!hasContentAfter)
53128
+ continue;
53056
53129
  const sepChar = widget.character ?? (settings.defaultSeparator ?? "|");
53057
53130
  const formattedSep = formatSeparator(sepChar);
53058
53131
  let separatorColor = widget.color ?? "gray";
@@ -53563,6 +53636,8 @@ class SessionClockWidget {
53563
53636
  if (context.isPreview) {
53564
53637
  return item.rawValue ? "2hr 15m" : "Session: 2hr 15m";
53565
53638
  }
53639
+ if (context.terminalEnv?.isMobile)
53640
+ return null;
53566
53641
  const duration3 = context.sessionDuration ?? "0m";
53567
53642
  return item.rawValue ? duration3 : `Session: ${duration3}`;
53568
53643
  }
@@ -54388,13 +54463,13 @@ class ClaudeSessionIdWidget {
54388
54463
  render(item, context, settings) {
54389
54464
  if (context.isPreview) {
54390
54465
  return item.rawValue ? "preview-session-id" : "Session ID: preview-session-id";
54391
- } else {
54392
- const sessionId = context.data?.session_id;
54393
- if (!sessionId) {
54394
- return null;
54395
- }
54396
- return item.rawValue ? sessionId : `Session ID: ${sessionId}`;
54397
54466
  }
54467
+ if (context.terminalEnv?.isMobile)
54468
+ return null;
54469
+ const sessionId = context.data?.session_id;
54470
+ if (!sessionId)
54471
+ return null;
54472
+ return item.rawValue ? sessionId : `Session ID: ${sessionId}`;
54398
54473
  }
54399
54474
  supportsRawValue() {
54400
54475
  return true;
@@ -54593,11 +54668,17 @@ function getErrorMessage(error43) {
54593
54668
  return "[Parse Error]";
54594
54669
  }
54595
54670
  }
54596
- function makeProgressBar(percent, width = 15) {
54671
+ var MOBILE_BAR_WIDTH = 4;
54672
+ var DEFAULT_BAR_WIDTH = 15;
54673
+ function makeProgressBar(percent, width = DEFAULT_BAR_WIDTH) {
54597
54674
  const filled = Math.round(percent / 100 * width);
54598
54675
  const empty = width - filled;
54599
54676
  return "[" + "█".repeat(filled) + "░".repeat(empty) + "]";
54600
54677
  }
54678
+ function formatUsageBar(label, shortLabel, percent, mobile) {
54679
+ const bar = makeProgressBar(percent, mobile ? MOBILE_BAR_WIDTH : DEFAULT_BAR_WIDTH);
54680
+ return `${mobile ? shortLabel : label}: ${bar} ${percent.toFixed(1)}%`;
54681
+ }
54601
54682
 
54602
54683
  class SessionUsageWidget {
54603
54684
  getDefaultColor() {
@@ -54620,8 +54701,7 @@ class SessionUsageWidget {
54620
54701
  return getErrorMessage(data.error);
54621
54702
  if (data.sessionUsage === undefined)
54622
54703
  return null;
54623
- const percent = data.sessionUsage;
54624
- return `Session: ${makeProgressBar(percent)} ${percent.toFixed(1)}%`;
54704
+ return formatUsageBar("Session", "S", data.sessionUsage, Boolean(context.terminalEnv?.isMobile));
54625
54705
  }
54626
54706
  supportsRawValue() {
54627
54707
  return false;
@@ -54652,8 +54732,7 @@ class WeeklyUsageWidget {
54652
54732
  return getErrorMessage(data.error);
54653
54733
  if (data.weeklyUsage === undefined)
54654
54734
  return null;
54655
- const percent = data.weeklyUsage;
54656
- return `Weekly: ${makeProgressBar(percent)} ${percent.toFixed(1)}%`;
54735
+ return formatUsageBar("Weekly", "W", data.weeklyUsage, Boolean(context.terminalEnv?.isMobile));
54657
54736
  }
54658
54737
  supportsRawValue() {
54659
54738
  return false;
@@ -54754,7 +54833,11 @@ class ContextBarWidget {
54754
54833
  const percent = total > 0 ? used / total * 100 : 0;
54755
54834
  const usedK = Math.round(used / 1000);
54756
54835
  const totalK = Math.round(total / 1000);
54757
- return `Context: ${makeProgressBar(percent)} ${usedK}k/${totalK}k (${Math.round(percent)}%)`;
54836
+ const mobile = Boolean(context.terminalEnv?.isMobile);
54837
+ const bar = makeProgressBar(percent, mobile ? MOBILE_BAR_WIDTH : DEFAULT_BAR_WIDTH);
54838
+ const label = mobile ? "C" : "Context";
54839
+ const suffix = mobile ? "" : ` (${Math.round(percent)}%)`;
54840
+ return `${label}: ${bar} ${usedK}k/${totalK}k${suffix}`;
54758
54841
  }
54759
54842
  supportsRawValue() {
54760
54843
  return false;
@@ -59729,12 +59812,15 @@ async function renderMultipleLines(data) {
59729
59812
  if (hasBlockTimer) {
59730
59813
  blockMetrics = getBlockMetrics();
59731
59814
  }
59815
+ const terminalEnv = detectTerminalEnvironment();
59732
59816
  const context = {
59733
59817
  data,
59734
59818
  tokenMetrics,
59735
59819
  sessionDuration,
59736
59820
  blockMetrics,
59737
- isPreview: false
59821
+ terminalWidth: terminalEnv.terminalWidth,
59822
+ isPreview: false,
59823
+ terminalEnv
59738
59824
  };
59739
59825
  const preRenderedLines = preRenderAllWidgets(lines, settings, context);
59740
59826
  const preCalculatedMaxWidths = calculateMaxWidthsFromPreRendered(preRenderedLines, settings);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline-usage",
3
- "version": "2.0.45",
3
+ "version": "2.1.1",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",