ccstatusline 2.0.12 → 2.0.14

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/LICENSE CHANGED
@@ -18,8 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
22
-
23
- ---
24
- Original author: Matthew Breedlove (https://github.com/sirmalloc)
25
- Official repository: https://github.com/sirmalloc/ccstatusline
21
+ SOFTWARE.
package/README.md CHANGED
@@ -34,7 +34,9 @@
34
34
  - [Recent Updates](#-recent-updates)
35
35
  - [Features](#-features)
36
36
  - [Quick Start](#-quick-start)
37
+ - [Windows Support](#-windows-support)
37
38
  - [Usage](#-usage)
39
+ - [API Documentation](#-api-documentation)
38
40
  - [Development](#️-development)
39
41
  - [Contributing](#-contributing)
40
42
  - [License](#-license)
@@ -44,6 +46,10 @@
44
46
 
45
47
  ## 🆕 Recent Updates
46
48
 
49
+ ### v2.0.14 - Add remaining mode toggle to Context Percentage widgets
50
+
51
+ - **Remaining Mode** - You can now toggle the Context Percentage widgets between usage percentage and remaining percentage when configuring them in the TUI by pressing the 'l' key.
52
+
47
53
  ### v2.0.12 - Custom Text widget now supports emojis
48
54
 
49
55
  - **👾 Emoji Support** - You can now paste emoji into the custom text widget. You can also turn on the merge option to get emoji labels for your widgets like this:
@@ -145,6 +151,176 @@ The interactive configuration tool provides a terminal UI where you can:
145
151
 
146
152
  ---
147
153
 
154
+ ## 🪟 Windows Support
155
+
156
+ ccstatusline works seamlessly on Windows with full feature compatibility across PowerShell (5.1+ and 7+), Command Prompt, and Windows Subsystem for Linux (WSL).
157
+
158
+ ### Installation on Windows
159
+
160
+ #### Option 1: Using Bun (Recommended)
161
+ ```powershell
162
+ # Install Bun for Windows
163
+ irm bun.sh/install.ps1 | iex
164
+
165
+ # Run ccstatusline
166
+ bunx ccstatusline@latest
167
+ ```
168
+
169
+ #### Option 2: Using Node.js
170
+ ```powershell
171
+ # Using npm
172
+ npx ccstatusline@latest
173
+
174
+ # Or with Yarn
175
+ yarn dlx ccstatusline@latest
176
+
177
+ # Or with pnpm
178
+ pnpm dlx ccstatusline@latest
179
+ ```
180
+
181
+ ### Windows-Specific Features
182
+
183
+ #### Powerline Font Support
184
+ For optimal Powerline rendering on Windows:
185
+
186
+ **Windows Terminal** (Recommended):
187
+ - Supports Powerline fonts natively
188
+ - Download from [Microsoft Store](https://aka.ms/terminal)
189
+ - Auto-detects compatible fonts
190
+
191
+ **PowerShell/Command Prompt**:
192
+ ```powershell
193
+ # Install JetBrains Mono Nerd Font via winget
194
+ winget install DEVCOM.JetBrainsMonoNerdFont
195
+
196
+ # Alternative: Install base JetBrains Mono font
197
+ winget install "JetBrains.JetBrainsMono"
198
+
199
+ # Or download manually from: https://www.nerdfonts.com/font-downloads
200
+ ```
201
+
202
+ #### Path Handling
203
+ ccstatusline automatically handles Windows-specific paths:
204
+ - Git repositories work with both `/` and `\` path separators
205
+ - Current Working Directory widget displays Windows-style paths correctly
206
+ - Full support for mapped network drives and UNC paths
207
+ - Handles Windows drive letters (C:, D:, etc.)
208
+
209
+ ### Windows Troubleshooting
210
+
211
+ #### Common Issues & Solutions
212
+
213
+ **Issue**: Powerline symbols showing as question marks or boxes
214
+ ```powershell
215
+ # Solution: Install a compatible Nerd Font
216
+ winget install JetBrainsMono.NerdFont
217
+ # Then set the font in your terminal settings
218
+ ```
219
+
220
+ **Issue**: Git commands not recognized
221
+ ```powershell
222
+ # Check if Git is installed and in PATH
223
+ git --version
224
+
225
+ # If not found, install Git:
226
+ winget install Git.Git
227
+ # Or download from: https://git-scm.com/download/win
228
+ ```
229
+
230
+ **Issue**: Permission errors during installation
231
+ ```powershell
232
+ # Use non-global installation (recommended)
233
+ npx ccstatusline@latest
234
+
235
+ # Or run PowerShell as Administrator for global install
236
+ ```
237
+
238
+ **Issue**: "Execution Policy" errors in PowerShell
239
+ ```powershell
240
+ # Temporarily allow script execution
241
+ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
242
+ ```
243
+
244
+ **Issue**: Windows Defender blocking execution
245
+ ```powershell
246
+ # If Windows Defender flags the binary:
247
+ # 1. Open Windows Security
248
+ # 2. Go to "Virus & threat protection"
249
+ # 3. Add exclusion for the ccstatusline binary location
250
+ # Or use temporary bypass (not recommended for production):
251
+ Add-MpPreference -ExclusionPath "$env:USERPROFILE\.bun\bin"
252
+ ```
253
+
254
+ #### Windows Subsystem for Linux (WSL)
255
+ ccstatusline works perfectly in WSL environments:
256
+
257
+ ```bash
258
+ # Install in WSL Ubuntu/Debian
259
+ curl -fsSL https://bun.sh/install | bash
260
+ source ~/.bashrc
261
+ bunx ccstatusline@latest
262
+ ```
263
+
264
+ **WSL Benefits**:
265
+ - Native Unix-style path handling
266
+ - Better font rendering in WSL terminals
267
+ - Seamless integration with Linux development workflows
268
+
269
+ ### Windows Terminal Configuration
270
+
271
+ For the best experience, configure Windows Terminal with these recommended settings:
272
+
273
+ #### Terminal Settings (settings.json)
274
+ ```json
275
+ {
276
+ "profiles": {
277
+ "defaults": {
278
+ "font": {
279
+ "face": "JetBrainsMono Nerd Font",
280
+ "size": 12
281
+ },
282
+ "colorScheme": "One Half Dark"
283
+ }
284
+ }
285
+ }
286
+ ```
287
+
288
+ #### Claude Code Integration
289
+ Configure ccstatusline in your Claude Code settings:
290
+
291
+ **For Bun users** (Windows: `%USERPROFILE%\.claude\settings.json`):
292
+ ```json
293
+ {
294
+ "statusLine": "bunx ccstatusline@latest"
295
+ }
296
+ ```
297
+
298
+ **For npm users**:
299
+ ```json
300
+ {
301
+ "statusLine": "npx ccstatusline@latest"
302
+ }
303
+ ```
304
+
305
+ ### Performance on Windows
306
+
307
+ ccstatusline is optimized for Windows performance:
308
+ - **Bun runtime**: Significantly faster startup times on Windows
309
+ - **Caching**: Intelligent caching of git status and file operations
310
+ - **Async operations**: Non-blocking command execution
311
+ - **Memory efficient**: Minimal resource usage
312
+
313
+ ### Windows-Specific Widget Behavior
314
+
315
+ Some widgets have Windows-specific optimizations:
316
+
317
+ - **Current Working Directory**: Displays Windows drive letters and UNC paths
318
+ - **Git Widgets**: Handle Windows line endings (CRLF) automatically
319
+ - **Custom Commands**: Support both PowerShell and cmd.exe commands
320
+ - **Block Timer**: Accounts for Windows timezone handling
321
+
322
+ ---
323
+
148
324
  ## 📖 Usage
149
325
 
150
326
  Once configured, ccstatusline automatically formats your Claude Code status line. The status line appears at the bottom of your terminal during Claude Code sessions.
@@ -291,6 +467,38 @@ When terminal width is detected, status lines automatically truncate with ellips
291
467
 
292
468
  ---
293
469
 
470
+ ## 📖 API Documentation
471
+
472
+ Complete API documentation is generated using TypeDoc and includes detailed information about:
473
+
474
+ - **Core Types**: Configuration interfaces, widget definitions, and render contexts
475
+ - **Widget System**: All available widgets and their customization options
476
+ - **Utility Functions**: Helper functions for rendering, configuration, and terminal handling
477
+ - **Status Line Rendering**: Core rendering engine and formatting options
478
+
479
+ ### Generating Documentation
480
+
481
+ To generate the API documentation locally:
482
+
483
+ ```bash
484
+ # Generate documentation
485
+ bun run docs
486
+
487
+ # Clean generated documentation
488
+ bun run docs:clean
489
+ ```
490
+
491
+ The documentation will be generated in the `docs/` directory and can be viewed by opening `docs/index.html` in your web browser.
492
+
493
+ ### Documentation Structure
494
+
495
+ - **Types**: Core TypeScript interfaces and type definitions
496
+ - **Widgets**: Individual widget implementations and their APIs
497
+ - **Utils**: Utility functions for configuration, rendering, and terminal operations
498
+ - **Main Module**: Primary entry point and orchestration functions
499
+
500
+ ---
501
+
294
502
  ## 🛠️ Development
295
503
 
296
504
  ### Prerequisites
@@ -51374,7 +51374,7 @@ import { execSync as execSync3 } from "child_process";
51374
51374
  import * as fs5 from "fs";
51375
51375
  import * as path4 from "path";
51376
51376
  var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils";
51377
- var PACKAGE_VERSION = "2.0.12";
51377
+ var PACKAGE_VERSION = "2.0.14";
51378
51378
  function getPackageVersion() {
51379
51379
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51380
51380
  return PACKAGE_VERSION;
@@ -52934,7 +52934,17 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
52934
52934
  if (!widget)
52935
52935
  continue;
52936
52936
  if (widget.type === "separator") {
52937
- if (i > 0 && !preRenderedWidgets[i - 1]?.content)
52937
+ let hasContentBefore = false;
52938
+ for (let j = i - 1;j >= 0; j--) {
52939
+ const prevWidget = widgets[j];
52940
+ if (prevWidget && prevWidget.type !== "separator" && prevWidget.type !== "flex-separator") {
52941
+ if (preRenderedWidgets[j]?.content) {
52942
+ hasContentBefore = true;
52943
+ break;
52944
+ }
52945
+ }
52946
+ }
52947
+ if (!hasContentBefore)
52938
52948
  continue;
52939
52949
  const sepChar = widget.character ?? (settings.defaultSeparator ?? "|");
52940
52950
  const formattedSep = formatSeparator(sepChar);
@@ -53298,23 +53308,52 @@ class ContextPercentageWidget {
53298
53308
  return "blue";
53299
53309
  }
53300
53310
  getDescription() {
53301
- return "Shows percentage of context window used (of 200k tokens)";
53311
+ return "Shows percentage of context window used or remaining (of 200k tokens)";
53302
53312
  }
53303
53313
  getDisplayName() {
53304
53314
  return "Context %";
53305
53315
  }
53306
53316
  getEditorDisplay(item) {
53307
- return { displayText: this.getDisplayName() };
53317
+ const isInverse = item.metadata?.inverse === "true";
53318
+ const modifiers = [];
53319
+ if (isInverse) {
53320
+ modifiers.push("remaining");
53321
+ }
53322
+ return {
53323
+ displayText: this.getDisplayName(),
53324
+ modifierText: modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined
53325
+ };
53326
+ }
53327
+ handleEditorAction(action, item) {
53328
+ if (action === "toggle-inverse") {
53329
+ const currentState = item.metadata?.inverse === "true";
53330
+ return {
53331
+ ...item,
53332
+ metadata: {
53333
+ ...item.metadata,
53334
+ inverse: (!currentState).toString()
53335
+ }
53336
+ };
53337
+ }
53338
+ return null;
53308
53339
  }
53309
53340
  render(item, context, settings) {
53341
+ const isInverse = item.metadata?.inverse === "true";
53310
53342
  if (context.isPreview) {
53311
- return item.rawValue ? "9.3%" : "Ctx: 9.3%";
53343
+ const previewValue = isInverse ? "90.7%" : "9.3%";
53344
+ return item.rawValue ? previewValue : `Ctx: ${previewValue}`;
53312
53345
  } else if (context.tokenMetrics) {
53313
- const percentage = Math.min(100, context.tokenMetrics.contextLength / 200000 * 100);
53314
- return item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
53346
+ const usedPercentage = Math.min(100, context.tokenMetrics.contextLength / 200000 * 100);
53347
+ const displayPercentage = isInverse ? 100 - usedPercentage : usedPercentage;
53348
+ return item.rawValue ? `${displayPercentage.toFixed(1)}%` : `Ctx: ${displayPercentage.toFixed(1)}%`;
53315
53349
  }
53316
53350
  return null;
53317
53351
  }
53352
+ getCustomKeybinds() {
53353
+ return [
53354
+ { key: "l", label: "(l)eft/remaining", action: "toggle-inverse" }
53355
+ ];
53356
+ }
53318
53357
  supportsRawValue() {
53319
53358
  return true;
53320
53359
  }
@@ -53328,23 +53367,52 @@ class ContextPercentageUsableWidget {
53328
53367
  return "green";
53329
53368
  }
53330
53369
  getDescription() {
53331
- return "Shows percentage of usable context window used (of 160k tokens before auto-compact)";
53370
+ return "Shows percentage of usable context window used or remaining (of 160k tokens before auto-compact)";
53332
53371
  }
53333
53372
  getDisplayName() {
53334
53373
  return "Context % (usable)";
53335
53374
  }
53336
53375
  getEditorDisplay(item) {
53337
- return { displayText: this.getDisplayName() };
53376
+ const isInverse = item.metadata?.inverse === "true";
53377
+ const modifiers = [];
53378
+ if (isInverse) {
53379
+ modifiers.push("remaining");
53380
+ }
53381
+ return {
53382
+ displayText: this.getDisplayName(),
53383
+ modifierText: modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined
53384
+ };
53385
+ }
53386
+ handleEditorAction(action, item) {
53387
+ if (action === "toggle-inverse") {
53388
+ const currentState = item.metadata?.inverse === "true";
53389
+ return {
53390
+ ...item,
53391
+ metadata: {
53392
+ ...item.metadata,
53393
+ inverse: (!currentState).toString()
53394
+ }
53395
+ };
53396
+ }
53397
+ return null;
53338
53398
  }
53339
53399
  render(item, context, settings) {
53400
+ const isInverse = item.metadata?.inverse === "true";
53340
53401
  if (context.isPreview) {
53341
- return item.rawValue ? "11.6%" : "Ctx(u): 11.6%";
53402
+ const previewValue = isInverse ? "88.4%" : "11.6%";
53403
+ return item.rawValue ? previewValue : `Ctx(u): ${previewValue}`;
53342
53404
  } else if (context.tokenMetrics) {
53343
- const percentage = Math.min(100, context.tokenMetrics.contextLength / 160000 * 100);
53344
- return item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx(u): ${percentage.toFixed(1)}%`;
53405
+ const usedPercentage = Math.min(100, context.tokenMetrics.contextLength / 160000 * 100);
53406
+ const displayPercentage = isInverse ? 100 - usedPercentage : usedPercentage;
53407
+ return item.rawValue ? `${displayPercentage.toFixed(1)}%` : `Ctx(u): ${displayPercentage.toFixed(1)}%`;
53345
53408
  }
53346
53409
  return null;
53347
53410
  }
53411
+ getCustomKeybinds() {
53412
+ return [
53413
+ { key: "l", label: "(l)eft/remaining", action: "toggle-inverse" }
53414
+ ];
53415
+ }
53348
53416
  supportsRawValue() {
53349
53417
  return true;
53350
53418
  }
@@ -53369,10 +53437,9 @@ class SessionClockWidget {
53369
53437
  render(item, context, settings) {
53370
53438
  if (context.isPreview) {
53371
53439
  return item.rawValue ? "2hr 15m" : "Session: 2hr 15m";
53372
- } else if (context.sessionDuration) {
53373
- return item.rawValue ? context.sessionDuration : `Session: ${context.sessionDuration}`;
53374
53440
  }
53375
- return null;
53441
+ const duration3 = context.sessionDuration ?? "0m";
53442
+ return item.rawValue ? duration3 : `Session: ${duration3}`;
53376
53443
  }
53377
53444
  supportsRawValue() {
53378
53445
  return true;
@@ -54034,11 +54101,13 @@ class CurrentWorkingDirWidget {
54034
54101
  const segments = item.metadata?.segments ? parseInt(item.metadata.segments, 10) : undefined;
54035
54102
  let displayPath = cwd2;
54036
54103
  if (segments && segments > 0) {
54037
- const pathParts = cwd2.split("/");
54104
+ const useBackslash = cwd2.includes("\\") && !cwd2.includes("/");
54105
+ const outSep = useBackslash ? "\\" : "/";
54106
+ const pathParts = cwd2.split(/[\\/]+/);
54038
54107
  const filteredParts = pathParts.filter((part) => part !== "");
54039
54108
  if (filteredParts.length > segments) {
54040
54109
  const selectedSegments = filteredParts.slice(-segments);
54041
- displayPath = ".../" + selectedSegments.join("/");
54110
+ displayPath = "..." + outSep + selectedSegments.join(outSep);
54042
54111
  }
54043
54112
  }
54044
54113
  return item.rawValue ? displayPath : `cwd: ${displayPath}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",
@@ -17,12 +17,15 @@
17
17
  "example": "cat scripts/payload.example.json | bun start",
18
18
  "prepublishOnly": "bun run build",
19
19
  "lint": "bun tsc --noEmit; eslint . --config eslint.config.js --max-warnings=999999 --fix",
20
- "test": "bun vitest"
20
+ "test": "bun vitest",
21
+ "docs": "typedoc",
22
+ "docs:clean": "rm -rf docs"
21
23
  },
22
24
  "devDependencies": {
23
25
  "@eslint/js": "^9.33.0",
24
26
  "@stylistic/eslint-plugin": "^5.2.3",
25
27
  "@types/bun": "latest",
28
+ "@types/pluralize": "^0.0.33",
26
29
  "@types/react": "^19.1.10",
27
30
  "chalk": "^5.5.0",
28
31
  "eslint": "^9.33.0",
@@ -34,16 +37,16 @@
34
37
  "ink": "^6.2.0",
35
38
  "ink-gradient": "^3.0.0",
36
39
  "ink-select-input": "^6.2.0",
40
+ "pluralize": "^8.0.0",
37
41
  "react": "^19.1.1",
38
42
  "react-devtools-core": "^6.1.5",
39
43
  "strip-ansi": "^7.1.0",
40
44
  "tinyglobby": "^0.2.14",
45
+ "typedoc": "^0.28.12",
41
46
  "typescript": "^5.9.2",
42
47
  "typescript-eslint": "^8.39.1",
43
48
  "vitest": "^3.2.4",
44
- "zod": "^4.0.17",
45
- "pluralize": "^8.0.0",
46
- "@types/pluralize": "^0.0.33"
49
+ "zod": "^4.0.17"
47
50
  },
48
51
  "keywords": [
49
52
  "claude",