ccstatusline 1.0.10 → 1.0.12

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
@@ -31,9 +31,12 @@ npx ccstatusline@latest
31
31
  ```
32
32
 
33
33
  This launches a TUI where you can:
34
- - Add/remove status line items
35
- - Reorder items with arrow keys
34
+ - Configure up to 3 separate status lines
35
+ - Add/remove/reorder status line items
36
36
  - Customize colors for each element
37
+ - Configure flex separator behavior
38
+ - Edit custom text items
39
+ - Install/uninstall to Claude Code settings
37
40
  - Preview your status line in real-time
38
41
 
39
42
  Your settings are saved to `~/.config/ccstatusline/settings.json`.
@@ -48,6 +51,7 @@ Once configured, ccstatusline automatically formats your Claude Code status line
48
51
  - **Git Branch** - Displays current git branch name
49
52
  - **Git Changes** - Shows uncommitted insertions/deletions (e.g., "+42,-10")
50
53
  - **Session Clock** - Shows elapsed time since session start (e.g., "2hr 15m")
54
+ - **Version** - Shows Claude Code version
51
55
  - **Tokens Input** - Shows input tokens used
52
56
  - **Tokens Output** - Shows output tokens used
53
57
  - **Tokens Cached** - Shows cached tokens used
@@ -55,55 +59,51 @@ Once configured, ccstatusline automatically formats your Claude Code status line
55
59
  - **Context Length** - Shows current context length in tokens
56
60
  - **Context Percentage** - Shows percentage of context limit used
57
61
  - **Terminal Width** - Shows detected terminal width (for debugging)
58
- - **Separator** - Visual divider between items (|)
62
+ - **Custom Text** - Add your own custom text to the status line
63
+ - **Separator** - Visual divider between items (customizable: |, -, comma, space)
59
64
  - **Flex Separator** - Expands to fill available space
60
65
 
61
- ## Configuration File
62
-
63
- The configuration file at `~/.config/ccstatusline/settings.json` looks like:
64
-
65
- ```json
66
- {
67
- "lines": [
68
- [
69
- {
70
- "id": "1",
71
- "type": "model",
72
- "color": "cyan"
73
- },
74
- {
75
- "id": "2",
76
- "type": "separator"
77
- },
78
- {
79
- "id": "3",
80
- "type": "git-branch",
81
- "color": "magenta"
82
- },
83
- {
84
- "id": "4",
85
- "type": "separator"
86
- },
87
- {
88
- "id": "5",
89
- "type": "session-clock",
90
- "color": "blue"
91
- }
92
- ]
93
- ],
94
- "colors": {
95
- "model": "cyan",
96
- "gitBranch": "magenta",
97
- "separator": "dim"
98
- }
99
- }
100
- ```
101
-
102
- ### Color Options
103
-
104
- Available colors:
105
- - `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, `gray`
106
- - `brightBlack`, `brightRed`, `brightGreen`, `brightYellow`, `brightBlue`, `brightMagenta`, `brightCyan`, `brightWhite`
66
+ ### TUI Controls
67
+
68
+ #### Main Menu
69
+ - **↑↓** - Navigate menu items
70
+ - **Enter** - Select item
71
+ - **Ctrl+C** - Exit
72
+
73
+ #### Line Editor
74
+ - **↑↓** - Select item
75
+ - **←→** - Change item type
76
+ - **Enter** - Enter move mode (reorder items)
77
+ - **a** - Add item at end
78
+ - **i** - Insert item before selected
79
+ - **d** - Delete selected item
80
+ - **c** - Clear entire line
81
+ - **r** - Toggle raw value mode (no labels)
82
+ - **e** - Edit custom text (for custom-text items)
83
+ - **Space** - Change separator character (for separator items)
84
+ - **ESC** - Go back
85
+
86
+ #### Color Configuration
87
+ - **↑↓** - Select item
88
+ - **Enter** - Cycle through colors
89
+ - **ESC** - Go back
90
+
91
+ #### Flex Options
92
+ Configure how flex separators calculate available width:
93
+ - **Full width always** - Uses full terminal width (may wrap with auto-compact message)
94
+ - **Full width minus 40** - Leaves space for auto-compact message (default)
95
+ - **Full width until compact** - Switches based on context percentage threshold
96
+
97
+ ### Raw Value Mode
98
+
99
+ Some items support "raw value" mode which displays just the value without a label:
100
+ - Normal: `Model: Claude 3.5 Sonnet` → Raw: `Claude 3.5 Sonnet`
101
+ - Normal: `Session: 2hr 15m` → Raw: `2hr 15m`
102
+ - Normal: `Ctx: 18.6k` → Raw: `18.6k`
103
+
104
+ ### Status Line Truncation
105
+
106
+ When terminal width is detected, status lines automatically truncate with ellipsis (...) if they exceed the available width, preventing line wrapping.
107
107
 
108
108
  ## Development
109
109
 
@@ -24,20 +24,47 @@ var SETTINGS_PATH = path.join(CONFIG_DIR, "settings.json");
24
24
  var DEFAULT_SETTINGS = {
25
25
  lines: [
26
26
  [
27
- { id: "1", type: "model", color: "cyan" },
28
- { id: "2", type: "separator" },
29
- { id: "3", type: "terminal-width", color: "dim" },
30
- { id: "4", type: "separator" },
31
- { id: "5", type: "git-branch", color: "magenta" },
32
- { id: "6", type: "separator" },
33
- { id: "7", type: "git-changes", color: "yellow" }
27
+ {
28
+ id: "1",
29
+ type: "model",
30
+ color: "cyan"
31
+ },
32
+ {
33
+ id: "2",
34
+ type: "separator"
35
+ },
36
+ {
37
+ id: "3",
38
+ type: "context-length",
39
+ color: "dim"
40
+ },
41
+ {
42
+ id: "4",
43
+ type: "separator"
44
+ },
45
+ {
46
+ id: "5",
47
+ type: "git-branch",
48
+ color: "magenta"
49
+ },
50
+ {
51
+ id: "6",
52
+ type: "separator"
53
+ },
54
+ {
55
+ id: "7",
56
+ type: "git-changes",
57
+ color: "yellow"
58
+ }
34
59
  ]
35
60
  ],
36
61
  colors: {
37
62
  model: "cyan",
38
63
  gitBranch: "magenta",
39
64
  separator: "dim"
40
- }
65
+ },
66
+ flexMode: "full-minus-40",
67
+ compactThreshold: 60
41
68
  };
42
69
  async function loadSettings() {
43
70
  try {
@@ -92,7 +119,9 @@ function migrateOldSettings(old) {
92
119
  }
93
120
  return {
94
121
  lines: [items],
95
- colors: old.colors || DEFAULT_SETTINGS.colors
122
+ colors: old.colors || DEFAULT_SETTINGS.colors,
123
+ flexMode: DEFAULT_SETTINGS.flexMode,
124
+ compactThreshold: DEFAULT_SETTINGS.compactThreshold
96
125
  };
97
126
  }
98
127
  async function saveSettings(settings) {
@@ -151,7 +180,19 @@ async function getExistingStatusLine() {
151
180
  }
152
181
 
153
182
  // src/tui.tsx
154
- import { jsxDEV } from "react/jsx-dev-runtime";
183
+ import * as fs3 from "fs";
184
+ import * as path3 from "path";
185
+ var __dirname = "/Users/sirmalloc/Desktop/ccstatusline/src";
186
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
187
+ function getPackageVersion() {
188
+ try {
189
+ const packageJsonPath = path3.join(__dirname, "..", "package.json");
190
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
191
+ return packageJson.version || "";
192
+ } catch {
193
+ return "";
194
+ }
195
+ }
155
196
  function canDetectTerminalWidth() {
156
197
  try {
157
198
  const tty = execSync("ps -o tty= -p $(ps -o ppid= -p $$)", {
@@ -182,15 +223,27 @@ function canDetectTerminalWidth() {
182
223
  return false;
183
224
  }
184
225
  }
185
- var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
186
- const width = widthDetectionAvailable ? terminalWidth : null;
226
+ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings) => {
227
+ let effectiveWidth = null;
228
+ if (widthDetectionAvailable && terminalWidth) {
229
+ const flexMode = settings?.flexMode || "full-minus-40";
230
+ if (flexMode === "full") {
231
+ effectiveWidth = terminalWidth - 2;
232
+ } else if (flexMode === "full-minus-40") {
233
+ effectiveWidth = terminalWidth - 40;
234
+ } else if (flexMode === "full-until-compact") {
235
+ effectiveWidth = terminalWidth - 2;
236
+ }
237
+ }
238
+ const width = terminalWidth;
239
+ const flexWidth = effectiveWidth;
187
240
  const elements = [];
188
241
  let hasFlexSeparator = false;
189
242
  items.forEach((item) => {
190
243
  switch (item.type) {
191
244
  case "model":
192
245
  const modelColor = chalk[item.color || "cyan"] || chalk.cyan;
193
- elements.push(modelColor("Model: Claude"));
246
+ elements.push(modelColor(item.rawValue ? "Claude" : "Model: Claude"));
194
247
  break;
195
248
  case "git-branch":
196
249
  const branchColor = chalk[item.color || "magenta"] || chalk.magenta;
@@ -202,40 +255,40 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
202
255
  break;
203
256
  case "tokens-input":
204
257
  const inputColor = chalk[item.color || "yellow"] || chalk.yellow;
205
- elements.push(inputColor("In: 15.2k"));
258
+ elements.push(inputColor(item.rawValue ? "15.2k" : "In: 15.2k"));
206
259
  break;
207
260
  case "tokens-output":
208
261
  const outputColor = chalk[item.color || "green"] || chalk.green;
209
- elements.push(outputColor("Out: 3.4k"));
262
+ elements.push(outputColor(item.rawValue ? "3.4k" : "Out: 3.4k"));
210
263
  break;
211
264
  case "tokens-cached":
212
265
  const cachedColor = chalk[item.color || "blue"] || chalk.blue;
213
- elements.push(cachedColor("Cached: 12k"));
266
+ elements.push(cachedColor(item.rawValue ? "12k" : "Cached: 12k"));
214
267
  break;
215
268
  case "tokens-total":
216
269
  const totalColor = chalk[item.color || "white"] || chalk.white;
217
- elements.push(totalColor("Total: 30.6k"));
270
+ elements.push(totalColor(item.rawValue ? "30.6k" : "Total: 30.6k"));
218
271
  break;
219
272
  case "context-length":
220
273
  const ctxColor = chalk[item.color || "cyan"] || chalk.cyan;
221
- elements.push(ctxColor("Ctx: 18.6k"));
274
+ elements.push(ctxColor(item.rawValue ? "18.6k" : "Ctx: 18.6k"));
222
275
  break;
223
276
  case "context-percentage":
224
277
  const ctxPctColor = chalk[item.color || "cyan"] || chalk.cyan;
225
- elements.push(ctxPctColor("Ctx: 9.3%"));
278
+ elements.push(ctxPctColor(item.rawValue ? "9.3%" : "Ctx: 9.3%"));
226
279
  break;
227
280
  case "session-clock":
228
281
  const sessionColor = chalk[item.color || "blue"] || chalk.blue;
229
- elements.push(sessionColor("Session: 2hr 15m"));
282
+ elements.push(sessionColor(item.rawValue ? "2hr 15m" : "Session: 2hr 15m"));
230
283
  break;
231
284
  case "version":
232
285
  const versionColor = chalk[item.color || "green"] || chalk.green;
233
- elements.push(versionColor("Version: 1.0.72"));
286
+ elements.push(versionColor(item.rawValue ? "1.0.72" : "Version: 1.0.72"));
234
287
  break;
235
288
  case "terminal-width":
236
289
  const termColor = chalk[item.color || "dim"] || chalk.dim;
237
290
  const detectedWidth = canDetectTerminalWidth() ? terminalWidth : "??";
238
- elements.push(termColor(`Term: ${detectedWidth}`));
291
+ elements.push(termColor(item.rawValue ? `${detectedWidth}` : `Term: ${detectedWidth}`));
239
292
  break;
240
293
  case "separator":
241
294
  const sepChar = item.character || "|";
@@ -253,10 +306,15 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
253
306
  elements.push("FLEX");
254
307
  hasFlexSeparator = true;
255
308
  break;
309
+ case "custom-text":
310
+ const customTextColor = chalk[item.color || "white"] || chalk.white;
311
+ const customText = item.customText || "";
312
+ elements.push(customTextColor(customText));
313
+ break;
256
314
  }
257
315
  });
258
316
  let statusLine = "";
259
- if (hasFlexSeparator && width) {
317
+ if (hasFlexSeparator && flexWidth) {
260
318
  const parts = [[]];
261
319
  let currentPart = 0;
262
320
  for (let i = 0;i < items.length; i++) {
@@ -277,7 +335,7 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
277
335
  });
278
336
  const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
279
337
  const flexCount = parts.length - 1;
280
- const totalSpace = Math.max(0, width - totalContentLength);
338
+ const totalSpace = Math.max(0, flexWidth - totalContentLength);
281
339
  const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
282
340
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
283
341
  statusLine = "";
@@ -294,15 +352,48 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
294
352
  } else {
295
353
  statusLine = elements.map((e) => e === "FLEX" ? chalk.dim(" | ") : e).join("");
296
354
  }
355
+ if (width && width > 0) {
356
+ const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
357
+ if (plainLength > width) {
358
+ let truncated = "";
359
+ let currentLength = 0;
360
+ let inAnsiCode = false;
361
+ let ansiBuffer = "";
362
+ const targetLength = width - 5;
363
+ for (let i = 0;i < statusLine.length; i++) {
364
+ const char = statusLine[i];
365
+ if (char === "\x1B") {
366
+ inAnsiCode = true;
367
+ ansiBuffer = char;
368
+ } else if (inAnsiCode) {
369
+ ansiBuffer += char;
370
+ if (char === "m") {
371
+ truncated += ansiBuffer;
372
+ inAnsiCode = false;
373
+ ansiBuffer = "";
374
+ }
375
+ } else {
376
+ if (currentLength < targetLength) {
377
+ truncated += char;
378
+ currentLength++;
379
+ } else {
380
+ break;
381
+ }
382
+ }
383
+ }
384
+ statusLine = truncated + "...";
385
+ }
386
+ }
297
387
  return statusLine;
298
388
  };
299
- var StatusLinePreview = ({ lines, terminalWidth }) => {
389
+ var StatusLinePreview = ({ lines, terminalWidth, settings }) => {
300
390
  const widthDetectionAvailable = canDetectTerminalWidth();
301
391
  const boxWidth = Math.min(terminalWidth - 4, process.stdout.columns - 4 || 76);
302
392
  const topLine = chalk.dim("╭" + "─".repeat(Math.max(0, boxWidth - 2)) + "╮");
303
393
  const middleLine = chalk.dim("│") + " > " + " ".repeat(Math.max(0, boxWidth - 5)) + chalk.dim("│");
304
394
  const bottomLine = chalk.dim("╰" + "─".repeat(Math.max(0, boxWidth - 2)) + "╯");
305
- const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, boxWidth, widthDetectionAvailable) : "").filter((line) => line !== "");
395
+ const availableWidth = boxWidth - 2;
396
+ const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : "").filter((line) => line !== "");
306
397
  return /* @__PURE__ */ jsxDEV(Box, {
307
398
  flexDirection: "column",
308
399
  children: [
@@ -316,8 +407,11 @@ var StatusLinePreview = ({ lines, terminalWidth }) => {
316
407
  children: bottomLine
317
408
  }, undefined, false, undefined, this),
318
409
  renderedLines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
319
- children: line
320
- }, index, false, undefined, this))
410
+ children: [
411
+ " ",
412
+ line
413
+ ]
414
+ }, index, true, undefined, this))
321
415
  ]
322
416
  }, undefined, true, undefined, this);
323
417
  };
@@ -390,6 +484,7 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
390
484
  const items = [
391
485
  { label: "\uD83D\uDCDD Edit Lines", value: "lines" },
392
486
  { label: "\uD83C\uDFA8 Configure Colors", value: "colors" },
487
+ { label: "⚡ Flex Options", value: "flex" },
393
488
  { label: isClaudeInstalled ? "\uD83D\uDDD1️ Uninstall from Claude Code" : "\uD83D\uDCE6 Install to Claude Code", value: "install" }
394
489
  ];
395
490
  if (hasChanges) {
@@ -417,9 +512,29 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
417
512
  var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
418
513
  const [selectedIndex, setSelectedIndex] = useState(0);
419
514
  const [moveMode, setMoveMode] = useState(false);
515
+ const [editingText, setEditingText] = useState(false);
516
+ const [textInput, setTextInput] = useState("");
420
517
  const separatorChars = ["|", "-", ",", " "];
421
518
  useInput((input, key) => {
422
- if (moveMode) {
519
+ if (editingText) {
520
+ if (key.return) {
521
+ const currentItem2 = items[selectedIndex];
522
+ if (currentItem2) {
523
+ const newItems = [...items];
524
+ newItems[selectedIndex] = { ...currentItem2, customText: textInput };
525
+ onUpdate(newItems);
526
+ }
527
+ setEditingText(false);
528
+ setTextInput("");
529
+ } else if (key.escape) {
530
+ setEditingText(false);
531
+ setTextInput("");
532
+ } else if (key.backspace || key.delete) {
533
+ setTextInput(textInput.slice(0, -1));
534
+ } else if (input && input.length === 1) {
535
+ setTextInput(textInput + input);
536
+ }
537
+ } else if (moveMode) {
423
538
  if (key.upArrow && selectedIndex > 0) {
424
539
  const newItems = [...items];
425
540
  const temp = newItems[selectedIndex];
@@ -461,17 +576,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
461
576
  "session-clock",
462
577
  "terminal-width",
463
578
  "version",
464
- "flex-separator"
579
+ "flex-separator",
580
+ "custom-text"
465
581
  ];
466
- const currentItem = items[selectedIndex];
467
- if (currentItem) {
468
- const currentType = currentItem.type;
582
+ const currentItem2 = items[selectedIndex];
583
+ if (currentItem2) {
584
+ const currentType = currentItem2.type;
469
585
  const currentIndex = types.indexOf(currentType);
470
586
  const prevIndex = currentIndex === 0 ? types.length - 1 : currentIndex - 1;
471
587
  const newItems = [...items];
472
588
  const prevType = types[prevIndex];
473
589
  if (prevType) {
474
- newItems[selectedIndex] = { ...currentItem, type: prevType };
590
+ newItems[selectedIndex] = { ...currentItem2, type: prevType };
475
591
  onUpdate(newItems);
476
592
  }
477
593
  }
@@ -490,17 +606,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
490
606
  "session-clock",
491
607
  "terminal-width",
492
608
  "version",
493
- "flex-separator"
609
+ "flex-separator",
610
+ "custom-text"
494
611
  ];
495
- const currentItem = items[selectedIndex];
496
- if (currentItem) {
497
- const currentType = currentItem.type;
612
+ const currentItem2 = items[selectedIndex];
613
+ if (currentItem2) {
614
+ const currentType = currentItem2.type;
498
615
  const currentIndex = types.indexOf(currentType);
499
616
  const nextIndex = (currentIndex + 1) % types.length;
500
617
  const newItems = [...items];
501
618
  const nextType = types[nextIndex];
502
619
  if (nextType) {
503
- newItems[selectedIndex] = { ...currentItem, type: nextType };
620
+ newItems[selectedIndex] = { ...currentItem2, type: nextType };
504
621
  onUpdate(newItems);
505
622
  }
506
623
  }
@@ -520,9 +637,8 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
520
637
  type: "separator"
521
638
  };
522
639
  const newItems = [...items];
523
- newItems.splice(selectedIndex + 1, 0, newItem);
640
+ newItems.splice(selectedIndex, 0, newItem);
524
641
  onUpdate(newItems);
525
- setSelectedIndex(selectedIndex + 1);
526
642
  } else if (input === "d" && items.length > 0) {
527
643
  const newItems = items.filter((_, i) => i !== selectedIndex);
528
644
  onUpdate(newItems);
@@ -533,15 +649,28 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
533
649
  onUpdate([]);
534
650
  setSelectedIndex(0);
535
651
  } else if (input === " " && items.length > 0) {
536
- const currentItem = items[selectedIndex];
537
- if (currentItem && currentItem.type === "separator") {
538
- const currentChar = currentItem.character || "|";
652
+ const currentItem2 = items[selectedIndex];
653
+ if (currentItem2 && currentItem2.type === "separator") {
654
+ const currentChar = currentItem2.character || "|";
539
655
  const currentCharIndex = separatorChars.indexOf(currentChar);
540
656
  const nextChar = separatorChars[(currentCharIndex + 1) % separatorChars.length];
541
657
  const newItems = [...items];
542
- newItems[selectedIndex] = { ...currentItem, character: nextChar };
658
+ newItems[selectedIndex] = { ...currentItem2, character: nextChar };
659
+ onUpdate(newItems);
660
+ }
661
+ } else if (input === "r" && items.length > 0) {
662
+ const currentItem2 = items[selectedIndex];
663
+ if (currentItem2 && currentItem2.type !== "separator" && currentItem2.type !== "flex-separator" && currentItem2.type !== "custom-text") {
664
+ const newItems = [...items];
665
+ newItems[selectedIndex] = { ...currentItem2, rawValue: !currentItem2.rawValue };
543
666
  onUpdate(newItems);
544
667
  }
668
+ } else if (input === "e" && items.length > 0) {
669
+ const currentItem2 = items[selectedIndex];
670
+ if (currentItem2 && currentItem2.type === "custom-text") {
671
+ setTextInput(currentItem2.customText || "");
672
+ setEditingText(true);
673
+ }
545
674
  } else if (key.escape) {
546
675
  onBack();
547
676
  }
@@ -580,10 +709,30 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
580
709
  return chalk.dim("Terminal Width");
581
710
  case "version":
582
711
  return chalk.green("Version");
712
+ case "custom-text":
713
+ const text = item.customText || "Empty";
714
+ return chalk.white(`Custom Text (${text})`);
583
715
  }
584
716
  };
585
717
  const hasFlexSeparator = items.some((item) => item.type === "flex-separator");
586
718
  const widthDetectionAvailable = canDetectTerminalWidth();
719
+ const currentItem = items[selectedIndex];
720
+ const isSeparator = currentItem?.type === "separator";
721
+ const isFlexSeparator = currentItem?.type === "flex-separator";
722
+ const isCustomText = currentItem?.type === "custom-text";
723
+ const canToggleRaw = currentItem && !isSeparator && !isFlexSeparator && !isCustomText;
724
+ let helpText = "↑↓ select, ←→ change type";
725
+ if (isSeparator) {
726
+ helpText += ", Space edit separator";
727
+ }
728
+ if (isCustomText) {
729
+ helpText += ", (e)dit text";
730
+ }
731
+ helpText += ", Enter to move, (a)dd, (i)nsert, (d)elete, (c)lear line";
732
+ if (canToggleRaw) {
733
+ helpText += ", (r)aw value";
734
+ }
735
+ helpText += ", ESC back";
587
736
  return /* @__PURE__ */ jsxDEV(Box, {
588
737
  flexDirection: "column",
589
738
  children: [
@@ -599,12 +748,26 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
599
748
  }, undefined, false, undefined, this)
600
749
  ]
601
750
  }, undefined, true, undefined, this),
602
- moveMode ? /* @__PURE__ */ jsxDEV(Text, {
751
+ editingText ? /* @__PURE__ */ jsxDEV(Box, {
752
+ flexDirection: "column",
753
+ children: [
754
+ /* @__PURE__ */ jsxDEV(Text, {
755
+ children: [
756
+ "Enter custom text: ",
757
+ textInput
758
+ ]
759
+ }, undefined, true, undefined, this),
760
+ /* @__PURE__ */ jsxDEV(Text, {
761
+ dimColor: true,
762
+ children: "Press Enter to save, ESC to cancel"
763
+ }, undefined, false, undefined, this)
764
+ ]
765
+ }, undefined, true, undefined, this) : moveMode ? /* @__PURE__ */ jsxDEV(Text, {
603
766
  dimColor: true,
604
767
  children: "↑↓ to move item, ESC or Enter to exit move mode"
605
768
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
606
769
  dimColor: true,
607
- children: "↑↓ select, ←→ change type, Space edit separator, Enter to move, (a)dd, (i)nsert, (d)elete, (c)lear line, ESC back"
770
+ children: helpText
608
771
  }, undefined, false, undefined, this),
609
772
  hasFlexSeparator && !widthDetectionAvailable && /* @__PURE__ */ jsxDEV(Box, {
610
773
  marginTop: 1,
@@ -633,9 +796,9 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
633
796
  index + 1,
634
797
  ". ",
635
798
  getItemDisplay(item),
636
- item.type === "separator" && index === selectedIndex && !moveMode && /* @__PURE__ */ jsxDEV(Text, {
799
+ item.rawValue && /* @__PURE__ */ jsxDEV(Text, {
637
800
  dimColor: true,
638
- children: " (Space to edit)"
801
+ children: " (raw value)"
639
802
  }, undefined, false, undefined, this)
640
803
  ]
641
804
  }, undefined, true, undefined, this)
@@ -645,7 +808,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
645
808
  }, undefined, true, undefined, this);
646
809
  };
647
810
  var ColorMenu = ({ items, onUpdate, onBack }) => {
648
- const colorableItems = items.filter((item) => ["model", "git-branch", "tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "session-clock", "version"].includes(item.type));
811
+ const colorableItems = items.filter((item) => ["model", "git-branch", "tokens-input", "tokens-output", "tokens-cached", "tokens-total", "context-length", "context-percentage", "session-clock", "version", "custom-text"].includes(item.type));
649
812
  const [selectedIndex, setSelectedIndex] = useState(0);
650
813
  useInput((input, key) => {
651
814
  if (key.escape) {
@@ -710,6 +873,8 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
710
873
  return "Session Clock";
711
874
  case "version":
712
875
  return "Version";
876
+ case "custom-text":
877
+ return `Custom Text (${item.customText || "Empty"})`;
713
878
  default:
714
879
  return item.type;
715
880
  }
@@ -805,6 +970,174 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
805
970
  ]
806
971
  }, undefined, true, undefined, this);
807
972
  };
973
+ var FlexOptions = ({ settings, onUpdate, onBack }) => {
974
+ const [selectedOption, setSelectedOption] = useState(settings.flexMode || "full-minus-40");
975
+ const [compactThreshold, setCompactThreshold] = useState(settings.compactThreshold || 60);
976
+ const [editingThreshold, setEditingThreshold] = useState(false);
977
+ const [thresholdInput, setThresholdInput] = useState(String(settings.compactThreshold || 60));
978
+ const [validationError, setValidationError] = useState(null);
979
+ const [highlightedOption, setHighlightedOption] = useState(settings.flexMode || "full-minus-40");
980
+ useInput((input, key) => {
981
+ if (editingThreshold) {
982
+ if (key.return) {
983
+ const value = parseInt(thresholdInput, 10);
984
+ if (isNaN(value)) {
985
+ setValidationError("Please enter a valid number");
986
+ } else if (value < 1 || value > 99) {
987
+ setValidationError(`Value must be between 1 and 99 (you entered ${value})`);
988
+ } else {
989
+ setCompactThreshold(value);
990
+ const updatedSettings = {
991
+ ...settings,
992
+ flexMode: selectedOption,
993
+ compactThreshold: value
994
+ };
995
+ onUpdate(updatedSettings);
996
+ setEditingThreshold(false);
997
+ setValidationError(null);
998
+ }
999
+ } else if (key.escape) {
1000
+ setThresholdInput(String(compactThreshold));
1001
+ setEditingThreshold(false);
1002
+ setValidationError(null);
1003
+ } else if (key.backspace || key.delete) {
1004
+ setThresholdInput(thresholdInput.slice(0, -1));
1005
+ setValidationError(null);
1006
+ } else if (input && /\d/.test(input)) {
1007
+ const newValue = thresholdInput + input;
1008
+ if (newValue.length <= 2) {
1009
+ setThresholdInput(newValue);
1010
+ setValidationError(null);
1011
+ }
1012
+ }
1013
+ } else {
1014
+ if (key.escape) {
1015
+ onBack();
1016
+ }
1017
+ }
1018
+ });
1019
+ const options = [
1020
+ {
1021
+ value: "full",
1022
+ label: "Full width always",
1023
+ description: `Uses the full terminal width minus 4 characters for terminal padding. If the auto-compact message appears, it may cause the line to wrap. This is due to a limitation where we cannot accurately detect the available width.
1024
+
1025
+ NOTE: If /ide integration is enabled, it's not recommended to use this mode as stuff like opening a file will cause text to appear on the right of the terminal that will force the status line to wrap.`
1026
+ },
1027
+ {
1028
+ value: "full-minus-40",
1029
+ label: "Full width minus 40",
1030
+ description: "Leaves a gap to the right of the status line to accommodate the auto-compact message. This prevents wrapping but may leave unused space. This limitation exists because we cannot detect when the message will appear."
1031
+ },
1032
+ {
1033
+ value: "full-until-compact",
1034
+ label: "Full width until compact",
1035
+ description: `Dynamically adjusts width based on context usage. When context reaches ${compactThreshold}%, it switches to leaving space for the auto-compact message. This provides a balance but requires guessing when the message appears.
1036
+
1037
+ NOTE: If /ide integration is enabled, it's not recommended to use this mode as stuff like opening a file will cause text to appear on the right of the terminal that will force the status line to wrap.`
1038
+ }
1039
+ ];
1040
+ const handleSelect = (item) => {
1041
+ const mode = item.value;
1042
+ setSelectedOption(mode);
1043
+ const updatedSettings = {
1044
+ ...settings,
1045
+ flexMode: mode,
1046
+ compactThreshold
1047
+ };
1048
+ onUpdate(updatedSettings);
1049
+ if (mode === "full-until-compact") {
1050
+ setEditingThreshold(true);
1051
+ }
1052
+ };
1053
+ const menuItems = options.map((opt) => ({
1054
+ label: opt.label + (opt.value === selectedOption ? " ✓" : ""),
1055
+ value: opt.value
1056
+ }));
1057
+ menuItems.push({ label: "← Back", value: "back" });
1058
+ const currentOption = options.find((o) => o.value === highlightedOption);
1059
+ return /* @__PURE__ */ jsxDEV(Box, {
1060
+ flexDirection: "column",
1061
+ children: [
1062
+ /* @__PURE__ */ jsxDEV(Text, {
1063
+ bold: true,
1064
+ children: "Flex Separator Width Options"
1065
+ }, undefined, false, undefined, this),
1066
+ /* @__PURE__ */ jsxDEV(Text, {
1067
+ dimColor: true,
1068
+ children: "Select how flex separators calculate available width"
1069
+ }, undefined, false, undefined, this),
1070
+ editingThreshold ? /* @__PURE__ */ jsxDEV(Box, {
1071
+ marginTop: 1,
1072
+ flexDirection: "column",
1073
+ children: [
1074
+ /* @__PURE__ */ jsxDEV(Text, {
1075
+ children: [
1076
+ "Enter compact threshold (1-99): ",
1077
+ thresholdInput,
1078
+ "%"
1079
+ ]
1080
+ }, undefined, true, undefined, this),
1081
+ validationError ? /* @__PURE__ */ jsxDEV(Text, {
1082
+ color: "red",
1083
+ children: validationError
1084
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
1085
+ dimColor: true,
1086
+ children: "Press Enter to confirm, ESC to cancel"
1087
+ }, undefined, false, undefined, this)
1088
+ ]
1089
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
1090
+ children: [
1091
+ /* @__PURE__ */ jsxDEV(Box, {
1092
+ marginTop: 1,
1093
+ children: /* @__PURE__ */ jsxDEV(SelectInput, {
1094
+ items: menuItems,
1095
+ initialIndex: options.findIndex((o) => o.value === selectedOption),
1096
+ onHighlight: (item) => {
1097
+ if (item.value !== "back") {
1098
+ setHighlightedOption(item.value);
1099
+ }
1100
+ },
1101
+ onSelect: (item) => {
1102
+ if (item.value === "back") {
1103
+ onBack();
1104
+ } else {
1105
+ handleSelect(item);
1106
+ }
1107
+ }
1108
+ }, undefined, false, undefined, this)
1109
+ }, undefined, false, undefined, this),
1110
+ currentOption && /* @__PURE__ */ jsxDEV(Box, {
1111
+ marginTop: 1,
1112
+ marginBottom: 1,
1113
+ borderStyle: "round",
1114
+ borderColor: "dim",
1115
+ paddingX: 1,
1116
+ children: /* @__PURE__ */ jsxDEV(Box, {
1117
+ flexDirection: "column",
1118
+ children: [
1119
+ /* @__PURE__ */ jsxDEV(Text, {
1120
+ children: [
1121
+ /* @__PURE__ */ jsxDEV(Text, {
1122
+ color: "yellow",
1123
+ children: currentOption.label
1124
+ }, undefined, false, undefined, this),
1125
+ highlightedOption === "full-until-compact" && ` | Current threshold: ${compactThreshold}%`
1126
+ ]
1127
+ }, undefined, true, undefined, this),
1128
+ /* @__PURE__ */ jsxDEV(Text, {
1129
+ dimColor: true,
1130
+ wrap: "wrap",
1131
+ children: currentOption.description
1132
+ }, undefined, false, undefined, this)
1133
+ ]
1134
+ }, undefined, true, undefined, this)
1135
+ }, undefined, false, undefined, this)
1136
+ ]
1137
+ }, undefined, true, undefined, this)
1138
+ ]
1139
+ }, undefined, true, undefined, this);
1140
+ };
808
1141
  var App = () => {
809
1142
  const { exit } = useApp();
810
1143
  const [settings, setSettings] = useState(null);
@@ -898,6 +1231,9 @@ Continue?`;
898
1231
  case "colors":
899
1232
  setScreen("colors");
900
1233
  break;
1234
+ case "flex":
1235
+ setScreen("flex");
1236
+ break;
901
1237
  case "install":
902
1238
  await handleInstallUninstall();
903
1239
  break;
@@ -930,8 +1266,11 @@ Continue?`;
930
1266
  children: /* @__PURE__ */ jsxDEV(Text, {
931
1267
  bold: true,
932
1268
  color: "cyan",
933
- children: "\uD83C\uDFA8 CCStatusline Configuration"
934
- }, undefined, false, undefined, this)
1269
+ children: [
1270
+ "CCStatusline Configuration ",
1271
+ getPackageVersion() && `v${getPackageVersion()}`
1272
+ ]
1273
+ }, undefined, true, undefined, this)
935
1274
  }, undefined, false, undefined, this),
936
1275
  /* @__PURE__ */ jsxDEV(Box, {
937
1276
  marginBottom: 1,
@@ -942,7 +1281,8 @@ Continue?`;
942
1281
  }, undefined, false, undefined, this),
943
1282
  /* @__PURE__ */ jsxDEV(StatusLinePreview, {
944
1283
  lines: settings.lines || [[]],
945
- terminalWidth
1284
+ terminalWidth,
1285
+ settings
946
1286
  }, undefined, false, undefined, this),
947
1287
  /* @__PURE__ */ jsxDEV(Box, {
948
1288
  marginTop: 2,
@@ -969,10 +1309,13 @@ Continue?`;
969
1309
  const newLines = settings.lines || [[]];
970
1310
  let flatIndex = 0;
971
1311
  for (let lineIndex = 0;lineIndex < newLines.length; lineIndex++) {
972
- for (let itemIndex = 0;itemIndex < newLines[lineIndex].length; itemIndex++) {
973
- if (flatIndex < items.length) {
974
- newLines[lineIndex][itemIndex] = items[flatIndex];
975
- flatIndex++;
1312
+ const line = newLines[lineIndex];
1313
+ if (line) {
1314
+ for (let itemIndex = 0;itemIndex < line.length; itemIndex++) {
1315
+ if (flatIndex < items.length && items[flatIndex]) {
1316
+ line[itemIndex] = items[flatIndex];
1317
+ flatIndex++;
1318
+ }
976
1319
  }
977
1320
  }
978
1321
  }
@@ -980,6 +1323,13 @@ Continue?`;
980
1323
  },
981
1324
  onBack: () => setScreen("main")
982
1325
  }, undefined, false, undefined, this),
1326
+ screen === "flex" && /* @__PURE__ */ jsxDEV(FlexOptions, {
1327
+ settings,
1328
+ onUpdate: (updatedSettings) => {
1329
+ setSettings(updatedSettings);
1330
+ },
1331
+ onBack: () => setScreen("main")
1332
+ }, undefined, false, undefined, this),
983
1333
  screen === "confirm" && confirmDialog && /* @__PURE__ */ jsxDEV(ConfirmDialog, {
984
1334
  message: confirmDialog.message,
985
1335
  onConfirm: confirmDialog.action,
@@ -994,13 +1344,14 @@ Continue?`;
994
1344
  }, undefined, true, undefined, this);
995
1345
  };
996
1346
  function runTUI() {
1347
+ process.stdout.write("\x1B[2J\x1B[H");
997
1348
  render(/* @__PURE__ */ jsxDEV(App, {}, undefined, false, undefined, this));
998
1349
  }
999
1350
 
1000
1351
  // src/ccstatusline.ts
1001
- import * as fs3 from "fs";
1352
+ import * as fs4 from "fs";
1002
1353
  import { promisify as promisify3 } from "util";
1003
- var readFile6 = fs3.promises?.readFile || promisify3(fs3.readFile);
1354
+ var readFile6 = fs4.promises?.readFile || promisify3(fs4.readFile);
1004
1355
  chalk2.level = 3;
1005
1356
  async function readStdin() {
1006
1357
  if (process.stdin.isTTY) {
@@ -1090,9 +1441,6 @@ function getGitChanges() {
1090
1441
  totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
1091
1442
  totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
1092
1443
  }
1093
- if (totalInsertions === 0 && totalDeletions === 0) {
1094
- return null;
1095
- }
1096
1444
  return { insertions: totalInsertions, deletions: totalDeletions };
1097
1445
  } catch {
1098
1446
  return null;
@@ -1100,7 +1448,7 @@ function getGitChanges() {
1100
1448
  }
1101
1449
  async function getSessionDuration(transcriptPath) {
1102
1450
  try {
1103
- if (!fs3.existsSync(transcriptPath)) {
1451
+ if (!fs4.existsSync(transcriptPath)) {
1104
1452
  return null;
1105
1453
  }
1106
1454
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1152,7 +1500,7 @@ async function getSessionDuration(transcriptPath) {
1152
1500
  }
1153
1501
  async function getTokenMetrics(transcriptPath) {
1154
1502
  try {
1155
- if (!fs3.existsSync(transcriptPath)) {
1503
+ if (!fs4.existsSync(transcriptPath)) {
1156
1504
  return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
1157
1505
  }
1158
1506
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1162,7 +1510,8 @@ async function getTokenMetrics(transcriptPath) {
1162
1510
  let outputTokens = 0;
1163
1511
  let cachedTokens = 0;
1164
1512
  let contextLength = 0;
1165
- let lastMessageWithUsage = null;
1513
+ let mostRecentMainChainEntry = null;
1514
+ let mostRecentTimestamp = null;
1166
1515
  for (const line of lines) {
1167
1516
  try {
1168
1517
  const data = JSON.parse(line);
@@ -1171,12 +1520,18 @@ async function getTokenMetrics(transcriptPath) {
1171
1520
  outputTokens += data.message.usage.output_tokens || 0;
1172
1521
  cachedTokens += data.message.usage.cache_read_input_tokens || 0;
1173
1522
  cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
1174
- lastMessageWithUsage = data;
1523
+ if (data.isSidechain !== true && data.timestamp) {
1524
+ const entryTime = new Date(data.timestamp);
1525
+ if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
1526
+ mostRecentTimestamp = entryTime;
1527
+ mostRecentMainChainEntry = data;
1528
+ }
1529
+ }
1175
1530
  }
1176
1531
  } catch {}
1177
1532
  }
1178
- if (lastMessageWithUsage?.message?.usage) {
1179
- const usage = lastMessageWithUsage.message.usage;
1533
+ if (mostRecentMainChainEntry?.message?.usage) {
1534
+ const usage = mostRecentMainChainEntry.message.usage;
1180
1535
  contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
1181
1536
  }
1182
1537
  const totalTokens = inputTokens + outputTokens + cachedTokens;
@@ -1187,7 +1542,23 @@ async function getTokenMetrics(transcriptPath) {
1187
1542
  }
1188
1543
  function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration) {
1189
1544
  const detectedWidth = getTerminalWidth();
1190
- const terminalWidth = detectedWidth ? detectedWidth - 40 : null;
1545
+ let terminalWidth = null;
1546
+ if (detectedWidth) {
1547
+ const flexMode = settings.flexMode || "full-minus-40";
1548
+ if (flexMode === "full") {
1549
+ terminalWidth = detectedWidth - 4;
1550
+ } else if (flexMode === "full-minus-40") {
1551
+ terminalWidth = detectedWidth - 40;
1552
+ } else if (flexMode === "full-until-compact") {
1553
+ const threshold = settings.compactThreshold || 60;
1554
+ const contextPercentage = tokenMetrics ? Math.min(100, tokenMetrics.contextLength / 200000 * 100) : 0;
1555
+ if (contextPercentage >= threshold) {
1556
+ terminalWidth = detectedWidth - 40;
1557
+ } else {
1558
+ terminalWidth = detectedWidth - 4;
1559
+ }
1560
+ }
1561
+ }
1191
1562
  const elements = [];
1192
1563
  let hasFlexSeparator = false;
1193
1564
  const formatTokens = (count) => {
@@ -1202,14 +1573,16 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1202
1573
  case "model":
1203
1574
  if (data.model) {
1204
1575
  const color = chalk2[item.color || settings.colors.model] || chalk2.cyan;
1205
- elements.push({ content: color(`Model: ${data.model.display_name}`), type: "model" });
1576
+ const text2 = item.rawValue ? data.model.display_name : `Model: ${data.model.display_name}`;
1577
+ elements.push({ content: color(text2), type: "model" });
1206
1578
  }
1207
1579
  break;
1208
1580
  case "git-branch":
1209
1581
  const branch = getGitBranch();
1210
1582
  if (branch) {
1211
1583
  const color = chalk2[item.color || settings.colors.gitBranch] || chalk2.magenta;
1212
- elements.push({ content: color(`⎇ ${branch}`), type: "git-branch" });
1584
+ const text2 = item.rawValue ? branch : `⎇ ${branch}`;
1585
+ elements.push({ content: color(text2), type: "git-branch" });
1213
1586
  }
1214
1587
  break;
1215
1588
  case "git-changes":
@@ -1223,57 +1596,66 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1223
1596
  case "tokens-input":
1224
1597
  if (tokenMetrics) {
1225
1598
  const color = chalk2[item.color || "yellow"] || chalk2.yellow;
1226
- elements.push({ content: color(`In: ${formatTokens(tokenMetrics.inputTokens)}`), type: "tokens-input" });
1599
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.inputTokens) : `In: ${formatTokens(tokenMetrics.inputTokens)}`;
1600
+ elements.push({ content: color(text2), type: "tokens-input" });
1227
1601
  }
1228
1602
  break;
1229
1603
  case "tokens-output":
1230
1604
  if (tokenMetrics) {
1231
1605
  const color = chalk2[item.color || "green"] || chalk2.green;
1232
- elements.push({ content: color(`Out: ${formatTokens(tokenMetrics.outputTokens)}`), type: "tokens-output" });
1606
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.outputTokens) : `Out: ${formatTokens(tokenMetrics.outputTokens)}`;
1607
+ elements.push({ content: color(text2), type: "tokens-output" });
1233
1608
  }
1234
1609
  break;
1235
1610
  case "tokens-cached":
1236
1611
  if (tokenMetrics) {
1237
1612
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1238
- elements.push({ content: color(`Cached: ${formatTokens(tokenMetrics.cachedTokens)}`), type: "tokens-cached" });
1613
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.cachedTokens) : `Cached: ${formatTokens(tokenMetrics.cachedTokens)}`;
1614
+ elements.push({ content: color(text2), type: "tokens-cached" });
1239
1615
  }
1240
1616
  break;
1241
1617
  case "tokens-total":
1242
1618
  if (tokenMetrics) {
1243
1619
  const color = chalk2[item.color || "white"] || chalk2.white;
1244
- elements.push({ content: color(`Total: ${formatTokens(tokenMetrics.totalTokens)}`), type: "tokens-total" });
1620
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.totalTokens) : `Total: ${formatTokens(tokenMetrics.totalTokens)}`;
1621
+ elements.push({ content: color(text2), type: "tokens-total" });
1245
1622
  }
1246
1623
  break;
1247
1624
  case "context-length":
1248
1625
  if (tokenMetrics) {
1249
1626
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1250
- elements.push({ content: color(`Ctx: ${formatTokens(tokenMetrics.contextLength)}`), type: "context-length" });
1627
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.contextLength) : `Ctx: ${formatTokens(tokenMetrics.contextLength)}`;
1628
+ elements.push({ content: color(text2), type: "context-length" });
1251
1629
  }
1252
1630
  break;
1253
1631
  case "context-percentage":
1254
1632
  if (tokenMetrics) {
1255
1633
  const percentage = Math.min(100, tokenMetrics.contextLength / 200000 * 100);
1256
1634
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1257
- elements.push({ content: color(`Ctx: ${percentage.toFixed(1)}%`), type: "context-percentage" });
1635
+ const text2 = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
1636
+ elements.push({ content: color(text2), type: "context-percentage" });
1258
1637
  }
1259
1638
  break;
1260
1639
  case "terminal-width":
1261
1640
  const detectedWidth2 = terminalWidth || getTerminalWidth();
1262
1641
  if (detectedWidth2) {
1263
1642
  const color = chalk2[item.color || "dim"] || chalk2.dim;
1264
- elements.push({ content: color(`Term: ${detectedWidth2}`), type: "terminal-width" });
1643
+ const text2 = item.rawValue ? `${detectedWidth2}` : `Term: ${detectedWidth2}`;
1644
+ elements.push({ content: color(text2), type: "terminal-width" });
1265
1645
  }
1266
1646
  break;
1267
1647
  case "session-clock":
1268
1648
  if (sessionDuration) {
1269
1649
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1270
- elements.push({ content: color(`Session: ${sessionDuration}`), type: "session-clock" });
1650
+ const text2 = item.rawValue ? sessionDuration : `Session: ${sessionDuration}`;
1651
+ elements.push({ content: color(text2), type: "session-clock" });
1271
1652
  }
1272
1653
  break;
1273
1654
  case "version":
1274
1655
  const versionString = data.version || "Unknown";
1275
1656
  const versionColor = chalk2[item.color || "green"] || chalk2.green;
1276
- elements.push({ content: versionColor(`Version: ${versionString}`), type: "version" });
1657
+ const text = item.rawValue ? versionString : `Version: ${versionString}`;
1658
+ elements.push({ content: versionColor(text), type: "version" });
1277
1659
  break;
1278
1660
  case "separator":
1279
1661
  const lastElement = elements[elements.length - 1];
@@ -1295,10 +1677,18 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1295
1677
  elements.push({ content: "FLEX", type: "flex-separator" });
1296
1678
  hasFlexSeparator = true;
1297
1679
  break;
1680
+ case "custom-text":
1681
+ const customTextColor = chalk2[item.color || "white"] || chalk2.white;
1682
+ const customText = item.customText || "";
1683
+ elements.push({ content: customTextColor(customText), type: "custom-text" });
1684
+ break;
1298
1685
  }
1299
1686
  }
1300
1687
  if (elements.length === 0)
1301
1688
  return "";
1689
+ while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") {
1690
+ elements.pop();
1691
+ }
1302
1692
  let statusLine = "";
1303
1693
  if (hasFlexSeparator && terminalWidth) {
1304
1694
  const parts = [[]];
@@ -1338,6 +1728,38 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1338
1728
  statusLine = elements.map((e) => e.content).join("");
1339
1729
  }
1340
1730
  }
1731
+ if (detectedWidth && detectedWidth > 0) {
1732
+ const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
1733
+ if (plainLength > detectedWidth) {
1734
+ let truncated = "";
1735
+ let currentLength = 0;
1736
+ let inAnsiCode = false;
1737
+ let ansiBuffer = "";
1738
+ const targetLength = detectedWidth - 7;
1739
+ for (let i = 0;i < statusLine.length; i++) {
1740
+ const char = statusLine[i];
1741
+ if (char === "\x1B") {
1742
+ inAnsiCode = true;
1743
+ ansiBuffer = char;
1744
+ } else if (inAnsiCode) {
1745
+ ansiBuffer += char;
1746
+ if (char === "m") {
1747
+ truncated += ansiBuffer;
1748
+ inAnsiCode = false;
1749
+ ansiBuffer = "";
1750
+ }
1751
+ } else {
1752
+ if (currentLength < targetLength) {
1753
+ truncated += char;
1754
+ currentLength++;
1755
+ } else {
1756
+ break;
1757
+ }
1758
+ }
1759
+ }
1760
+ statusLine = truncated + "...";
1761
+ }
1762
+ }
1341
1763
  return statusLine;
1342
1764
  }
1343
1765
  async function renderStatusLine(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",