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 +49 -49
- package/dist/ccstatusline.js +470 -73
- package/package.json +1 -1
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
|
-
-
|
|
35
|
-
-
|
|
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
|
-
- **
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
|
package/dist/ccstatusline.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 &&
|
|
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,
|
|
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
|
|
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:
|
|
320
|
-
|
|
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 (
|
|
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
|
|
467
|
-
if (
|
|
468
|
-
const currentType =
|
|
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] = { ...
|
|
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
|
|
496
|
-
if (
|
|
497
|
-
const currentType =
|
|
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] = { ...
|
|
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
|
|
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
|
|
537
|
-
if (
|
|
538
|
-
const currentChar =
|
|
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] = { ...
|
|
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
|
-
|
|
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:
|
|
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.
|
|
774
|
+
item.rawValue && /* @__PURE__ */ jsxDEV(Text, {
|
|
637
775
|
dimColor: true,
|
|
638
|
-
children: " (
|
|
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:
|
|
934
|
-
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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
|
|
1327
|
+
import * as fs4 from "fs";
|
|
1002
1328
|
import { promisify as promisify3 } from "util";
|
|
1003
|
-
var readFile6 =
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
-
|
|
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 (
|
|
1179
|
-
const 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|