ccstatusline 1.0.9 → 1.0.11

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
 
@@ -37,7 +37,9 @@ var DEFAULT_SETTINGS = {
37
37
  model: "cyan",
38
38
  gitBranch: "magenta",
39
39
  separator: "dim"
40
- }
40
+ },
41
+ flexMode: "full-minus-40",
42
+ compactThreshold: 75
41
43
  };
42
44
  async function loadSettings() {
43
45
  try {
@@ -92,7 +94,9 @@ function migrateOldSettings(old) {
92
94
  }
93
95
  return {
94
96
  lines: [items],
95
- colors: old.colors || DEFAULT_SETTINGS.colors
97
+ colors: old.colors || DEFAULT_SETTINGS.colors,
98
+ flexMode: DEFAULT_SETTINGS.flexMode,
99
+ compactThreshold: DEFAULT_SETTINGS.compactThreshold
96
100
  };
97
101
  }
98
102
  async function saveSettings(settings) {
@@ -151,7 +155,19 @@ async function getExistingStatusLine() {
151
155
  }
152
156
 
153
157
  // src/tui.tsx
154
- import { jsxDEV } from "react/jsx-dev-runtime";
158
+ import * as fs3 from "fs";
159
+ import * as path3 from "path";
160
+ var __dirname = "/Users/sirmalloc/Desktop/ccstatusline/src";
161
+ import { jsxDEV, Fragment } from "react/jsx-dev-runtime";
162
+ function getPackageVersion() {
163
+ try {
164
+ const packageJsonPath = path3.join(__dirname, "..", "package.json");
165
+ const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf-8"));
166
+ return packageJson.version || "";
167
+ } catch {
168
+ return "";
169
+ }
170
+ }
155
171
  function canDetectTerminalWidth() {
156
172
  try {
157
173
  const tty = execSync("ps -o tty= -p $(ps -o ppid= -p $$)", {
@@ -182,15 +198,27 @@ function canDetectTerminalWidth() {
182
198
  return false;
183
199
  }
184
200
  }
185
- var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
186
- const width = widthDetectionAvailable ? terminalWidth : null;
201
+ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable, settings) => {
202
+ let effectiveWidth = null;
203
+ if (widthDetectionAvailable && terminalWidth) {
204
+ const flexMode = settings?.flexMode || "full-minus-40";
205
+ if (flexMode === "full") {
206
+ effectiveWidth = terminalWidth - 2;
207
+ } else if (flexMode === "full-minus-40") {
208
+ effectiveWidth = terminalWidth - 40;
209
+ } else if (flexMode === "full-until-compact") {
210
+ effectiveWidth = terminalWidth - 2;
211
+ }
212
+ }
213
+ const width = terminalWidth;
214
+ const flexWidth = effectiveWidth;
187
215
  const elements = [];
188
216
  let hasFlexSeparator = false;
189
217
  items.forEach((item) => {
190
218
  switch (item.type) {
191
219
  case "model":
192
220
  const modelColor = chalk[item.color || "cyan"] || chalk.cyan;
193
- elements.push(modelColor("Model: Claude"));
221
+ elements.push(modelColor(item.rawValue ? "Claude" : "Model: Claude"));
194
222
  break;
195
223
  case "git-branch":
196
224
  const branchColor = chalk[item.color || "magenta"] || chalk.magenta;
@@ -202,54 +230,66 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
202
230
  break;
203
231
  case "tokens-input":
204
232
  const inputColor = chalk[item.color || "yellow"] || chalk.yellow;
205
- elements.push(inputColor("In: 15.2k"));
233
+ elements.push(inputColor(item.rawValue ? "15.2k" : "In: 15.2k"));
206
234
  break;
207
235
  case "tokens-output":
208
236
  const outputColor = chalk[item.color || "green"] || chalk.green;
209
- elements.push(outputColor("Out: 3.4k"));
237
+ elements.push(outputColor(item.rawValue ? "3.4k" : "Out: 3.4k"));
210
238
  break;
211
239
  case "tokens-cached":
212
240
  const cachedColor = chalk[item.color || "blue"] || chalk.blue;
213
- elements.push(cachedColor("Cached: 12k"));
241
+ elements.push(cachedColor(item.rawValue ? "12k" : "Cached: 12k"));
214
242
  break;
215
243
  case "tokens-total":
216
244
  const totalColor = chalk[item.color || "white"] || chalk.white;
217
- elements.push(totalColor("Total: 30.6k"));
245
+ elements.push(totalColor(item.rawValue ? "30.6k" : "Total: 30.6k"));
218
246
  break;
219
247
  case "context-length":
220
248
  const ctxColor = chalk[item.color || "cyan"] || chalk.cyan;
221
- elements.push(ctxColor("Ctx: 18.6k"));
249
+ elements.push(ctxColor(item.rawValue ? "18.6k" : "Ctx: 18.6k"));
222
250
  break;
223
251
  case "context-percentage":
224
252
  const ctxPctColor = chalk[item.color || "cyan"] || chalk.cyan;
225
- elements.push(ctxPctColor("Ctx: 9.3%"));
253
+ elements.push(ctxPctColor(item.rawValue ? "9.3%" : "Ctx: 9.3%"));
226
254
  break;
227
255
  case "session-clock":
228
256
  const sessionColor = chalk[item.color || "blue"] || chalk.blue;
229
- elements.push(sessionColor("Session: 2hr 15m"));
257
+ elements.push(sessionColor(item.rawValue ? "2hr 15m" : "Session: 2hr 15m"));
230
258
  break;
231
259
  case "version":
232
260
  const versionColor = chalk[item.color || "green"] || chalk.green;
233
- elements.push(versionColor("Version: 1.0.72"));
261
+ elements.push(versionColor(item.rawValue ? "1.0.72" : "Version: 1.0.72"));
234
262
  break;
235
263
  case "terminal-width":
236
264
  const termColor = chalk[item.color || "dim"] || chalk.dim;
237
265
  const detectedWidth = canDetectTerminalWidth() ? terminalWidth : "??";
238
- elements.push(termColor(`Term: ${detectedWidth}`));
266
+ elements.push(termColor(item.rawValue ? `${detectedWidth}` : `Term: ${detectedWidth}`));
239
267
  break;
240
268
  case "separator":
241
269
  const sepChar = item.character || "|";
242
- const sepContent = sepChar === "," ? chalk.dim(`${sepChar} `) : chalk.dim(` ${sepChar} `);
270
+ let sepContent;
271
+ if (sepChar === ",") {
272
+ sepContent = chalk.dim(`${sepChar} `);
273
+ } else if (sepChar === " ") {
274
+ sepContent = chalk.dim(" ");
275
+ } else {
276
+ sepContent = chalk.dim(` ${sepChar} `);
277
+ }
243
278
  elements.push(sepContent);
244
279
  break;
245
280
  case "flex-separator":
246
281
  elements.push("FLEX");
247
282
  hasFlexSeparator = true;
248
283
  break;
284
+ case "custom-text":
285
+ const customTextColor = chalk[item.color || "white"] || chalk.white;
286
+ const customText = item.customText || "";
287
+ elements.push(customTextColor(customText));
288
+ break;
249
289
  }
250
290
  });
251
291
  let statusLine = "";
252
- if (hasFlexSeparator && width) {
292
+ if (hasFlexSeparator && flexWidth) {
253
293
  const parts = [[]];
254
294
  let currentPart = 0;
255
295
  for (let i = 0;i < items.length; i++) {
@@ -270,7 +310,7 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
270
310
  });
271
311
  const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
272
312
  const flexCount = parts.length - 1;
273
- const totalSpace = Math.max(0, width - totalContentLength);
313
+ const totalSpace = Math.max(0, flexWidth - totalContentLength);
274
314
  const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
275
315
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
276
316
  statusLine = "";
@@ -287,15 +327,48 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
287
327
  } else {
288
328
  statusLine = elements.map((e) => e === "FLEX" ? chalk.dim(" | ") : e).join("");
289
329
  }
330
+ if (width && width > 0) {
331
+ const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
332
+ if (plainLength > width) {
333
+ let truncated = "";
334
+ let currentLength = 0;
335
+ let inAnsiCode = false;
336
+ let ansiBuffer = "";
337
+ const targetLength = width - 5;
338
+ for (let i = 0;i < statusLine.length; i++) {
339
+ const char = statusLine[i];
340
+ if (char === "\x1B") {
341
+ inAnsiCode = true;
342
+ ansiBuffer = char;
343
+ } else if (inAnsiCode) {
344
+ ansiBuffer += char;
345
+ if (char === "m") {
346
+ truncated += ansiBuffer;
347
+ inAnsiCode = false;
348
+ ansiBuffer = "";
349
+ }
350
+ } else {
351
+ if (currentLength < targetLength) {
352
+ truncated += char;
353
+ currentLength++;
354
+ } else {
355
+ break;
356
+ }
357
+ }
358
+ }
359
+ statusLine = truncated + "...";
360
+ }
361
+ }
290
362
  return statusLine;
291
363
  };
292
- var StatusLinePreview = ({ lines, terminalWidth }) => {
364
+ var StatusLinePreview = ({ lines, terminalWidth, settings }) => {
293
365
  const widthDetectionAvailable = canDetectTerminalWidth();
294
366
  const boxWidth = Math.min(terminalWidth - 4, process.stdout.columns - 4 || 76);
295
367
  const topLine = chalk.dim("╭" + "─".repeat(Math.max(0, boxWidth - 2)) + "╮");
296
368
  const middleLine = chalk.dim("│") + " > " + " ".repeat(Math.max(0, boxWidth - 5)) + chalk.dim("│");
297
369
  const bottomLine = chalk.dim("╰" + "─".repeat(Math.max(0, boxWidth - 2)) + "╯");
298
- const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, boxWidth, widthDetectionAvailable) : "").filter((line) => line !== "");
370
+ const availableWidth = boxWidth - 2;
371
+ const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : "").filter((line) => line !== "");
299
372
  return /* @__PURE__ */ jsxDEV(Box, {
300
373
  flexDirection: "column",
301
374
  children: [
@@ -309,8 +382,11 @@ var StatusLinePreview = ({ lines, terminalWidth }) => {
309
382
  children: bottomLine
310
383
  }, undefined, false, undefined, this),
311
384
  renderedLines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
312
- children: line
313
- }, index, false, undefined, this))
385
+ children: [
386
+ " ",
387
+ line
388
+ ]
389
+ }, index, true, undefined, this))
314
390
  ]
315
391
  }, undefined, true, undefined, this);
316
392
  };
@@ -383,6 +459,7 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
383
459
  const items = [
384
460
  { label: "\uD83D\uDCDD Edit Lines", value: "lines" },
385
461
  { label: "\uD83C\uDFA8 Configure Colors", value: "colors" },
462
+ { label: "⚡ Flex Options", value: "flex" },
386
463
  { label: isClaudeInstalled ? "\uD83D\uDDD1️ Uninstall from Claude Code" : "\uD83D\uDCE6 Install to Claude Code", value: "install" }
387
464
  ];
388
465
  if (hasChanges) {
@@ -410,9 +487,29 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
410
487
  var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
411
488
  const [selectedIndex, setSelectedIndex] = useState(0);
412
489
  const [moveMode, setMoveMode] = useState(false);
490
+ const [editingText, setEditingText] = useState(false);
491
+ const [textInput, setTextInput] = useState("");
413
492
  const separatorChars = ["|", "-", ",", " "];
414
493
  useInput((input, key) => {
415
- if (moveMode) {
494
+ if (editingText) {
495
+ if (key.return) {
496
+ const currentItem2 = items[selectedIndex];
497
+ if (currentItem2) {
498
+ const newItems = [...items];
499
+ newItems[selectedIndex] = { ...currentItem2, customText: textInput };
500
+ onUpdate(newItems);
501
+ }
502
+ setEditingText(false);
503
+ setTextInput("");
504
+ } else if (key.escape) {
505
+ setEditingText(false);
506
+ setTextInput("");
507
+ } else if (key.backspace || key.delete) {
508
+ setTextInput(textInput.slice(0, -1));
509
+ } else if (input && input.length === 1) {
510
+ setTextInput(textInput + input);
511
+ }
512
+ } else if (moveMode) {
416
513
  if (key.upArrow && selectedIndex > 0) {
417
514
  const newItems = [...items];
418
515
  const temp = newItems[selectedIndex];
@@ -454,17 +551,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
454
551
  "session-clock",
455
552
  "terminal-width",
456
553
  "version",
457
- "flex-separator"
554
+ "flex-separator",
555
+ "custom-text"
458
556
  ];
459
- const currentItem = items[selectedIndex];
460
- if (currentItem) {
461
- const currentType = currentItem.type;
557
+ const currentItem2 = items[selectedIndex];
558
+ if (currentItem2) {
559
+ const currentType = currentItem2.type;
462
560
  const currentIndex = types.indexOf(currentType);
463
561
  const prevIndex = currentIndex === 0 ? types.length - 1 : currentIndex - 1;
464
562
  const newItems = [...items];
465
563
  const prevType = types[prevIndex];
466
564
  if (prevType) {
467
- newItems[selectedIndex] = { ...currentItem, type: prevType };
565
+ newItems[selectedIndex] = { ...currentItem2, type: prevType };
468
566
  onUpdate(newItems);
469
567
  }
470
568
  }
@@ -483,17 +581,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
483
581
  "session-clock",
484
582
  "terminal-width",
485
583
  "version",
486
- "flex-separator"
584
+ "flex-separator",
585
+ "custom-text"
487
586
  ];
488
- const currentItem = items[selectedIndex];
489
- if (currentItem) {
490
- const currentType = currentItem.type;
587
+ const currentItem2 = items[selectedIndex];
588
+ if (currentItem2) {
589
+ const currentType = currentItem2.type;
491
590
  const currentIndex = types.indexOf(currentType);
492
591
  const nextIndex = (currentIndex + 1) % types.length;
493
592
  const newItems = [...items];
494
593
  const nextType = types[nextIndex];
495
594
  if (nextType) {
496
- newItems[selectedIndex] = { ...currentItem, type: nextType };
595
+ newItems[selectedIndex] = { ...currentItem2, type: nextType };
497
596
  onUpdate(newItems);
498
597
  }
499
598
  }
@@ -513,9 +612,8 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
513
612
  type: "separator"
514
613
  };
515
614
  const newItems = [...items];
516
- newItems.splice(selectedIndex + 1, 0, newItem);
615
+ newItems.splice(selectedIndex, 0, newItem);
517
616
  onUpdate(newItems);
518
- setSelectedIndex(selectedIndex + 1);
519
617
  } else if (input === "d" && items.length > 0) {
520
618
  const newItems = items.filter((_, i) => i !== selectedIndex);
521
619
  onUpdate(newItems);
@@ -526,15 +624,28 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
526
624
  onUpdate([]);
527
625
  setSelectedIndex(0);
528
626
  } else if (input === " " && items.length > 0) {
529
- const currentItem = items[selectedIndex];
530
- if (currentItem && currentItem.type === "separator") {
531
- const currentChar = currentItem.character || "|";
627
+ const currentItem2 = items[selectedIndex];
628
+ if (currentItem2 && currentItem2.type === "separator") {
629
+ const currentChar = currentItem2.character || "|";
532
630
  const currentCharIndex = separatorChars.indexOf(currentChar);
533
631
  const nextChar = separatorChars[(currentCharIndex + 1) % separatorChars.length];
534
632
  const newItems = [...items];
535
- newItems[selectedIndex] = { ...currentItem, character: nextChar };
633
+ newItems[selectedIndex] = { ...currentItem2, character: nextChar };
536
634
  onUpdate(newItems);
537
635
  }
636
+ } else if (input === "r" && items.length > 0) {
637
+ const currentItem2 = items[selectedIndex];
638
+ if (currentItem2 && currentItem2.type !== "separator" && currentItem2.type !== "flex-separator" && currentItem2.type !== "custom-text") {
639
+ const newItems = [...items];
640
+ newItems[selectedIndex] = { ...currentItem2, rawValue: !currentItem2.rawValue };
641
+ onUpdate(newItems);
642
+ }
643
+ } else if (input === "e" && items.length > 0) {
644
+ const currentItem2 = items[selectedIndex];
645
+ if (currentItem2 && currentItem2.type === "custom-text") {
646
+ setTextInput(currentItem2.customText || "");
647
+ setEditingText(true);
648
+ }
538
649
  } else if (key.escape) {
539
650
  onBack();
540
651
  }
@@ -573,10 +684,30 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
573
684
  return chalk.dim("Terminal Width");
574
685
  case "version":
575
686
  return chalk.green("Version");
687
+ case "custom-text":
688
+ const text = item.customText || "Empty";
689
+ return chalk.white(`Custom Text (${text})`);
576
690
  }
577
691
  };
578
692
  const hasFlexSeparator = items.some((item) => item.type === "flex-separator");
579
693
  const widthDetectionAvailable = canDetectTerminalWidth();
694
+ const currentItem = items[selectedIndex];
695
+ const isSeparator = currentItem?.type === "separator";
696
+ const isFlexSeparator = currentItem?.type === "flex-separator";
697
+ const isCustomText = currentItem?.type === "custom-text";
698
+ const canToggleRaw = currentItem && !isSeparator && !isFlexSeparator && !isCustomText;
699
+ let helpText = "↑↓ select, ←→ change type";
700
+ if (isSeparator) {
701
+ helpText += ", Space edit separator";
702
+ }
703
+ if (isCustomText) {
704
+ helpText += ", (e)dit text";
705
+ }
706
+ helpText += ", Enter to move, (a)dd, (i)nsert, (d)elete, (c)lear line";
707
+ if (canToggleRaw) {
708
+ helpText += ", (r)aw value";
709
+ }
710
+ helpText += ", ESC back";
580
711
  return /* @__PURE__ */ jsxDEV(Box, {
581
712
  flexDirection: "column",
582
713
  children: [
@@ -592,12 +723,26 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
592
723
  }, undefined, false, undefined, this)
593
724
  ]
594
725
  }, undefined, true, undefined, this),
595
- moveMode ? /* @__PURE__ */ jsxDEV(Text, {
726
+ editingText ? /* @__PURE__ */ jsxDEV(Box, {
727
+ flexDirection: "column",
728
+ children: [
729
+ /* @__PURE__ */ jsxDEV(Text, {
730
+ children: [
731
+ "Enter custom text: ",
732
+ textInput
733
+ ]
734
+ }, undefined, true, undefined, this),
735
+ /* @__PURE__ */ jsxDEV(Text, {
736
+ dimColor: true,
737
+ children: "Press Enter to save, ESC to cancel"
738
+ }, undefined, false, undefined, this)
739
+ ]
740
+ }, undefined, true, undefined, this) : moveMode ? /* @__PURE__ */ jsxDEV(Text, {
596
741
  dimColor: true,
597
742
  children: "↑↓ to move item, ESC or Enter to exit move mode"
598
743
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
599
744
  dimColor: true,
600
- children: "↑↓ select, ←→ change type, Space edit separator, Enter to move, (a)dd, (i)nsert, (d)elete, (c)lear line, ESC back"
745
+ children: helpText
601
746
  }, undefined, false, undefined, this),
602
747
  hasFlexSeparator && !widthDetectionAvailable && /* @__PURE__ */ jsxDEV(Box, {
603
748
  marginTop: 1,
@@ -626,9 +771,9 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
626
771
  index + 1,
627
772
  ". ",
628
773
  getItemDisplay(item),
629
- item.type === "separator" && index === selectedIndex && !moveMode && /* @__PURE__ */ jsxDEV(Text, {
774
+ item.rawValue && /* @__PURE__ */ jsxDEV(Text, {
630
775
  dimColor: true,
631
- children: " (Space to edit)"
776
+ children: " (raw value)"
632
777
  }, undefined, false, undefined, this)
633
778
  ]
634
779
  }, undefined, true, undefined, this)
@@ -638,7 +783,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
638
783
  }, undefined, true, undefined, this);
639
784
  };
640
785
  var ColorMenu = ({ items, onUpdate, onBack }) => {
641
- 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));
786
+ 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));
642
787
  const [selectedIndex, setSelectedIndex] = useState(0);
643
788
  useInput((input, key) => {
644
789
  if (key.escape) {
@@ -703,6 +848,8 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
703
848
  return "Session Clock";
704
849
  case "version":
705
850
  return "Version";
851
+ case "custom-text":
852
+ return `Custom Text (${item.customText || "Empty"})`;
706
853
  default:
707
854
  return item.type;
708
855
  }
@@ -798,6 +945,174 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
798
945
  ]
799
946
  }, undefined, true, undefined, this);
800
947
  };
948
+ var FlexOptions = ({ settings, onUpdate, onBack }) => {
949
+ const [selectedOption, setSelectedOption] = useState(settings.flexMode || "full-minus-40");
950
+ const [compactThreshold, setCompactThreshold] = useState(settings.compactThreshold || 75);
951
+ const [editingThreshold, setEditingThreshold] = useState(false);
952
+ const [thresholdInput, setThresholdInput] = useState(String(settings.compactThreshold || 75));
953
+ const [validationError, setValidationError] = useState(null);
954
+ const [highlightedOption, setHighlightedOption] = useState(settings.flexMode || "full-minus-40");
955
+ useInput((input, key) => {
956
+ if (editingThreshold) {
957
+ if (key.return) {
958
+ const value = parseInt(thresholdInput, 10);
959
+ if (isNaN(value)) {
960
+ setValidationError("Please enter a valid number");
961
+ } else if (value < 1 || value > 99) {
962
+ setValidationError(`Value must be between 1 and 99 (you entered ${value})`);
963
+ } else {
964
+ setCompactThreshold(value);
965
+ const updatedSettings = {
966
+ ...settings,
967
+ flexMode: selectedOption,
968
+ compactThreshold: value
969
+ };
970
+ onUpdate(updatedSettings);
971
+ setEditingThreshold(false);
972
+ setValidationError(null);
973
+ }
974
+ } else if (key.escape) {
975
+ setThresholdInput(String(compactThreshold));
976
+ setEditingThreshold(false);
977
+ setValidationError(null);
978
+ } else if (key.backspace || key.delete) {
979
+ setThresholdInput(thresholdInput.slice(0, -1));
980
+ setValidationError(null);
981
+ } else if (input && /\d/.test(input)) {
982
+ const newValue = thresholdInput + input;
983
+ if (newValue.length <= 2) {
984
+ setThresholdInput(newValue);
985
+ setValidationError(null);
986
+ }
987
+ }
988
+ } else {
989
+ if (key.escape) {
990
+ onBack();
991
+ }
992
+ }
993
+ });
994
+ const options = [
995
+ {
996
+ value: "full",
997
+ label: "Full width always",
998
+ 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.
999
+
1000
+ 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.`
1001
+ },
1002
+ {
1003
+ value: "full-minus-40",
1004
+ label: "Full width minus 40",
1005
+ 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."
1006
+ },
1007
+ {
1008
+ value: "full-until-compact",
1009
+ label: "Full width until compact",
1010
+ 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.
1011
+
1012
+ 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.`
1013
+ }
1014
+ ];
1015
+ const handleSelect = (item) => {
1016
+ const mode = item.value;
1017
+ setSelectedOption(mode);
1018
+ const updatedSettings = {
1019
+ ...settings,
1020
+ flexMode: mode,
1021
+ compactThreshold
1022
+ };
1023
+ onUpdate(updatedSettings);
1024
+ if (mode === "full-until-compact") {
1025
+ setEditingThreshold(true);
1026
+ }
1027
+ };
1028
+ const menuItems = options.map((opt) => ({
1029
+ label: opt.label + (opt.value === selectedOption ? " ✓" : ""),
1030
+ value: opt.value
1031
+ }));
1032
+ menuItems.push({ label: "← Back", value: "back" });
1033
+ const currentOption = options.find((o) => o.value === highlightedOption);
1034
+ return /* @__PURE__ */ jsxDEV(Box, {
1035
+ flexDirection: "column",
1036
+ children: [
1037
+ /* @__PURE__ */ jsxDEV(Text, {
1038
+ bold: true,
1039
+ children: "Flex Separator Width Options"
1040
+ }, undefined, false, undefined, this),
1041
+ /* @__PURE__ */ jsxDEV(Text, {
1042
+ dimColor: true,
1043
+ children: "Select how flex separators calculate available width"
1044
+ }, undefined, false, undefined, this),
1045
+ editingThreshold ? /* @__PURE__ */ jsxDEV(Box, {
1046
+ marginTop: 1,
1047
+ flexDirection: "column",
1048
+ children: [
1049
+ /* @__PURE__ */ jsxDEV(Text, {
1050
+ children: [
1051
+ "Enter compact threshold (1-99): ",
1052
+ thresholdInput,
1053
+ "%"
1054
+ ]
1055
+ }, undefined, true, undefined, this),
1056
+ validationError ? /* @__PURE__ */ jsxDEV(Text, {
1057
+ color: "red",
1058
+ children: validationError
1059
+ }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
1060
+ dimColor: true,
1061
+ children: "Press Enter to confirm, ESC to cancel"
1062
+ }, undefined, false, undefined, this)
1063
+ ]
1064
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsxDEV(Fragment, {
1065
+ children: [
1066
+ /* @__PURE__ */ jsxDEV(Box, {
1067
+ marginTop: 1,
1068
+ children: /* @__PURE__ */ jsxDEV(SelectInput, {
1069
+ items: menuItems,
1070
+ initialIndex: options.findIndex((o) => o.value === selectedOption),
1071
+ onHighlight: (item) => {
1072
+ if (item.value !== "back") {
1073
+ setHighlightedOption(item.value);
1074
+ }
1075
+ },
1076
+ onSelect: (item) => {
1077
+ if (item.value === "back") {
1078
+ onBack();
1079
+ } else {
1080
+ handleSelect(item);
1081
+ }
1082
+ }
1083
+ }, undefined, false, undefined, this)
1084
+ }, undefined, false, undefined, this),
1085
+ currentOption && /* @__PURE__ */ jsxDEV(Box, {
1086
+ marginTop: 1,
1087
+ marginBottom: 1,
1088
+ borderStyle: "round",
1089
+ borderColor: "dim",
1090
+ paddingX: 1,
1091
+ children: /* @__PURE__ */ jsxDEV(Box, {
1092
+ flexDirection: "column",
1093
+ children: [
1094
+ /* @__PURE__ */ jsxDEV(Text, {
1095
+ children: [
1096
+ /* @__PURE__ */ jsxDEV(Text, {
1097
+ color: "yellow",
1098
+ children: currentOption.label
1099
+ }, undefined, false, undefined, this),
1100
+ highlightedOption === "full-until-compact" && ` | Current threshold: ${compactThreshold}%`
1101
+ ]
1102
+ }, undefined, true, undefined, this),
1103
+ /* @__PURE__ */ jsxDEV(Text, {
1104
+ dimColor: true,
1105
+ wrap: "wrap",
1106
+ children: currentOption.description
1107
+ }, undefined, false, undefined, this)
1108
+ ]
1109
+ }, undefined, true, undefined, this)
1110
+ }, undefined, false, undefined, this)
1111
+ ]
1112
+ }, undefined, true, undefined, this)
1113
+ ]
1114
+ }, undefined, true, undefined, this);
1115
+ };
801
1116
  var App = () => {
802
1117
  const { exit } = useApp();
803
1118
  const [settings, setSettings] = useState(null);
@@ -891,6 +1206,9 @@ Continue?`;
891
1206
  case "colors":
892
1207
  setScreen("colors");
893
1208
  break;
1209
+ case "flex":
1210
+ setScreen("flex");
1211
+ break;
894
1212
  case "install":
895
1213
  await handleInstallUninstall();
896
1214
  break;
@@ -923,8 +1241,11 @@ Continue?`;
923
1241
  children: /* @__PURE__ */ jsxDEV(Text, {
924
1242
  bold: true,
925
1243
  color: "cyan",
926
- children: "\uD83C\uDFA8 CCStatusline Configuration"
927
- }, undefined, false, undefined, this)
1244
+ children: [
1245
+ "CCStatusline Configuration ",
1246
+ getPackageVersion() && `v${getPackageVersion()}`
1247
+ ]
1248
+ }, undefined, true, undefined, this)
928
1249
  }, undefined, false, undefined, this),
929
1250
  /* @__PURE__ */ jsxDEV(Box, {
930
1251
  marginBottom: 1,
@@ -935,7 +1256,8 @@ Continue?`;
935
1256
  }, undefined, false, undefined, this),
936
1257
  /* @__PURE__ */ jsxDEV(StatusLinePreview, {
937
1258
  lines: settings.lines || [[]],
938
- terminalWidth
1259
+ terminalWidth,
1260
+ settings
939
1261
  }, undefined, false, undefined, this),
940
1262
  /* @__PURE__ */ jsxDEV(Box, {
941
1263
  marginTop: 2,
@@ -962,10 +1284,13 @@ Continue?`;
962
1284
  const newLines = settings.lines || [[]];
963
1285
  let flatIndex = 0;
964
1286
  for (let lineIndex = 0;lineIndex < newLines.length; lineIndex++) {
965
- for (let itemIndex = 0;itemIndex < newLines[lineIndex].length; itemIndex++) {
966
- if (flatIndex < items.length) {
967
- newLines[lineIndex][itemIndex] = items[flatIndex];
968
- flatIndex++;
1287
+ const line = newLines[lineIndex];
1288
+ if (line) {
1289
+ for (let itemIndex = 0;itemIndex < line.length; itemIndex++) {
1290
+ if (flatIndex < items.length && items[flatIndex]) {
1291
+ line[itemIndex] = items[flatIndex];
1292
+ flatIndex++;
1293
+ }
969
1294
  }
970
1295
  }
971
1296
  }
@@ -973,6 +1298,13 @@ Continue?`;
973
1298
  },
974
1299
  onBack: () => setScreen("main")
975
1300
  }, undefined, false, undefined, this),
1301
+ screen === "flex" && /* @__PURE__ */ jsxDEV(FlexOptions, {
1302
+ settings,
1303
+ onUpdate: (updatedSettings) => {
1304
+ setSettings(updatedSettings);
1305
+ },
1306
+ onBack: () => setScreen("main")
1307
+ }, undefined, false, undefined, this),
976
1308
  screen === "confirm" && confirmDialog && /* @__PURE__ */ jsxDEV(ConfirmDialog, {
977
1309
  message: confirmDialog.message,
978
1310
  onConfirm: confirmDialog.action,
@@ -987,13 +1319,14 @@ Continue?`;
987
1319
  }, undefined, true, undefined, this);
988
1320
  };
989
1321
  function runTUI() {
1322
+ process.stdout.write("\x1B[2J\x1B[H");
990
1323
  render(/* @__PURE__ */ jsxDEV(App, {}, undefined, false, undefined, this));
991
1324
  }
992
1325
 
993
1326
  // src/ccstatusline.ts
994
- import * as fs3 from "fs";
1327
+ import * as fs4 from "fs";
995
1328
  import { promisify as promisify3 } from "util";
996
- var readFile6 = fs3.promises?.readFile || promisify3(fs3.readFile);
1329
+ var readFile6 = fs4.promises?.readFile || promisify3(fs4.readFile);
997
1330
  chalk2.level = 3;
998
1331
  async function readStdin() {
999
1332
  if (process.stdin.isTTY) {
@@ -1083,9 +1416,6 @@ function getGitChanges() {
1083
1416
  totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
1084
1417
  totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
1085
1418
  }
1086
- if (totalInsertions === 0 && totalDeletions === 0) {
1087
- return null;
1088
- }
1089
1419
  return { insertions: totalInsertions, deletions: totalDeletions };
1090
1420
  } catch {
1091
1421
  return null;
@@ -1093,7 +1423,7 @@ function getGitChanges() {
1093
1423
  }
1094
1424
  async function getSessionDuration(transcriptPath) {
1095
1425
  try {
1096
- if (!fs3.existsSync(transcriptPath)) {
1426
+ if (!fs4.existsSync(transcriptPath)) {
1097
1427
  return null;
1098
1428
  }
1099
1429
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1145,7 +1475,7 @@ async function getSessionDuration(transcriptPath) {
1145
1475
  }
1146
1476
  async function getTokenMetrics(transcriptPath) {
1147
1477
  try {
1148
- if (!fs3.existsSync(transcriptPath)) {
1478
+ if (!fs4.existsSync(transcriptPath)) {
1149
1479
  return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
1150
1480
  }
1151
1481
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1155,7 +1485,8 @@ async function getTokenMetrics(transcriptPath) {
1155
1485
  let outputTokens = 0;
1156
1486
  let cachedTokens = 0;
1157
1487
  let contextLength = 0;
1158
- let lastMessageWithUsage = null;
1488
+ let mostRecentMainChainEntry = null;
1489
+ let mostRecentTimestamp = null;
1159
1490
  for (const line of lines) {
1160
1491
  try {
1161
1492
  const data = JSON.parse(line);
@@ -1164,12 +1495,18 @@ async function getTokenMetrics(transcriptPath) {
1164
1495
  outputTokens += data.message.usage.output_tokens || 0;
1165
1496
  cachedTokens += data.message.usage.cache_read_input_tokens || 0;
1166
1497
  cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
1167
- lastMessageWithUsage = data;
1498
+ if (data.isSidechain !== true && data.timestamp) {
1499
+ const entryTime = new Date(data.timestamp);
1500
+ if (!mostRecentTimestamp || entryTime > mostRecentTimestamp) {
1501
+ mostRecentTimestamp = entryTime;
1502
+ mostRecentMainChainEntry = data;
1503
+ }
1504
+ }
1168
1505
  }
1169
1506
  } catch {}
1170
1507
  }
1171
- if (lastMessageWithUsage?.message?.usage) {
1172
- const usage = lastMessageWithUsage.message.usage;
1508
+ if (mostRecentMainChainEntry?.message?.usage) {
1509
+ const usage = mostRecentMainChainEntry.message.usage;
1173
1510
  contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
1174
1511
  }
1175
1512
  const totalTokens = inputTokens + outputTokens + cachedTokens;
@@ -1180,7 +1517,23 @@ async function getTokenMetrics(transcriptPath) {
1180
1517
  }
1181
1518
  function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration) {
1182
1519
  const detectedWidth = getTerminalWidth();
1183
- const terminalWidth = detectedWidth ? detectedWidth - 40 : null;
1520
+ let terminalWidth = null;
1521
+ if (detectedWidth) {
1522
+ const flexMode = settings.flexMode || "full-minus-40";
1523
+ if (flexMode === "full") {
1524
+ terminalWidth = detectedWidth - 4;
1525
+ } else if (flexMode === "full-minus-40") {
1526
+ terminalWidth = detectedWidth - 40;
1527
+ } else if (flexMode === "full-until-compact") {
1528
+ const threshold = settings.compactThreshold || 75;
1529
+ const contextPercentage = tokenMetrics ? Math.min(100, tokenMetrics.contextLength / 200000 * 100) : 0;
1530
+ if (contextPercentage >= threshold) {
1531
+ terminalWidth = detectedWidth - 40;
1532
+ } else {
1533
+ terminalWidth = detectedWidth - 4;
1534
+ }
1535
+ }
1536
+ }
1184
1537
  const elements = [];
1185
1538
  let hasFlexSeparator = false;
1186
1539
  const formatTokens = (count) => {
@@ -1195,14 +1548,16 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1195
1548
  case "model":
1196
1549
  if (data.model) {
1197
1550
  const color = chalk2[item.color || settings.colors.model] || chalk2.cyan;
1198
- elements.push({ content: color(`Model: ${data.model.display_name}`), type: "model" });
1551
+ const text2 = item.rawValue ? data.model.display_name : `Model: ${data.model.display_name}`;
1552
+ elements.push({ content: color(text2), type: "model" });
1199
1553
  }
1200
1554
  break;
1201
1555
  case "git-branch":
1202
1556
  const branch = getGitBranch();
1203
1557
  if (branch) {
1204
1558
  const color = chalk2[item.color || settings.colors.gitBranch] || chalk2.magenta;
1205
- elements.push({ content: color(`⎇ ${branch}`), type: "git-branch" });
1559
+ const text2 = item.rawValue ? branch : `⎇ ${branch}`;
1560
+ elements.push({ content: color(text2), type: "git-branch" });
1206
1561
  }
1207
1562
  break;
1208
1563
  case "git-changes":
@@ -1216,64 +1571,80 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1216
1571
  case "tokens-input":
1217
1572
  if (tokenMetrics) {
1218
1573
  const color = chalk2[item.color || "yellow"] || chalk2.yellow;
1219
- elements.push({ content: color(`In: ${formatTokens(tokenMetrics.inputTokens)}`), type: "tokens-input" });
1574
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.inputTokens) : `In: ${formatTokens(tokenMetrics.inputTokens)}`;
1575
+ elements.push({ content: color(text2), type: "tokens-input" });
1220
1576
  }
1221
1577
  break;
1222
1578
  case "tokens-output":
1223
1579
  if (tokenMetrics) {
1224
1580
  const color = chalk2[item.color || "green"] || chalk2.green;
1225
- elements.push({ content: color(`Out: ${formatTokens(tokenMetrics.outputTokens)}`), type: "tokens-output" });
1581
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.outputTokens) : `Out: ${formatTokens(tokenMetrics.outputTokens)}`;
1582
+ elements.push({ content: color(text2), type: "tokens-output" });
1226
1583
  }
1227
1584
  break;
1228
1585
  case "tokens-cached":
1229
1586
  if (tokenMetrics) {
1230
1587
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1231
- elements.push({ content: color(`Cached: ${formatTokens(tokenMetrics.cachedTokens)}`), type: "tokens-cached" });
1588
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.cachedTokens) : `Cached: ${formatTokens(tokenMetrics.cachedTokens)}`;
1589
+ elements.push({ content: color(text2), type: "tokens-cached" });
1232
1590
  }
1233
1591
  break;
1234
1592
  case "tokens-total":
1235
1593
  if (tokenMetrics) {
1236
1594
  const color = chalk2[item.color || "white"] || chalk2.white;
1237
- elements.push({ content: color(`Total: ${formatTokens(tokenMetrics.totalTokens)}`), type: "tokens-total" });
1595
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.totalTokens) : `Total: ${formatTokens(tokenMetrics.totalTokens)}`;
1596
+ elements.push({ content: color(text2), type: "tokens-total" });
1238
1597
  }
1239
1598
  break;
1240
1599
  case "context-length":
1241
1600
  if (tokenMetrics) {
1242
1601
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1243
- elements.push({ content: color(`Ctx: ${formatTokens(tokenMetrics.contextLength)}`), type: "context-length" });
1602
+ const text2 = item.rawValue ? formatTokens(tokenMetrics.contextLength) : `Ctx: ${formatTokens(tokenMetrics.contextLength)}`;
1603
+ elements.push({ content: color(text2), type: "context-length" });
1244
1604
  }
1245
1605
  break;
1246
1606
  case "context-percentage":
1247
1607
  if (tokenMetrics) {
1248
1608
  const percentage = Math.min(100, tokenMetrics.contextLength / 200000 * 100);
1249
1609
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1250
- elements.push({ content: color(`Ctx: ${percentage.toFixed(1)}%`), type: "context-percentage" });
1610
+ const text2 = item.rawValue ? `${percentage.toFixed(1)}%` : `Ctx: ${percentage.toFixed(1)}%`;
1611
+ elements.push({ content: color(text2), type: "context-percentage" });
1251
1612
  }
1252
1613
  break;
1253
1614
  case "terminal-width":
1254
1615
  const detectedWidth2 = terminalWidth || getTerminalWidth();
1255
1616
  if (detectedWidth2) {
1256
1617
  const color = chalk2[item.color || "dim"] || chalk2.dim;
1257
- elements.push({ content: color(`Term: ${detectedWidth2}`), type: "terminal-width" });
1618
+ const text2 = item.rawValue ? `${detectedWidth2}` : `Term: ${detectedWidth2}`;
1619
+ elements.push({ content: color(text2), type: "terminal-width" });
1258
1620
  }
1259
1621
  break;
1260
1622
  case "session-clock":
1261
1623
  if (sessionDuration) {
1262
1624
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1263
- elements.push({ content: color(`Session: ${sessionDuration}`), type: "session-clock" });
1625
+ const text2 = item.rawValue ? sessionDuration : `Session: ${sessionDuration}`;
1626
+ elements.push({ content: color(text2), type: "session-clock" });
1264
1627
  }
1265
1628
  break;
1266
1629
  case "version":
1267
1630
  const versionString = data.version || "Unknown";
1268
1631
  const versionColor = chalk2[item.color || "green"] || chalk2.green;
1269
- elements.push({ content: versionColor(`Version: ${versionString}`), type: "version" });
1632
+ const text = item.rawValue ? versionString : `Version: ${versionString}`;
1633
+ elements.push({ content: versionColor(text), type: "version" });
1270
1634
  break;
1271
1635
  case "separator":
1272
1636
  const lastElement = elements[elements.length - 1];
1273
1637
  if (elements.length > 0 && lastElement && lastElement.type !== "separator") {
1274
1638
  const sepColor = chalk2[settings.colors.separator] || chalk2.dim;
1275
1639
  const sepChar = item.character || "|";
1276
- const sepContent = sepChar === "," ? sepColor(`${sepChar} `) : sepColor(` ${sepChar} `);
1640
+ let sepContent;
1641
+ if (sepChar === ",") {
1642
+ sepContent = sepColor(`${sepChar} `);
1643
+ } else if (sepChar === " ") {
1644
+ sepContent = sepColor(" ");
1645
+ } else {
1646
+ sepContent = sepColor(` ${sepChar} `);
1647
+ }
1277
1648
  elements.push({ content: sepContent, type: "separator" });
1278
1649
  }
1279
1650
  break;
@@ -1281,10 +1652,18 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1281
1652
  elements.push({ content: "FLEX", type: "flex-separator" });
1282
1653
  hasFlexSeparator = true;
1283
1654
  break;
1655
+ case "custom-text":
1656
+ const customTextColor = chalk2[item.color || "white"] || chalk2.white;
1657
+ const customText = item.customText || "";
1658
+ elements.push({ content: customTextColor(customText), type: "custom-text" });
1659
+ break;
1284
1660
  }
1285
1661
  }
1286
1662
  if (elements.length === 0)
1287
1663
  return "";
1664
+ while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") {
1665
+ elements.pop();
1666
+ }
1288
1667
  let statusLine = "";
1289
1668
  if (hasFlexSeparator && terminalWidth) {
1290
1669
  const parts = [[]];
@@ -1324,6 +1703,38 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1324
1703
  statusLine = elements.map((e) => e.content).join("");
1325
1704
  }
1326
1705
  }
1706
+ if (detectedWidth && detectedWidth > 0) {
1707
+ const plainLength = statusLine.replace(/\x1b\[[0-9;]*m/g, "").length;
1708
+ if (plainLength > detectedWidth) {
1709
+ let truncated = "";
1710
+ let currentLength = 0;
1711
+ let inAnsiCode = false;
1712
+ let ansiBuffer = "";
1713
+ const targetLength = detectedWidth - 7;
1714
+ for (let i = 0;i < statusLine.length; i++) {
1715
+ const char = statusLine[i];
1716
+ if (char === "\x1B") {
1717
+ inAnsiCode = true;
1718
+ ansiBuffer = char;
1719
+ } else if (inAnsiCode) {
1720
+ ansiBuffer += char;
1721
+ if (char === "m") {
1722
+ truncated += ansiBuffer;
1723
+ inAnsiCode = false;
1724
+ ansiBuffer = "";
1725
+ }
1726
+ } else {
1727
+ if (currentLength < targetLength) {
1728
+ truncated += char;
1729
+ currentLength++;
1730
+ } else {
1731
+ break;
1732
+ }
1733
+ }
1734
+ }
1735
+ statusLine = truncated + "...";
1736
+ }
1737
+ }
1327
1738
  return statusLine;
1328
1739
  }
1329
1740
  async function renderStatusLine(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",