ccstatusline 1.0.10 → 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,40 +230,40 @@ 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 || "|";
@@ -253,10 +281,15 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
253
281
  elements.push("FLEX");
254
282
  hasFlexSeparator = true;
255
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;
256
289
  }
257
290
  });
258
291
  let statusLine = "";
259
- if (hasFlexSeparator && width) {
292
+ if (hasFlexSeparator && flexWidth) {
260
293
  const parts = [[]];
261
294
  let currentPart = 0;
262
295
  for (let i = 0;i < items.length; i++) {
@@ -277,7 +310,7 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
277
310
  });
278
311
  const totalContentLength = partLengths.reduce((sum, len) => sum + len, 0);
279
312
  const flexCount = parts.length - 1;
280
- const totalSpace = Math.max(0, width - totalContentLength);
313
+ const totalSpace = Math.max(0, flexWidth - totalContentLength);
281
314
  const spacePerFlex = flexCount > 0 ? Math.floor(totalSpace / flexCount) : 0;
282
315
  const extraSpace = flexCount > 0 ? totalSpace % flexCount : 0;
283
316
  statusLine = "";
@@ -294,15 +327,48 @@ var renderSingleLine = (items, terminalWidth, widthDetectionAvailable) => {
294
327
  } else {
295
328
  statusLine = elements.map((e) => e === "FLEX" ? chalk.dim(" | ") : e).join("");
296
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
+ }
297
362
  return statusLine;
298
363
  };
299
- var StatusLinePreview = ({ lines, terminalWidth }) => {
364
+ var StatusLinePreview = ({ lines, terminalWidth, settings }) => {
300
365
  const widthDetectionAvailable = canDetectTerminalWidth();
301
366
  const boxWidth = Math.min(terminalWidth - 4, process.stdout.columns - 4 || 76);
302
367
  const topLine = chalk.dim("╭" + "─".repeat(Math.max(0, boxWidth - 2)) + "╮");
303
368
  const middleLine = chalk.dim("│") + " > " + " ".repeat(Math.max(0, boxWidth - 5)) + chalk.dim("│");
304
369
  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 !== "");
370
+ const availableWidth = boxWidth - 2;
371
+ const renderedLines = lines.map((lineItems) => lineItems.length > 0 ? renderSingleLine(lineItems, availableWidth, widthDetectionAvailable, settings) : "").filter((line) => line !== "");
306
372
  return /* @__PURE__ */ jsxDEV(Box, {
307
373
  flexDirection: "column",
308
374
  children: [
@@ -316,8 +382,11 @@ var StatusLinePreview = ({ lines, terminalWidth }) => {
316
382
  children: bottomLine
317
383
  }, undefined, false, undefined, this),
318
384
  renderedLines.map((line, index) => /* @__PURE__ */ jsxDEV(Text, {
319
- children: line
320
- }, index, false, undefined, this))
385
+ children: [
386
+ " ",
387
+ line
388
+ ]
389
+ }, index, true, undefined, this))
321
390
  ]
322
391
  }, undefined, true, undefined, this);
323
392
  };
@@ -390,6 +459,7 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
390
459
  const items = [
391
460
  { label: "\uD83D\uDCDD Edit Lines", value: "lines" },
392
461
  { label: "\uD83C\uDFA8 Configure Colors", value: "colors" },
462
+ { label: "⚡ Flex Options", value: "flex" },
393
463
  { label: isClaudeInstalled ? "\uD83D\uDDD1️ Uninstall from Claude Code" : "\uD83D\uDCE6 Install to Claude Code", value: "install" }
394
464
  ];
395
465
  if (hasChanges) {
@@ -417,9 +487,29 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges }) => {
417
487
  var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
418
488
  const [selectedIndex, setSelectedIndex] = useState(0);
419
489
  const [moveMode, setMoveMode] = useState(false);
490
+ const [editingText, setEditingText] = useState(false);
491
+ const [textInput, setTextInput] = useState("");
420
492
  const separatorChars = ["|", "-", ",", " "];
421
493
  useInput((input, key) => {
422
- 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) {
423
513
  if (key.upArrow && selectedIndex > 0) {
424
514
  const newItems = [...items];
425
515
  const temp = newItems[selectedIndex];
@@ -461,17 +551,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
461
551
  "session-clock",
462
552
  "terminal-width",
463
553
  "version",
464
- "flex-separator"
554
+ "flex-separator",
555
+ "custom-text"
465
556
  ];
466
- const currentItem = items[selectedIndex];
467
- if (currentItem) {
468
- const currentType = currentItem.type;
557
+ const currentItem2 = items[selectedIndex];
558
+ if (currentItem2) {
559
+ const currentType = currentItem2.type;
469
560
  const currentIndex = types.indexOf(currentType);
470
561
  const prevIndex = currentIndex === 0 ? types.length - 1 : currentIndex - 1;
471
562
  const newItems = [...items];
472
563
  const prevType = types[prevIndex];
473
564
  if (prevType) {
474
- newItems[selectedIndex] = { ...currentItem, type: prevType };
565
+ newItems[selectedIndex] = { ...currentItem2, type: prevType };
475
566
  onUpdate(newItems);
476
567
  }
477
568
  }
@@ -490,17 +581,18 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
490
581
  "session-clock",
491
582
  "terminal-width",
492
583
  "version",
493
- "flex-separator"
584
+ "flex-separator",
585
+ "custom-text"
494
586
  ];
495
- const currentItem = items[selectedIndex];
496
- if (currentItem) {
497
- const currentType = currentItem.type;
587
+ const currentItem2 = items[selectedIndex];
588
+ if (currentItem2) {
589
+ const currentType = currentItem2.type;
498
590
  const currentIndex = types.indexOf(currentType);
499
591
  const nextIndex = (currentIndex + 1) % types.length;
500
592
  const newItems = [...items];
501
593
  const nextType = types[nextIndex];
502
594
  if (nextType) {
503
- newItems[selectedIndex] = { ...currentItem, type: nextType };
595
+ newItems[selectedIndex] = { ...currentItem2, type: nextType };
504
596
  onUpdate(newItems);
505
597
  }
506
598
  }
@@ -520,9 +612,8 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
520
612
  type: "separator"
521
613
  };
522
614
  const newItems = [...items];
523
- newItems.splice(selectedIndex + 1, 0, newItem);
615
+ newItems.splice(selectedIndex, 0, newItem);
524
616
  onUpdate(newItems);
525
- setSelectedIndex(selectedIndex + 1);
526
617
  } else if (input === "d" && items.length > 0) {
527
618
  const newItems = items.filter((_, i) => i !== selectedIndex);
528
619
  onUpdate(newItems);
@@ -533,15 +624,28 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
533
624
  onUpdate([]);
534
625
  setSelectedIndex(0);
535
626
  } else if (input === " " && items.length > 0) {
536
- const currentItem = items[selectedIndex];
537
- if (currentItem && currentItem.type === "separator") {
538
- const currentChar = currentItem.character || "|";
627
+ const currentItem2 = items[selectedIndex];
628
+ if (currentItem2 && currentItem2.type === "separator") {
629
+ const currentChar = currentItem2.character || "|";
539
630
  const currentCharIndex = separatorChars.indexOf(currentChar);
540
631
  const nextChar = separatorChars[(currentCharIndex + 1) % separatorChars.length];
541
632
  const newItems = [...items];
542
- newItems[selectedIndex] = { ...currentItem, character: nextChar };
633
+ newItems[selectedIndex] = { ...currentItem2, character: nextChar };
543
634
  onUpdate(newItems);
544
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
+ }
545
649
  } else if (key.escape) {
546
650
  onBack();
547
651
  }
@@ -580,10 +684,30 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
580
684
  return chalk.dim("Terminal Width");
581
685
  case "version":
582
686
  return chalk.green("Version");
687
+ case "custom-text":
688
+ const text = item.customText || "Empty";
689
+ return chalk.white(`Custom Text (${text})`);
583
690
  }
584
691
  };
585
692
  const hasFlexSeparator = items.some((item) => item.type === "flex-separator");
586
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";
587
711
  return /* @__PURE__ */ jsxDEV(Box, {
588
712
  flexDirection: "column",
589
713
  children: [
@@ -599,12 +723,26 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
599
723
  }, undefined, false, undefined, this)
600
724
  ]
601
725
  }, undefined, true, undefined, this),
602
- 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, {
603
741
  dimColor: true,
604
742
  children: "↑↓ to move item, ESC or Enter to exit move mode"
605
743
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(Text, {
606
744
  dimColor: true,
607
- children: "↑↓ select, ←→ change type, Space edit separator, Enter to move, (a)dd, (i)nsert, (d)elete, (c)lear line, ESC back"
745
+ children: helpText
608
746
  }, undefined, false, undefined, this),
609
747
  hasFlexSeparator && !widthDetectionAvailable && /* @__PURE__ */ jsxDEV(Box, {
610
748
  marginTop: 1,
@@ -633,9 +771,9 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
633
771
  index + 1,
634
772
  ". ",
635
773
  getItemDisplay(item),
636
- item.type === "separator" && index === selectedIndex && !moveMode && /* @__PURE__ */ jsxDEV(Text, {
774
+ item.rawValue && /* @__PURE__ */ jsxDEV(Text, {
637
775
  dimColor: true,
638
- children: " (Space to edit)"
776
+ children: " (raw value)"
639
777
  }, undefined, false, undefined, this)
640
778
  ]
641
779
  }, undefined, true, undefined, this)
@@ -645,7 +783,7 @@ var ItemsEditor = ({ items, onUpdate, onBack, lineNumber }) => {
645
783
  }, undefined, true, undefined, this);
646
784
  };
647
785
  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));
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));
649
787
  const [selectedIndex, setSelectedIndex] = useState(0);
650
788
  useInput((input, key) => {
651
789
  if (key.escape) {
@@ -710,6 +848,8 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
710
848
  return "Session Clock";
711
849
  case "version":
712
850
  return "Version";
851
+ case "custom-text":
852
+ return `Custom Text (${item.customText || "Empty"})`;
713
853
  default:
714
854
  return item.type;
715
855
  }
@@ -805,6 +945,174 @@ var ColorMenu = ({ items, onUpdate, onBack }) => {
805
945
  ]
806
946
  }, undefined, true, undefined, this);
807
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
+ };
808
1116
  var App = () => {
809
1117
  const { exit } = useApp();
810
1118
  const [settings, setSettings] = useState(null);
@@ -898,6 +1206,9 @@ Continue?`;
898
1206
  case "colors":
899
1207
  setScreen("colors");
900
1208
  break;
1209
+ case "flex":
1210
+ setScreen("flex");
1211
+ break;
901
1212
  case "install":
902
1213
  await handleInstallUninstall();
903
1214
  break;
@@ -930,8 +1241,11 @@ Continue?`;
930
1241
  children: /* @__PURE__ */ jsxDEV(Text, {
931
1242
  bold: true,
932
1243
  color: "cyan",
933
- children: "\uD83C\uDFA8 CCStatusline Configuration"
934
- }, undefined, false, undefined, this)
1244
+ children: [
1245
+ "CCStatusline Configuration ",
1246
+ getPackageVersion() && `v${getPackageVersion()}`
1247
+ ]
1248
+ }, undefined, true, undefined, this)
935
1249
  }, undefined, false, undefined, this),
936
1250
  /* @__PURE__ */ jsxDEV(Box, {
937
1251
  marginBottom: 1,
@@ -942,7 +1256,8 @@ Continue?`;
942
1256
  }, undefined, false, undefined, this),
943
1257
  /* @__PURE__ */ jsxDEV(StatusLinePreview, {
944
1258
  lines: settings.lines || [[]],
945
- terminalWidth
1259
+ terminalWidth,
1260
+ settings
946
1261
  }, undefined, false, undefined, this),
947
1262
  /* @__PURE__ */ jsxDEV(Box, {
948
1263
  marginTop: 2,
@@ -969,10 +1284,13 @@ Continue?`;
969
1284
  const newLines = settings.lines || [[]];
970
1285
  let flatIndex = 0;
971
1286
  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++;
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
+ }
976
1294
  }
977
1295
  }
978
1296
  }
@@ -980,6 +1298,13 @@ Continue?`;
980
1298
  },
981
1299
  onBack: () => setScreen("main")
982
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),
983
1308
  screen === "confirm" && confirmDialog && /* @__PURE__ */ jsxDEV(ConfirmDialog, {
984
1309
  message: confirmDialog.message,
985
1310
  onConfirm: confirmDialog.action,
@@ -994,13 +1319,14 @@ Continue?`;
994
1319
  }, undefined, true, undefined, this);
995
1320
  };
996
1321
  function runTUI() {
1322
+ process.stdout.write("\x1B[2J\x1B[H");
997
1323
  render(/* @__PURE__ */ jsxDEV(App, {}, undefined, false, undefined, this));
998
1324
  }
999
1325
 
1000
1326
  // src/ccstatusline.ts
1001
- import * as fs3 from "fs";
1327
+ import * as fs4 from "fs";
1002
1328
  import { promisify as promisify3 } from "util";
1003
- var readFile6 = fs3.promises?.readFile || promisify3(fs3.readFile);
1329
+ var readFile6 = fs4.promises?.readFile || promisify3(fs4.readFile);
1004
1330
  chalk2.level = 3;
1005
1331
  async function readStdin() {
1006
1332
  if (process.stdin.isTTY) {
@@ -1090,9 +1416,6 @@ function getGitChanges() {
1090
1416
  totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
1091
1417
  totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
1092
1418
  }
1093
- if (totalInsertions === 0 && totalDeletions === 0) {
1094
- return null;
1095
- }
1096
1419
  return { insertions: totalInsertions, deletions: totalDeletions };
1097
1420
  } catch {
1098
1421
  return null;
@@ -1100,7 +1423,7 @@ function getGitChanges() {
1100
1423
  }
1101
1424
  async function getSessionDuration(transcriptPath) {
1102
1425
  try {
1103
- if (!fs3.existsSync(transcriptPath)) {
1426
+ if (!fs4.existsSync(transcriptPath)) {
1104
1427
  return null;
1105
1428
  }
1106
1429
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1152,7 +1475,7 @@ async function getSessionDuration(transcriptPath) {
1152
1475
  }
1153
1476
  async function getTokenMetrics(transcriptPath) {
1154
1477
  try {
1155
- if (!fs3.existsSync(transcriptPath)) {
1478
+ if (!fs4.existsSync(transcriptPath)) {
1156
1479
  return { inputTokens: 0, outputTokens: 0, cachedTokens: 0, totalTokens: 0, contextLength: 0 };
1157
1480
  }
1158
1481
  const content = await readFile6(transcriptPath, "utf-8");
@@ -1162,7 +1485,8 @@ async function getTokenMetrics(transcriptPath) {
1162
1485
  let outputTokens = 0;
1163
1486
  let cachedTokens = 0;
1164
1487
  let contextLength = 0;
1165
- let lastMessageWithUsage = null;
1488
+ let mostRecentMainChainEntry = null;
1489
+ let mostRecentTimestamp = null;
1166
1490
  for (const line of lines) {
1167
1491
  try {
1168
1492
  const data = JSON.parse(line);
@@ -1171,12 +1495,18 @@ async function getTokenMetrics(transcriptPath) {
1171
1495
  outputTokens += data.message.usage.output_tokens || 0;
1172
1496
  cachedTokens += data.message.usage.cache_read_input_tokens || 0;
1173
1497
  cachedTokens += data.message.usage.cache_creation_input_tokens || 0;
1174
- 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
+ }
1175
1505
  }
1176
1506
  } catch {}
1177
1507
  }
1178
- if (lastMessageWithUsage?.message?.usage) {
1179
- const usage = lastMessageWithUsage.message.usage;
1508
+ if (mostRecentMainChainEntry?.message?.usage) {
1509
+ const usage = mostRecentMainChainEntry.message.usage;
1180
1510
  contextLength = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0) + (usage.cache_creation_input_tokens || 0);
1181
1511
  }
1182
1512
  const totalTokens = inputTokens + outputTokens + cachedTokens;
@@ -1187,7 +1517,23 @@ async function getTokenMetrics(transcriptPath) {
1187
1517
  }
1188
1518
  function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration) {
1189
1519
  const detectedWidth = getTerminalWidth();
1190
- 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
+ }
1191
1537
  const elements = [];
1192
1538
  let hasFlexSeparator = false;
1193
1539
  const formatTokens = (count) => {
@@ -1202,14 +1548,16 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1202
1548
  case "model":
1203
1549
  if (data.model) {
1204
1550
  const color = chalk2[item.color || settings.colors.model] || chalk2.cyan;
1205
- 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" });
1206
1553
  }
1207
1554
  break;
1208
1555
  case "git-branch":
1209
1556
  const branch = getGitBranch();
1210
1557
  if (branch) {
1211
1558
  const color = chalk2[item.color || settings.colors.gitBranch] || chalk2.magenta;
1212
- elements.push({ content: color(`⎇ ${branch}`), type: "git-branch" });
1559
+ const text2 = item.rawValue ? branch : `⎇ ${branch}`;
1560
+ elements.push({ content: color(text2), type: "git-branch" });
1213
1561
  }
1214
1562
  break;
1215
1563
  case "git-changes":
@@ -1223,57 +1571,66 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1223
1571
  case "tokens-input":
1224
1572
  if (tokenMetrics) {
1225
1573
  const color = chalk2[item.color || "yellow"] || chalk2.yellow;
1226
- 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" });
1227
1576
  }
1228
1577
  break;
1229
1578
  case "tokens-output":
1230
1579
  if (tokenMetrics) {
1231
1580
  const color = chalk2[item.color || "green"] || chalk2.green;
1232
- 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" });
1233
1583
  }
1234
1584
  break;
1235
1585
  case "tokens-cached":
1236
1586
  if (tokenMetrics) {
1237
1587
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1238
- 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" });
1239
1590
  }
1240
1591
  break;
1241
1592
  case "tokens-total":
1242
1593
  if (tokenMetrics) {
1243
1594
  const color = chalk2[item.color || "white"] || chalk2.white;
1244
- 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" });
1245
1597
  }
1246
1598
  break;
1247
1599
  case "context-length":
1248
1600
  if (tokenMetrics) {
1249
1601
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1250
- 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" });
1251
1604
  }
1252
1605
  break;
1253
1606
  case "context-percentage":
1254
1607
  if (tokenMetrics) {
1255
1608
  const percentage = Math.min(100, tokenMetrics.contextLength / 200000 * 100);
1256
1609
  const color = chalk2[item.color || "cyan"] || chalk2.cyan;
1257
- 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" });
1258
1612
  }
1259
1613
  break;
1260
1614
  case "terminal-width":
1261
1615
  const detectedWidth2 = terminalWidth || getTerminalWidth();
1262
1616
  if (detectedWidth2) {
1263
1617
  const color = chalk2[item.color || "dim"] || chalk2.dim;
1264
- 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" });
1265
1620
  }
1266
1621
  break;
1267
1622
  case "session-clock":
1268
1623
  if (sessionDuration) {
1269
1624
  const color = chalk2[item.color || "blue"] || chalk2.blue;
1270
- 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" });
1271
1627
  }
1272
1628
  break;
1273
1629
  case "version":
1274
1630
  const versionString = data.version || "Unknown";
1275
1631
  const versionColor = chalk2[item.color || "green"] || chalk2.green;
1276
- elements.push({ content: versionColor(`Version: ${versionString}`), type: "version" });
1632
+ const text = item.rawValue ? versionString : `Version: ${versionString}`;
1633
+ elements.push({ content: versionColor(text), type: "version" });
1277
1634
  break;
1278
1635
  case "separator":
1279
1636
  const lastElement = elements[elements.length - 1];
@@ -1295,10 +1652,18 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1295
1652
  elements.push({ content: "FLEX", type: "flex-separator" });
1296
1653
  hasFlexSeparator = true;
1297
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;
1298
1660
  }
1299
1661
  }
1300
1662
  if (elements.length === 0)
1301
1663
  return "";
1664
+ while (elements.length > 0 && elements[elements.length - 1]?.type === "separator") {
1665
+ elements.pop();
1666
+ }
1302
1667
  let statusLine = "";
1303
1668
  if (hasFlexSeparator && terminalWidth) {
1304
1669
  const parts = [[]];
@@ -1338,6 +1703,38 @@ function renderSingleLine2(items, settings, data, tokenMetrics, sessionDuration)
1338
1703
  statusLine = elements.map((e) => e.content).join("");
1339
1704
  }
1340
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
+ }
1341
1738
  return statusLine;
1342
1739
  }
1343
1740
  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.11",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",