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