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